Skip to content

feat(mobile): Home's New Workspace opens the desktop's create drawer over Home (native part ships with the next app build) - #31305

Merged
Jinwoo-H merged 16 commits into
mainfrom
phone-new-workspace-action
Oct 22, 2026
Merged

Jinwoo-H merged 16 commits into
mainfrom
phone-new-workspace-action

Conversation

@Jinwoo-H

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

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​316 $\color{#cf222e}{\Huge{\mathbf{−}}}$​172 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​144
Prod 45 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1145 $\color{#cf222e}{\Huge{\mathbf{−}}}$​634 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​511

ELI5

On the phone's home screen, tapping New Workspace used to drop you on that computer's workspace list, and you had to tap + again. Now the familiar "Create worktree" drawer opens right over the home screen. If several computers are connected, you first pick one, the same way Tasks asks.

The drawer comes from your computer (the desktop app), so it updates whenever the desktop does. Only the button, the computer picker and a short loading drawer live in the phone app. The home-screen part reaches phones with the next native app build; current phones keep today's behavior until then.

What Changed

Flow: Home → New Workspace → (picker, if more than one desktop is connected) → a short loading drawer (spinner, or the download bar after a desktop update) → the desktop's real Create worktree drawer, identical to the host screen's + → create → the new session.

Native (app build):

  • New screen: Home's New Workspace pushes /new-workspace?hostIds=…, a transparent modal over Home (app/new-workspace.tsx).
  • Picker inside it: the host picker runs inside that modal (NewWorkspaceHostPicker), because iOS can't present one modal while another is presenting.
  • Loading drawer: NewWorkspaceLoadingDrawer is short and deliberately not form-height. It reuses the shared drawer styles and dim, and tap-outside, drag or Back cancels. It stays mounted for the whole load and fades out when the page paints.
  • Shell overlay mode: passing onOverlayLoad turns the shell into an overlay. It reports loading/painted/unavailable and keeps the WebView invisible until the page paints. When the page opens the session it replaces the modal instead of stacking on it.
  • Exits: NewWorkspaceOverlay owns every way out. A host that can't serve the drawer (old page without the route, wall, failed, offline, blocked) closes the overlay and opens the host list (OpenHostListInstead).
  • Redirect: native /h/[hostId]/new-workspace redirects to the overlay, so a future hop there never renders a frozen native form.

OTA (served by the desktop):

  • New page route: /h/[hostId]/new-workspace (new-workspace.web.tsx → NewWorkspacePageScreen) renders the same NewWorktreeModal the host screen uses, over a transparent page. Closing pops the modal; creating opens the session.
  • Registration: the route is in the page route tables, the census and the sweep. Its grants are navigate, storage, externalLink and haptics.

Removed / simplified:

  • Route param: the old ?action=newWorktree param and its state machine (host-route-action-state.ts) are gone. Only the session-route builder remains, now in host-new-worktree-session-route.ts.
  • Sheet controller: NewWorktreeModalController is a plain visible/onClose component (no imperative handle), and the host screen has one setter for whether its sheet is open.
  • HostProtocolGate: gains two props: transparentWhilePending, and blocked so a blocked host exits the overlay.
  • Host labels: the page drawer reads SSH host names through the same function as the host screen (host-ssh-target-labels-read.ts), so SSH projects show their host's name, not its id.

Why

The owner asked for the drawer over Home, with the rule that host-independent UI is native and everything host-dependent is OTA, because native builds are rare. An earlier version of this PR drew a native copy of the drawer on Home. It worked, but that copy would only change with app builds and could drift from the host screen's. This version keeps the native part to the button, the picker and the loading drawer.

Alternatives we tried on the emulator and dropped:

  • A native drawer hosting the page's form: fixed height, two sides fighting over the keyboard, and drag-vs-scroll conflicts.
  • Dimming Home during loading: it flickered and the download bar had poor contrast.
  • A form-height placeholder: the swap visibly jumped.

Mixed versions:

  • New app + old desktop: the page lacks the route, so the phone opens the host list (today's behavior).
  • Old app + new desktop: the new route is never asked for. Removing action changes nothing, because no shipped app ever passed it to the page.

Bundle: the new route re-splits chunks it shares with Tasks (tasks goes from 11 to 14 scripts per route). The page-asset ceiling projection now crosses at 25 routes instead of 27. That is a derived envelope, not measured growth: there are 91 scripts across 16 routes today. Noting the lost headroom.

Linked Issue

None. The owner reported this directly.

Visual Proof

Android emulator (API 36): a dev client loads this branch through Metro, and headless dev desktops serve the page built at this commit.

Picker (2 desktops) Loading drawer (warm) Loading drawer (download)
The desktop's drawer over Home Same drawer: Home vs host screen + After create

Old desktop (page without the route) falls back to the host list:

Known, accepted:

  • Swap: the swap from the short loading drawer to the full drawer is a one-frame height change, with 1–2 slightly darker frames (~80 ms warm, up to ~200 ms cold) while the two dims overlap. There are no undimmed frames.
  • Cold load: after a desktop update, the existing shell download sits at "8.6 of 8.6 MB" for ~4 s, then the UI stalls ~0.6 s while the WebView is created. Every host screen does this today, so it's out of scope here.
  • Back: from the new session, Back goes to the host list, the same as Home's Resume card.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Emulator:

  • warm and cold opens;
  • cancel by tap-outside, drag and Back (also mid-download);
  • picker cancel;
  • the agent sub-picker scrolls without closing the drawer;
  • create lands on the session, and the host list shows the new worktree;
  • the host screen's + is unchanged and identical to Home's drawer;
  • the old-desktop fallback;
  • Android WebView at opacity 0 still reaches painted, with no stall.

Tests:

  • NewWorkspaceOverlay.test.tsx: the loading drawer is one persistent element across load states, plus the exits.
  • new-workspace-overlay-route.test: single host, picker pick and cancel.
  • NewWorkspacePageScreen.test: close, and create replacing the overlay with the session.
  • Updated route-table, census and sweep tests.
  • NewWorktreeModal.test.tsx for visible/onClose.
  • Five rpc-recording goldens re-recorded; the only change is that routeActionState is gone.

Checks run locally, all passing:

  • mobile/: tsc --noEmit, oxfmt --check ., full vitest run --maxWorkers=4.
  • root: pnpm tc, changed-code quality, React Doctor on changed lines, max-lines ratchet, anti-slop oxlint, root oxlint, build:mobile-web + verify, mobile web app checks.

Failing on main, not from this PR: CI's e2e typecheck ratchet fails on tests/e2e/helpers/phone-feed-list.ts for every PR that touches mobile/, because root and mobile pin different zod versions. #31260 fixes that.

Not verified: iOS (reasoned from code: the WebView is already transparent in MobileWebShellView.swift, the modal is a transparentModal, and the picker runs inside it) and tablets.

Review

Four Opus review rounds across the iterations. The last round found no blockers; its cleanups are in:

  • the overlay owns all exits;
  • one prop instead of two for overlay mode;
  • one boolean on the protocol gate;
  • stale comments and a no-op key removed;
  • the unused clipboard grant dropped.

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

  • Platforms: phone, Android verified; iOS reasoned from code; tablet layouts unverified.
  • Remote/SSH/folder workspaces: these go through the same NewWorktreeModal and executionHost as the host screen.
  • Security: no new RPC surface. The new page route's grants match the host route's minus clipboard.

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)

…gain

The native host-list route dropped the home screen's action=newWorktree when it built
the page's route, so the page opened as a plain list. The route now forwards it, and
the page names it back once applied so a reload does not reopen the sheet.

Reaches phones only with the next native build.
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 3dbd8fc7-33b3-4aed-87bf-ce15d9ffb91c
📥 Commits

Reviewing files that changed from the base of the PR and between 8a10382 and 917624c.

📒 Files selected for processing: 1
  • mobile/src/new-workspace/NewWorkspaceHostPicker.tsx
🚧 Files skipped from review as they are similar to previous changes: 1
  • mobile/src/new-workspace/NewWorkspaceHostPicker.tsx

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

📝 Walkthrough

Home now opens a workspace-creation overlay with connected host IDs. The app selects one host or displays a host picker, then loads the host’s Create Workspace page in a transparent overlay. The overlay reports loading progress and supports cancellation and fallback navigation. Host modal visibility is now controlled through props and local state. Worktree action recordings use revised state snapshots and checkpoint references.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 91762

The host picker’s selection and fallback paths show no remaining merge-blocking issue.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 47.27% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 55 functions across 56 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: Home’s New Workspace flow now opens the desktop create drawer over Home. It is specific and related to the changeset.
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.
Description check Passed The pull request description is detailed and follows the required template. It explains the user impact, implementation, rationale, testing, visual proof, compatibility considerations, review notes, a…
Full details: Description check

Explanation

The pull request description is detailed and follows the required template. It explains the user impact, implementation, rationale, testing, visual proof, compatibility considerations, review notes, and checklist status. The Linked Issue section states "None," which does not meet the template requirement for outside contributors, but the template allows maintainers to omit needless issues; the author’s status is not provided.

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

Replaces the action=newWorktree route hand-off. The quick action picks a
host (as Tasks does), then mounts the host's create sheet on Home; a
create opens the new workspace's session marked as just created.

The sheet controller becomes a plain visible/onClose component, and the
host screen's route-action state, the action route param and its builder
are deleted.
Home's cold-start seed is a pathless resume row that the name suggester
cannot read, so the sheet now seeds from proven rows and is keyed by host.
The host screen gets one setter for the sheet's visibility and its ref.
@Jinwoo-H Jinwoo-H changed the title fix(mobile): home screen New Workspace opens the create sheet (reaches phones with the next native build) fix(mobile): Home's New Workspace opens the create sheet over Home (reaches phones with the next native build) Oct 21, 2026
… page's create form

Home pushes /new-workspace/<host>, a react-native-screens formSheet over Home whose content is
MobileWebShellScreen for the page route /h/<host>/new-workspace. The page renders the shared
Create Workspace form with its drawers inline, so the form and its pickers fill the native
drawer. The shell's loading, download and failure states render inside the drawer. Prototype only.
…t over a transparent modal

The native route is a transparentModal hosting the shell over Home; the page route renders the
host screen's NewWorktreeModal on a transparent page, so the drawer is the same component. While
the page loads, the shell shows a placeholder of that sheet (scrim, grabber, spinner or download
bar), and the page's sheet opens in place without its slide. A host that cannot serve the page
(old desktop, blocked, failed) opens the host list instead of a wall in a drawer.
…r a dimmed Home

A transparent native overlay route hosts the host picker and then the page's
/h/[hostId]/new-workspace route, which renders the same NewWorktreeModal as the
host screen's +. The native scrim matches the drawer backdrop and the page's
first drawer opens already dimmed, so Home stays dimmed throughout. Old desktops
without the route fall back to the host list.
The overlay route owns a single tappable loading layer (spinner, or the
download on a small card) that stays mounted from the pick until the page
paints, so the fade and spinner no longer restart per shell state. The shell
reports its load and shows the host list for anything it cannot open; the
picker hands the pick over at once and keeps sliding away. The page's first
drawer takes the dimmed backdrop once, and a gate's overlay pending view no
longer stacks a second scrim over a mounted stack. Native /h/[hostId]/new-workspace
redirects to the overlay. Drops the unused session-route params, the
clipboard grant, and pass-through tests.
…Home

While the page loads, the overlay shows one short native drawer in the shared
drawer chrome with a spinner or the download, and nothing dims Home: a tap
above it, a drag down or Back cancels. The page's real drawer then slides up
with its own backdrop exactly as on the host screen's +. Drops the native
scrim, the loading card and the already-dimmed backdrop handoff; the overlay's
WebView stays invisible until the page paints, since a fresh one draws opaque.
The short loading drawer opens with the shared drawer backdrop and show
duration, and a tap on that backdrop, a drag down or Back cancels. It stays
in the screen's own window (a native Modal over the loading WebView stalls
the page) and goes in the frame the page paints: the shell reports its load
from a layout effect so the WebView shows and the drawer leaves together.
A drawer opening as its page screen mounts holds that screen's first-paint
report until its open animation ends, through a hold on the existing report
rather than a new bridge message. The New Workspace overlay's loading drawer
now stays until the create drawer is up, so a cold open no longer shows Home
undimmed between the two.
…ng drawer fades out

The shell no longer imports the feature: an overlay is a shell with
onOverlayLoad, which reports `unavailable` for what it cannot show, and the
overlay route opens the host list for every exit. HostProtocolGate takes one
transparentWhilePending flag. The loading drawer's drag handler follows a
stable cancel, and on paint the drawer fades out eased in, as the shell's page
cover does, because the WebView takes a frame or two to show the page after it
is revealed; removing it outright left Home undimmed for those frames.
@Jinwoo-H Jinwoo-H changed the title fix(mobile): Home's New Workspace opens the create sheet over Home (reaches phones with the next native build) feat(mobile): Home's New Workspace opens the desktop's create drawer over Home (native part ships with the next app build) Oct 21, 2026

@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: 6a088d37-a32d-4db9-beab-6ef6cc442478
📥 Commits

Reviewing files that changed from the base of the PR and between 1470f8e and 8a10382.

📒 Files selected for processing: 35
  • config/scripts/build-mobile-web-app-bundle.test.mjs
  • config/scripts/mobile-web-app-page-route-modules.mjs
  • config/scripts/mobile-web-app-session-dictation-capture.test.mjs
  • config/scripts/mobile-web-page-routes.mjs
  • config/scripts/verify-mobile-web-app-bundle.mjs
  • mobile/app/_layout.tsx
  • mobile/app/_layout.web.tsx
  • mobile/app/h/[hostId]/new-workspace.tsx
  • mobile/app/h/[hostId]/new-workspace.web.tsx
  • mobile/app/h/_layout.tsx
  • mobile/app/new-workspace.tsx
  • mobile/src/components/HostProtocolGate.tsx
  • mobile/src/components/MobileHomeQuickActions.test.ts
  • mobile/src/components/MobileHomeQuickActions.tsx
  • mobile/src/components/bottom-drawer-constants.ts
  • mobile/src/components/mounted-bottom-drawer.tsx
  • mobile/src/components/use-connected-host-picker.ts
  • mobile/src/home/MobileHomeListFooter.test.tsx
  • mobile/src/home/MobileHomeListFooter.tsx
  • mobile/src/home/MobileHomeScreen.tsx
  • mobile/src/mobile-web-shell/MobileWebShellScreen.tsx
  • mobile/src/mobile-web-shell/ShellDevFacts.tsx
  • mobile/src/mobile-web-shell/shell-overlay-load.ts
  • mobile/src/navigation/host-stack.web.tsx
  • mobile/src/new-workspace/NewWorkspaceHostPicker.tsx
  • mobile/src/new-workspace/NewWorkspaceLoadingDrawer.tsx
  • mobile/src/new-workspace/NewWorkspaceOverlay.test.tsx
  • mobile/src/new-workspace/NewWorkspaceOverlay.tsx
  • mobile/src/new-workspace/NewWorkspacePageScreen.test.tsx
  • mobile/src/new-workspace/NewWorkspacePageScreen.tsx
  • mobile/src/new-workspace/new-workspace-overlay-route.test.tsx
  • mobile/src/new-workspace/new-workspace-overlay-route.ts
  • mobile/src/new-workspace/open-host-list-instead.tsx
  • mobile/src/new-workspace/overlay-page-route.ts
  • mobile/src/new-workspace/use-host-worktree-rows.ts
💤 Files with no reviewable changes: 2
  • mobile/src/home/MobileHomeListFooter.test.tsx
  • mobile/src/components/MobileHomeQuickActions.test.ts

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

An unreadable host catalog, or offered desktops that are all gone, left the
picker invisible inside the transparent overlay, holding a screen over Home
with nothing to dismiss it. The catalog read now resolves to no hosts on
failure, and an empty pick list goes back to Home.
…does

The page drawer opened from Home passed no host labels, so a project on an SSH
host showed its run target by raw host id. Reading the SSH target summaries
and display overrides moves into one function that both the host screen's
metadata fetch and the page drawer call.
@Jinwoo-H
Jinwoo-H merged commit f47f632 into main Oct 22, 2026
28 of 30 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