Skip to content

Feature/timeline filters optimization - #990

Open
yimjr wants to merge 4 commits into
ActivityWatch:masterfrom
yimjr:feature/timeline-filters-optimization
Open

yimjr wants to merge 4 commits into
ActivityWatch:masterfrom
yimjr:feature/timeline-filters-optimization

Conversation

@yimjr

@yimjr yimjr commented Sep 18, 2026

Copy link
Copy Markdown

Summary

This branch improves the Timeline page filters and adds i18n support for all currently supported application languages.

Changes

Filter functionality

  • Replaced the Host, Client, and Categories dropdown filters with multi-select checkbox lists.
  • Added an All option to each checkbox group.
  • Changed the selection behavior so that:
    • All options are selected by default.
    • Selecting no options produces an empty chart.
    • Changes are kept as draft values until Confirm is clicked.
  • Added Confirm, Cancel, and Reset controls.
  • Reset restores all filter values to their defaults without applying the changes until Confirm is clicked.
  • Added fixed-height filter panel styling and separators between filter sections.
  • Improved spacing and alignment between filter labels and their options.
  • Improved handling of long host, client, and category names.
  • Added internal scrolling for long option lists while keeping the overall menu height fixed.

Duration filtering

  • Replaced the duration dropdown with minimum and maximum range inputs.
  • Each endpoint supports seconds, minutes, or hours.
  • Either endpoint can be left empty:
    • Both empty: no duration restriction.
    • Only minimum set: events must be at least that long.
    • Only maximum set: events must be no longer than that.
  • Added validation for invalid ranges where the minimum exceeds the maximum.
  • Range validation is performed when Confirm is clicked.

AFK and merge filters

  • Changed AFK filtering and merge-by-application controls from switches to checkboxes.
  • Placed both controls on the same row.
  • Preserved their existing behavior and application semantics.

Timeline behavior and performance

  • Coalesced multiple filter changes into a single scheduled refresh.
  • Prevented filter initialization from replacing the initially queried timeline interval.
  • Preserved the existing timeline window when filters are initialized.
  • Added handling for empty Host, Client, and Categories selections.
  • Added tests covering filtering, duration ranges, reset/cancel behavior, initialization, empty selections, and multi-selection summaries.

Internationalization

  • Localized all filter-related text, including:
    • Filter title
    • Duration labels and units
    • Minimum/maximum placeholders and accessibility labels
    • All, Reset, Confirm, and Cancel
    • Validation messages
    • Filter summary text
  • Added translations for all currently supported locales:
    • English
    • Simplified Chinese
    • German
    • Russian
    • Ukrainian
    • Swedish
  • Added locale-specific pluralization rules for Russian and Ukrainian filter counts, with the helper reusable for future pluralized count messages.
  • Updated filter summaries to use localized plural forms.

Validation

  • Full test suite passed: 40 test suites, 335 tests.
  • Timeline-specific tests passed.
  • Production build completed successfully.
  • Lint completed with no errors.
  • git diff --check passed.
  • The locale checker still reports pre-existing issues outside this branch, including missing nav.aiSummary keys in several locales.

Notes and limitations

  • This implementation was AI-assisted. The changes were reviewed as carefully as possible and validated with unit tests, the full test suite, lint, and a production build.
  • I only manually reviewed the English and Simplified Chinese translations. The translations for German, Russian, Ukrainian, and Swedish have not been manually reviewed by a fluent speaker because I do not know those languages.
  • The filter menu’s visual design and consistency with the rest of the application may still benefit from additional UI/UX refinement in a future iteration.

@greptile-apps

greptile-apps Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 2/5

This PR is not safe to merge until the filter-state, nested-category, and invalid-duration failures are corrected and the required performance measurements are supplied.

Findings

  1. P1 Interval Changes Lose Selections
  2. P1 Invalid Durations Remove Bounds
  3. P2 Optimization Lacks Required Profiling

Summary

