Skip to content

feat(mobile): phone Compact rows fold to one line like the desktop compact card - #29127

Closed
Jinwoo-H wants to merge 132 commits into
mainfrom
feat/workspace-options-7-phone-compact-row
Closed

Jinwoo-H wants to merge 132 commits into
mainfrom
feat/workspace-options-7-phone-compact-row

Conversation

@Jinwoo-H

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

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​143 $\color{#cf222e}{\Huge{\mathbf{−}}}$​8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​135
Prod 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​100 $\color{#cf222e}{\Huge{\mathbf{−}}}$​55 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​45

ELI5

On the phone, picking "Compact" in the workspace options barely changed anything: each workspace still took two or three lines. Now Compact on the phone looks like Compact on the desktop: one line per workspace, with the project shown as a small icon in front of the name.

What Changed

Before (phone, Compact): every workspace row kept its second line with the project name, the branch or folder path, and the "Child" badge. Only the host badge and the agent list went away.

After (phone, Compact): the row is a single line in the common case. In order: status, a small framed project icon (left out when the list is already grouped by project, and for folder workspaces, same as the desktop), the workspace name, the PR badge and other indicators, and the terminal count. The second line is gone. A remote project (SSH or Orca server) keeps a small server icon in the title line, the same way the desktop's compact card keeps an icon-only host marker. The "N children" toggle stays because the desktop keeps it too. Detailed is unchanged.

With the new card style turned on, the phone now also shows the project as the title icon instead of repeating its name on the second line, which is what the desktop does.

Mechanism: getWorktreeCardDetails in src/shared/worktree/card-properties.ts gained two answers: repoInTitle (the project goes in the title as an icon) and metaRow (the card keeps its second, identity line); the host badge is part of that line. The desktop card (worktree-card-presentation.tsx) reads those answers from the details its foundation computes once, with identical results, and the phone list hands the same one details object to each row. The desktop's other dense-layout rules (title-row indicators, the icon-only SSH control) stay its own. A remote row in Compact keeps a small host icon, shown whenever its host is remote (isRemoteExecutionHostId, next to parseExecutionHostId).

Why

The phone is meant to mirror the desktop. Copying the desktop's conditions into the phone would let them drift apart again, so the decision now lives in one place that both read. I kept the shared rule to the two decisions the phone actually needs; desktop-only concerns (hover cards, quick actions, cache timers) stay in the desktop file. The desktop's compact card shows the branch only in a hover card; the phone has no hover, and showing the branch as a second line would make most rows two lines again, so the phone follows the desktop and leaves it out of Compact.

Linked Issue

Stacked on #28997 (workspace options on the phone).

Visual Proof

Android emulator, phone page built from this branch (footer build 13ec8575), paired to a test desktop with one local and four Orca-server workspaces.

Detailed (group by None) Compact (group by None) Compact (group by Project)

Compact is one line per workspace: the server icon marks Orca-server workspaces (the local "main" has none), the framed folder is the project icon, and it is left out when the list is already grouped by project.

Testing

  • Shared rule test in src/shared/worktree/card-properties.test.ts; row tests in mobile/src/components/WorktreeListRow.test.ts (Compact drops the second line, shows the project icon, hides it when grouped by project or for folders, keeps the PR badge, keeps a remote host icon but none for local, new card style keeps the branch line).

  • Desktop sidebar tests (src/renderer/src/components/sidebar/, 365 files) pass with no test bodies changed.

  • pnpm tc:web, pnpm run check:code-quality:changed, mobile tsc, mobile tests-typecheck ratchet, mobile oxlint and oxfmt check all pass.

  • I manually tested these changes locally (Android emulator, screenshots above)

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Elegance and bug review by a separate reviewer; its should-fix items (remote host icon on compact rows, a new-card-style test, passing the computed layout from the list) are addressed in this PR.

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

No wire or host changes: the phone already receives the card style setting. SSH and Orca server workspaces are covered by the remote host icon. Folder workspaces get no project icon, as on the desktop. No platform-specific code.

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.
… options pager forgets a gone page in an effect
…heet' into feat/workspace-options-7-phone-compact-row

# Conflicts:
#	mobile/src/components/WorktreeListRow.test.ts
…heet' into feat/workspace-options-7-phone-compact-row
…s Workspace options; tighter card glyph wiring

- The phone's Card layout and card style now live in its view state; one parallel settings.get
  reads them with pinned placement beside ui.get.
- The Card layout preset flag is a write option, no longer a view field or a change-event field.
- getWorktreeCardDetails reads the raw desktop settings and resolves the layout once; the desktop
  card computes it once in its foundation.
- The row computes its glyph presence in one place; Ports is hidden through one set.
- Drops the vanished-page effect, the native-only a11y props and the web-only switch branch.
…h tests; pinned placement changes announce themselves
…orca-lanes/wso into feat/workspace-options-7-phone-compact-row

# Conflicts:
#	mobile/src/components/WorktreeListRow.test.ts
#	mobile/src/host-screen/host-workspace-list.tsx
#	src/renderer/src/components/sidebar/worktree-card-presentation.tsx
…isplay names folder paths when the desktop has folder workspaces
…s-6-phone-sheet

# Conflicts:
#	mobile/src/host-screen/host-workspace-list.tsx
#	src/renderer/src/components/sidebar/worktree-card-presentation.tsx
…ce-options-7-phone-compact-row

# Conflicts:
#	mobile/src/components/WorktreeListRow.tsx
#	src/renderer/src/components/sidebar/worktree-card-presentation.tsx
@Jinwoo-H
Jinwoo-H changed the base branch from feat/workspace-options-6-phone-sheet to main October 16, 2026 02:33
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Shared card details now control repository identity placement, metadata-row visibility, and host-badge visibility. The renderer and mobile card components use those details to render repository and host indicators. Mobile compact-card tests cover repository chips, branch lines, host glyphs, badges, folder rows, and linked PRs. Remote host detection now uses isRemoteExecutionHostId.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 3cc70

Screen-reader users may miss host and project identity in Compact rows. Add those names to the row label; this is a bounded issue rather than a broad merge blocker.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 7 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 and concisely describes the main change: mobile Compact rows now use a single line like the desktop compact card.
Description check Passed The description is complete and relevant. It covers the user-facing change, implementation, rationale, linked issue, visual proof, testing, AI disclosure, compatibility notes, and checklist items.
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ca44bec0-67b0-42a2-a3c4-aff50a231ade
📥 Commits

Reviewing files that changed from the base of the PR and between a542367 and 3cc7032.

📒 Files selected for processing: 7
  • mobile/src/components/HostKindIcon.tsx
  • mobile/src/components/WorktreeListRow.test.ts
  • mobile/src/components/WorktreeListRow.tsx
  • src/renderer/src/components/sidebar/worktree-card-presentation.tsx
  • src/shared/execution-host.ts
  • src/shared/worktree/card-properties.test.ts
  • src/shared/worktree/card-properties.ts

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

<View style={styles.worktreeMain}>
<View style={styles.worktreeNameRow}>
{showHostGlyph && (
<View accessibilityLabel={hostAccessibilityLabel || 'Remote host'}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '48,200p' mobile/src/components/WorktreeListRow.tsx
sed -n '405,535p' mobile/src/components/WorktreeListRow.test.ts
rg -n 'accessibilityLabel|accessible=|accessibilityRole|hostAccessibilityLabel' mobile/src/components/WorktreeListRow.tsx

Repository: stablyai/orca

Length of output: 11230


🏁 Script executed:

set -eu
printf '%s\n' '--- WorktreeListRow imports and enclosing declaration ---'
sed -n '1,135p' mobile/src/components/WorktreeListRow.tsx
printf '%s\n' '--- Pressable and WorktreeListRow references ---'
rg -n -F --glob '*.{ts,tsx}' -e 'from ''react-native''' -e '<WorktreeListRow' -e 'WorktreeListRow' mobile/src | head -160 || true
printf '%s\n' '--- React Native version declarations ---'
rg -n -F --glob 'package.json' -e '"react-native"' -e '"@react-native"' package.json mobile/package.json pnpm-lock.yaml 2>/dev/null | head -80 || true
printf '%s\n' '--- card detail definitions and compact callers ---'
rg -n -F --glob '*.{ts,tsx}' -e 'getWorktreeCardDetails' -e 'repoInTitle' -e 'metaRow' mobile/src src/shared | head -200 || true

Repository: stablyai/orca

Length of output: 10212


🏁 Script executed:

set -eu
printf '%s\n' '--- host workspace list caller ---'
sed -n '1,235p' mobile/src/host-screen/host-workspace-list.tsx
printf '%s\n' '--- shared card details ---'
sed -n '120,180p' src/shared/worktree/card-properties.ts
printf '%s\n' '--- relevant row styles and remaining component ---'
sed -n '135,235p' mobile/src/components/WorktreeListRow.tsx

Repository: stablyai/orca

Length of output: 15366


🌐 Web query:

official React Native 0.83 Pressable accessibility accessible parent children accessibilityLabel documentation

💡 Result:

For **React Native 0.83**, the official docs say:

- `accessible={true}` makes a view discoverable to assistive technologies. RN notes that nested accessibility elements may not both receive focus; by default, touchable elements are accessible. ([reactnative.dev](https://reactnative.dev/docs/0.83/accessibility))
- `accessibilityLabel` supplies the screen-reader label. Without one, RN constructs the label by joining descendant `Text` node content with spaces. ([reactnative.dev](https://reactnative.dev/docs/0.83/accessibility))
- The 0.83 `Pressable` page lists its press-specific props, but doesn’t separately document these accessibility props. That omission doesn’t mean they’re unavailable: the 0.83 implementation is the relevant source for checking how `Pressable` receives props. ([reactnative.dev](https://reactnative.dev/docs/0.83/pressable))

So for a button-like `Pressable` with child content, provide an explicit label when the visible text doesn’t clearly describe the action:

```tsx
<Pressable
  accessibilityRole="button"
  accessibilityLabel="Open settings"
  onPress={openSettings}
>
  <Text>Settings</Text>
</Pressable>
```

The docs establish the label behavior and accessibility semantics; actual focus and announcement can vary between iOS and Android. ([reactnative.dev](https://reactnative.dev/docs/0.83/accessibility))

Citations:

- 1: https://reactnative.dev/docs/0.83/accessibility
- 2: https://reactnative.dev/docs/0.83/accessibility
- 3: https://reactnative.dev/docs/0.83/pressable
- 4: https://reactnative.dev/docs/0.83/accessibility

Include icon-only identity in the compact row label.

Compact rows can show host and project identities only through child View labels. React Native treats the Pressable as the accessible touchable and groups its descendants, so those labels do not reliably name the row. Add the applicable host and project names to the row label while retaining the workspace name.

Suggested fix
+  const iconOnlyIdentityLabels = [
+    showHostGlyph ? hostAccessibilityLabel || 'Remote host' : null,
+    showRepoChip ? `Project ${item.repo}` : null
+  ].filter((label): label is string => Boolean(label))
+
   return (
     <Pressable
+      accessibilityLabel={
+        iconOnlyIdentityLabels.length > 0
+          ? [item.displayName || item.repo, ...iconOnlyIdentityLabels].join(', ')
+          : undefined
+      }
       style={({ pressed }) => [

@Jinwoo-H

Copy link
Copy Markdown
Contributor Author

Included in #29225, which merged with this PR's compact row changes, so closing this one.

@Jinwoo-H Jinwoo-H closed this Oct 16, 2026
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