* feat(mobile): offer Sign in on a signed-out chat via a host terminal login
* feat(mobile): say which host is out in the chat composer, with Reconnect for the desktop link
* refactor(mobile): fit the host outage line beside main's chat changes
* perf(native-chat): stop rescanning whole chat history on every status update and subagent label
Status re-derived after every journal write built a map of every item body in the
chat to find conversation-command turns; index only command entries instead.
Same-named sibling subagents restarted their ordinal search at 2 for each sibling
(quadratic); resume each label's search where it stopped. Output is unchanged.
* perf(native-chat): find command turns through an indexed read on each status update
Status re-derived after every journal write still walked the whole history to
learn which turns belong to conversation commands. The host now passes a by-key
read of the same status rows (same display state, presentation membership and
stop-note projection, limited to the current /clear context), and the request,
preview and interruption readers check only the turns their backward scans reach,
the way the usage-limit reader already does. Clients without an index keep the
scan. A contract test drives real journal writes at 10x history and pins both the
constant indexed reads and the remaining per-row work.
* test(native-chat): count status body reads without Reflect.get or casts
* chore: drop accidental lockfile churn
* perf(native-chat): read the status display state once and pin indexed status parity
The host's status lookup now reads the journal's display state once per projection.
The status contract test covers the /clear boundary, an interruption verdict behind
a skipped command turn, a revised command turn, and the journal's per-row render
budget; the context-rows test expects the returned /clear sequence.
* test(native-chat): bound status work on the write that records an Orca stop
* test(native-chat): status follows a turn that becomes a command turn
* fix(native-chat): take the status lookup from the status read itself
Status read the journal's display state directly, which journals without queued
messages (test stand-ins, older wiring) do not have. The status read now returns
the by-key lookup bound to the state it rendered; readers whose status read has
none keep the scan.
* Add collapse and expand all projects controls
Support a sidebar button, keyboard shortcut, and Alt-click on project
headers, with a contextual tip to introduce the feature.
* rm mockup file
* Clean up project header toggle handling and fixtures
* feat(mobile): continue an interrupted chat from the phone
Desktop offers Continue on a turn Orca's own stop cut off; the phone only
showed the words. The offer and failure rules move to
src/shared/native-chat/native-chat-interrupted-continue.ts, desktop uses
them unchanged, and the phone drives them from its session over
agentSession.continueInterrupted when the host advertises it.
* refactor(native-chat): type the Continue answer instead of casting it
* fix(mobile): let a phone call agentSession.continueInterrupted
The host refused the method from phones, so Continue on the phone always
failed. Allow it on the execution-host route and advertise
agent-session.continue-interrupted.phone.v1, which the phone gates on:
hosts that advertise only continue-interrupted.v1 still refuse phones.
* fix(mobile): hide Continue when an older paired desktop relays the chat
A newer page on a server routed through an older desktop would offer
Continue that the desktop refuses to forward. Gate it on the relaying
desktop too; the workspace server context moves to a react-native-free
module so session hooks can read it. Long failure text now shrinks so
the banner's dismiss stays on screen.
* refactor(mobile): reuse the chat's Try Again look and shared Continue wording
The Continue button takes the Try Again pill styles and common.continue,
the shared helper takes the RPC's answer instead of a call wrapper, and
the phone hook memoizes its result and keys late answers in one place.
* fix(i18n): keep the Continue failure copy inline so catalogs rebuild it
* refactor(mobile): keep the structured session hook under its line cap
* fix(native-chat): check the session's agent audience before continuing a turn
* Add child worktree creation to the context menu
Preselect the clicked parent and its owning host when opening the
workspace composer, including nested SSH worktrees.
* Fix child worktree creation across local and remote hosts
Chat links used the body text color, so a link only showed as a faint
underline and a theme's textLink.foreground had no effect. They now
paint --chat-link: the theme's link color, else the built-in note blue
for the chat's own scheme, mixed toward the chat foreground like the
other chat hues. Code and bold link labels follow the link color.
When the chat paints a terminal palette the app theme doesn't match,
the app theme's link color is cleared, as its bubble already is. The
theme contrast check now measures the chat link on the theme's user
bubble and drops only the bubble when it fails.
* feat(mobile): render PR bodies and comments with the shared Markdown renderer
The PR view drew PR bodies, comments and review threads with its own small
parser, so strikethrough, footnotes, task lists, entities, nested lists and
quotes, images and bare links came out raw or mangled, and nothing could be
selected. It now uses MobileMarkdown, like chat, file preview and Tasks.
MobileMarkdown gains collapsible <details> sections (closed unless authored
open), the one thing only the PR parser had. Images without alt text no
longer draw the word "image".
* fix(mobile): keep <details> parsing bounded and browser-like
Cap section nesting, keep each markup scan linear on hostile input, leave
markup inside HTML comments alone, read open as a real attribute, and close
a section whose </details> sits inside a list item with that list.
* fix(mobile): close <details> at an inline </details> and ignore <details-*> tags
A </details> at the end of a paragraph or list item ends the section with
that block, as in a browser. Custom <details-menu>-style tags no longer
open a section, and heading markup in a summary keeps only its text.
* refactor(mobile): simplify <details> parsing and reuse DisclosureChevron
Keep the close-with-container mark on the node instead of a module set,
skip HTML blocks with no details markup up front, scan inline HTML once,
and turn the summary chevron the way other disclosures do.
* chore(mobile): register the <details> mdast node in a declaration file both lint gates accept
* chore(mobile): keep the lint config plain JSON
* refactor(native-chat): move chat link rules and the file-existence checker to shared
GitHub reference linking, the reference-repo rule, chat file-path linkification,
the per-chat existence checker and the path-existence batcher are platform-free.
Move them to src/shared so the phone's chat can use the same rules; desktop keeps
its host adapters and behaves the same.
* feat(mobile): link #123 refs, check file paths and wrap code in chat
* test(mobile): pin which chat paths and refs become links
* refactor(mobile): parse repo link fields instead of casting
* refactor(mobile): own chat link state in the chat overlay
* fix(mobile): keep unchecked chat paths as precise as before
* fix(mobile): only peek at known paths while reasoning streams
* test(mobile): leave #123 splitting rules to the shared transform's tests
* refactor(native-chat): tidy shared link helpers and skip separator-free text
* test(native-chat): import the existence type from its shared home
* style(mobile): fold the code-span link handler onto one line
* feat(chat): resume working chats after restart by default for new users
New profiles start with automatic resume on. Profiles saved before this
change that never chose keep it off, so existing users see no change.
* fix(chat): decide launch resume only after settings load
The launch reads the resume preference once. When held chats enabled it
before settings loaded, it read the preference as off and opened the
review dialog for the session. Also drop the web storage pin: the web
client never runs the launch resume.
* feat(mobile): paste images and long text into the chat composer
* test(mobile): pin which composer pastes attach
* fix(mobile): keep a long paste as text when no upload can start
* fix(mobile): re-encode pasted images agents can't read as PNG
* test(mobile): pin composer paste routing at the composer field
* refactor(mobile): fold composer paste routing into its hook
* fix(mobile): read pasted images in the uploader so the composer needs no shell bridge
* test(mobile): stub the context ring in the composer paste test
* feat(mobile): show the context usage ring in the chat composer
Desktop's ring reads the structured session's context facts; the phone
never read the host's contextUsage answer. The summary rules move to
src/shared so both read the same numbers, and the title card's anchored
card machinery is generalized so the ring's breakdown opens above the
composer without dropping the keyboard.
* refactor(native-chat): share the context ring's critical threshold
* test(mobile): stub the context ring where composer tests stub its siblings
* fix(mobile): keep the context card with the composer and the keyboard up
Touching the card no longer takes focus from the draft, the open card
moves with the composer as the keyboard or draft resizes it, and the
composer's own menu closes it.
* fix(mobile): close the context card when the composer hides, and let long titles wrap
* refactor(mobile): share the anchored card layer and the ring readout
* test(e2e): pass the new options arg in the permission publication test
* feat(native-chat): show the chat account usage for /usage
* fix(native-chat): derive usage card lifetime from its owning turn
* test(native-chat): keep runtime manifest coverage additive
* fix(native-chat): advertise usage from the host options reader
* fix(native-chat): read cold chat usage through the chat's own Codex launch
- Cold /usage starts a short-lived app-server the way the chat launches
(configured command, args, environment, pinned home), as Codex rename does;
the status-bar probe returns to its original path.
- Report each reset outcome inline after the card refreshes.
- Send the chat's account as a typed kind plus email; the client owns copy.
- API-key accounts are recognised by a typed error, not message text.
- Keep the caret after kept text; drop unreachable WSL matching.
* refactor(native-chat): share the at-rest Codex app-server launch between rename and usage
* fix(native-chat): keep a reset's outcome when the usage refresh fails; label accounts by kind
* fix(native-chat): drop stale usage replies by request id instead of a render-time ref
* test(native-chat): keep the usage card test inside the web project
* fix(native-chat): clear the usage card when a waiting prompt changes; retire its callbacks on unmount
* chore: drop accidental pnpm lockfile churn from the main merge
* fix(native-chat): put the caret after kept text on /usage; offer the reset once the host's first Codex read lands
* fix(native-chat): type the usage snapshot wait and its test state
* fix(native-chat): re-read the usage offer when the chat host's Codex state changes, instead of forcing a host read
* fix(native-chat): watch the usage host from the card's opening and keep one background re-read at a time
* fix(native-chat): bind the usage card's background re-read to its opening
* feat(native-chat): open /usage as a modal like /resume
The chat's Codex usage card now opens in a root-hosted dialog with the
same surface, width and dismissal as /resume's Session History, instead of
a full-width card docked above the composer. Closing the modal retires its
reads; docked-only clearing on turn, prompt and session changes is gone.
* fix(native-chat): give the /usage modal /resume's fixed height and drop a replayed mount's first read
* fix(native-chat): size the /usage modal to its card and keep the card's layout while loading
* fix(native-chat): pin the /usage modal's top where /resume's sits so loading never moves its title
* chore(native-chat): describe the /usage loading placeholder accurately
* feat(mobile): dock the chat's plan above the composer
Desktop docks the latest plan above the composer; the phone only drew it
inline in the transcript. Move desktop's docked-plan rule to src/shared and
draw it on the phone as a collapsible strip in the background-tasks box.
* perf(native-chat): keep the docked plan's identity until the plan changes
* refactor(native-chat): read the docked plan from the newest message back
* refactor(mobile): drop a cast in the docked plan's open memory
* test(mobile): pass the tray's new taskList in the recovery test
* feat(mobile): keep unsent chat drafts across an app reload
Chat drafts and their skill picks lived only in the page document's
memory, so killing the app or an OTA reload lost them. Each
workspace's tab drafts are now saved in the page storage slot shells
since 0.0.59 already allow (left unused by #37900). A clear saves at
once so a sent draft never returns, and tabs the host stops listing
or the phone closes are dropped from storage. A restored draft that
leads with a skill loads the skill catalog so its chip shows again.
* fix(mobile): let a pre-load send win and keep skill scan retries
A send or clear made before a workspace's saved drafts finished
loading no longer gets overwritten by the saved text. A page whose
shell refuses the key stops re-reading it, a dropped write is retried
on the next save, and a failed skill scan is still retried the next
time the menu opens.
* fix(mobile): save pending chat typing when the app goes to the background
A write that fails partway no longer leaves a remembered value that
makes the next clear look already saved, and typing still inside the
250 ms window saves when the page is hidden.
* refactor(mobile): simplify chat draft persistence
Read the saved value back once per write through persistMirroredReadBack,
build the key with the allowlist's own workspaceScopedKey, keep tab-scope
parsing in one place, track the load as one state, stop holding
retirements once the load lands, and skip the per-draft size loop when
the whole value fits. Drops the test-only settle export.
* feat(mobile): name and link agent senders on queued chat messages
Queued cards and the queued-message editor drew the sender from the saved snapshot name as plain text. They now share the transcript card's sender row, so a queued message names its sender's chat by its current title and taps through to it, as desktop does.
* fix(mobile): keep the queued sender line on one row
* refactor(mobile): tidy the queued sender row and editor heading
* fix(mobile): bound the queued sender row to its container width
Cmd+J and the new-tab search built open-tab rows only for a hand-kept
list of tab kinds, which never included native chats. Workspace tab
kinds are now every kind except browser and simulator (which have their
own rows). Chat rows use the tab strip's chat name and provider icon,
and selecting one activates it the way clicking its tab does, including
dismissing a paired-device page placeholder over it. The new-tab search
reads only its own workspace's chat names.
Tracked in STA-13638.
* mobile(native-chat): render agent questions through chat markdown so their file paths open
Question cards, multi-step asks, awaiting-input rows and answer receipts drew the
agent's question as plain text, so paths in it did nothing. They now render through
one shared question text on the chat's markdown adapter, like desktop.
* mobile(native-chat): keep question numbers, selection and announcements in the awaiting row
Numbers sit beside each question instead of inside its markdown, transcript questions stay
selectable like transcript prose, and a pending question stays inside the row's live region.
* mobile(native-chat): key awaiting-row questions by their text, like desktop
* test(mobile): mock markdown leaves in the ask card test and match question text by content
* mobile(native-chat): keep repeated questions' keys unique in the awaiting row
* mobile(markdown): keep bold weight inside strong-toned text, so bold in a question title still stands out
* test(mobile): flatten the question title style without a type assertion
* fix(tabs): show the agent session's name on terminal tabs
The tab strip rebuilt each terminal tab without its session title and then re-ranked it, so the first-prompt title won over the session's own name. Carry the session title through the projection, and give the tab strip, unified tab label and sidebar agent row one shared name order so they cannot drift.
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* fix(tabs): paired desktops take the host's session name for mirrored terminal tabs
The host projection now forwards the window's session title to paired clients, and the client's rebuild adopts it (older hosts omit it, so the client keeps its own). Without it a mirrored tab re-ranked to the client's first-prompt title.
* fix(tabs): one writer per session for a mirrored tab's session name
The paired client's own title sync keeps a session it already tracks current; the host's name only fills a session the client doesn't know, so a stale host snapshot can't revert a rename the client already read.
* fix(tabs): a paired host is the only writer of a session name it publishes
* fix(tabs): only a host window that resolves session names speaks for them
* fix(tabs): derive session title authority from live publishers
* fix(tabs): keep the session title publisher check concrete for runtime subclasses
* fix(tabs): one publication when a closing window drops browsers and title authority
* test(tabs): follow main's runtime test helper layout
* fix(tabs): paired desktops keep a quick-command name ahead of the session name
* fix(tabs): a name the host cleared doesn't come back as the idle pane's title
* test(tabs): build the idle-pane fixture as its own surface variant
* fix(tabs): refresh live session names from their authoritative source
* fix(tabs): preserve title recovery and bound refresh retries
* fix(tabs): preserve Codex index names at EOF
* fix(tabs): track the observed Codex index end
* fix(tabs): point the moved session-title projection at its new shared path
---------
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* feat(mobile): show background task rows in the chat transcript
* fix(mobile): truncate the task output path like desktop
* fix(mobile): scale and select task rows like the rest of the transcript
* fix(mobile): translate the task output line
* refactor(mobile): read task rows from the shared row content
* test(mobile): restore the text reader for task row tests
* test: remove 98 low-value mobile tests
* test: drop stale typecheck allowance for removed browser budget test
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(mobile): ask where to open a link tapped in chat
Tapping a web link in a chat message went straight to the system browser.
It now opens a sheet with Open in Orca browser (a browser tab on the host,
like terminal links), Open in Safari, and Copy link. The Orca option is left
out where a browser tab can't open (floating workspace, host without
browser streaming). Other markdown screens keep opening links directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mobile): label the external link action "Open in browser"
The action hands the URL to the phone's default browser, which is not
always Safari, so it now says so on every platform, like the browser
setting does. The chat link routing moves into openMarkdownLink so
openMarkdownHref keeps its upstream signature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mobile): open mail links in chat directly instead of the link sheet
The Orca browser rejects mailto: URLs, so the sheet's first choice failed with "Enter a valid URL". Also folds openMarkdownLink back into openMarkdownHref and drops the label-list test.
* fix(mobile): send chat web-search result taps to the link sheet too
* chore(mobile): allowlist the chat link sheet strings until session screens are localized
* fix(mobile): offer the Orca browser for a link only on a connected host known to stream it
Before the screencast probe answered, while reconnecting, or with no host client, the link sheet
offered Open in Orca browser and the tap ended in a refusal toast or nothing. It now offers only
the phone's choices until the host is connected and supports it. Tests cover the sheet's three
actions, its two-action form and a failed copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mobile): name the link sheet's phone choice "Open in system browser"
It opens whichever browser the phone is set to use, not a particular one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mobile): keep the chat link sheet with the chat it was opened from
The sheet's URL lived in the session screen, so it survived a notification
switching chats and reopened when returning from a file tab. It now lives in
the chat's row overlays next to the message actions sheet, which already
resets per conversation, and stays mounted until it has slid away. The Orca
browser choice now checks the live connection; showOfflineState is never
true while a chat is on screen.
* refactor(mobile): confirm a copied chat link like other copy actions
Copy link now flips to Copied with a success haptic, as the PR menu does,
instead of a session toast, so the chat overlay no longer threads showToast.
The link handler context defaults to the system browser, which drops the
null fallbacks from its readers.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* feat(mobile): copy a file's name from the file explorer
Long-pressing a file or folder row opens a sheet with Copy Name and
Copy Relative Path, named like the desktop File Explorer menu.
* fix(mobile): keep a slow copy from closing a later file's sheet
* refactor(mobile): close the file actions sheet at once, like sibling copy actions
* refactor(mobile): own the file row actions state beside its sheet
* fix(mobile): keep iPad page headers clear of the window controls
The reserved-regions probe reported the window's plain safe area, which on
iPadOS 26+ windowed mode does not cover the window controls. On iOS 27.1+
pages take their top inset from that value, so the host header sat under the
controls while native screens, which get the corner-adapted inset, cleared
them.
* fix(mobile): only raise the page's top inset for window controls
* fix(native-chat): show defaults for chats that have never started
* fix(native-chat): share default projection across controls and picks
* test(native-chat): prove controls recover from cached discovery
* test(native-chat): type deferred catalog as its wire result
* test(native-chat): preserve the catalog origin literal
* test(native-chat): remove duplicate RPC work-count cases
* Show source scan notices accurately in mobile Agent History
Reuse the existing desktop presentation policy in shared code, keep host failures and source notices separate from skipped transcript files, and retain their scanner-authored messages.
Adapted from the remaining mobile presentation in original PR #15671. Rebased onto main: the classifier now lives in src/shared/ai-vault/scanning/, desktop keeps its translated skip reasons, and mobile reuses its existing skipped-transcript notice.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* refactor(ai-vault): share the scan issue row key between desktop and mobile
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(tabs): show the agent session's name on terminal tabs
The tab strip rebuilt each terminal tab without its session title and then re-ranked it, so the first-prompt title won over the session's own name. Carry the session title through the projection, and give the tab strip, unified tab label and sidebar agent row one shared name order so they cannot drift.
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* fix(tabs): paired desktops take the host's session name for mirrored terminal tabs
The host projection now forwards the window's session title to paired clients, and the client's rebuild adopts it (older hosts omit it, so the client keeps its own). Without it a mirrored tab re-ranked to the client's first-prompt title.
* fix(tabs): one writer per session for a mirrored tab's session name
The paired client's own title sync keeps a session it already tracks current; the host's name only fills a session the client doesn't know, so a stale host snapshot can't revert a rename the client already read.
* fix(tabs): a paired host is the only writer of a session name it publishes
* fix(tabs): only a host window that resolves session names speaks for them
* fix(tabs): derive session title authority from live publishers
* fix(tabs): keep the session title publisher check concrete for runtime subclasses
* fix(tabs): one publication when a closing window drops browsers and title authority
* test(tabs): follow main's runtime test helper layout
* fix(tabs): paired desktops keep a quick-command name ahead of the session name
* fix(tabs): a name the host cleared doesn't come back as the idle pane's title
* test(tabs): build the idle-pane fixture as its own surface variant
* fix(tabs): point the moved session-title projection at its new shared path
---------
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>