Files
orca/tests
Jinwoo Hong da8de743eb feat(mobile): the phone filters and sorts with the desktop's shared Workspace options rules (#28986)
* fix(mobile): phone server rows follow the desktop's other-client and 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.

* test(e2e): dedupe sidebar row ids in the visibility spec

* fix(mobile): address review: owner naming, old-server relayed phones, scope trim

* refactor: move the sidebar's project section grouping into shared

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.

* fix(mobile): group by Project with the desktop sidebar's own sections

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).

* refactor(mobile): record a phone's owner as creator and fold desktop 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.

* test(e2e): poll the phone and the sidebar together in the visibility spec

* fix(mobile): one Project-section builder for desktop and phone, with 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.

* fix(mobile): use the repo-derived catalog until a host's project lists first answer

* fix(mobile): the desktop serves its sidebar's Project catalog instead 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.

* fix(mobile): keep uploaded repo icons on the phone, and isolate the snapshot tests

* test(e2e): the desktop window serves its sidebar's Project catalog to the phone

* chore(i18n): regenerate the runtime English catalog (drift already on main)

* fix(mobile): read the Project catalog as its own request, outside the 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.

* refactor(sidebar): one shared Workspace options filter and sort evaluator

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.

* wip: workspace view descriptors

* refactor(sidebar): address review of the shared Workspace options evaluator

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.

* fix(mobile): fetch the Project catalog at once when the screen moves 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.

* wip: desktop option items from shared choices

* refactor(sidebar): the Workspace options menu renders from one shared 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.

* wip: row facts

* refactor(sidebar): address review of the shared Workspace options list

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.

* feat(runtime): worktree.ps carries the Workspace options row facts

Each worktree.ps row now says whether it is the project's default-branch
workspace, created by an automation, created by the CLI, or on a
detached HEAD, answered by the same shared predicates the desktop
sidebar filters with. The phone has none of these facts today, so its
copies of the filters either guess (default branch) or are missing.

Additive and inert: optional fields that nothing reads yet. An older
host omits them; readers must treat absence as 'not that kind', so the
row stays visible. Server rows relayed through the desktop pass them
through unchanged.

* feat(mobile): the desktop's own phone reads and writes its other-client filter

ui.get and ui.set strip the pairing-local UI fields for every paired
client. That rule exists for clients with their own host catalog (a
desktop paired to a server, a web client). A phone has none: it shows
its desktop's view, so 'Hide other-client workspaces' must be one shared
value. For the host's own phone (clientKind mobile, no relaying parent)
ui.get now always answers the field and ui.set persists it; delegated
phones, paired desktops and web clients keep the stripping rule.

The phone's view-settings schema reads the rest of the Workspace options
values it does not read yet (the automation, CLI and detached-HEAD
filters, project order, card properties, agent activity layout), each
salvaged to absent so an older desktop keeps the phone's local value.

* feat(runtime): address review of the worktree.ps row facts

Name the fact defaultBranch, and say what absence means for it: a reader
keeps its own guess, because 'hide default branch' must not start hiding
or showing rows an older host never described. Folder workspace rows get
the facts too, so every row from a newer host carries them. The
desktopOtherClient comment now says it is a verdict, not a fact. The test
covers the rows where the host disagrees with the phone's current guess.

* wip: phone uses the shared evaluator

* feat(mobile): address review of the phone's other-client filter mirror

One isHostOwnPhone predicate decides both who shares the filter value
(ui.get/ui.set) and who gets the other-client row marks, so the two
cannot drift. The pairing-local field list notes the exception. Tests
cover a paired desktop still being stripped, and a phone write that
leaves the field out not touching the host value.

* feat(mobile): the phone filters and sorts with the desktop's shared Workspace options rules

The phone's own filter chain and comparator are replaced by the shared
evaluator, reading the facts the host stamps on each row. Phone filter
state takes the desktop's setting names and carries every filter; the
badge and Clear filters match the desktop's. The desktop's other-client
marks become facts the phone filters with the shared value.

A parity test runs the same workspaces through the desktop's reading
and the phone's (facts stamped as the host stamps them) over every
filter combination, folder projects included.

* fix(mobile): host scope in shared filters, patch-key writes, live view push

Folds the phone's host filter into the shared filters so Clear and the badge agree,
writes back only the touched filter keys, and pushes workspaceViewChanged so an open
phone list re-reads desktop Workspace options.

* fix(mobile): Clear sends only changed filters; skip a view re-read older than a local edit

An older host's strict ui.set refuses a key it lacks, so Clear names only what differs. A ui.get
that started before a local edit no longer overwrites it; the edit's own change event re-reads.
The change publisher's field list now lives with it and derives the filter fields.

* fix(workspace-options): all hosts and clear reach the desktop; one filter key list drives every surface

Host visibility: the legacy host scope fallback moves to shared code; the phone reads it, and the
host resets the scope whenever a client writes the host list, so All hosts and Clear stick.
An older desktop that keeps the other-client filter to itself has its marks treated as hide.
Badge counts, is-anything-filtered, Clear and reveal derive from the shared filter keys on the
desktop sidebar and the kanban filter menu. The phone uses the desktop's group and sort values,
holds its view in one state value, and reads its UI fields from one shared list.

* feat(workspace-options): Status and PR grouping read the shared tables

The phone groups by status with the desktop's status helpers and default catalog (its own copy
had the defaults in reverse order) and by PR with a shared lane table, so its labels now read
In review / In progress as the desktop's do. The desktop's PR lane decision ends in the same
shared state-to-lane step.

* test(sidebar): folder rows survive restrictive filters; create grace ignores non-finite times

* fix(mobile): write the host scope with the host list, and wait for the first read before forcing the other-client filter

An older desktop narrows back to its legacy single-host scope when the phone
writes only a null host list, so the phone now also writes the scope for a null
or one-host list. The other-client fallback waits for the first ui.get so a new
desktop's marks don't hide rows before its filter value arrives. Inline the
clear-filters key list and note the PR state asymmetry.

* test(mobile): re-record view-settings goldens for the unknown other-client state

* fix(sidebar): reuse the palette's default host instead of reading the focus setting again

* fix(sidebar): read the host options once in Workspace options and pass them to the Hosts item

* fix: parse the ui.get reply into a named type, fix the row memo test's filters, prune the focus-read baseline

* refactor(sidebar): one switch-to-kind table, and fold the leftover helper 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.
2026-10-15 07:32:59 -04:00
..