The diff-review dock has no next-step bar, so its bottom action now offers
Resolve with <agent> for a conflicting pull request, with the launch notice.
In the hub the bar shows its agent launch notice on the Pull request tab
too, since the removed box was the one that showed it there, and an Unlink
failure reaches the bar's notice now that the PR tab has no footer.
* Show the native default effort for OpenCode models
* Show the effort OpenCode 1.x actually runs when none is picked
OpenCode 1.x ACP runs a listed default variant, else the first listed
one, so declare that same variant instead of a no-override Default. The
draft and launch default stay as on main; the resting fallback now shows
it too. Shared declared-effort consumers expect no effort for a readback
without that model's menu.
* Name the recorded OpenCode listing test for what it checks
The Source Control hub's next-step bar is now the only bottom action on the
PR tab: the tab body takes its footer from the caller, and only the
diff-review dock passes the old bottom action. The legacy Conflicts box is
gone, since the gate row and the bar's Resolve already cover it, so a
conflicting PR no longer says so four times. The bar keeps its reason line
only where its button does not name the blocker.
Rows lead with the file name and put its folder under it, so names stop
truncating. The whole row opens the diff, one quiet button stages or
unstages, and discard moves to a left swipe that still asks first. A
status line, a Commits row and a conflict banner with Abort sit above
Uncommitted and Staged sections that carry Stage all and Unstage all.
The rarer git actions move into the header's grouped menu.
The close button sat at a fixed 16pt from the right edge and ignored the
window's right safe-area inset, which on the iPhone Duo is the status
column holding the clock and the camera. Offset it by that inset, and size
it to 44pt itself because page Pressables ignore hitSlop.
* fix(emulator): scope emulator list to the worktree's sessions
emulator list ignored --worktree and returned serve-sim's global iOS helper list. It now lists the session registry's attached sessions (iOS and Android) for the selected worktree, or every worktree's for --worktree all.
* fix(emulator): drop stopped devices before listing sessions
* fix(emulator): point the no-active hint at emulator devices
* test(emulator): assert list results, not serve-sim calls
* refactor(emulator): prune stopped devices inside the registry listing
The PR tab and the Checks and Comments screens refresh by themselves while
they are open and the app is in the foreground, on desktop's checks-panel
cadence (policy moved to src/shared). A failed background read keeps the
PR on screen and backs off instead of replacing it with an error.
Review threads show the lines they are about: the host forwards the tail
of each thread root's diff hunk, and mobile numbers it back from the
thread's line. Bot findings get "Ask <agent> to fix", which launches
desktop's Resolve comments prompt (moved to src/shared) for that thread.
Fix and Ask buttons name the agent that would start.
The PR tab now leads with the PR's identity, one row per merge gate
(checks, unresolved threads, approvals, conflicts) that opens the screen or
sheet that resolves it, a changes card and a collapsed description. Close,
Unlink, Auto-merge, Edit title and Copy link move into the ⋯ menu.
Checks and Comments become pushed screens: failures open with their
annotation and Fix / Rerun / Open / Log on the failure, running checks
next, passed folded; comments filter by Unresolved / All / Hide bots with
Resolve and Reply on each thread and a comment box at the bottom.
Merge opens a sheet that lists only the methods the repo allows, starting
on its default. Reviewers move into a sheet with search to add and remove.
Every Create PR entry point opens one sheet: title and description seeded
from the branch's commits (shared seed rule in src/shared, GitHub's own
default), Write with AI, base picker and draft. It pushes first when
needed, works for all five providers and lands on the PR tab for GitHub.
* fix(chat): move forward readers to the covered tip after a no-op save
A write whose fold changes nothing stores no outcome record, so its
sequence never appears in a forward read. Readers placed themselves at
the last returned row and stayed behind the live cursor, so open chats
re-read an empty page and recomputed the prompt projection on every
later delivery until the next real save.
Forward journal reads now report the highest sequence they covered.
History pages move there when the read was complete, prompt reuse and
native responses compare against it, and a read cut short by its limit,
byte budget or damage still stops at the last returned row.
* fix(chat): append unlimited outcome reads without spreading, and report coverage from the mobile journal fake
* fix(chat): skip the retention scan for batches that change no journal items
* feat(mobile): one next-step bar under both Source Control tabs
Changes and Pull request are the only segments. A bar at the foot of both
shows the branch's one next action: the commit box, push or pull, create
the review, resolve or fix with the workspace's agent, merge when ready,
merge, or why it can't. Commits opens as a pushed screen.
* fix(mobile): keep Merge reachable and show bar failures on both tabs
* test(mobile): expect the two Source Control segments in the page render test
* Show a card in the chat when its agent hands work to another agent
An agent that hands work off with 'orca agent launch' from a native chat
now leaves a 'Handed off to agent' card there, linking to the new chat or
terminal, the way a supervised worker-start leaves 'Started worker'.
The CLI names its chat on agent launches (same-host only); the host records
a display-only agent-handoff item after a successful launch with a submitted
prompt, once per launch operation. Nothing about the card can refuse or delay
the launch.
Tracked in STA-13209.
* Keep orca commands working in a native chat that has no terminal handle
The launch-caller evidence read the host stamp off an absent evidence object, so every orca command from an ordinary native chat threw before sending. Adds CLI tests for which requests name the launching chat, restores the lockfile to the base, and fixes the Korean heading.
* Read a card's heading from its creation flags together
A caller passing only the started flag drew a handoff as Started worker; the heading now takes the message's flags as one argument.
* Show the handoff card for every agent a chat launches
A launch without a delivered task now records the card too, with no task text, as the common pattern records every agent a chat creates. The handoff tests now run the real chat host and journal: one card per launch operation, none on replay, a removed card stays removed across a restart, and the clock is fixed.
* Head a launch card Started agent when no task reached the new agent
Handed off to agent now appears only when the card carries a task; a card for an agent started with no task (no prompt, a draft, or a prompt that wasn't delivered) reads Started agent. The heading is derived from the card's text, so nothing new is saved. Also drops a self-launch check the launch path already refuses first.
* Show the handoff headings on the phone's agent cards
The phone card passed only the started flag, so with the new heading rules it would draw every creation card as a plain message. It now passes the message and has a heading for every case, so a missing one fails the build. The heading function now requires the message's creation facts, so no caller can leave them out. Adds the phone strings in every language, and the Japanese "Started agent" now uses the catalog's term for agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tapping a file on the commit page now opens the shared diff screen for
that file in that commit (openDiffForFile kind 'commit'), in today's
Commits tab and inside the hub's Commits page.
* feat(mobile): GitHub threads, viewed sync and line comments on the diff screen
Committed files of a GitHub pull request now show its review threads under
their lines with Reply and Resolve, take their viewed state from GitHub and
sync the checkbox and Viewed · next file back through setPRFileViewed. A tap
or a long-press drag picks lines and opens a sheet that sends the note to
the workspace's agent through the existing send path, or posts it as a
review comment. Threads and GitHub comments appear only while the local
branch is at the PR head, the same contents desktop shows them on;
otherwise the file says to push. openDiffForFile takes an optional line for
a thread's Open in diff.
* test(mobile): use a type import for the line-mutations recording adapter
* fix(mobile): name the staged or unstaged side in the jump sheet's file labels
The Commits list now stops where the branch left its base. It groups
commits into Not pushed (hollow dots, with Push) and On origin, ends at
"Branched from <base>" with Update branch, and loads more past 50. A
commit opens its own page: message, author, date, pushed state, files
with +/-, and Copy SHA / Open on GitHub in the menu.
Older hosts ignore the new params, so the list falls back to the full
history without groups or the base marker.
The review screen becomes the one diff screen from the approved design.
It walks the Changes section the file came from (uncommitted, staged or on
the branch) or one commit's files, so 'N of M' and next/previous stay in
that list. The title opens a jump-to-file sheet built on the shared
source-control tree, with search and viewed boxes. Unchanged runs fold the
way desktop's diff does. The bar's main button is Stage · next file for
uncommitted files and Viewed · next file otherwise; Discard and Unstage move
to the menu. Filter chips, hunk jumps and Stage Reviewed are gone.
openDiffForFile also takes { kind: 'commit', commitOid, filePath }, read
through git.commitCompare and git.commitDiff.
Source Control rows used to open a file three ways: the review screen, a
desktop editor tab the session then switched to, or a text-only branch
drawer. Every row now calls one openDiffForFile entry point that pushes the
review screen. The drawer, the session tab reveal and their handlers are
gone; Open in Session stays in the review screen's menu.
The queue card's invisible full-text line-count probe overflowed its two-line
row and stretched the queue's scroll range far past the real rows. Clip it to
the card text.
The queued-message editor drew Save and Cancel labels inside 38pt round icon
controls, so translated labels spilled over each other. They are now pills that
hug their label and truncate only when the row runs out of room.
A Claude Read of a PNG returns only base64; the path is on the call. A shared
rule turns each finished file-read of a raster image into an image-ref under
the tool run header on desktop and mobile. Mobile chat prose now draws
https, data and absolute-path markdown images inline, every image in a
paragraph, through the existing host image read.
* fix(mobile): quote expo-constants iOS build paths so checkouts with spaces build
Backports the expo-constants 58 quoting of the app.config build phase
(podspec bash -l -c argument and basename $PROJECT_DIR) to 55.0.17.
* fix(mobile): quote react-native-xcode.sh call in the iOS bundle phase
Expo SDK 55's template leaves the command substitution unquoted, so the
Bundle React Native code and images phase splits a checkout path with a
space. Expo's SDK 58 template applies the same quoting.
* fix(mobile): scroll wide tables in the rich Markdown editor instead of squashing columns
* fix(mobile): keep CJK table columns from shrinking to one character
* fix(mobile): keep Android split screen from restarting or squeezing the app
One UI gives a split-screen pane its own density. Android recreated
MainActivity on that change, and React Native seeds its display metrics
from the application context, which keeps the full-screen density, so
the pane was laid out at the wrong scale. A config plugin declares
density in configChanges and re-seeds the metrics from the activity.
* fix(mobile): catch React Native's rotation re-seed before the frame draws
A one-shot listener added on configuration change missed a cold start or
a 180-degree flip at a non-zero rotation, where React Native's root view
re-seeds from the application context without a configuration change.
* fix(mobile): publish re-seeded display metrics in one step
Text measurement reads the holder off the UI thread, so build the
pane-density screen metrics before publishing them.
* fix(mobile): show workspace images in session Markdown tabs
* fix(mobile): show workspace images restored by undo or paste
* fix(mobile): keep session image loader current and inside the workspace
* fix(mobile): classify entity-encoded image URLs by their decoded scheme
* refactor(mobile): classify editor images with the shared Markdown href router
* test(mobile): expect workspace image markup in the code-block selection check
* fix(mobile): save iPhone library photos as JPEG named by their bytes
The iOS photo picker handed back the asset's HEIC bytes as-is, and the host
named every pasted image .png, so agents read a HEIC file as a PNG and failed.
Ask the picker for the compatible representation (JPEG), and name pasted
images by their signature so the extension always matches the bytes.
* fix(mobile): keep unknown pasted bytes on the .png name
A .heic name falls outside the shared image MIME table, so chat previews
and agent prompts would drop the image. The picker now sends JPEG, so
leave HEIC detection out.
* refactor(mobile): keep the pasted image extension list module-private
The iOS web shell cancelled every subframe navigation, including the sealed about:srcdoc frames that HTML previews and chat visual cards render in, so they stayed blank. Allow exactly about:srcdoc subframes, let only a main-frame load spend the shell-load allowance, and never fail the shell document over a subframe response. Chromium never asks the Android shell about about: subframes, so Android already renders them.
* fix(native-chat): stop async question Submit from queuing duplicate answers
When the agent is mid-turn, answering a Codex async question queues the
answer as an ordinary message. The card stayed up until a transcript row
retired it, which a queued message never posts until the turn ends, so
every Submit queued another copy. Hide the occurrence once its answer is
queued, on desktop and phone.
* fix(native-chat): steer async question answers into the running turn
An answer to the agent's own mid-turn question belongs to that turn.
Queuing it behind the follow-up setting held it until the turn ended,
after the agent had already proceeded without it. Send it with the
steer intent whenever the follow-up resolver would queue a plain send,
on desktop and phone. Stop and a waiting command still queue, and the
queued fallback still hides the card.
* fix(native-chat): hold async questions while a queued answer waits
Replace the mount-scoped hide on a queued answer with one derived from
the queue: a question stays hidden while a queued card quotes it, across
remounts, and returns if that card is deleted or edited away. Lock
Submit until the send settles, and keep a returned answer out of the
composer draft.
* fix(native-chat): keep structured session under the line cap
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
UIKit's split-keyboard update crashes breaking our constraints on
keyboardLayoutGuide, so the probe reads keyboardWillChangeFrame instead.
The hinge interaction goes too: UIKit already relays out a view that
read reserved regions in layoutSubviews when they change.
Fixes#36742
* feat(mobile): collapse sub-agent lists by default and group idle sub-agents
Mobile follow-up to #36936: per-parent child disclosure with +N count and a
live-state dot when collapsed, and a quiet 'N idle' group for two or more
idle children once expanded.
* fix(mobile): keep sub-agent disclosures reachable and remembered
- Hit slop extends only downward so toggles no longer steal taps from the row above
- Disclosure state survives workspace list remounts via a bounded module cache
- Collapsed signal considers all descendants, not just direct children
The free-slot probe fills every desktop slot, so end()'s final chunk forward
is refused by construction. Since #36922 reserved native slots, that final
read now succeeds and its forward is refused, failing Mobile verify under Bun.
Desktop RPCs and on-device native verbs shared the bridge's 64 in-flight request slots on both page and shell. Unanswered desktop calls could hold all of them, refusing native.media.pick and native.media.release; refused releases stranded staged handles until expiry, so later picks hit native_media_handle_cap. Desktop calls now stop at 60 so native verbs always have the last 4.
* fix(mobile): run the repo's archive hook when deleting a workspace from the phone
Mobile's delete sent worktree.rm without runHooks, so the host skipped the
orca.yaml archive hook that desktop runs. The phone now does what desktop's
ensureHooksConfirmed('archive') does: it asks to trust an unapproved shared
archive script, sends runHooks with desktop's reply timeout, and when the hook
fails it shows the host's error with Delete Anyway, which resends with
allowFailedArchiveHook.
repo.hooks now reports archiveTrust (hash and content of the shared orca.yaml
archive script, or null when none needs trust). Older hosts omit it, and the
phone then keeps skipping the hook rather than run a script it cannot check.
* fix(mobile): share one Modal for delete drawers and pin each prompt to its host
Delete's confirm, archive trust prompt and hook failure now share one
BottomDrawerModalHost so iOS never presents over a closing drawer. A pending
prompt or failure keeps the clients it started on, the row hides as soon as
Delete is tapped, repo-wide trust counts server-owned repos, and folder repos
report no archive trust since their removal runs no hook.
* fix(mobile): recheck archive trust before Delete Anyway and count server repos once
Delete Anyway goes back through the trust decision, so a script changed since
approval is asked about again. Repo-wide trust counts each server repo once
rather than once per workspace, and restoring a row never duplicates it.
* fix(mobile): honour repo-wide archive trust only with the desktop catalog loaded
Until the catalog loads, a same-id repo on another host cannot be ruled out,
so only an approval of that exact script counts. The trust drawer's actions
take the pending request synchronously so a double tap cannot both run and
skip the hook.
* fix(mobile): drop archive prompts and failures from another desktop
A trust prompt or hook failure belongs to the desktop it started on. After a
host switch it is no longer shown or acted on, so a retry can never pair one
desktop's trust with another desktop's repo catalog.
* refactor(mobile): one archive trust check and an optional drawer busy flag
* fix(mobile): file delete-failure strings under the host screen overlays
Also pins the host's archive hash as a literal digest so the node test no
longer imports renderer code.
* fix(mobile): carry delete haptics and automations onto the renamed hook trust drawer
The phone's liveness probe read a full status.get (~12 KB, ~2.3 KB gzipped)
and threw it away on every idle and foreground probe. It now sends
`status.get {probe: true}` and the desktop answers `{runtimeId}` alone,
skipping the machine-name wait. status.get keeps accepting any params, as
when it declared none; older desktops ignore the flag and answer the full
status, which still proves liveness.
terminal.list takes `includePreview: false`. The phone's session inventory
and automation pane lookup never read the 300-char output tail, so they ask
the desktop to drop it; older desktops strip the unknown key and keep
sending it.
Compressed terminal binary frames now use gzip level 1. On recorded-style
terminal output it keeps 93-99% of the level-6 saving (57-95% of bytes
saved) at 1.2-4x less main-thread CPU; text replies keep the default level,
where level 1 costs ~45% more bytes on the wire.
* fix(mobile): guard catalog snapshots against late replies and trim the snapshot paths
A slow unchanged or delta reply that predates a client switch (A→B→A)
could re-point the paired-server snapshot token at rows it no longer
matched; the next row delta then rebuilt onto the wrong base. Pending
reads now carry the token they asked with, and admit drops any non-full
reply whose token is no longer held.
Single-flight reads with one trailing join move into
WorktreeCatalogSnapshotClient.read(), so paired-server rows get the same
protection home had; home's own copy goes away. Server rows use one read
path for old and new desktops and drop snapshots for servers that leave.
The host screen asks for a first page only while nothing is shown and
the full read is not failing, and drops it on an invalid reply. The
shell no longer seeds the page with a status proven on a connection
that has since dropped.
Cleanup: drop exactActivity (unreleased; home only ranks by coarsened
activity and its exact rows defeated delta reuse), fold the legacy opt-in
check into the snapshot resolvers, remove unused worktree.ps result
types and a duplicate page subscription, and re-record the affected
RPC goldens.
* fix(mobile): keep server-row snapshots per client and skip first pages for loaded empty hosts
A shared per-host snapshot let a slow read on a replaced client reclaim
ownership from the new one, discarding the new client's reply. Snapshots
are now kept per client, and a delta rebuild retry runs only for the
current owner. An empty host that already loaded no longer asks for a
first page on every poll.
* fix(mobile): stop page bounce and input zoom; URL keyboard for new browser tabs
* feat(mobile): smoother motion, press feedback everywhere, reduced motion for sheets
Working rings and the tool-run pulse run as compositor animations on the
page. Sheets, the right panel, the page stack and the pager cross-fade under
Reduce Motion. The right panel shares the sheet's drag physics. The composer
cross-fades Send/Stop/Save and the mic, jump-to-latest and prompt cards ease
in, banners ease their height, and every Pressable shows a pressed state.
Pairing gets a success beat, and the clean state fades in after a commit.
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.