Repository navigation
refactor(sidebar): the Workspace options menu renders from one shared option list - #28931
Conversation
…sleeping filters A phone created through a desktop now records whom it acted for, so the desktop's Hide other-client workspaces filter keeps the user's own phone workspaces (locally and on servers) and hides only other people's. The desktop relay marks each server row its sidebar would hide, and the rows its own browser or chat tabs keep awake; the phone applies both.
The project sections the desktop sidebar draws under Group by Project are built from the project/setup projection, the per-host catalog fold, the endpoint host stamp, and the section labelling and ordering rules. Move the pure parts of each into src/shared (no behaviour change) so the paired phone can call the same code instead of guessing the grouping.
The phone grouped Project sections by repo display name, so it merged checkouts the desktop keeps apart (several local stablyai/orca checkouts, or a server's copy) and split what the desktop merges. The phone now reads each host's repo.list, project.list and projectHostSetup.list (the desktop's own and each server's through the relay), folds them with the renderer's shared catalog fold and projection merge, and builds sections with the desktop's shared grouping, labelling and ordering code. Keys, labels and Recent order equal the sidebar's; a host that predates the project lists gets the repo-derived projection, as the desktop does. Header colour and icon come from the section's anchor repo and rows from their own repo by id, as on the desktop. The repo filter lists every catalog repo by id per host instead of merging same-named repos. Group-by labels read Project and None. project.list and projectHostSetup.list are routed for paired phones (read-only, workspace permission).
…tab activity into the host flag A phone now records its owner as the creator (this host for a directly paired phone, the relaying desktop for a delegated one), so the shared rule is unchanged and onBehalfOf is gone. The relay ORs the desktop's saved browser/chat tabs into hasHostSidebarActivity, so the phone's status dot follows too, and local worktree.ps counts the same signals. The old-server fallback is removed.
…ct-grouping-shared
…ct-grouping-shared
…the desktop's repo order Review fixes. The sidebar's buildOrderedGroups now calls the shared buildProjectSections for Group by Project (notice and pending repos are optional inputs), so desktop and phone run one function. The phone ranks manual header order by its folded catalog order, which is each host's repo.list order with the desktop's own repos first, as the renderer loads them; only the desktop's cross-host manualRepoOrder (pairing-local) is unknown to it. The projection selector is now a memo over the shared resolver. A transient project-list failure keeps that host's last catalog; only forbidden or method_not_found falls back to the repo-derived projection. Catalog rows are checked on every member grouping reads, and the parity test answers the renderer's real catalog fetch, includes a server's own project rows, and fixes Manual and Recent orders that differ.
… of the phone fetching it The phone fetched repo.list, project.list and projectHostSetup.list from the desktop and every relay-ready server each minute and folded them itself, so rows from offline or older servers fell back to repo sections and keys moved on cold start. The desktop renderer now publishes the inputs its own Project headers use (state.repos in sidebar order, the projection selector's projects and setups, projectOrderBy) on the runtime window graph it already sends to main, only when they change. Main keeps the last one and answers it on a new paired-desktop read, mobileRelay.projectSections. The phone reads it with its repo metadata and groups with the shared builder; a desktop that predates the read (forbidden or method_not_found) leaves the phone grouping rows by repo from the desktop's repo.list, and a transient failure keeps the last catalog. The payload carries only grouping and label fields, no hooks, credentials or uploaded icons. The phone's own catalog fetching and its project.list/projectHostSetup.list routes are removed.
… recorded repo-metadata flow The extra read inside the repo-metadata refresh stalled its recorded RPC sequence. It now runs beside it with its own throttle. The repo-metadata goldens are re-recorded for the intended state change: the name-keyed colour, icon and id maps became desktopRepos.
# Conflicts: # config/scripts/ci-shard-timings.json
…ator The sidebar, the jump palette and the options badge each applied the Workspace options filters with their own copy of the rules. They now call one pure evaluator in src/shared/workspace-view, which reads each row through a small fact reader so the phone can call it next with the facts its host serves. The sort comparator moves there too, with each surface supplying its own Agent activity rank. No behaviour change: a parity test runs the old sidebar chain and the new evaluator over every toggle combination, and the old comparator against the new one for each sort mode.
…ng-shared # Conflicts: # config/scripts/ci-shard-timings.json # mobile/src/host-screen/host-workspace-list.tsx # src/renderer/src/i18n/en-runtime-required.json
…luator One predicate builder instead of a per-row scope argument; one exemption-narrowing function taking the hide flag; consumers import the shared predicates directly; Agent activity ties fall to the label in the shared comparator. The activation filter lift reuses the shared predicates. The parity test now covers other-client rows, live agent attention classes and the sort-label map.
…-options-2-descriptors
…-options-2-descriptors
…to another host The refresh window is now kept per client, so a host screen reused for another desktop doesn't wait out the previous host's 60s window before showing its Project headers.
…-options-2-descriptors
… option list src/shared/workspace-view/workspace-view-options.ts lists every Workspace options control (Hosts, Projects, Group by, Sort by, Project order, Card layout, Show properties or Card display, and each filter) with its section, its English copy, its choices and when it shows. The desktop menu walks that list and renders each option through a table keyed on the option key, so a new option fails to compile until the desktop has a control for it; the phone sheet will do the same. The choice lists (sort modes, project order, card layout, agent activity layout, card properties) are derived from the shared ids, and a test checks the desktop's English copy matches the shared copy. The unused classic branch of the card property list is removed. No visible change.
…-options-2-descriptors
The show rules get their own tests with fixed expectations (the cases the old filter-section test covered, plus the rest), and the menu test checks each option's own control rather than any matching text. Card display rows toggle one property each, with the folder-path copy in shared. The unused Status and Tasks copy left by the removed classic card list is deleted from the catalogs. Filter rows write through the store directly; files are named for what they hold.
…ignores non-finite times
…-options-2-descriptors
… focus setting again
…-options-2-descriptors
…s them to the Hosts item
…s-1-evaluator # Conflicts: # src/renderer/src/components/activity/activity-scope-filter.ts # src/renderer/src/components/sidebar/add-repo-skip-finalization.ts # src/renderer/src/components/sidebar/default-branch-folder-project-root.test.ts # src/renderer/src/components/sidebar/default-branch-visible-under-hide-sleeping.test.ts # src/renderer/src/components/sidebar/sidebar-filter-state.test.ts # src/renderer/src/components/sidebar/visible-worktree-kinds.ts # src/renderer/src/components/sidebar/visible-worktrees.ts # src/renderer/src/components/sidebar/worktree-list/listing/use-filters.ts # src/renderer/src/components/use-worktree-jump-palette-worktrees.ts
…-options-2-descriptors
…lper files back - The four kind switches come from WORKSPACE_KIND_SWITCH in workspace-kinds; the shared evaluator and main's phone-push check both apply it, and the filter type derives its switch fields from it. - git-identity.ts folds back into git-identity-display.ts, as on main. - SidebarFilterState moves into sidebar-filter-actions.ts; visible-worktree-kinds.ts is gone. - buildWorkspaceRowPredicate is module-private.
…-options-2-descriptors
…s-1-evaluator # Conflicts: # src/renderer/src/components/sidebar/sidebar-filter-state.test.ts # src/renderer/src/components/worktree-jump-palette-sleeping-filter.test.ts
…-options-2-descriptors
…kspace-options-2-descriptors
…kspace-options-2-descriptors
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
ELI5
The desktop's "Workspace options" popover is now drawn from one shared list of options. Each entry says which section it sits in, what it is called, and when it appears. The desktop menu looks exactly the same. The phone's options sheet will be drawn from the same list in a later PR, so adding an option to the desktop without giving the phone a control for it will fail to compile.
What Changed
src/shared/workspace-view/workspace-view-options.ts:workspace-options-menu-items.tsx) walks the list and draws each option from a table keyed on the option's name. A missing entry is a type error.worktree-card-option-items.tsx), and the filter rows into one keyed table (workspace-filter-option-row.tsx).sidebar-workspace-option-items.ts) are built from the shared ids. Translation keys and default strings are unchanged.Before and after, as a user sees it: identical.
Why
Today the phone shows four of these controls and has its own idea of their order and wording ("Repo", "PR Status"). Sharing the list, the show rules and the copy is what keeps the two from drifting. The desktop keeps its translated labels with literal keys, because the extractor needs literal strings, and a test checks that the desktop's English copy equals the shared copy the phone will show.
I kept the choice lists as separate constants rather than nesting them inside each option. Every surface already needs a renderer per option, and a nested union type would add casts without catching anything more.
Linked Issue
Second of the four-PR Workspace options parity series for the phone (#28915, this one, #28986, #28997). Stacked on #28915.
Visual Proof
N/A: no visible change. The menu render test checks that every shown option gets its control, in order, under several setups.
Testing
workspace-view-options.test.ts: the show rules, with fixed expected option lists (single local project, remote hosts, several projects, grouping by project, hide sleeping, servers present or loading, the filter already on, experimental card style).workspace-options-menu-items.test.tsx: renders the real menu and checks each shown option's own control appears, in order, with its section heading, including while the server catalog is still loading.sidebar-workspace-option-items.test.ts: the desktop's English copy equals the shared copy for every choice list.Sidebar suites pass (366 files).
pnpm tc:web,check:code-quality:changed, and the localization catalog checks pass.I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Reviewed by an Opus bug pass and an Opus design pass before opening; the second commit folds in their findings (stronger tests, single-property card rows, catalog cleanup).
Agent skill upstream boundary
Notes
Desktop only; no wire or settings change.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)