feat(desktop): add time filter to sidebar project tree#4150
Conversation
Add a clock button in the project tree header that opens a dropdown with time-based filters: - All / Latest 10 / Last 1h / 3h / 5h / 24h - Pure frontend filtering using topicActivityTime() - Persistent filter state via localStorage - Shows empty-state message + clear filter button when no results
…default styling and adaptive width - Move time filter clock button to the left of collapse button - Remove fixed width:22px, use min-width:22px so button expands when label shown - Share combined selectors with add-project/collapse-all for consistent default styling
…ime) with separators
…cus-visible style
There was a problem hiding this comment.
💡 Codex Review
DeepSeek-Reasonix/desktop/frontend/src/styles.css
Lines 1453 to 1455 in 0b9f3da
The project tree lives inside .sidebar, which has overflow: hidden, and this menu is absolutely positioned from the left edge of a button in the right-aligned header actions. In the default All state the button sits only about the other two action buttons away from the sidebar edge, so an 84px (or wider localized) menu extends past the sidebar and gets clipped, hiding part of the options. Align the menu to the right edge or render it in a portal like other floating UI.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const times = new Set<number>(); | ||
| const collect = (nodes: ProjectNode[]) => { | ||
| for (const n of nodes) { | ||
| if (n.kind === "topic" || n.kind === "global_topic") times.add(topicActivityTime(n)); | ||
| collect(asArray(n.children)); | ||
| } | ||
| }; | ||
| collect(tree); | ||
| const sorted = [...times].sort((a, b) => b - a); | ||
| return sorted.length >= 10 ? sorted[9] : (sorted.length > 0 ? sorted[sorted.length - 1] : null); |
There was a problem hiding this comment.
Count topics instead of distinct timestamps
For Latest 10/Latest 20, this stores activity times in a Set, so topics that share the same timestamp are collapsed before selecting the cutoff. In workspaces with legacy/blank topics where topicActivityTime() returns 0 for many nodes, or when several topics have identical millisecond activity times, the cutoff can become 0 (or a tied timestamp) and topicMatchesTime() then includes every topic at that value, causing the “Latest 10” filter to show far more than 10 topics. Build the cutoff from sorted topic entries rather than unique timestamps, with a deterministic tie-breaker if needed.
Useful? React with 👍 / 👎.
| const sorted = [...times].sort((a, b) => b - a); | ||
| return sorted.length >= 20 ? sorted[19] : (sorted.length > 0 ? sorted[sorted.length - 1] : null); | ||
| })() | ||
| : Date.now() - diff; |
There was a problem hiding this comment.
Refresh relative cutoff as time passes
When a duration filter such as Last hour is selected, the cutoff is computed from Date.now() only when this useMemo reruns, but its dependencies are just query, tree, and timeFilter. If the user leaves the sidebar open with no tree refresh, topics that have aged past the selected window remain visible indefinitely until some unrelated state change recomputes the memo, so the time filter can show stale results. Add a timer/tick dependency while a duration filter is active or recompute on render for these filters.
Useful? React with 👍 / 👎.
esengine
left a comment
There was a problem hiding this comment.
Nicely self-contained — i18n in both locales, theme tokens throughout, persisted filter state, and a real empty state with a clear-filter action. I read through the filter logic (the time + count cutoffs and the folder-visibility handling) and it holds up.
One real thing, which @chatgpt-codex-connector also caught: the dropdown is positioned left: 0 inside the overflow: hidden sidebar, so on a narrow sidebar — and with the longer zh labels — it clips at the right edge. Merging this and right-aligning the menu in a quick follow-up so you don't have to round-trip. Thanks for the contribution!
|
To use Codex here, create a Codex account and connect to github. |
…ip (#4196) Follow-up to #4150. The dropdown was positioned `left: 0` inside the `overflow: hidden` sidebar; the trigger sits in the right-aligned header, so on a narrow sidebar — and with the longer zh labels — the menu ran past the right edge and got clipped. Right-align it so it opens leftward into the sidebar. Also fold the duplicated Latest-10 / Latest-20 cutoff IIFEs into one helper; behavior is identical (same Set-dedup, sort, and short-list fallback).
Follow-up to #4150. The dropdown is marked up as role=menu/menuitem but was mouse-only. Add Escape-to-close (focus returns to the trigger), move focus into the menu on open (the selected option, else the first), and ArrowUp/Down/Home/End roving between options. The trigger gains aria-haspopup / aria-expanded.

Add a clock button in the project tree header that lets users filter sidebar topics by time.
topicActivityTime()localStorageNotes
CreatedAtpersistence onProjectNodewere already present in upstream, no backend changes needed