The phone gave a direct socket 5 s from open to authenticated, while
the desktop keeps an unauthenticated socket for 10 s. On a slow link
(a handshake is two round trips, so ~2.5 s RTT is enough) the phone
dropped the socket the desktop would have accepted, then redialed into
the same deadline forever: the 'Can't connect' loop a test documented.
Both ends now share PAIRED_E2EE_HANDSHAKE_DEADLINE_MS (10 s). A link
that needs 6 s to authenticate now connects on the first dial; one that
never authenticates still escalates the same way, 5 s later. Relay
dials are unaffected: they keep their own stage budgets and race the
direct dial after 2.5 s.
The very first time a host is opened there is no cached page, so a
single failed dial (a 12 s connect deadline or a 5 s handshake on a
slow link) put up the offline screen, 'Connect to this host to download
the latest app', while the phone was still redialing and often seconds
from connecting.
The shell's gates now carry whether the dial is still within the
attempts the connection label calls connecting (the same three it
waits before warning). A first open keeps checking through them; when
they run out the gate verdict changes, the flow restarts and the
offline screen shows as before. A cached page still opens at once, as
today.
The download-failure decision moves to mobile-web-shell-download-failure.ts
unchanged, to keep the session reducer under the size cap.
Opening a host with nothing cached waited for the whole worktree
catalog before drawing a row. On a real desktop with 1,084 workspaces
that is ~153 KB compressed, about 1.2 s at 1 Mbps.
The first read now also asks for the host's first 60 rows by its own
ranking (pinned, unread, active, most recent output), ~17 KB on the same
desktop, and draws them while the list is still empty. The full catalog
replaces them as soon as it lands, as it replaces cached rows today. The
first page is never marked loaded or cached, and a failed full read
clears it so the screen still reports the failure instead of a partial
host. Hosts of every version accept the limit parameter.
Opening a host ran status.get twice in a row: once for the shell's
compatibility gate, then again from the page's own gate, which held the
host screen (and with it the first worktree.ps) for one more round trip.
Over relay that round trip is the slowest part of opening a host.
The shell now hands the page the status its gate proved, in init. The
page's gate starts from it, computing its own compatibility verdict from
those fields, and still reads the host itself; its own answer replaces
the shell's when it lands. Reconnects keep today's behavior: the page
re-reads before trusting the host again.
The shell's gate handling moves to use-mobile-web-shell-gates.ts so the
session hook stays under the size cap.
Mixed versions: init's hostStatus is optional and salvaged, so older
shells omit it and the page asks as before; older pages ignore it.
The host screen re-read repo.list every minute and on every repo or SSH
event. Each reply carried every repo's uploaded icon inline as a data URL
(up to 400 KB each, barely compressible), although repos rarely change.
repo.list now takes an optional afterSnapshotId and answers unchanged
when the caller already holds the exact reply. The id is content-derived
(the hashing moves to content-snapshot-id.ts, shared with worktree.ps).
The phone keeps the last catalog per client, reuses it on unchanged
replies and still refreshes SSH host labels and health.
repo.list declared no params, so older hosts ignore the field and answer
the full catalog as before; callers that send nothing get the byte-exact
legacy reply. Icons still render from the same data, at the same size.
The eight native Settings screens use the shared NavBar and grouped rows,
Source Control's tabs move into the bar's segmented control, and Accounts,
Troubleshoot, the custom key editor, the Tasks pickers and drawers and the
workspace details sheet drop their hand-rolled rows for ListRow.
The host screen re-read every paired server's full worktree catalog on
each 3 s poll, through the desktop's mobileRelay.hosts.worktrees, even
when nothing changed: the same cost worktree.ps had before row deltas.
mobileRelay.hosts.worktrees now answers snapshot and row-delta reads with
the same resolver as worktree.ps (made generic over row type). The phone
keeps one WorktreeCatalogSnapshotClient per server, which now takes the
catalog request it snapshots, so server rows reuse the same token,
rebuild and copy-on-admit logic as desktop rows.
The params schema is strict, so the phone sends the snapshot fields only
to a desktop advertising mobile.relay-host-worktree-deltas.v1; older
desktops keep getting plain reads. Callers that send neither field, and
servers whose rows are not fetched yet, get the plain reply.
* feat(mobile): polish press feedback, sheet motion, states, spacing and layout jumps
Every control presses and disables the same way, with one haptic rule.
Sheets follow the finger and settle without bounce, pickers stack instead of
swapping, and screens slide over a dimmed, shifted parent. Loading, empty,
error and offline states share one view with plain-language errors, rows and
labels line up on one grid, and screens stop jumping as data arrives.
* fix(mobile): settle voice, tasks and accounts states; keep host snapshot current
Voice settings with no paired desktop shows the connect state instead of
loading forever. A failed task settings read after a successful probe retries
hydration. The shared host-list snapshot drops on invalidation and ignores
stale passes. Accounts shows a re-pair state on auth failure, and busy
action-sheet rows keep their disabled state for screen readers.
Two paths showed Disconnected while the phone was still connecting:
- A relay dial started from idle (after a background suspend, or when no
session was live) left the client disconnected until the relay hello,
because the replacement session starts in connecting without announcing
it. The migration now forwards the replacement's initial dial state.
- The host screen page never learned which path the shell was dialing, so
a direct dial that failed over to relay read as Reconnecting, then Can't
connect and the unreachable empty list, while relay was still working.
Bridge connection snapshots now carry the active and pending paths and
the relay verdict, and the shell sends a connectionPath frame when only
those change, so the page shows Connecting via Relay like the native
screens. Path frames fire path listeners, never state listeners, so
nothing on the page mistakes them for a reconnect.
Mixed versions: the snapshot fields are optional, so older shells omit
them and the page keeps its previous defaults. Pages announce
connectionPathFrames in ready; older pages never receive the new frame.
* Show agent message cards in phone chats
* test: type the phone markdown mock with its component props
* Fix phone agent message cards and sender navigation
* Open native chats on the phone in place
* Preserve desktop host and agent when opening chats
* Separate phone cards from shared chat opening foundation
* Show phone agent cards through shared sender opening
* Preserve chat opening and captured lifecycle contracts
* test: cover sender focus and unconfirmed chat close outcomes
* test: exercise structured worker CLI lifecycle through runtime socket
* test: use checked parked mail observation in CLI integration
* test: validate provider notification correlation identities
* test: construct checked lifecycle and navigation fixtures
* Keep cross-program session proofs in their test program
* Localize mobile chat opening feedback and complete runtime contracts
* Use the mocked text component identity in History feedback proof
* Temporarily expose grown E2E type diagnostics
* Keep E2E timer and fetch fixtures in their runtime domains
* Reconcile decreased E2E wait pattern counts
* Preserve live test clocks and expose remaining type diagnostics
* Keep mobile session data types independent of native views
* Complete structured session wire surface fixtures
* Expose existing grown E2E type diagnostics temporarily
* Preserve checked wire fixture tab mock signatures
* Preserve worker redrive when automatic close is refused
* Honor automatic close admission before settlement and retirement
* test: retain History reveal and close publication race
* Match automation reveal callback to activation result
* Align CLI integration fixture with current settings and output
* Prevent retained chats from reopening removed workspaces
* Import notification types directly from their owner
* Unify local removal fences for accepted worktree aliases
* Keep cross-surface authority tests in the combined test project
* Initialize coordinator mail test workspace ownership
* Register the retained automation chat test workspace
* Use checked inputs in worker start failure test
* Preserve the automation test repository host literal
* Open phone card senders through shared caller navigation
* Carry the phone sender deadline through shared admission
* Finish empty phone card measurement and consumer coverage
* test(mobile): target native hosts in image-only card regression
* fix(mobile): fade folded agent message edges
The connection snapshot schema, the shell's snapshot builder and the
page's not-ready guard move to bridge-connection-snapshot.ts and
BridgeConnectionCache.require(). No behavior change: the envelope
re-exports the schema, so every frame parses and serializes as before.
Prepares the next change, which adds connection path fields to the
snapshot, without pushing the bridge modules over the size cap.
Every JSON reply and stream event reached the phone as base64 of sealed
UTF-8, a third larger than the JSON itself, and terminal snapshots (up to
512 KB per attach) went out uncompressed. Opening a host on a real desktop
moved ~2 MB, about 17 s on a 1 Mbps link.
Phones now advertise transport.compressed-frames.v1. For those sockets the
desktop gzips text frames of 1 KiB or more, and terminal frames of that
size, and seals them as binary frames tagged with a new kind byte, the
payload kind and the inflated length. The phone inflates them (native
gunzip when available) back into the same text or binary frame before
routing. Screencast frames are never compressed, and the relaying desktop
never offers the capability to paired servers, whose frames it rewrites.
On the same desktop, opening a host now moves ~180 KB.
Desktop CPU is unchanged: gzip then seal of the 1.5 MB catalog takes the
same time as seal then base64 of the plain one. Setting
ORCA_DISABLE_MOBILE_COMPRESSED_FRAMES=1 on a desktop turns compression off
without a release. Older phones never advertise the capability and older
desktops ignore it, so both keep plain frames.
* Open native chats on the phone in place
* Preserve desktop host and agent when opening chats
* Preserve chat opening and captured lifecycle contracts
* test: cover sender focus and unconfirmed chat close outcomes
* test: exercise structured worker CLI lifecycle through runtime socket
* test: use checked parked mail observation in CLI integration
* test: validate provider notification correlation identities
* test: construct checked lifecycle and navigation fixtures
* Keep cross-program session proofs in their test program
* Localize mobile chat opening feedback and complete runtime contracts
* Use the mocked text component identity in History feedback proof
* Temporarily expose grown E2E type diagnostics
* Keep E2E timer and fetch fixtures in their runtime domains
* Reconcile decreased E2E wait pattern counts
* Preserve live test clocks and expose remaining type diagnostics
* Keep mobile session data types independent of native views
* Complete structured session wire surface fixtures
* Expose existing grown E2E type diagnostics temporarily
* Preserve checked wire fixture tab mock signatures
* Preserve worker redrive when automatic close is refused
* Honor automatic close admission before settlement and retirement
* test: retain History reveal and close publication race
* Match automation reveal callback to activation result
* Align CLI integration fixture with current settings and output
* Prevent retained chats from reopening removed workspaces
* Import notification types directly from their owner
* Unify local removal fences for accepted worktree aliases
* Keep cross-surface authority tests in the combined test project
* Initialize coordinator mail test workspace ownership
* Register the retained automation chat test workspace
* Use checked inputs in worker start failure test
* Preserve the automation test repository host literal
Home re-read the full worktree catalog for every connected host on each
focus, reconnect and change event just to show counts and a resume row.
It now keeps a snapshot per host and client, so repeat reads are
unchanged replies or row deltas. One read runs per host at a time, and a
refresh asked mid-read joins a single trailing read, so concurrent reads
cannot discard each other's delta and no refresh settles with older rows.
Home ranks and shows exact activity, so it asks for it: worktree.ps takes
exactActivity to skip poll coarsening, and acceptRowDelta alone now opts a
first read into snapshots. Home's first read sends no snapshot id, so a
host without row deltas answers its exact legacy catalog and every later
read stays legacy; home's data is unchanged on every host version.
A working agent's heartbeat changes the catalog snapshot id on nearly every
3 s poll, so the host screen re-downloaded the whole catalog each time
(1.5 MB for ~1000 workspaces, ~26 MB/min on a real desktop). Callers that
hold the base snapshot now opt in with acceptRowDelta and receive only the
changed rows; unchanged rows arrive as indexes into the base catalog.
The host keeps only row hashes for the 16 most recent snapshots, so an
evicted base falls back to the full catalog. Older hosts ignore the
parameter and older phones never send it.
* feat(mobile): expand Mermaid diagrams into a pinch-zoom viewer; image lightbox zooms the image, not the page
* fix(mobile): expand Mermaid labels with nonbreaking spaces instead of a blank image
* feat(mobile): draw SVG files with a Preview/Source toggle; pinch, drag and double-tap to zoom images
* test(mobile): render an SVG preview under the shipped shell policy and pinch it
* fix(mobile): double-tap after a drag at fit zooms in instead of re-fitting
* fix(mobile): pinch follows the fingers, Back resets zoom, and writable SVG artifacts get a preview
* fix(mobile): keep a writable SVG's save failure visible in Preview
* fix(native-chat): an open chat moves to the account you switch to before its next message
A running chat's agent read its account only when it started, and nothing compared it with the
account a start would use now, so open chats kept answering on the old account after a switch.
The agent a chat runs now records the account its launch used, and the host's replace-before-the-
next-send check replaces an idle one whose account is no longer what a start would use. A chat
mid-reply finishes on the old account first. Claude chats follow; agents whose chats keep the
account they were created on are never compared.
A chat's model-list reads and saves now use the account it runs on, so a moved chat no longer
writes its new account's list under its first account (STA-11281).
Tracked in STA-11211.
* fix(native-chat): declare the launch-account read for the runtime layer that wires the host
* fix(claude): find custom chat history before managed-account resume
* test(claude): assign the complete custom account home
* Preserve unknown account model facts and legacy option replay
* Clarify terminal account-switch feedback in translated locales
* Keep option mutation plans checked against their contract
* Cover unknown model choices across chat consumers
* Keep consumer fixtures aligned with canonical types
* Match the mobile picker label in its consumer test
* fix: clarify terminal scope in translated Claude account settings
* Update integration tests for current session and remote client contracts
* Keep native worker replies on their receiving account
* Preserve receiving accounts across cold starts and nested reports
* Preserve the delivery receiver when reading chat commands
* Clarify native reply matching and consolidate imports
* Resolve catalog scope without re-entering conversation queues
Give owned option mutations and delivery settlement a resolved catalog input while external reads acquire scope once. Preserve account routing, catalog lifetime and current response obligations.
* Align catalog and mobile test fixtures with current contracts
Explain the existing timeout budgets and provide the idle turn identity and truthful model-only picker label in the mobile fixture.
* Align receiving-account readers and fixtures with typed contracts
* Repair account-switch fixtures and measure receiving work
* Use the checked account-home path in catalog tests
* Restore executed-option recorder context
* Control receiving catalog deadline test clock
* Align unknown-model consumer fixtures with send capture
* Consolidate session host type imports
* Align cross-version catalog fixtures with host read contracts
* test(native-chat): bind Runs through the current start authority in receiving fixtures
Main now resolves a worker start lazily through bindRun with objective and task references, and creating a Run no longer unbinds a coordinator's earlier Runs; run-use does. The receiving fixtures follow the same production owners so every original scenario, input and bound is exercised unchanged. The receiving runtime subclass moves verbatim into its own support module.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(native-chat): follow the selected account before its folder exists
A live Claude chat compared its account against the selected one only after that account's folder passed an availability check, so an account still being set up (or whose setup failed) left the chat silently answering on the previous account, and a resting chat refused its start before preparation could run. The retirement comparison and the start pre-read now ask the router for the selected route as identity, through the existing resolver purposes; catalog, create and probe reads keep checking availability. Preparation, typed launch failure and parking are unchanged.
Session History details show the account a chat was last launched under when the host recorded one, else its founding home.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(ai-vault): build the stale acquisition receipt without a type assertion
The History account case now reads the fixture receipt through the typed acquisition reader instead of spreading the record field through a cast; the three assertions and their inputs are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): narrow the rig acquisition link before relinking it
The account-setup cases spread an optional link, which typed its id as possibly undefined; the rig always links a handle, so a guard that cannot fire narrows it. No assertion or input changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): follow main's chat admission in fixtures and bound the History account projection
Chat admission no longer reads an experimental host setting, so the continuation-context test drops the spy main dropped in its own fixtures. The two History consumers that install a minimal chat host give their fake store the record accessor and host id the real store has, since the projection now reads the acquisition receipt. An operation-count contract holds the production History projection to one roster read and one record lookup per owned row at 10 and 100 rows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): give the declared-effort catalog fake its capture port
The declared-effort fixture builds a catalog service without the capture member the options reader asks every catalog for, which failed its desktop, catalog and phone consumers; it now supplies the inert test operation like the other fakes. The History projection contract also drops an unused map parameter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Consume main's outcome change feed in the receiving-account index
Main's keyed journal records deliver each change after an epoch's frozen prefix as an
outcome sequence naming touched keys instead of typed rows. The receiving-account index
now re-reads those keys at their current values: queue cards by id, a deleted submission
retires its exact notice, and a deleted item drives the existing recovery rebuild, with
Stop marks still excluded. The in-process read row exposes the already-persisted
queue.cards keys and per-key deletion facts; itemIds and submissionIds keep listing every
touched key. The mobile unknown-consumer test mocks MobileSwitch as main's picker test does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Advertise mid-turn queueing as queued-messages.v2
Released clients read the v1 name with an unfinished queue UI; they never
see v2 and keep sending immediately.
(cherry picked from commit f39382e090)
* Let each desktop send choose queue or steer while the agent works
The follow-up choice is now a Queue / Steer setting saved under
nativeChatFollowUpBehavior, Steer by default; the old
nativeChatQueueFollowUps switch, which every install saved as true
while queueing was dark, is retired on load. Every reader goes through
native-chat-follow-up-behavior.
While the agent works, Enter follows the setting and Cmd/Ctrl+Enter
sends the draft the other way; the alternate flips the setting before
the queue's own rules (a waiting command still queues, prompts no one
here can answer still send now) and never applies while Stopping.
Cmd/Ctrl+Shift+Enter steers the head card only and leaves the draft.
An idle agent's card reads Send, and the card menu no longer offers
"Turn off queueing".
(cherry picked from commit 55b48694e7)
* Hold Send on the phone to queue or steer a message mid-turn
While the agent works on a host that queues, holding Send opens a
Queue / Steer sheet; a choice other than the host's setting sends the
alternate, resolved against the chat as it is when it lands, by the
desktop's rule. Release and dismiss send nothing. The phone reads the
host's new follow-up setting (absent reads as Steer), and an idle
agent's card reads Send.
(cherry picked from commit db87cf51ea)
* Read the follow-up default from one place on desktop and phone
(cherry picked from commit 8e1438bfb6)
* Keep phone follow-up choices aligned with live host state
(cherry picked from commit dc9215be3d)
* Update stream and wire fixtures for live follow-up preferences
(cherry picked from commit e4d038cf0b)
* Document minimal runtime test fixture assertions
(cherry picked from commit 254f751ef3)
* Keep mobile preference tests in the mobile compiler project
(cherry picked from commit ae2aa8910e)
* feat(native-chat): queue follow-ups by default with a toggle
(cherry picked from commit 70969d6a43)
* test(mobile): assert shared preference renders on real changes
(cherry picked from commit 3f50de9b2a)
* Remove obsolete follow-up option translations
(cherry picked from commit b87b360cf3)
* Describe Cmd/Ctrl+Enter relative to the Queue follow-ups switch
The Settings description said Cmd/Ctrl+Enter sends into the running
turn, which is wrong while the switch is off. Describe it as doing the
opposite for one message, in every language.
* Show what a mid-turn phone Send does, and offer the other way only when it differs
While the agent works, the phone's Send button now shows Queue or Steer.
Holding it lists that action first with a check and the other second,
each with a one-line description, and appears only when both choices
would actually lead to different sends. A waiting command or prompts
nobody here can answer make a hold behave like a tap.
* Steer the head card once while its shortcut is held
Holding Cmd/Ctrl+Shift+Enter auto-repeats the key, which steered each
card that became the head in turn. Ignore the repeats.
* Show beside Stop what Enter does while the agent works
While a turn runs and the draft has something to send, a send control
now sits beside Stop. Its icon and label say whether Enter queues the
message or steers it into the running turn, and its tooltip names Enter
and, when it would send differently, Cmd/Ctrl+Enter. Clicking it sends
as Enter does. Stop keeps its place and nothing changes while Stopping,
with an empty draft, or before the host says it queues.
The phone and desktop now read what a send does from one shared helper
over the send's own resolver.
* Test the send keys and Queue follow-ups search against real wiring
One desktop test drives Enter and Cmd+Enter through the real composer,
transport and session to the outgoing send and checks its delivery with
queueing on and off. Settings search is checked against the capability
list this build's host advertises.
* Move the composer's send-hold check into its own helper
The pane had grown past the file length limit after the main merge.
* Disable the mid-turn Queue/Steer button whenever Send would be disabled
While an image is still loading, another device holds the input, or a send is blocked for a stated reason, the button beside Stop is now disabled and says why, the same way the idle Send button does. Before, it stayed clickable and a click did nothing.
* Hide the mid-turn Queue/Steer button in goal mode
In goal mode Enter sets the goal, so a button promising to queue or steer the message was wrong. It is now hidden there, as it already is while editing a queued message.
* Fix the Russian queue hint to say the message runs after this turn
It read as queueing until the end of this turn.
* Stub window.api without a type assertion in the Queue follow-ups settings search test
* Pass sendHeld to the composer field in tests and drop an unknown setting from the German Queue test
* Let Cmd-click (Ctrl-click elsewhere) on the mid-turn Queue/Steer button send the other way, like Cmd/Ctrl+Enter
The button shows the other action while the modifier is held, and its tooltip names the click. Only when the other way would send differently; otherwise a modified click is a plain send.
* Merge the duplicate composer-types import and give the composer's round buttons a Button size instead of restyling them
The focused code-quality check rejects importing one module twice, and the design-system check rejects rounded-full on a <Button>, which the new Queue/Steer button copied from Send. Both composer buttons now use a round icon size on the Button primitive and render as before.
* Share one window key listener across every chat's Queue/Steer button, and count Cmd/Ctrl as held only from its own key press
Each mounted chat with a mid-turn draft used to add its own window key listeners, so every keystroke did work per chat, hidden tabs included. One shared listener set now serves them all and wakes a button only when the held state really changes.
Other keys can now only clear the held state, so a dictation paste that leaves the Cmd flag on later key events no longer flips the button with Cmd up. Also tests the Ctrl-click on Windows and Linux.
* Remove the desktop Queue/Steer button shown beside Stop while the agent works
While the agent works, the desktop composer goes back to its usual
controls: Stop, with Enter and the send shortcuts doing what they did.
Enter still follows the queue setting, Cmd/Ctrl+Enter still sends one
message the other way, and Cmd/Ctrl+Shift+Enter still steers the first
queued card.
This also removes what only that button used: its Cmd/Ctrl-click
alternate, the shared Cmd/Ctrl key listener, the extra round Button
size, the send-preview value the desktop session passed to the
composer, and the button's text in every language. The phone is
unchanged.
* Put the composer's send-hold checks back inline in the pane
The separate send-hold helper only existed because the removed mid-turn
Queue/Steer button pushed the pane over the file-length limit. Without the
button the pane fits again, so it goes back to main's inline code.
* Share chat option writes across desktop and phone
* Restore main's failure notice scope for chat option picks
Held launch refusals toast again while the transport is still closed, desktop
notices follow only the chat's presentation scope so a fence or status change
on the same chat still reports, and the phone banner no longer depends on the
read record.
* Share chat options read ownership across desktop and phone
* Start a new chat context's options read at once
Reads were coalesced on one queue for the whole view, so after a reconnect, provider restart, phone path change or chat switch the current read waited behind the previous context's request, up to the transport timeout. Each attachment now has its own queue: reads still coalesce to one active and one trailing request within a context, while a new context reads immediately and the old context's reply stays inert. Restores the shared option refresh contract and the reopen test to their original expectations.
* Let desktop options tests wait for the shared read queue
The shared read queue starts a read one microtask after it is scheduled, where the old desktop runner started it synchronously. The selection lifetime and context usage tests now wait for each read to start before asserting on it, keeping every original assertion, including one active and one trailing refresh. The paired server test compares transport targets by value, since the options view memoizes an equal target per environment.
* Keep chat support projections stable between options answers
Desktop and phone rebuilt the conversation, image and command support objects every render, and a host that omits commands produced a fresh empty list each time. Project them once per accepted answer.
* Import options wire types once in the selection reads performance test
* Keep chat support visible across a controller change
Replacing the selection controller, as a desktop re-pair does, cleared the last accepted support, so slash commands, the goal row and the context meter disappeared until the next options answer. On the phone, returning to a chat before the other chat answered showed fallbacks. The support stays tagged with its session and every consumer already filters by session, so it is now kept, as before the shared owner.
* Coalesce phone options rereads across turns and status changes
Each new turn or host status change re-attached the phone's options reader, so a quick run of turns sent one parallel options request per turn. These changes now reread within the current context, as on desktop: one request runs and at most one waits behind it, and only the latest answer publishes. A status change that arrives while a read is in flight now waits for that read, which the transport bounds.
* Restore the phone status wake assertion and type the burst test commands
A refused pick is dropped rather than kept as a finished operation, and the path change replaced the options record, so no revalidation read is in flight when the status wake arrives. The wake therefore reads at once, as the original test asserted. The burst test's superseded answer now carries a real conversation command.
Sheets slide on iOS's curve over their own height, ease when their content
changes, and paint a lighter surface with grouped rows and a drawn switch.
Create worktree, the model picker, the GitHub issue sheet, file preview,
Agent Session History and the task list take the redesign draft's layouts
at the app's smaller type sizes.
* fix(chat): collapse settled permission notices
* Keep notice scroll addresses unique per rendered row
* test(native-chat): cover notice scroll bounds and shared classification
* fix(mobile): preserve notice summary long-press actions
* Preserve mobile notice selection and caret rendering
* Use type imports in mobile notice web tests
* Resolve real SVG test host from mobile dependencies
* fix(acp): separate recovered history from outside activity
* Fix repeated recovered history divider corrections
* Compose ACP replay targets before checked settlement
* Explain ACP scaling fixture timeout
* Complete ACP fixture types and runtime English catalog
* Type exported ACP fixture warning mock
* Restore ACP history without an extra notice
* feat(mobile): compact top bar, bottom search and filter chips
Every list screen gets one compact bar (back, centred title, at most two
buttons, the rest in a ⋯ pull-down), with search moved to a wide bar at
the bottom and filters as chips. The workspaces page shows the host name
at the top of the list and folds it into the bar as you scroll.
* test(mobile): pass the Tasks bar's accessory in the host subtitle test
* fix(mobile): stretch filter chips to fill the row; follow moved header controls in render checks
* fix(mobile): draw Claude Code's ⏵⏵ mode marker on Android terminals
Android ships no font with U+23F4-23F7, so the terminal drew boxes.
Bundle a 664-byte Noto Sans Symbols 2 subset, inline it in both
terminal hosts, append it to the Android font stack only, and load it
with the Nerd font before the WebGL atlas first paints.
* fix(mobile): spell the terminal font probe glyphs as escapes
* refactor(mobile): tidy terminal symbols font wiring
* feat(mobile): tap the session title to see its full name, path and host
A sideways phone caps the title at 160pt beside the tabs, so long worktree names were cut off with no way to read them. Tapping the title now opens a card under the top bar, built like desktop's tab hover card, with the full name, the worktree's path and the host. It fades and slides in over 150ms (fade only with Reduce Motion), and any tap outside closes it.
* fix(mobile): keep the worktree-name reset effect unchanged; type-only View import; lazy card animation value
* fix(mobile): preserve title card bounds and execution host identity
* test(mobile): complete title card platform mocks
* fix(mobile): separate detail card from title trigger
* style(mobile): apply pinned formatter wrapping
* fix(mobile): reopen session details during the closing animation
* style(mobile): wrap the session title imports
* Keep session title activation on one target through card exit
* Host session details above page navigation and retain input scope
* Keep the title target clear as its layout changes
* Clarify title-card types and observer cleanup for checks
* Keep mobile title-card requests and visual exits under one owner
* Make the existing mobile title an explicit web click target
* Keep mobile title details nonmodal through their visual exit
* Follow the active mobile palette in session title details
* Use array syntax for title card motion fixture
* Localize the mobile workspace details hint
* test(mobile): cover title card release to a higher sheet
* Refresh measured mobile web chunk envelope
* Show session tab details on phone taps
* Keep phone tab details current through layout and handoff
* Refresh cached phone tab footer colors with appearance
* Use checked fixture state and top-level test owner types
* Supply app visibility in the title card Surface fixture
* fix(mobile): keep empty terminal detail titles readable
* fix(mobile): keep Markdown editor selection visible deep in long files
iOS WebKit stopped drawing the selection highlight and handles about two
screens into the rich Markdown editor because the editable element was
scrolled by its non-editable host. Make the editable element scroll itself.
* test(mobile): find the editor's document rule by a property it still has
* fix(mobile): lead workspace-list agent rows with the conversation name
* fix(mobile): name a same-tab child once its parent row is gone
* refactor(runtime): share the structured chat tab id parser
* test(runtime): build the row-name summary without a cast
* perf(runtime): read each worktree's session once per worktree.ps pass
* feat(mobile): give every corner one of three shapes
Corners had drifted to 17 different radii, so a screen mixed 6 pt
buttons, 14 pt cards, 12 pt groups and 4 pt badges. Every corner now
comes from the shape of what it is:
- control (12): anything you tap — buttons, icon buttons, fields,
pickers, segmented controls, key caps.
- surface (16): anything that holds content — cards, groups, sheets,
banners, code boxes. Dialogs and the composer box use floating (24).
- pill: badges, chips, status pills, dots, avatars and the composer's
own round buttons.
- tile (10) for icon squares and mark (4) for inline code and check
squares; nested shapes take the parent's radius minus the gap.
Every rounded style also asks for the continuous (squircle) corner
curve; iOS draws it, Android and the web ignore it.
* fix(mobile): keep circles and pills on the circular corner curve
The continuous curve squares off a corner radius that reaches half the element's size, so the FAB, dots, avatars and the composer's round buttons stopped being round on iOS.
* fix(mobile): put the anchored detail card on the surface radius
* fix(mobile): keep the circular curve on unclipped bordered styles
React Native strokes an unclipped visible border on a circular path while it fills the background with the continuous curve, so the two edges drifted apart at the corners.
* feat(mobile): tap the session title to see its full name, path and host
A sideways phone caps the title at 160pt beside the tabs, so long worktree names were cut off with no way to read them. Tapping the title now opens a card under the top bar, built like desktop's tab hover card, with the full name, the worktree's path and the host. It fades and slides in over 150ms (fade only with Reduce Motion), and any tap outside closes it.
* fix(mobile): keep the worktree-name reset effect unchanged; type-only View import; lazy card animation value
* fix(mobile): preserve title card bounds and execution host identity
* test(mobile): complete title card platform mocks
* fix(mobile): separate detail card from title trigger
* style(mobile): apply pinned formatter wrapping
* fix(mobile): reopen session details during the closing animation
* style(mobile): wrap the session title imports
* Keep session title activation on one target through card exit
* Host session details above page navigation and retain input scope
* Keep the title target clear as its layout changes
* Clarify title-card types and observer cleanup for checks
* Keep mobile title-card requests and visual exits under one owner
* Make the existing mobile title an explicit web click target
* Keep mobile title details nonmodal through their visual exit
* Follow the active mobile palette in session title details
* Use array syntax for title card motion fixture
* Localize the mobile workspace details hint
* test(mobile): cover title card release to a higher sheet
* Refresh measured mobile web chunk envelope
* Keep Linear refresh results authoritative in Tasks
* Preserve scoped Linear edits through read settlement
* Complete Linear consumer entry and source teardown
* Fix scoped Linear read lifecycle and external refresh
* Complete Linear terminal reads and causal refresh dispatch
* Cover cached Linear read reconciliation under pending writes
* Fix cold Linear lookup invalidation
* Make Linear regression fixtures deterministic
* Align Linear test fixture naming with lint policy
* Consolidate Linear test imports
* Keep Linear saved publications coherent through React replay
* Preserve confirmed Linear accounts through unavailable checks
* fix(linear): retire recovered credential errors on acknowledgement
* fix(linear): keep runtime cache reset on account acknowledgement
* Fix Linear CI fixtures after final main integration
* Correct Linear mobile fixtures and scoped lint annotations
* Keep the mobile fixture assertion annotation on its expression
* Fix Linear static contracts and integration test boundaries
* Complete audited Linear static gates and preserve no-op selection
* Separate Linear selection decisions from observations and preserve create completion
* Name exported Linear fixture mock types with public annotations
* Preserve exact submitted drafts through create settlement
* Read draft retention mode from its committed snapshot
* Publish the current closing draft after queued form replay
* Type Linear create attempts from their form owner
* fix: supply complete task creation test fixtures
* fix: complete checked Linear creation metadata fixtures
* test: complete Linear dialog fixture composition
* test: use host-qualified workspace keys for board sync fixture