* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP
orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).
The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
* test(serve): pin orca serve handing orcad the app's web client root
* fix(layout-owner): a frame rebuild keeps a background terminal's live agent title (dark, layout-owner build only)
The frame slice builder reuses restore's row hydration, which resets any
agent-activity title because a prior session's agents have exited. A
title main reports live for a running pane is not stale, so rows keep it.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(layout-owner): restore's row hydration skips the agent-title reset for live titles
Moves the live-title keep into the row hydration so the frame builder
adds no layout write of its own; restore passes nothing and is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Save-divergence detector: skip empty workspaces, see dropped tab-bar entries and kept pane trees (dark, layout-owner build only)
- A workspace key that names nothing (or only places a stored closed-tab record) is no longer
reported as "workspace added/removed" or "terminal row owner changed".
- A save that drops a terminal tab's tab-bar entry while keeping its row is reported, read from
the stored session before the Loader rebuilds the entry.
- A pane tree main keeps over the window's (windowSaveForMain) is reported, so a window-side
pane repair shows up.
* Detector: keep reporting empty-row flips, cover every overridden pane tree and one-sided closed tabs (dark, layout-owner build only)
* Detector: a save with no pane tree for a tab main keeps one for is reported too (dark, layout-owner build only)
* Detector: a closed tab record moved to another worktree in one save is reported (dark, layout-owner build only)
A user's Disconnect of an SSH host is now recorded for the session, and background
dials (a managed server's tunnel and status checks, census, host-resume recovery,
pane and automation restores) refuse it until an explicit Connect. Disconnecting a
managed host from Settings also stops its server's status checks, and the status
bar's Disconnect of a managed server also disconnects the SSH host it runs on, so
both surfaces read the same verdict.
Fixes#30086Fixes#30085
* fix(sidebar): draw a project group once when two hosts publish it under one id
Moving a project to a managed server copies its project group there under the same id while the desktop keeps its copy. The row builder emitted a header per copy, so the second sat empty and both shared one virtual-row key.
Fixes#30045
* fix(cmd-j): search a same-id project group by the copy the sidebar shows
* fix(sidebar): reveal a same-id project group through the copy the sidebar draws
* fix(sidebar): reveal a folder workspace through the group parents the sidebar draws
* fix(sidebar): reveal from the host-filtered groups the rows were built from
A project's group membership is stored on the project's own host, and that host stores a group id it does not hold as 'no group' while still returning the row. Moving a remote-only project into a local group (menu or drag) therefore looked successful, and could even take it out of its current group.
The store now refuses a move into a group that only exists in another host's catalog, and reports failure when the host answers with the group cleared.
Refs #13699
* fix(layout): main's Loader repairs a terminal bound in two panes of one tab (dark, layout-owner build only)
* fix(layout): a window save stores main's repaired pane tree, so the duplicate-terminal repair survives the save (dark)
* test(layout): the SSH duplicate-terminal case builds an SSH-hosted tab
* refactor(sidebar): move the Smart comparator and attention scoring to src/shared/sidebar-list
Main needs them to own the frozen order. Renderer helpers they depend on move with
them; the window imports the shared copies. No behaviour change.
* refactor(sidebar): one shared ranking for the frozen sort order
The window's sort memo and the palette's Smart sort now call rankWorktreesForSort,
so main can produce the same order from its own records. The pure state-history
step moves to shared for the same reason. No behaviour change.
* feat(sidebar): main keeps the list's time rules on one clock, checked against the window in dev and test
* test(sidebar): fake-clock parity of main's temporal owner against the window
* fix(sidebar): main restarts the settle window only where the window's sortEpoch bumps
* test(sidebar): type the owner harness without an assertion
* fix(sidebar): main's temporal owner stays idle until a dev or test window begins a session
* fix(sidebar): main reads Hide sleeping from the field the window persists
* refactor(sidebar): keep the begin params in their own contract module so the snapshot schema compiles out
* chore: restore files the merge's formatter rewrote to their origin/main content
On a headless orcad with no browser provider, every browser RPC went to a
rejecting proxy, including browser.tabClose. Client-hosted page records and
session rows are runtime records, so closing them never needed the engine;
the proxy now retires them, and orcad's provider proxy checks its own page
registry before asking a provider that never held the page.
Orphan recovery republished a pending host row as ready on the strength of the
client's retained wake binding alone, even when the host's terminal inventory
omitted the handle. The mirror then counted the dead handle as live, so a slept
workspace came back awake in the sidebar. A pending row now stays pending unless
the host's inventory lists its handle; the wake binding is still kept.
Fixes#30108
* feat(sidebar): main serves workspace icons as small data URLs for the phone, unused until the phone switch
* fix(sidebar): settle review findings in the icons RPC and the manual-redirect port fix
* docs(sidebar): scope the icon fetch-slot bound to a lone read
* fix(sidebar): gate the icons RPC behind the sidebar-inputs switch; prove re-encoding end to end
* fix(sidebar): restore two files the merge reformatted; one pure annotation for the icons params
* fix(sidebar): restore two files the pre-commit formatter rewrote during the merge
* fix(sidebar): icons fetch through their own manual-redirect request; shared desktop HTTP client back to main
* fix(sidebar): settle review findings in the icons manual-redirect request
* fix(sidebar): end a bodyless icon response at once; clearer fetchRepoIcon doc
* fix(native-chat): the queue button you pressed shows a working spinner while its action is on its way
* test: keep the pending Resume test's action typed
* fix: the shared spinner shows its still hourglass under reduced motion instead of nothing
* fix(mobile): a compact queue: shorter header, a capped scrolling list, and an editor that fits the card
* fix(mobile): keep queue actions visible and reveal new cards
* test(mobile): allow shared SVG host fixture
* fix(mobile): queue header and editor controls grow with large system text instead of clipping
* fix(mobile): at large text sizes the queue header's buttons wrap below its label instead of squeezing it out
* fix(mobile): at large text sizes the queued editor's Cancel and Save wrap below its heading instead of truncating it
* fix(mobile): at large text sizes the queue header's and editor's buttons take a line of their own
* fix(mobile): the large-text button lines wrap when one button is all a small phone's line holds
* test(mobile): the composer tray test's react-native stub reports the text scale
* fix(mobile): while a card is edited at large text, the paused header's button line gives its height to the editor
* refactor(pty): one spawn step stamps a terminal's pane identity for both spawn lanes
The window spawn controller and the host runtime's caller each wrote
ORCA_PANE_KEY/ORCA_TAB_ID/ORCA_WORKTREE_ID and jcode's runtime dir, in
different layers, so per-pane env behavior drifted (the jcode daemon
pre-warm exists only on the window lane). stampPtySpawnPaneEnv now writes
them in both controllers from the spawn's own tab/leaf/worktree ids;
buildTerminalWorkspaceEnv no longer stamps them. No behavior change: a
TEMPORARY prewarmsJcodeOnDaemonHost input keeps host daemon spawns from
pre-warming until its own PR.
* test(runtime): read a fake spawn's pane env without type assertions
* test(pty): host serializer-settlement spawns name their pane by tab and leaf, as the runtime does
* fix(pty): preserve caller runtime directory after managed preparation fails
* feat(mobile): name the desktop under the Tasks title
Tasks can open on any connected desktop, so the header now shows which
one, in the same subtitle line the host screen uses under its title.
* fix(mobile): reserve the Tasks desktop line so the title does not jump
The subtitle line now holds its height before the host profile is read,
a failed read leaves it blank, and the tests cover a desktop switch
while a read is in flight.
* test(mobile): re-measure the page script sweep after the Tasks subtitle
The Tasks route now shares the host screen's styles, so it emits one
script fewer (77 to 76 at its prefix, 80 to 79 for the tree). Re-swept
every route prefix; the worst route step is now 11.
* feat(acp): an agent's chat starts wait out a change to its shared sign-in
A launch spec can carry a gate that refuses new chat starts while an operation
replaces or removes the sign-in all of that agent's chats share on this host,
and aborts starts already under way when one begins. Antigravity's row sets it.
The refusal reuses the accountSwitchInProgress reason, now worded for any agent.
* Drain ACP starts before stopping chats for account changes
* Forward gated aborts into ACP launch preparation
* fix(acp): the launch gate holds every chat child it admits until it exits, so an operation can't run beside a chat still starting
A start leaves the gate when its child is up, but the host only lists the chat as loaded once its
attach finishes. A sign-in switch (and later Remove) in that window found no start to abort and no
loaded chat to stop. The gate now keeps each admitted child until it exits, and an operation closes
and proves gone any it still holds after the loaded chats stopped; otherwise it fails without
running.
* refactor(native-chat): one helper for failure sentences that only name the agent
Keeps agent-session-failure-words.ts under its line limit now that main grew it; the five sentences
read the same.
* fix(acp): a conversation copy runs the agent too, so it waits out a sign-in change like a chat start
* feat(linear): edit and delete Linear comments from the CLI
Adds orca linear comment edit <commentId> and orca linear comment delete <commentId>
(alias rm). The comment id alone names the comment; Orca finds the connected workspace
that owns it unless --workspace is given. Both refresh a linked worktree's issue and
report unconfirmed writes with a read-back step. The orca-linear guide documents them.
* test(linear): use a checked linked worktree fixture
* fix(linear): bound and cancel comment actions
* fix(native-chat): a Codex chat is Working only while Codex holds the send
Working read 'a send still pending delivery', so any echo or idle signal
that never arrived (a hook-blocked prompt, an answer naming no turn, idle
before a timed-out send was written) kept the chat Working, Stop up, the
queue frozen and /compact refused until the agent exited.
Codex now reports which sends it still holds: unanswered, answered into a
turn that has not ended, or answered into none while a status read after
that answer says the thread runs. Replies and statuses are ordered as they
are read, so a stale status cannot release a newer send. The host reads
that set (plus the short window while it hands a send over) for Working,
publishes a send the child stopped holding as in doubt on every page and
frame so older clients agree, and republishes when the set changes with no
row. The Codex idle latch and its release are removed.
* fix(native-chat): name the handover journal type; reverse without mutating
* test(native-chat): a lost Codex answer while idle is no longer the chat's work; run the held-sends test with the database runtime
* fix(native-chat): bound work updates and own command handovers
* refactor(native-chat): reuse session delivery plumbing
* test(native-chat): allow separate ownership restatements
* test(wire): resolve the release tab predicate after relocation
* test(native-chat): reuse existing promise and empty-batch primitives
A steer Codex answered into a turn that then ended without taking it is
rejected and moved to the thread, so the sidebar verdict and the
completion feed treated it as a refused request of its own and reported
it in place of the turn it joined.
* fix(mobile): ask which desktop to open Tasks on when several are connected
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(mobile): offer only desktops that have the tapped task provider
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(sidebar): ui.setCollapsed changes one section's collapse, ordered per writer
* fix(sidebar): ui.setCollapsed accepts ui.set's callers; require a fresh writer per counter
* feat(sidebar): register ui.setCollapsed only with ORCA_SIDEBAR_INPUTS=main
* test(sidebar): hidden-app check of ui.setCollapsed in both switch builds
* test(sidebar): off-census covers every gated method; e2e skips before launching
* test(sidebar): type the gated method-name set as strings
* test(sidebar): type the collapse test's dispatch context
* fix(terminal): resume a relaunched workspace's sleeping agents only when shown
After a relaunch every terminal tab of the opened workspace mounts, and each
pane's connect resumed its agent from the saved sleeping record whether or
not the pane was visible, so all of them restarted at once.
A hidden pane with a sleeping record now arms the same reveal wake a
hibernated pane uses and spawns nothing. A hidden local reattach first asks
the host whether the session is still alive; only an authoritative "exited"
holds it, and the reveal re-runs the same reattach. Held panes count as
owning their record so the activation sweep does not relaunch them. Targeted
background mounts (phone, CLI, orchestration) still start their tabs'
agents immediately, and the phone wake still wakes held panes.
* fix(terminal): keep a held resume through an exit its wake did not start
* feat(acp): open Antigravity in the Chat UI over its ACP server
With the experimental chat setting on, Antigravity chats run Google's
Antigravity ACP server, which Orca installs only when the person clicks
Install: a pinned 1.3.0 release whose archive and both executables are
verified by hash and size (macOS arm64/x64, Linux x64/arm64). A start
never downloads it.
Each host keeps one private sign-in folder for these chats under Orca's
data folder, made owner-only before every start; the server keeps its
sign-in there as a file. A chat signs in with the server's Google method
through the existing auth-required retry; with no saved sign-in the
server's sign-in prompt ends the chat signed out. The Agent Permissions
setting maps to the server's own mode, and a question the server asks
shows as a question that takes one of its choices, never auto-answered.
A chat reopens its session with session/resume.
* fix(acp): separate Antigravity chat command and require pinned host
* test(acp): assert Antigravity launch arguments across hosts
* feat(acp): Antigravity chats offer Ask, Accept edits and Full access per chat
Each mode maps to the server's own (default, auto_edit, yolo). No Approve for me:
the server has no automatic reviewer. Accept edits gets its own description,
since auto_edit approves file edit tools only.
* feat(acp): Antigravity installs from Settings and Accounts, and says its download size
The Antigravity chat server installs only when the person clicks Install. Besides
the chat's own Install card, Settings > Chat gains a Providers section and the
Antigravity accounts section gains the same install row for its host (never a WSL
distro), both following the host's install status. A host now reports its pinned
release's download size, so an idle row reads "111.5 MB download." instead of
"Not installed.".
* test(acp): the launch parity install puts the Antigravity harness beside its server
* fix(acp): Antigravity chats also drop the gcloud project variables
* fix(acp): Antigravity command rows, a refused /logout, the risky Allow Always warning, and an install that proves the server runs
- A command's line and output now show in Orca's command row: the dialect maps the server's
command_line/combinedOutput/exitCode to the shared shape and drops its duplicate output copy.
- The server's own /logout is hidden from the command list, and a message that is only /logout is
refused before it is sent: it would sign out every Antigravity chat on this host outside Orca's
sign-in gate. Dialects can now name withheld commands; the refusal has its own plain sentence.
- The server's prompt-injection warning on "Allow Always (risky)" is shown on the approval card.
- Install starts the unpacked server once under a throwaway home, asks initialize only, and keeps
the copy only if it answers as the pinned release and its process is proven stopped.
- The server runs with PYTHONUNBUFFERED=1; its environment lives beside its runtime.
- The permission mode option is found by its category, so the spec no longer names it.
* fix(antigravity): the install check accepts only the protocol version Orca's connection speaks
* fix(acp): keep the structured session create support within its line limit
Main's file already sat at the limit, so the account-home services pass inline instead of
through a variable.
* test(acp): Antigravity's launch snapshots follow main's per-chat permission launches
Main now starts ACP chats without full access and applies the chat's own mode after
start, as the Grok and OpenCode snapshots already record.
* feat(sidebar): main keeps a registry of workspace creations in progress, checked against the window in dev and test
* fix(sidebar): open the runtime creation entry after the create's own repo resolve
* refactor(sidebar): one creation handle opened after the create's repo lookup; move create startup resolution to its own module
* feat(sidebar): put the creation registry behind the ORCA_SIDEBAR_INPUTS dark build switch
* test(sidebar): cover the creation registry with the switch off and the gated creation id
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): delete native screens the OTA shell no longer reaches
With every build the OTA shell, these native siblings run nowhere: the
native-executed closure (index.js plus every non-web route) does not reach
them, and the page resolves their .web siblings. Each .web sibling takes the
plain name, its web-overrides entry goes, and the native-only rich Markdown
editor and Mermaid WebView generators and their outputs go with them.
Kept: native siblings the node test suite still renders shared screens
through (terminal WebView, browser frame path, clipboard, dictation, media
picker, live input, snapshot budget, input styles).
Claude-Session: https://claude.ai/code/session_6cea92f6
* test(mobile): drop the phone-preview comparison the deleted native sibling held
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps
MobileSelectableText was react-native's Text, so callers use Text. The rich
Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are
required now that the page is its only host; the WebView defaults go. Comments
that described deleted native components are rewritten, and
expo-image-manipulator and react-native-uitextview leave package.json and the
lockfile.
Claude-Session: https://claude.ai/code/session_6cea92f6
* feat(mobile): draw the host Accounts screen from the desktop's page
Accounts was the last host screen the shell still drew natively, so its
changes needed a phone build. It is now a page route like Tasks: the
screen moves to src/accounts/MobileAccountsScreen.tsx, accounts.web.tsx
renders it on the page, and accounts.tsx mounts the shell for it.
What React Native Web needed: Alert.alert is a no-op there, so the
reset confirmation, its outcome and a failed account switch are
ConfirmModal drawers (ConfirmModal can now drop Cancel for a notice);
Back goes through the route handoff, which pops the shell's stack; and
the reset idempotency key is built from getRandomBytes, since
randomUUID needs a secure context the iOS custom scheme is not.
edit.tsx edits the phone's own pairing and never renders on the page,
so the route manifest leaves it out and the page's host stack does not
declare it. The script sweep is re-measured for the 14-route tree.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(accounts): answer a same-offer Codex reset retry with its recorded outcome; mobile mirrors the desktop
The desktop keeps no client journal for a reset credit: the host's ledger
owns the attempt. A remote client that lost a reply and retried the same
offer under a new key was refused with "already attempted" (or "unknown
outcome" while the provider call was unresolved), which is why the phone
carried its own durable journal to replay the old key.
The coordinator now answers a request for an offer an earlier key already
claimed as that attempt: the settled outcome, the running attempt, or a
resume of the pending one under its original key. A different offer for
the account is still refused while an outcome is unknown, so nothing can
spend twice. The reply shape is the one a same-key replay always had, so
installed clients with their journal read it unchanged.
With that, the Accounts page sends a fresh key per attempt like the
desktop and keeps nothing on the phone: the AsyncStorage journal, its
client singleflight and the retry-record warnings are deleted, and the
journal-resume recording goes with them.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(accounts): resume an account's unresolved Codex reset when the retry names a newer offer
Review follow-ups:
- Host: after an unknown provider outcome the usage refresh moves the
offer, so a retry names a newer one. The coordinator now answers it as
the account's unresolved attempt (resumed under its own key and scope),
as the desktop's consumeCurrent does, instead of refusing forever. One
ledger lookup finds own key, offer claimant, then unresolved attempt.
- Mobile: a reply is matched on target, account and account revision,
not the offer, so the resumed attempt's scope is accepted.
- The key is minted inside requestCodexResetCredit from randomV4Uuid, the
session helper moved to src/platform/random-v4-uuid.ts.
- RefreshControl goes; the header refresh stays. ConfirmModal's
onConfirm is optional for an OK-only notice.
- Stale journal comments and the mobile docs line are corrected.
- The edit.tsx page exclusion is dropped from this PR; the script sweep
is re-measured for 15 routes.
- Root oxlint: escape the apostrophe; zod v4 strictObject.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): secure UUID fallback from expo-crypto; pin reset retry routing
- randomV4Uuid never falls back to Math.random: without crypto.randomUUID
it builds the v4 UUID from ExpoCrypto.getRandomBytes, as the old reset
key did. Tests get Node's secure bytes for expo-crypto from the setup
file, since the native module has no Node build.
- Host tests: a new offer after a settled attempt spends again rather
than replaying, and a retry naming a newer offer joins a resumed
attempt in flight (one provider call, original key).
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* chore: restore main's bytes for files the merge's pre-commit format touched
The merge commit's lint-staged run reformatted 27 files this PR does not
change. Committed without the hook so they match origin/main exactly.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): stand in for expo-crypto with a Node alias, not a setup-file mock
Anti-slop forbids module mocking outside test files. The vitest config
now aliases expo-crypto to a faithful Node implementation of the names
shared code reaches; a test file's own mock still wins.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs(mobile): match the bridged-parity comments to the measured 795-golden tally
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Switch-on builds only; switch-off behaviour is unchanged.
Main stores a tab's preview flag only while it is true. A non-preview
editor open created its tab-bar entry with `isPreview: false`, and making
a preview permanent by an edit, by "keep open" or by pinning wrote the
same, so the window's next save changed that tab in main's model (the
save-divergence check's `tab <id> changed: isPreview` after a CLI file
open). New tabs now carry the flag only when true, and those writers drop
it, by the rule E2-4 gave activation, now in one place (tab-preview-flag).
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
* fix(native-chat): a running Claude chat keeps its model when two Claude Code versions list differently
A send's stand-in for a model the list lacks took its rows and default from the
chat's own answer but kept only rows the account's saved list names, and every
running chat rewrote that list whatever CLI version it ran. After Claude Code
2.1.280 -> 2.1.302 the filter dropped the default row and sends moved to Sonnet.
- The options answer names the ids its agent listed (listedModelIds); a live
catalog is verified only by its own listing. An older host's answer may move
only to the chat's own default row, and only where the host's list names it.
- Only a session's start (and the probe) writes the account's list; later reads
no longer do, so an older running CLI can't replace a newer start's list.
Tracked in STA-10947.
* fix: keep running chat model selection within its own listing
Preserve learned model menus on later reads and check launch-added model membership before saving.
Tracked in STA-10947.
* test: validate ACP menu regression fixtures
Use the protocol schema for config options and supply the catalog row default flag.
Tracked in STA-10947.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): delete native screens the OTA shell no longer reaches
With every build the OTA shell, these native siblings run nowhere: the
native-executed closure (index.js plus every non-web route) does not reach
them, and the page resolves their .web siblings. Each .web sibling takes the
plain name, its web-overrides entry goes, and the native-only rich Markdown
editor and Mermaid WebView generators and their outputs go with them.
Kept: native siblings the node test suite still renders shared screens
through (terminal WebView, browser frame path, clipboard, dictation, media
picker, live input, snapshot budget, input styles).
Claude-Session: https://claude.ai/code/session_6cea92f6
* test(mobile): drop the phone-preview comparison the deleted native sibling held
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps
MobileSelectableText was react-native's Text, so callers use Text. The rich
Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are
required now that the page is its only host; the WebView defaults go. Comments
that described deleted native components are rewritten, and
expo-image-manipulator and react-native-uitextview leave package.json and the
lockfile.
Claude-Session: https://claude.ai/code/session_6cea92f6
* Open into the window's emptied root beside a split branch in one layout command (dark, layout-owner build only)
A tab opened into a group the window kept empty reaches main with the
split that places it, now also when the group's neighbour is itself split
(named by the branch's first and last groups) or is another empty group
main lacks. Main makes the group with its tab beside that branch. Switch-off
builds send no such command.
* Comment the refusal of a branch main's tree does not hold (dark)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(terminal): let account-switch pane restarts name their agent
The queued account-switch restart only knew how to relaunch Codex. The
queue now records which agent each pane restarts as, and the resume
startup builder takes that agent, so the same mounted and detached
restart paths can move Claude sessions too. Codex behaviour is unchanged.
* feat(accounts): offer to move running Claude sessions after an account switch
Switching the selected Claude account only reached Claudes started later,
so users had to /exit and resume every running session by hand. After a
switch from the status bar or Settings, Orca now finds local terminal
panes running a resumable Claude session in the switched lane and shows
one toast offering to move them. Confirming restarts idle sessions with
claude --resume <id> in place; sessions mid-reply move when their turn
ends, and are dropped if the pane closes or the user switches again.
* fix(accounts): wait out permission prompts and keep hand-started Claude args
A Claude session at a permission prompt is mid-turn, so a move now waits
for it like a session that is replying. Resuming a Claude pane with no
recorded launch config no longer adds the user's default agent arguments
or environment, so a hand-started claude is not resumed with a
permission bypass. Codex keeps its existing fallback.
* feat(accounts): track which Claude account each terminal pane runs on
Orca had no record of the account a running Claude used, so an account
switch could only assume every session was on the old one. A renderer
tracker now attributes sessions that start after launch to their lane's
selected account and records the account Orca restarts a session on.
Sessions it cannot attribute stay unknown and keep today's behaviour.
* feat(accounts): ask in a dialog before restarting Claude sessions on a switch
Replaces the move toast. After a Claude account switch, a dialog names the
new account and how many sessions are still on the old one, with Keep as
is and Restart, plus Remember my choice. Closing or Escape keeps sessions.
The remembered choice lives in Settings > Accounts as When you switch
accounts while Claude is running: Ask, Always restart, Never restart.
* refactor(accounts): read Claude session accounts from their transcripts and use one restart queue
Which account a running Claude uses now comes from the transcript path its
hooks already report (<CLAUDE_CONFIG_DIR>/projects/...), so the renderer
tracker and its guesses are gone and unknown sessions are never counted.
Restarts queue straight into the store's pending map through one
queuePaneAccountRestarts action; the pane effect and the detached
scheduler hold a Claude restart while its turn runs and skip it when the
session can no longer be resumed. Codex behaviour is unchanged.
* style(accounts): left-align Keep as is in the Claude account switch dialog
Co-Authored-By: Claude <noreply@anthropic.com>
* copy(accounts): say busy rather than replying, and drop the menu's switching note
The deferred restart waits on any working or blocked turn, not only a reply. The
switch dialog now covers running sessions, so the menu footer note is redundant.
* copy(accounts): say busy sessions wait for idle before restarting
* test(accounts): type the switch-session store stub without broad object
* fix(accounts): keep a queued Claude restart when Codex restart notices are cleared
Codex notice cleanup shares the pending-restart map with Claude switch restarts;
it now drops only Codex entries.
* fix(accounts): wait out Claude questions and keep accepted restarts across switches
- A Claude permission prompt or question reports 'waiting'; a queued restart now
waits for 'done' instead of only 'working'/'blocked', so it no longer drops them.
- A later switch keeps accepted restarts still waiting on a turn (they relaunch on
whatever is selected) and drops only those whose session is already on the new
account, scoped to the switch's lane. The blanket replaceQueued option is gone.
- A server account switch returns before touching the local dialog or queue.
- Pane restart state reads through one selector to stay inside the subscription
budget, and the focus-read baseline follows the restart-startup rename.
* test(accounts): feed the account-restart e2e fixture the shared restart queue
* feat(accounts): make the switch dialog's checkbox one-way, like native chat's resume prompt
"Don't ask again (restart automatically)" turns on automatic restarts however the
dialog is left, including Escape; Settings → AI Provider Accounts turns it off.
* feat(accounts): choose which Claude sessions restart after an account switch
With more than one session, the switch dialog lists them by workspace using the
native-chat resume tree rows, all ticked; Restart covers only the ticked ones.
* Merge origin/main into claude-sign-in and rename the host-twin teardown's restart queue
* fix(accounts): localize the session tree's agent label
* fix(accounts): restart a pane on the PTY it has when the dialog is answered
A reattach can rebind a pane while the dialog is open and a closed pane has no
PTY, so the ids it listed may name nothing.
* fix(accounts): read a layout's pane PTYs optionally
* fix(accounts): offer to move sessions of a saved account that runs on System default
A saved account with no login of its own runs on System default, so its sessions'
transcripts read as System default. The switch now compares where launches run
(runsOnSystemDefault on the account summary) and asks nothing when that is unchanged.
* refactor(accounts): give the Claude account switch one boundary and key the whole prompt
The post-switch coordinator now takes the before/after account snapshots and
derives launch folders and labels itself, so the status bar and Settings stop
assembling the same request. The status bar's settings projection reuses the
host's row, which keeps runsOnSystemDefault: a covered account switched from
the status bar now finds its System default sessions. Never restart returns
before any session scan. The outcome collapses to show-confirmation | handled.
The dialog keys one component that owns the whole Dialog with a plain boolean.
* refactor(terminal): subscribe mounted panes to this tab's ready account restarts
Replaces claudeTurnStatus, the whole agent-status map passed through the
controller as a re-run signal, with a shallow selector of the PTY ids whose
queued restart can run now. Other tabs' status updates no longer re-render or
re-run every mounted pane once a Claude restart is queued. The effect re-checks
readiness against the current store before consuming. The detached Claude test
now runs through the scheduler's real subscription instead of manual sweeps.
* docs(accounts): shorten Claude account-move comments to their whys
* test(terminal): give the account-restart e2e a callable store and a queued restart
The mounted pane now reads its ready restarts from the store through useShallow.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* fix(accounts): follow an Orca Claude account switch when the shell sets CLAUDE_CONFIG_DIR
A user whose shell config or environment sets CLAUDE_CONFIG_DIR switched Claude accounts in Orca,
but `claude` relaunched in an open terminal kept running the old account: the terminal's claude
function treated any CLAUDE_CONFIG_DIR it did not inject as a deliberate per-terminal override.
Host terminals now always carry the user's own folder (ORCA_CLAUDE_USER_CONFIG_DIR, from the login
shell's env), also on System default and before that env arrives. The claude function (POSIX, fish,
PowerShell) treats a CLAUDE_CONFIG_DIR equal to it, ignoring a trailing separator, as System
default's folder and follows the selected account; any other value set in the terminal still wins.
WSL panes carry no user folder, so they behave as before.
The terminal daemon protocol moves to 49: hourly builds already run v48 daemons whose wrapper has
the old function. v48 stays attachable.
* fix(accounts): match the user's Claude folder on PowerShell whatever its separators
Orca records the user's CLAUDE_CONFIG_DIR resolved (backslashes on Windows), so a
pane value written with forward slashes read as a deliberate override and stopped
following the account switch.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(claude): pass the user's own CLAUDE_CONFIG_DIR to terminals exactly as set
Claude keys its login by the literal folder string, so a resolved spelling (a
dropped trailing slash) could switch System default to another saved login, and
Git Bash on Windows compared a backslash spelling against its rc's forward one.
* refactor(claude): one reader decides whose CLAUDE_CONFIG_DIR it is; one base pane env
The resolved reader now derives from the as-set one, every host pane env builds on
one base patch, and the router tests share one shell probe.
---------
Co-authored-by: Claude <noreply@anthropic.com>
In a layout-owner build the window's chat-pane pick (chatLeafId) reached
main only through its session save, so a frame could undo it and the save
changed main's layout. The window now records it with main's existing
setChatPane command. Codex restart and background-agent pane trees are
already written by main; tests pin that.
Co-authored-by: Claude <noreply@anthropic.com>
A desktop connected to a remote Orca server kept its own copy of a tab's
manual name, and ranked its saved-session title (a resumed Codex chat)
above the live title the server publishes. So orca terminal rename on the
server never showed on a resumed tab, and a desktop rename never reached
the server.
The server now publishes the tab's manual name as an optional customTitle
on each terminal surface, and the desktop mirror adopts it. Desktop renames
go to the server: terminal.rename by handle, plus session.tabs.setTabProps
customTitle by tab id for panes that are asleep or starting. Older servers
omit customTitle and strip the new param, so the desktop keeps its own
name with them, as before.
Fixes#29975
* fix(mobile): prevent markdown and file tab reload and screen flicker on retap and revisit
* fix(mobile): preserve newer saves and refresh document metadata
* test(mobile): supply current document state in recorded reads
---------
Co-authored-by: Neil <neil@stably.ai>
* feat(sidebar): main produces per-workspace PR facts, checked against the window in dev and test
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(sidebar): share the PR cache key candidates instead of splitting an import
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>