Skip to content

refactor(sidebar): the Workspace options menu renders from one shared option list - #28931

Merged
Jinwoo-H merged 49 commits into
mainfrom
feat/workspace-options-2-descriptors
Oct 15, 2026
Merged

Jinwoo-H merged 49 commits into
mainfrom
feat/workspace-options-2-descriptors

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​330 $\color{#cf222e}{\Huge{\mathbf{−}}}$​180 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​150
Prod 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​923 $\color{#cf222e}{\Huge{\mathbf{−}}}$​826 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​97

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

  • New src/shared/workspace-view/workspace-view-options.ts:
    • The option list, in menu order: Hosts, Projects, Group by, Sort by, Project order, Card layout, Show properties (or Card display in the experimental card style), and the six filters, including "Except default branch".
    • The choices for each picker (group modes, sort modes, project order, card layout, agent activity layout, card properties), with their English copy.
    • The rules for when each option shows (for example, Project order only while grouping by project, and the other-client filter only when servers may exist or while it is on).
  • The desktop menu (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.
  • The card menu is split into one component per option (worktree-card-option-items.tsx), and the filter rows into one keyed table (workspace-filter-option-row.tsx).
  • The desktop's picker lists (sidebar-workspace-option-items.ts) are built from the shared ids. Translation keys and default strings are unchanged.
  • Removed: the classic branch of the card property list, which nothing rendered, and its two now-unused translations ("Status", "Tasks").

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

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Desktop only; no wire or settings change.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…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.
…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.
…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.
… 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.
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.
…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
…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.
…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
@Jinwoo-H
Jinwoo-H changed the base branch from feat/workspace-options-1-evaluator to main October 15, 2026 09:51
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 42cbda7f-caf5-4ad4-8ca7-caba72c2792d
📥 Commits

Reviewing files that changed from the base of the PR and between 1e2c0a7 and a17caea.

📒 Files selected for processing: 19
  • src/renderer/src/components/sidebar/SidebarWorkspaceFilterSection.test.tsx
  • src/renderer/src/components/sidebar/SidebarWorkspaceFilterSection.tsx
  • src/renderer/src/components/sidebar/WorktreeCardDisplayMenuSection.tsx
  • src/renderer/src/components/sidebar/sidebar-workspace-option-items.test.ts
  • src/renderer/src/components/sidebar/sidebar-workspace-option-items.ts
  • src/renderer/src/components/sidebar/workspace-filter-option-row.tsx
  • src/renderer/src/components/sidebar/workspace-options-menu-items.test.tsx
  • src/renderer/src/components/sidebar/workspace-options-menu-items.tsx
  • src/renderer/src/components/sidebar/worktree-card-display-property-options.ts
  • src/renderer/src/components/sidebar/worktree-card-option-items.test.tsx
  • src/renderer/src/components/sidebar/worktree-card-option-items.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/workspace-view/workspace-view-options.test.ts
  • src/shared/workspace-view/workspace-view-options.ts
💤 Files with no reviewable changes: 10
  • src/renderer/src/components/sidebar/SidebarWorkspaceFilterSection.tsx
  • src/renderer/src/components/sidebar/worktree-card-display-property-options.ts
  • src/renderer/src/components/sidebar/WorktreeCardDisplayMenuSection.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/components/sidebar/SidebarWorkspaceFilterSection.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

📝 Walkthrough

The sidebar workspace options menu now uses shared definitions for option choices, ordering, section headings, and context-based visibility. The menu renders filter toggles and worktree-card controls through separate components. New tests cover shared option visibility, localized option labels, and menu rendering across several contexts. The locale files remove the workspace-option labels for status and tasks.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to a17ca

This refactors the Workspace options menu onto a shared option list with no intended visible change. No concrete merge-blocking risk was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly identifies the main change: the Workspace options menu now renders from one shared option list.
Description check Passed The description covers the required sections, explains the user-visible behavior and implementation, identifies the linked issue series, documents testing, and completes the checklist. It also correct…
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Jinwoo-H
Jinwoo-H merged commit a89b105 into main Oct 15, 2026
39 of 59 checks passed
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