feat(linear): add issue presets and status/assignee/label filters#7388
feat(linear): add issue presets and status/assignee/label filters#7388daniphant wants to merge 5 commits into
Conversation
Adds a server-side preset radio (Assigned/Created/All/Completed) and a client-side Filters popover (status, team, assignee, label) to the Linear issues tab. Presets flow through the fetch request signature, resume state, and warm prefetch; row filters derive their options from fetched issues and reconcile stale selections on preset or workspace switches. Extracts the GitHub PR toolbar's filter chrome into provider-neutral shared components (task-filter-pickers, task-filter-section-menu, task-filter-pill, task-preset-buttons) consumed by both toolbars, and localizes all new strings for es/ja/ko/zh. Claude-Session: https://claude.ai/code/session_01EigB95j2H5tPwtQ41CGrUp
# Conflicts: # src/renderer/src/i18n/locales/es.json # src/renderer/src/i18n/locales/ja.json # src/renderer/src/i18n/locales/ko.json # src/renderer/src/i18n/locales/zh.json
📝 WalkthroughWalkthroughChangesThis PR adds shared task-filter primitives, Linear-specific assignee/label/status filter helpers, and a new Linear filter toolbar with preset-driven issue fetching. TaskPage now restores and applies the active Linear preset during fetches and persistence. GitHub PR filter pills and section controls now use shared components. Locale files were updated for the new filter labels, menu text, and empty-state messages. Sequence Diagram(s)Included within the hidden review stack artifact. Related Issues: Not specified in provided information. Related PRs: Not specified in provided information. Suggested labels: review_needed_senior_swe, review_depth_deep Suggested reviewers: Not specified in provided information. Poem 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/renderer/src/components/task-filter-pickers.tsx (2)
87-93: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFallback strings bypass localization.
'Loading…','Search text is too large.','Press Enter to use the typed value.', and'No matches'are hardcoded, while every other user-facing string in this file goes throughtranslate(...). Given the PR's goal of localizing filter UI strings for es/ja/ko/zh, these will silently stay English.🌐 Proposed fix
- const fallback = loading - ? 'Loading…' - : queryTooLarge - ? 'Search text is too large.' - : showCustom - ? 'Press Enter to use the typed value.' - : (error ?? emptyText ?? 'No matches') + const fallback = loading + ? translate('auto.components.task.filter.pickers.loading', 'Loading…') + : queryTooLarge + ? translate('auto.components.task.filter.pickers.query.too.large', 'Search text is too large.') + : showCustom + ? translate('auto.components.task.filter.pickers.press.enter', 'Press Enter to use the typed value.') + : (error ?? emptyText ?? translate('auto.components.task.filter.pickers.no.matches', 'No matches'))Apply the equivalent change to the
MultiSelectListfallback (Lines 173-177).Also applies to: 173-177
199-207: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win"Clear (N)" label splits translation across concatenation.
translate(...)only wraps'Clear ('; the count and closing paren are appended as raw JSX. Translators can't reorder the count relative to the verb, so this breaks correct phrasing in es/ja/ko/zh — the exact locales this PR targets.🌐 Proposed fix using interpolation
- {translate('auto.components.task.filter.pickers.2534e82b7d', 'Clear (')} - {selected.length}) + {translate('auto.components.task.filter.pickers.2534e82b7d', 'Clear ({{count}})', { + count: selected.length + })}
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ea964f87-7b9a-4834-93e9-c6b93171f6e0
📒 Files selected for processing (25)
src/renderer/src/components/TaskPage.tsxsrc/renderer/src/components/github/PRFilterDropdowns.tsxsrc/renderer/src/components/github/PRFilterPickers.test.tssrc/renderer/src/components/github/PRFilterSections.tsxsrc/renderer/src/components/linear-assignee-filter.test.tssrc/renderer/src/components/linear-assignee-filter.tssrc/renderer/src/components/linear-issue-filters.tsxsrc/renderer/src/components/linear-label-filter.test.tssrc/renderer/src/components/linear-label-filter.tssrc/renderer/src/components/linear-status-filter.test.tssrc/renderer/src/components/linear-status-filter.tssrc/renderer/src/components/task-filter-pickers.test.tssrc/renderer/src/components/task-filter-pickers.tsxsrc/renderer/src/components/task-filter-pill.tsxsrc/renderer/src/components/task-filter-section-menu.tsxsrc/renderer/src/components/task-filter-selection.tssrc/renderer/src/components/task-page-localized-options.tsxsrc/renderer/src/components/task-preset-buttons.tsxsrc/renderer/src/hooks/useReconciledFilterSelection.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/store/slices/ui.ts
💤 Files with no reviewable changes (1)
- src/renderer/src/components/github/PRFilterPickers.test.ts
The header scope selector already owns the team dimension, so the popover offered a second UI path to the same filter. Team filtering itself is unchanged — the scope selector still drives it.
Fall back to the connection viewer's display name when no single workspace is selected. The workspace displayName is already the viewer's per-org name (not the org label), but it is null under "all workspaces", which silently dropped the Me pin. Addresses CodeRabbit review on stablyai#7388.
The single/multi select fallback messages (Loading, query-too-large, custom-value hint, no matches) were hardcoded English, and the Clear (N) label split the count outside translate(), so translators could not reorder it. Both flagged by CodeRabbit on stablyai#7388.
Description
The Linear issues tab fetched one hardcoded list (
filter: 'all') and offered only search plus the team scope selector. This adds the two filtering layers the GitHub toolbar already has:Server-side presets. An Assigned / Created / All / Completed radio drives
listLinearIssues— the preset is part of the request signature (so switching presets refetches without a nonce bump), persists viataskResumeState.linearPreset, and the warm prefetch inui.tsnow reads the resumed preset instead of unconditionally warmingall, so the cache entry it prepares is the one the page actually requests on mount.Client-side row filters. A collapsed Filters popover with drill-in sections for Status, Assignee, and Label, rendered as closeable pills when active. Options are derived from the fetched rows (Linear has no cheap per-workspace endpoint for these): statuses keep Linear's workflow ordering (
triage → backlog → unstarted → started → completed → canceled) with their state colors, assignees get the viewer pinned on top with aMetag plus anUnassignedsentinel (__unassigned__, can't collide with a real UUID). Empty selection means "all";reconcileTaskFilterSelectiondrops ids that vanish after a preset/workspace switch so a stale selection can't silently filter everything out, and returns the sameSetreference when nothing changed so the reconcile effect never causes an extra render. The team dimension intentionally stays with the existing header scope selector — the popover does not duplicate it, so each filter has exactly one UI path.Rather than copying the GitHub toolbar's markup, its chrome moved into provider-neutral components consumed by both:
PRFilterPickers→task-filter-pickers(out ofcomponents/github/, lists now generic over the option type so callers carry color/avatar on the option instead of re-joining by key per row),task-filter-section-menu(drill-in menu + back header),task-filter-pill, andtask-preset-buttons— the existing Jira preset row now renders through that last one too.ELI5
The Linear tab used to be one long list you could only search. Now it has quick tabs — Assigned, Created, All, Completed — that ask Linear for different lists, and a Filters button to narrow what's on screen by status, assignee, or label, exactly like the GitHub PRs toolbar. Under the hood the GitHub toolbar's filter widgets were turned into shared parts instead of being copy-pasted, so the two toolbars can't drift apart.
Screenshots
Evidence
New unit tests pin the behavior that matters: option collection (dedupe, sort, workflow-state ordering, unassigned appended last), match semantics (empty = all, sentinel matching), reconciliation (drops stale ids, falls back to all when every id is stale, returns the same
Setreference when nothing changed — the contract the reconcile hook relies on), and the picker query-size guards after the move/rename.Full gates green locally:
pnpm lint(incl. locale parity + coverage verifiers),pnpm typecheck(node/cli/web),pnpm test,pnpm build.Trade-offs
components/github/PRFilterPickersmoved and renamed. Blast radius is the GitHub PR toolbar, which now consumes the shared components; its picker tests moved with the module and the affected i18n keys were migrated to the new namespaces with existing translations preserved. One deliberate visual delta outside Linear: the Jira active preset pill dropsbackdrop-blur-md(a no-op on an opaque background), and the GitHub filter menu heading gains the space it was missing ("Filterpull requests").Scope
Renderer-only plus one line in the
ui.tsstore slice; no new IPC channels, no main-process changes, no dependencies. Nothing platform-, SSH-, or provider-specific: no shortcuts, paths, or shell behavior touched, and filtering runs over rows already fetched through the existing Linear IPC. Preset ids are double-validated (renderer resume-state whitelist + existingVALID_FILTERSin the main process), so IPC can't be driven with arbitrary filter strings. Avatar URLs come from the Linear API and render through the same<img>pattern the Linear UI already uses.AI review: audited with Claude Code (repo-convention pass + four-agent reuse/simplification/efficiency/altitude review); confirmed findings were applied before opening — shared-chrome extraction instead of a second markup copy, one reconcile hook instead of three copy-pasted effects, generic pickers instead of per-row
Array.find, one preset-row component for Linear and Jira.X: @pinchyNeb