Commit Graph
1538 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin 02db0146c6 perf(chat): order queued turns without repeated transcript scans (#34945)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 20:07:52 -07:00
OrcaWinandOrcaWin 34536771a7 perf(mobile): skip projection for abandoned diff replies (#34873)
* perf(mobile): skip projection for abandoned diff replies

* test(mobile): consolidate abandoned diff projection regressions

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 20:07:45 -07:00
OrcaWinandOrcaWin cde2472a83 fix(mobile): refuse PDF opens from hidden pages (#34869)
* fix(mobile): refuse PDF opens from hidden pages

* test(mobile): assert one refused hidden PDF open instead of scaled loops

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 20:07:42 -07:00
OrcaWinandOrcaWin d27c795b76 fix(mobile): bound file search cache after empty queries (#34841)
* fix(mobile): bound file search cache after empty queries

* test(mobile): fold empty-query cache bound regression into existing suite

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 20:07:32 -07:00
OrcaWinandOrcaWin 0909b148ad fix(mobile): skip retired file search ranking (#34837)
* fix(mobile): skip retired file search ranking

* test(mobile): fold stale-rank check into existing slow-inventory case

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 20:07:29 -07:00
bfa0dc01ba fix(mobile): preserve long terminal and chat pastes (#31473)
* fix(mobile): preserve long terminal and chat pastes

Agents can keep only the last ~1 KiB read of unframed input (#13332).
Frame long single-line chat messages like multi-line ones, and frame Live
input appends over 1 KiB (an OS paste) with the terminal's paste rule,
including held text flushed before a terminal click.

Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org>

* fix(mobile): end the live input session after a framed paste

Agents such as Codex collapse a framed paste into one token, so later
per-character erases from the field mirror desynced the line. A long
append to a terminal in bracketed paste mode now commits the whole field
in one frame and clears the field, recording it like other session ends
so a late native echo cannot send the paste twice; without that mode the
paste stays unframed and editable. Chat sends measure the sanitized bytes
when deciding to frame.

---------

Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 03:02:08 +00:00
b7aa695182 fix(mobile): make agent question cards the one answering surface and keep long ones reachable (#31663)
* fix(mobile): give agent questions one answering surface that fits the screen

A shown prompt card now replaces the chat composer, as on desktop. The
composer stays mounted underneath so its draft and attachments survive,
and comes back when the card collapses, clears, or dictation is running.

The card keeps the conversation visible above it (60% cap) and takes the
whole area while its reply is being typed. Question and Ask cards scroll
inside that bound and keep a focused reply in view as the card shrinks.

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: fancivez <fancivez@gmail.com>

* fix(mobile): open each ask step at its question

The ask card's body now scrolls inside a bounded card, so a new step must not inherit the previous step's scroll offset.

* fix(mobile): keep the queued tray shrinkable and approval actions reachable

The composer wrapper passes the remaining height down again, the queued-message tray unmounts while a card owns input so a focused editor cannot keep typing out of sight, and an approval card scrolls as a whole when its choices exceed the card's bound.

* fix(mobile): keep the composer for an approval this build cannot answer

Its choices are disabled and only a new turn can cancel it, as desktop's pendingPromptsAllUnanswerableHere rule keeps the composer for.

* fix(mobile): keep the approval card's choices pinned and the composer for a terminal-only ask

A long plan shrinks inside the approval card again instead of pushing its choices below a scroll. An ask only the terminal can answer leaves the composer (and Stop) in place, like an approval this build cannot draw.

* fix(mobile): keep the queued tray mounted under a prompt card

Unmounting it dropped its in-flight guards, so Resume could run while Clear all was still deleting. A hidden focused editor loses focus with display none.

* fix(mobile): keep the composer while dictation lands, and each ask step at its question

Transcribed text arriving after the mic stops stays visible in the composer. A focused reply is revealed only when its card shrinks, so a step that grows the card keeps its reset to the question.

* fix(mobile): keep queued work in view under a question, and end an ask step's edit on navigation

Desktop leaves queued messages and background-task controls visible while a prompt card shows; only the composer steps aside. Moving between ask steps ends the reply edit so typing can't land in a field scrolled out of view.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: fancivez <fancivez@gmail.com>
2026-10-29 02:57:12 +00:00
Neil ce44117d91 refactor(shared): colocate native chat cut-turn notices (#35617) 2026-10-28 19:54:34 -07:00
eb4f541cda fix(mobile): show live input caret and preserve selection edits (#31477)
* fix(mobile): show live input caret and preserve selection edits

Display the existing input directly so its native caret shows. Accessory
Backspace/Delete edit at the field's caret, read from the input when pressed,
so edits that keep the caret (IME composition) still delete, and the caret is
restored for subsequent typing.
Port and adapt community PR #28294 to the current command dock.

Co-authored-by: Kyino <kyino1025@gmail.com>

* fix(mobile): keep live input caret keys local and the dock height fixed

Hardware Left/Right/Home/End moved both the visible field caret and the
PTY cursor, so the mirror erased at the wrong place. With field text they
now move only the field caret, like Backspace and Delete; Right/End at the
field end still reach the PTY so shells can accept an autosuggestion. On
Android the key binding lets plain caret keys through to that decision
instead of sending them to the PTY. The dictation and upload status covers
the field instead of adding a row, so starting or stopping either no longer
resizes the terminal.

* test(mobile): annotate the DOM field cast in the accessory input test

* chore(mobile): allowlist the live input field strings for localization coverage

---------

Co-authored-by: Kyino <kyino1025@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 02:43:47 +00:00
NeilandKelvin Amoaba c9745246d4 fix(mobile): preserve new browser typing when an earlier send fails (#31710)
* fix(mobile): preserve browser typing across delayed keyboard sends

A failed send restores an old draft over newer typing, null success is treated as refusal, and rapid presses duplicate sends. Preserve newer typing, interpret acknowledgement explicitly, and fence page/client ownership.

* test(mobile): preserve browser drafts through same-computer connection cutover

* test(mobile): record browser keyboard acknowledgement semantics

* fix(mobile): warn only when a browser keyboard send's delivery is unknown

A refused send restores the text silently as before; only a lost reply or connection cutover shows the unconfirmed toast, using the app's existing unconfirmed-delivery wording.

* fix(mobile): keep a browser keyboard send's text through a reconnect

The pane remounts per page, so a reconnect that swaps the client keeps the same draft; only unmount should stop a late reply from restoring it.

* fix(mobile): warn only for written browser keyboard sends with lost replies

A cutover error already carries the delivery-unknown mark when its request was written, so the extra cutover check only warned about text that never left the phone. Classifying outside the resolved path also keeps an unconfirmed send from clearing the error banner.

* fix(mobile): let newer browser typing send while an earlier send is in flight

Claiming the draft from a ref stops a repeat tap before re-render without blocking a second send of newly typed text.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 02:43:32 +00:00
Neil c8b6d38bd4 fix(ci): keep mobile localization out of desktop-only test installs (#35576)
Load the Hermes plural-rules polyfill from the phone app entry instead of
mobile-i18n, and transform mobile-i18n and its catalog loader without the
Expo tsconfig in root vitest.
2026-10-28 19:06:34 -07:00
Kelvin Amoaba f5c84eae58 fix(mobile): the first Back while dictating stops it and keeps what was said (#35477) 2026-10-29 01:44:01 +00:00
Kelvin AmoabaandNico Hu 18c4e6bc47 feat(mobile): give chat code, tables, quotes and diagrams a clearer layout (#34456)
Code blocks get a header with the language and copy button, syntax
colours from the file reader's highlighter, a line-number column and
horizontal scrolling. Short tables fill the message width, cells get more
room, quotes sit on a panel, bullets are dots, and Mermaid diagrams use
the app's borders and type.

Co-authored-by: Nico Hu <haoyu.hu@lexmount.com>
2026-10-29 01:36:56 +00:00
Kelvin Amoaba f640517132 feat(mobile): show chat images that live on the paired desktop (#34462)
* feat(mobile): show chat images that live on the paired desktop

* fix(mobile): read every image type desktop chat shows

* refactor(runtime): share the bounded preview read and chat-image type check

* test: keep the chat-image tests typed and off importActual
2026-10-29 01:27:57 +00:00
NeilandClaude c14c99bb49 feat(wsl-host): a paired phone reaches the desktop's WSL host and never starts its distro (E6) (#33911)
* feat(wsl-host): a paired phone reaches the desktop's WSL host and never starts its distro (E6)

Behind ORCA_INTERNAL_WSL_HOSTS and the Experimental switch. The desktop relay
lists its WSL hosts to phones. Every relayed call (resolve, call, watchChanges,
status.get) passes a never-start check, and a WSL host serves a phone only
while it already runs.

- WslHostPhoneGate holds the phone's view. The call check reuses E9's
  focusReachability, so with no bridge in this process it refuses without a
  running-list read. A host shows as "stopped" only on a confirmed stop
  (liveness's distro-stopped, or a running list read by the check); anything
  else gets a neutral "isn't available" message. Hosts are hidden from phones
  while the Experimental switch is off or a Remove is pending.
- The relay host list sends optional `stoppedOnDesktop` only for a confirmed
  stop. The phone badges those rows "Start on computer"; older peers ignore
  the field.
- The paired-server rows producer carries WSL hosts like any server, so the
  main-computed sidebar feed a phone reads has their rows.
- The paired-phone e2e drives a WSL host project end to end and asserts a
  stopped distro is never started.

* fix(wsl-host): keep the phone's no-start check off non-WSL servers, and a stale stop off a ready host

- status.get runs the no-start check only for a WSL host, through a
  check-only helper. SSH-managed and paired servers reach the status owner
  exactly as on main: no reconnect, no redeploy, and the manual-disconnect,
  coalescing and repair handling stay in place. The preparation option is
  stripped before the probe.
- WslHostPhoneGate drops a 'stopped' read when a ready status arrived while it
  ran (a per-host ready generation), and clears a recorded stop when a check
  finds the host answering.
- A phone asking about a host that is switched off or being removed gets copy
  that says so, not "open one of its projects".

* test(wsl-host): the real no-start helper leaves SSH-managed and paired servers untouched

* fix(wsl-host): tie a phone's confirmed WSL stop to that host's status, and label a host not connected

- A stop a phone's check confirmed is stored with that host's own status
  object and holds only while that status is current. Another host's change no
  longer clears it. Any status of its own (ready, a disable's closed, a failed
  Remove's closed) does. A read never records a stop over a ready status, so a
  read shared with one begun before ready can't. Removed hosts drop their
  entry. The generation map and status listener are gone.
- The relay sends optional `unavailableOnDesktop` for a WSL host this desktop
  isn't connected to and doesn't know is stopped. New phones badge it "Open on
  computer"; older ones still show Connecting.
- The relay list reads the WSL host records once per poll (reachAll).

* refactor(wsl-host): one records read per phone check; note the gate's status identity compare

* test(wsl-host): read the phone's host flags as booleans in the paired-phone e2e

toMatchObject({ stoppedOnDesktop: undefined }) requires the key to be
present, and the desktop omits an unset flag, so the ready poll could never
match. The poll now compares relay plus both flags read as booleans.

* test(wsl-host): expect the neutral refusal once main's fake drops a stopped distro's bridge as closed

Main's fake connector (#34573) now publishes 'closed' when a stopped distro's
bridge drops, not liveness's 'distro-stopped'. So the phone's check reads the
stop under a ready status and doesn't claim it. The phone gets the neutral
copy and unavailableOnDesktop; the confirmed-stop copy stays unit-tested.

* test(wsl-host): the paired-phone e2e expects the confirmed stop again, and checks nothing booted the distro

Main's fake now publishes 'distro-stopped' when it drops a stopped distro's
bridge (#34624), as liveness does, so the phone gets "isn't running" and
stoppedOnDesktop. The spec also checks distros.json still reads 'stopped' at
the end: the fake boots a distro by rewriting it.

* test(wsl-host): the startup reconcile suite stubs the phone gate built beside the service

The reconcile still runs before the connector exists; the suite only lacked the new
setWslHostPhoneGate export on its service-instance mock.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 18:23:02 -07:00
OrcaWinandOrcaWin 5ed3c8c072 feat(mobile): localize shared components, pull requests and diff review (#35532)
Moves ~600 strings in the shared components (modals, drawers, host cards, dictation,
accounts), the pull request sidebar and diff review to the translation API, with all
ten translations.

- Invalid account snapshots are detected by error type, not by matching English text
- Credit expiry wording uses separate keys for one and several credits instead of
  plural rules, which would pick the wrong sentence in Japanese or Russian
- The PR chip memo recomputes on a language change

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 18:17:58 -07:00
OrcaWinandOrcaWin ccd0829e6f feat(mobile): localize app screens, settings, pairing and host lists (#35498)
Moves ~600 strings in the root screens, settings, pairing, onboarding, accounts,
agent history, diagnostics, connection status, home and host workspace lists to the
translation API, with all ten translations.

- Connection verdicts carry an explicit `dialing` flag instead of matching the
  "…" ending of an English label
- Update-failure copy and connection-path accessibility labels are built from data,
  not by editing translated text
- Workspace status names stay the host's labels, as on desktop; transport errors
  stay English
- The stale-key check keeps a plural family while any of its forms is referenced

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 18:03:37 -07:00
NeilandKelvin Amoaba 5470ff907c fix(mobile): stop review notes from disappearing or coming back after deletion (#31699)
* fix(mobile): stop review notes disappearing or coming back after deletion

The phone sent its whole notes list on every add or delete, and the host
stored it as is. A note added on desktop after the phone loaded was wiped
by the phone's next edit, two quick taps could overwrite each other, and a
failed save rolled back to an old snapshot.

worktree.set now takes an optional diffCommentsBase, like linkedItemsBase:
the host applies only the writer's own adds, edits and removals to the
stored notes. The phone sends the list it started from, shows the host's
merged list from the reply, and undoes only its own change on failure.
Older hosts drop the field and replace the list as before; older phones
send no base and get the same.

Co-authored-by: Neil <neil@stably.ai>

* fix(mobile): keep loaded review notes when an early save fails

The first load was discarded once any save started, so a save that failed
before it landed left the host's notes hidden. The load now applies the
phone's changes made during the read onto the host's list instead.

* fix(mobile): send review-note requests one at a time

Two in-flight saves could reach the host in either order, so adopting the
later reply hid a note the earlier save had stored. Session notes now send
one request at a time and reconcile every reply with the phone's changes
made since it was sent, replacing the sequence counters.

* fix(mobile): drop review-note saves queued behind a failed one

A save queued behind one that failed still sent the list built on the
failed change, and an older host stores that list whole. Those saves now
undo their own change instead of sending. Also renames the shared base
type so it no longer shares a name with the renderer's mutation record.

* fix(mobile): show review notes as the host's list plus pending saves

A queued reply was reconciled against the list captured at tap time, so a
note desktop deleted meanwhile came back on the phone. The phone now keeps
the host's last list and its unanswered saves per workspace and shows the
two combined; a reply for another workspace is ignored. The review screen
also shows the host's list after a save, so an edit to a note desktop
deleted no longer looks saved.

* fix(mobile): fail queued review-note requests while disconnected

A notes request queued behind a pending load waited for a reconnect with
no timeout, holding every later save. Notes requests now fail at once when
the connection is down, like terminal input.

* fix(mobile): drop every review-note save built on a failed or dropped one

A save made while a dropped save was still pending carried its change,
so an older host could store it. Each failed or dropped save now moves
the failure count.

* fix(mobile): send each review-note save as the host's list plus its change

Each queued save now builds its payload when it is sent, from the host's
last list plus only its own change. A note added before the first load
no longer replaces the loaded notes on an older host, and a failed save's
change never rides along, so later saves no longer need dropping.
Delivered-note cleanup waits for a reconnect again so those notes are not
sent twice.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 00:40:56 +00:00
Kelvin Amoaba 785cd30ee9 chore(mobile): bump to 0.0.60 and Android versionCode 24 (#35505) 2026-10-29 00:39:21 +00:00
OrcaWinandOrcaWin 12a7d4c13a feat(mobile): localization infrastructure and language setting (#35399)
* feat(mobile): add UI localization infrastructure

Mobile follows the phone's language (or an in-app choice under Settings > Appearance)
across the same 11 languages as desktop, switching live like the theme does.

- i18next with desktop's translate(key, fallback, options) call shape; catalogs in
  mobile/src/i18n/locales load lazily, English fallbacks stay inline
- Hermes lacks Intl.PluralRules: load the FormatJS polyfill with the 11 languages' rules
- OTA pages get the shell's language through an optional bridge init field
- iOS permission prompts localized; iOS and Android 13+ per-app language pickers
- verify:mobile-localization and a coverage ratchet run in lint, check:changed and CI

* fix(mobile): mount pages after the newest language request; tighten localization gates

- Wait for the latest page language activation, not a superseded one
- Keep literal fallbacks when a key also has a dynamic one
- Count headerTitle as visible copy; select the mobile check when the runtime catalog generator changes

* feat(mobile): repaint themed components on language changes

Components that read the theme already subscribe for repaint; a language switch
repaints them too, so labels built by helpers during render follow it. RPC
recordings load the real i18next and JSON catalogs.

* fix(mobile): scope the translate() gate to render bodies and memos; prune stale keys on sync

Handlers and effects may call translate() when they run. sync:mobile-localization now
removes keys no code references from every catalog.

* fix(mobile): use node: imports in the locale config plugin; pass uiLocale in bridge hook test

* chore(mobile): refresh the localization coverage baseline

* fix(mobile): follow render callbacks in the translate() gate; keep used plural siblings on prune

Also let anonymous default-exported screens count as components, flag any plural
family a locale started, and mark JSON modules as ES modules in the recording loader.

* fix(mobile): treat async and named local callbacks as handlers in the translate() gate

* chore(mobile): refresh the localization coverage baseline

* chore(mobile): refresh the localization coverage baseline

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 17:14:05 -07:00
NeilandKelvin Amoaba 90ef900757 fix(mobile): only clear terminal output after the computer accepts Clear (#31703)
* fix(mobile): clear terminal output only after the computer accepts Clear

The phone erased its terminal before sending terminal.clearBuffer and
reported success on any reply, refusal included. Wait for the host's
{ clear: { cleared: true } } reply, fail immediately when disconnected so
the request cannot replay after reconnect, ignore late replies after the
client or terminal changed, and resubscribe a subscribed terminal so its
snapshot (including output that arrived during the request) replaces the
old screen. An unsubscribed tab draws its snapshot when it next subscribes.

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>

* test(mobile): return the subscribe result from the gesture recording adapter

* fix(mobile): keep accepted clears when the terminal view re-renders

The WebView handle is rebuilt on every render and a relay/direct cutover
bumps the connection generation without changing the host, so both
checks treated accepted clears as stale: the phone neither reloaded the
output nor showed a toast. Only a replaced client is stale now.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 23:09:18 +00:00
Brennan Benson 78ad9f4255 fix: keep chat option actions independent of inventory (#31927)
* fix: separate chat option input from inventory observations

* fix: preserve scoped chat choices and publication ownership

* fix: complete scoped catalog and applied control ownership

* test: make catalog fixture dependency boundaries explicit

* test: request catalog discovery explicitly in demand contracts

* fix(chat): preserve option observations and operation boundaries

* fix(chat): finish pending option and catalog lifetimes

* fix(chat): project saved permissions without launch preparation

* fix(chat): correct option rejection and catalog retirement

* Preserve Claude effort validation advice through chat rejection

* test: align refresh mocks and preserve fixture type evidence

* fix: share catalog claims only across equivalent owners

* test: use typed catalog corpus instrumentation

* fix: consolidate structured launch imports

* Validate SDK queue regression envelopes without assertions

* fix: complete catalog runtime and fixture type contracts

* Align native option fixtures with explicit discovery and applied facts

* Keep initial compatibility catalog reads on held memory

* Complete the installed catalog fixture envelope

* Observe learned defaults after asynchronous projection settles

* Consolidate host fixture journal type imports

* test(native-chat): align goal and seed fixtures with applied inputs

* fix(codex): retain applied proof when goal settings skip a write

* test: load rejection wording before timed queue regression

* Fix catalog fixtures and queued option snapshot typing

* Align permission publication fixture with passive option reads

* Separate startup config from held turn permission fixture

* Align input lifecycle fixtures with sparse applied choices

* Simplify scoped catalog refresh ownership and coalesce cache snapshots

* Load catalog persistence real-module fixture during collection

* Compose catalog availability fixtures from valid session records

* Align catalog and startup fixtures with current option contracts

* Fix permission read types and separate catalog fixture preparation

* Align option and restart fixtures with current lifecycle owners

* test(mobile): retain reported model alongside discovery

* test(mobile): recalibrate the measured route script envelope

* test: let the preflight shell-capture mock run its detection callback
2026-10-28 16:08:01 -07:00
NeilandKelvin Amoaba 737ba1595b fix(mobile): keep voice settings in the order you change them (#31691)
* fix(mobile): drop voice settings replies older than the latest change

An older save that replies last could put the previous setting back on screen. Ignore mutation replies and errors that a newer change has superseded.

* fix(mobile): keep the Voice screen on the connected desktop

Key the screen by host so a late reply from a desktop the phone switched away from cannot land on the newly connected desktop's settings.

* fix(mobile): re-read Voice settings when an older save succeeds late

The host finishes a model delete only after removing its files, so a newer change can reply first with the model still selected. Dropping the older reply left the deleted model shown as in use; re-read instead.

* fix(mobile): keep Voice model errors and the screen through reconnects

A newer setting change does not replace a failed model select, download or delete, so show those errors again. Key the screen on the last connected desktop so a reconnect of the same desktop no longer remounts it.

* test(mobile): pin Voice model-delete errors after a newer change

* fix(mobile): re-read Voice settings after any failed save

* fix(mobile): re-read Voice settings only after every pending change settles

A read sent while a model delete is still running can return the deleted model after the delete's own reply. Skip reads while a change is pending, and re-read once when all settle if any reply was skipped.

* fix(mobile): keep a failed Voice model action's error on screen

A lone failed select or delete changed nothing on the phone, so it no longer starts a re-read that clears its error. A failed switch still re-reads to undo its optimistic flip.

* fix(mobile): let Voice reads clear only their own load errors

A re-read after a failed or superseded change cleared the change's error before the user could read it. Keep load and change errors apart; re-read after any failure again, which also undoes a failed switch flip.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 23:06:53 +00:00
Brennan Benson f0d55e7319 Nest chat-UI orchestration workers under the agent that started them (#34601)
* Nest chat-UI orchestration workers under the agent that started them

Main's lineage projection only walked terminal panes, so workers running
in the chat UI never got a parent link and listed flat in the sidebar.
It now also projects chat workers, keyed by their visible chat row, and
resolves a chat creator or coordinator to that chat's visible row
instead of its handle or private mailbox pane key.

* Fix public worker lineage across assignment and row identities

* Complete terminal lineage provenance and bound active session selection

* Repair lineage test runtime boundaries and SQLite counters

* Batch public lineage verification after exact recency selection

* Use checked runtime values in lineage integration tests

* Document the trusted Dispatch SQL row boundary

* Keep lineage integration imports within their test programs

* Load native lineage fixtures before test execution

* test: complete lineage inputs in runtime and sidebar fixtures

* test: seed benchmark facts from active dispatches

* test: replace partial orchestration DB casts with typed fixtures

* test: complete stale-leaf mailbox fixture session input

* fix(test): render worker lineage fixture with production list
2026-10-28 16:06:38 -07:00
Kelvin Amoaba 9dd4dc5d17 fix(mobile): show the dictation mic's starting state, hold-mode copy and screen-reader status (#35441)
The chat mic only got isRecording/isProcessing, so it looked idle while starting; it now shows the live look the terminal mic does. The terminal status row says 'Release mic to stop' in hold mode, both mics expose role and busy state, and one live region announces Starting mic / Listening / Processing.

Fixes #35422
2026-10-28 23:02:08 +00:00
Kelvin Amoaba 0774fc9373 test(mobile): type the file preview retry test helpers so the test typecheck passes (#35431) 2026-10-28 22:50:34 +00:00
NeilandKelvin Amoaba 876193458c fix(mobile): keep file edits safe when previews load or refresh slowly (#31696)
* fix(mobile): preserve file drafts across refresh and reject stale reads

Overlapping preview reads overwrite current content, and refreshed metadata or equivalent route objects revoke dirty editor state. Keep document-owned drafts editable and accept only the current load.

* fix(mobile): prevent duplicate artifact saves before rerender

* refactor(mobile): drop the dead dirty-draft guard and share preview test setup

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 22:35:08 +00:00
Kelvin Amoaba e5b45ac90f fix(mobile): keep dictation recording through a reconnect and keep what was already said (#35338)
* fix(mobile): keep dictation recording through a reconnect

The host tied a phone's dictation to its socket, so any reconnect cancelled
it, and the phone ended the recording as soon as the connection dropped.
The host now owns the session by device and waits a grace period for the
phone to come back. The phone keeps recording through the reconnect and
keeps the text transcribed so far if the connection does not return.

Fixes #35211

* fix(mobile): bound dictation reconnect waits and release retired relay waiters

* fix(mobile): resend an unsent dictation finish after a connection move and send the text so far only when it changes

* fix(mobile): hold dictation requests through a suspended relay and bound the start wait

* fix(mobile): recognise a suspended relay client across the page bridge

* refactor(dictation): refresh one abandon timer, report text by version and share the reconnect wait

* fix(mobile): resend dictation text each second and never wait offline for a cancel
2026-10-28 22:27:21 +00:00
Neil 4881182aad refactor: group native chat image transcript preparation (#35301) 2026-10-28 15:22:44 -07:00
NeilandKelvin Amoaba 64344142fa fix(mobile): stop held terminal keys when you switch terminals or leave the app (#31712)
* fix(mobile): restore lifecycle-safe terminal accessory repeat

The selection hook replaced the established repeat controller with unbounded timers, allowing held keys to cross terminal/client changes. Reintegrate the existing pinned sender, lifecycle cancellation and backpressure.

* test(mobile): stop held terminal input after refusal or disconnect

* test(mobile): give accessory-repeat harnesses the PTY modes and focus hook main now reads

* fix(mobile): stop a held key when its strip unmounts and repeat through the current live-input mode

* fix(mobile): type the accessory repeat sender's input

* refactor(mobile): read app state live in the accessory repeat gate

* test(mobile): pin the key bar's held press to the repeat controller's first send

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 22:17:20 +00:00
NeilandKelvin Amoaba f703896887 fix(mobile): show setup settings from the computer you just selected (#31693)
* fix(mobile): scope workspace setup permissions to the responding host

Changing hosts can expose the previous desktop runtime settings and setup approvals. Own published data and setters by host and client.

* fix(mobile): keep an earlier selection's settings callbacks retired when the host is picked again

Ownership compared client and host id, so A -> B -> A made the first A's Save callbacks match again. Each selection now gets its own owner object.

* fix(mobile): own the workspace runtime context by host, not client object

Any server's relay change rebuilds every scoped client, which wiped the same host's settings and approvals.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 22:09:30 +00:00
Jinwoo Hong 93ff4832e4 feat(orchestration): worker-start new worktrees default to a generated name (#35363) 2026-10-28 17:51:08 -04:00
Brennan Benson d0bfbd22ac Keep host-removed rows out of desktop and phone chats (#34996)
* Keep removed chat rows out of coalesced updates

* test: bound destructive history burst work

* test: verify removed history through transcript projection

* test: count retained row reads with typed getters
2026-10-28 13:50:25 -07:00
Neil 7b3fb541f4 Built-in syntax highlighting for .env files (#34542)
* feat(editor): built-in syntax highlighting for .env files

.env, .env.* and *.env files now open as a dedicated dotenv language with
comment, key, export, value, quoted-string and ${VAR} colouring instead of
INI. .envrc opens as shell, since it is sourced as a shell script.

* fix(mobile): map dotenv to the ini grammar for mobile highlighting

* fix(editor): keep quotes after the first dotenv value character literal
2026-10-28 13:40:03 -07:00
Kelvin Amoaba 6c54b4fac5 fix(mobile): show the chat mic busy while a stopped dictation transcribes (#35320)
Fixes #35313
2026-10-28 20:38:01 +00:00
OrcaWinandOrcaWin 33800f8c8b fix(mobile): stop bundle work after caller cancellation (#34793)
* fix(mobile): stop bundle work after caller cancellation

* test(mobile): use a type import for the fflate mock

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 13:37:53 -07:00
OrcaWinandOrcaWin 9fecb29aa9 fix(mobile): batch notification catch-up ledger writes (#34785)
* fix(mobile): batch notification catch-up ledger writes

* test(mobile): use a fixed expiry in catch-up storage test

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 13:37:47 -07:00
Kelvin Amoaba 88662806c0 fix(mobile): explain dictation setup before asking for the microphone (#35248)
A mic tap opened capture, and with it the OS permission prompt, before
anything asked the host whether it could transcribe; the setup sheet only
appeared after the host refused dictation.start. The session screen now
gates the start on the setup read it already does, opens the setup sheet
when setup is missing, and hands the next tap back to the host and re-reads
when the sheet closes. A host that never had a model selected now offers
the mic. The sheet's switch uses MobileSwitch.

Fixes #35203
2026-10-28 20:14:39 +00:00
Kelvin Amoaba 1f45c87c50 fix(dictation): say dictation is in use elsewhere instead of dictation_already_active on desktop (#35269)
Move the host dictation error map from mobile to src/shared so the desktop start toast and the phone read the same sentences.
2026-10-28 20:10:43 +00:00
OrcaWinandOrcaWin 7d29cd0839 fix(mobile): recover host profile reads without discarding cached bundles (#34806)
* fix(mobile): retry unavailable host profiles without discarding cache

* fix(mobile): cancel startup work on terminal shell failures via flow advance

Merging main pushed use-mobile-web-shell-session.ts over max-lines; move
the host-profile cancellation into the reducer so the flow-change cancel
path from #34780 handles it.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 12:48:17 -07:00
OrcaWinandOrcaWin e0420b3b58 fix(mobile): skip queued generation stages after cancellation (#34809)
* fix(mobile): skip queued generation stages after cancellation

* test(mobile): fold canceled queued-stage case into download suite

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 12:48:11 -07:00
OrcaWinandOrcaWin d4b6e928ac fix(mobile): index reusable bundle assets once per generation (#34789)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 12:48:05 -07:00
92a754a088 fix(mobile): suspend hidden and background terminal gestures (#31468)
* fix(mobile): suspend gestures in hidden and background terminals

Adapt the retained-pane interaction lifecycle from community PR #30324.
Restore policy across readiness/recovery and keep output/query replies flowing.
Release cancelled gesture frames, discard touches interrupted by suspension,
and restore the resting pinch transform on every gesture cancellation.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Neil <nwparker@users.noreply.github.com>

* test(mobile): stub route focus where the web mount renders terminal panes

* test(mobile): send production-shaped file taps and the clipboard prop in activity tests

* fix(mobile): keep inactive and interrupted terminal moves away from xterm

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Neil <nwparker@users.noreply.github.com>
2026-10-28 18:53:58 +00:00
6caa44eefb fix(mobile): keep the terminal reading position when resize snapshots overlap (#31350)
* fix(mobile): keep the reading position when resize snapshots overlap

A snapshot that overtook a hidden replay took its scroll anchor from the
hidden terminal, which sits at the bottom, so the reader jumped down. The
newer replay now keeps the anchor the overtaken one would have restored,
unless live output, a clear or an alternate screen made the hidden buffer
the source. A superseded replay also hands the live terminal back before
theme painting reads it.

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: ogarciarevett <ogarciarevett@gmail.com>

* fix(mobile): keep the overtaken anchor when live output arrives during a replay

* test(mobile): use T[] for the replay harness completions

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: ogarciarevett <ogarciarevett@gmail.com>
2026-10-28 18:38:00 +00:00
Neil 80bbbaff7f refactor: give native chat readability one concrete owner (#35231) 2026-10-28 11:28:03 -07:00
Brennan Benson 28d6883756 fix(mobile): grey the queue's other controls while Clear all or Resume is on its way (#29219)
* feat(native-chat): one queue header that becomes the pause row; no composer Resume or send dialog

The queued cards now sit under one header row on desktop and phone: "Queued"
and the count normally, and "Paused" with the count, Clear all and Resume
while the host holds the queue after a Stop. A chevron folds only the cards;
the choice is local to the view and resets when the queue empties.

The composer's Send no longer turns into a second Resume, and a message sent
into a paused queue no longer asks "Send message?": it sends, its accepted
turn lifts the pause, and the cards follow it. Clear all deletes every card
through the existing per-card delete. No wire or host change.

* test(mobile): find the background-tasks strip's toggle inside the strip, not the queue header's

* Make queued Clear all atomic across clients

* Show queued message failures in collapsed headers

* Use per-card delete for paused queue Clear all

* Join repeated paused queue Clear actions

* fix(mobile): grey the queue's other controls while Clear all or Resume is on its way

On the phone, Resume and each card's Steer and Delete stayed tappable while
Clear all was still deleting the cards, so Resume could start sending cards
Clear was deleting, and a card's Steer could race its own delete. Clear all
and Resume now share one in-flight key, so each greys during the other, and
Clear all holds every card it deletes, leaving out a card whose own action
is already running. Same mechanism as the desktop fix.

* fix(mobile): wait for pending cards before clearing the queue

* style(mobile): format queue control contract test

* fix(mobile): Clear all and Resume share the queue's pending key, as on desktop

Clear all holds every card it deletes and greys Resume until it settles, and
waits for any card's own action instead of starting over it.

* test(mobile): bound queued message conflict work
2026-10-28 11:08:46 -07:00
844c7f6d96 fix(mobile): preserve Korean 10-Key live-input syllables (#31388)
* fix(mobile): preserve Korean 10-Key live-input syllables

Hold unsent conjoining jamo until committed so grapheme-deleting editors do not erase preceding syllables. Based on community PR #24821; verified real current iOS shell keyboard taps and actual Claude PTY replay.

Co-authored-by: Hojin <hojinkim971597@gmail.com>

* test(mobile): cover held Korean jamo editing boundaries

* test(mobile): keep held-jamo commit tests that add coverage

Boundary cases (Tab, accessory Enter, external send, tab switch, disconnect) already run through the existing held-text tests; the jamo hold reaches them the same way.

---------

Co-authored-by: Hojin <hojinkim971597@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 18:07:21 +00:00
NeilandKelvin Amoaba e0b5a42852 fix(mobile): handle invalid pairing links and start fresh for each invite (#31705)
* fix(mobile): validate pairing routes and isolate invite lifetimes

Repeated code parameters crash parsing, and replacing an invite retains old attempts and callbacks. Validate route input and key confirmation state by invite; allow confirmation content to scroll on short screens.

* fix(mobile): retire pairing immediately when Back is pressed

* fix(mobile): keep the pair confirmation layout unchanged

The ScrollView wrapped ConnectionLog's own vertical scroller without nested
scrolling on Android and was not needed for the invite-lifetime fix. The
repeated-code case is covered by the route-state test.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-28 17:57:24 +00:00
Kelvin Amoaba 49e53bd345 fix(mobile): greet a just-started terminal Claude with "Start a chat" (#35219)
Claude reports its session at startup but writes no transcript until the
first prompt, so the host answers pending and the phone said "Can't find
this conversation yet". A session the running agent reported live is new;
only remembered sessions keep the not-found copy.
2026-10-28 17:41:03 +00:00
Jinjing 117bbc48e6 Extract mobile native chat prompt controller (#34244) 2026-10-28 10:36:44 -07:00