This PR replaces the standalone Timeline page's immediate single-value filters with localized, draft-based multi-select and duration-range controls, then batches applied filter refreshes.

  • Adds Confirm, Cancel, and Reset semantics for draft filter state.
  • Adds host, client, and category checkbox lists plus minimum/maximum duration filtering.
  • Adds translated filter messages and Russian/Ukrainian pluralization.
  • Adds unit coverage for common filtering, reset, initialization, and pluralization paths.
  • The review found selection-loss, hierarchical-category, and invalid-duration failures in the new filtering behavior, plus a repository measurement-rule violation.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Open[Open filter panel] --> Draft[Copy applied filters to draft state]
  Draft --> Edit[Edit hosts, clients, categories, duration, AFK, and merge]
  Edit --> Cancel[Cancel]
  Cancel --> Draft
  Edit --> Reset[Reset draft to defaults]
  Reset --> Edit
  Edit --> Confirm{Confirm}
  Confirm -->|Invalid range| Error[Show validation error]
  Confirm -->|Valid| Apply[Commit applied filters]
  Apply --> Schedule[Coalesce refresh on next tick]
  Schedule --> Query[Query buckets for selected interval]
  Query --> Filter[Apply host, client, duration, category, AFK, and merge filters]
  Filter --> Timeline[Render timeline]
Loading

Reviews (1) · Last reviewed commit: "Localize timeline filters across support..."

Comment thread src/views/Timeline.vue
this.filter_hostnames = nextHostnames;
}
} else {
const nextHostnames = this.filter_hostnames.filter(host => this.hosts.includes(host));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Interval Changes Lose Selections

When a selected host or client is absent from a newly queried interval, this code permanently removes it from the applied filter. For example, selecting host A, navigating to an interval containing only host B, and then returning to the original interval leaves the selection empty and the chart blank because nothing restores A. Preserve applied selections independently of the options available in the current interval. The same issue affects client pruning at line 605.

Knowledge Base Used: Timeline visualizations

Comment thread src/views/Timeline.vue
Comment on lines +509 to +513
normalizeDuration(value, unit) {
if (value === '' || value === null || value === undefined) return null;
const duration = Number(value) * this.durationUnitFactor(unit);
return Number.isFinite(duration) && duration >= 0 ? duration : null;
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Invalid Durations Remove Bounds

Negative duration values are silently converted to an empty endpoint instead of being rejected. Because Confirm is a normal button rather than a form submission, the input's min="0" constraint does not block -1; normalizeDuration returns null, range validation accepts it, and applying a negative maximum removes the upper bound and unexpectedly restores long events. Show a validation error instead of treating an invalid value as blank.

Knowledge Base Used: Timeline visualizations

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b1e6f92. Negative duration values are now explicitly rejected on Confirm instead of being normalized to an empty bound, and tests were added for negative minimum and maximum values.

Comment thread src/views/Timeline.vue
Comment on lines +493 to +501
scheduleBucketsRefresh() {
if (this.buckets_refresh_scheduled) return;

this.buckets_refresh_scheduled = true;
this.$nextTick(() => {
this.buckets_refresh_scheduled = false;
this.getBuckets();
});
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Optimization Lacks Required Profiling

This scheduler adds refresh batching as a performance optimization, but the repository requires actual performance measurements with profiling tools before implementing such optimizations. No profiling or benchmark evidence accompanies this change. The required measurements must be captured and used to justify or adjust the optimization before merging.

Rule Used: Before implementing performance optimizations, measure actual performance using profiling tools like pytest-profiling and --durations to identify real bottlenecks rather than assuming where performance issues exist. (source)

Learned From
gptme/gptme#707

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.

  • P1 Unchecked Categories Still Appear src/views/Timeline.vue:668

    A child category cannot be excluded while its parent remains selected. The checkbox toggle removes only the exact child path, but this prefix comparison still admits every descendant through the selected parent. As a result, an unchecked category such as “Work > Programming” continues to appear. Either cascade parent selections to their descendants or avoid presenting overlapping parent and child options as independent checkboxes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant