* build(mobile): upgrade to Expo SDK 58 and React Native 0.88
Expo 58 drops Zod from @expo/cli, which clears the last Zod 3 advisory
(GHSA-mrhx-c7xc-m3w5) in mobile along with its local patch and override.
- Expo 55 -> 58, React Native 0.83 -> 0.88, React 19.3, gesture-handler 3,
webview 14, reanimated 4.7, worklets 0.13 (SDK 58 bundled versions,
pinned below today's batch to respect minimumReleaseAge).
- Port the react-native and react-native-webview patches unchanged; drop
expo-constants, expo-notifications, @expo/cli, expo-router, zod and
query-string patches plus the zod/query-string/xcode>uuid overrides,
which upstream fixed or no longer need.
- iOS minimum rises to 16.4 (Expo 58 / RN 0.88 requirement).
- Migrate to RN 0.88 strict types: instance ref types, NamedStyles,
AppState.currentState, list header/footer nulls.
- Replace removed runtime APIs: InteractionManager -> idle callback with a
macrotask fallback; StyleSheet.absoluteFillObject -> absoluteFill;
FileHandle.readBytes -> readBytesSync (it became async).
- Resolve react-native/asset-registry for shared assets in Metro and alias
the old registry path for react-native-svg in the desktop mobile-web bundle.
- Reduce the iOS scene plugin to the reconnect override; Expo 58's template
now ships the scene delegate itself.
* fix(mobile): bridge react-native-svg web imports and libsodium runtime for RN 0.88
- Share react-native-svg web shims between the mobile-web build and its render
checks: map the old asset-registry path to react-native-web's registry and stub
the Android-only touch-sound TurboModule.
- Patch react-native-libsodium 1.7.0 to read the JSI runtime from the bridge proxy;
RN 0.88 removed RCTCxxBridge.runtime.
* fix(mobile): use Expo 58's legacy promise resolver for audio permissions
* fix(mobile): skip null handles in the RPC recording timer shims for RN 0.88 types
* style(mobile): format MobilePRSidebar after rebase
* fix(mobile): name RN 0.88 text input refs for declarations; allow queued back in stack render check
- Type text input refs with an exported interface so declaration emit can name hook results
(RN 0.88 aliases TextInputInstance to an unexported class).
- Expo Router 58 drains back() after a commit; check the interrupted push's exit from the
frame it starts, bounded to three frames of lag.
* chore(mobile): justify the text input handle interface for React Doctor
* chore(mobile): use a nameable intersection alias for text input refs
* fix(mobile): use sync expo-file-system writes, copies and moves on Expo 58
Expo 58 made File.write/copy/move and FileHandle.writeBytes return promises.
Our callers never awaited them, so failures escaped their try/catch and the
store's tmp-then-move and PDF writes could race their close or rename.
* build(mobile): raise the page bundle budget to 10 MiB for Expo 58
Expo 58 adds about 310 KB to the page: expo-router (+258 KB, now carrying
React Navigation, net about +110 KB) and Reanimated 4.7 (+152 KB). Main had
85 KB of headroom left. 10 MiB still stays within the 10 MiB per-asset contract.
* fix(mobile): drop the window-metrics seed RN 0.88 removed from Android
RN 0.88 no longer holds window metrics: DeviceInfoModule reads the activity's
WindowMetrics on demand and setWindowDisplayMetrics is gone, so MainActivity
no longer compiled. Layout scale still comes from the screen metrics we reseed.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* refactor(mobile): group host catalog credentials descriptors and connection history
* refactor(mobile): import RPC sender and result types from defining owners
* refactor(mobile): colocate push delivery with registration operations
* refactor(mobile): point push recordings at delivery owner
* feat(mobile): keep dictating after leaving the session, with a floating pill
* feat(mobile): translate the dictation pill and its toasts
* fix(mobile): type away words without claiming the terminal, and keep the pill above screens
* fix(mobile): float the pill above bottom bars, reopen the owner's own screen, and tell a refused start wherever you are
* fix(mobile): float the dictation pill above the source control bar
* fix(mobile): keep pill state out of the host context and reopen the starting screen first
* refactor(mobile): subscribe the pill only while it shows and trim the session dictation hook
* fix(mobile): key bar clearances by symbol
* perf(mobile): open Home with last-known workspaces and counts
* fix(mobile): keep disconnected desktops on Home and judge agents by the client's connect time
* fix(mobile): store the seeded Home counts before removing the old snapshot
* fix(mobile): floor router reads after failures, catch up reads that span a stream reopen, seed the wide host list from Home
* fix(mobile): keep the old Home snapshot until the store can take it, and never re-date stored totals
* perf(mobile): key Home's counts in the host list module so the page boot never loads Home's code
* test(mobile): type the Home catalog adapter's modules with type imports
* perf(mobile): read Home's stored rows in one batch beside the host list, and keep mark-read a delta read
* perf(mobile): paint Home's desktop rows without waiting for the stored counts
* perf(mobile): restore Home's stored rows after the desktop list paints, from its host ids
* perf(mobile): read Home's counts on the connected edge itself, not a render later
* feat(mobile): show folder workspace Source Control on the phone
A folder workspace now has Source Control on the phone: one row per attached
worktree (repo, name, branch, ahead/behind, change count), read from the host
list's own lineage projection and each worktree's git.status. Tapping a row
opens that worktree's existing Source Control in place; Back returns to the list.
* fix(mobile): folder Source Control routing, back, hidden reads and failure states
- A desktop worktree under a server's folder is listed and opens with desktop routing and gates.
- A child opened in the dock gets Back to the folder list beside Close.
- Rows and the catalog stop reading while the screen is covered, as the single-worktree screen does.
- Offline, failed and missing-folder states; rows whose status can't be read say Unavailable.
- Server rows poll like the host list; branch shown without refs/heads/.
- Perf contract: git.status reads per signal at N and 10N rows.
* perf(mobile): folder Source Control reads summaries and rebuilds only on lineage changes
- Rows read a status summary without per-file line stats; relayed servers' rows open no desktop event stream.
- Membership resolves over a projection of lineage and label fields, so agent and terminal churn rebuilds nothing.
- A row whose re-read failed or whose host reported readError shows Unavailable.
- A connection still coming up shows loading, not offline.
- Perf contracts at N/10N rows and H/10H catalog rows.
* perf(mobile): folder membership keys on lineage only; child status reads update the folder list
Membership resolves again only when a row's lineage fields change, keying
only rows the catalog replaced; labels come from the current rows. A child
Source Control's newer status read is handed to the folder list's summary,
so Back shows what an action did without another read.
* fix(mobile): folder summary takes child reads as they land and ignores server health
Membership depends only on each relayed server's installation, so health
and relay flips no longer resolve the folder again. A child Source
Control hands each status read to the folder summary as it is accepted,
so a summary read answering just after can't hide it, and an older read
never replaces a newer summary failure.
* fix(mobile): folder summary skips restored and cached reads that could undo newer ones
Persisted status is marked restored before anyone hears of it, so a
child screen never hands an earlier launch's status to the folder list.
A cached read caught up on opening writes only an idle summary and never
cancels one in flight. Perf contracts count real lineage serializations,
committed row renders and summary publications.
* fix(mobile): clear the restored mark on any answer but the restore's own
A live answer timed before the stored record, as after a clock
correction, is still this launch's, so its reads and the folder summary
treat it as confirmed.
* fix(mobile): the restore's own write skips clearing its mark only once
A listener replacing restored data as it hears of it writes this
launch's answer, so the mark clears for it too.
* test(mobile): type folder Source Control test fixtures without assertions
* test(mobile): name mocked modules with type-only namespace imports
* feat(mobile): show folder worktrees as cards on the sheet surface
Each attached worktree is its own grouped card on the grey sheet
surface, the look the phone's option sheets use, so rows read apart
from the page.
* fix(mobile): keep folder Source Control's states on the card surface
Loading, empty, error and the list now share the sheet surface, so the
screen no longer changes background between states.
* refactor(mobile): give relay and encryption concrete owners
* refactor(mobile): organize session and review feature owners
* refactor(mobile): organize RPC and host ownership
* test(mobile): point render fixtures at concrete owners
* refactor(mobile): use explicit type-only owner imports
* perf(mobile): open Jump back in and notifications inside the host document
* fix(mobile): gate open targets on page 3, keep notification suppression, fold double taps
* fix(mobile): report the shown session from host documents and only open this host's sessions
* test(mobile): ignore the page's location report in the agent-history notify check
* fix(mobile): let a Back before the paint gate settle the host stack
* refactor(mobile): build the route-param clear frame beside its name
* feat(pairing): unscanned desktop pairing offers expire (QR 10 min, link 60 min)
Headless serve/orcad mint offers without a TTL, so they never expire.
Paired rows and rows without offerExpiresAt are unaffected.
* feat(pairing): refresh the mobile QR before it expires and show runtime link expiry
The QR re-mints a minute before expiry while visible; an unused runtime
link shows its expiry and, once expired, is replaced by a notice. Web
clients rejected while pairing now suggest asking for a new link.
* feat(mobile): say a refused pairing code expired or was replaced
A first pairing the desktop refuses (direct or Relay) now asks for a new
code instead of showing the raw transport error.
* refactor(pairing): replace type assertions in code-refusal detection and tests
* test(mobile): re-record pairing race golden for refused-code error
* fix(pairing): keep pairing codes valid across sleep and stale registry writers
- QR refresh rechecks the wall clock every 15 s and on window focus, so a
computer waking from sleep never keeps showing an expired code.
- A registry save from a process with an older copy can no longer reset a
paired row back to unscanned (which would let its offer expire).
- The QR caption says how long a copied code works.
* test(pairing): annotate the IPC handler stub cast
* feat(mobile): let the host page open a target inside its own document
* fix(mobile): skip a duplicate open, re-render the stack only for a waiting route
* fix(mobile): resolve the open target through Expo and open a target that changed before mount
* test(mobile): keep Back in one open-target case
* refactor(mobile): hold routes under the top with one released flag
* refactor(mobile): own the navigator ready listener in the effect
* fix(mobile): leave single-route stacks drawn from the start
* feat(mobile): add an encrypted on-device store
One AES-256-GCM key in the pairing keychain seals one file per value, bound to its host, table and key. Cache rows are LRU-capped at 128 MiB; drafts are written as tmp, delete, move and refused past 16 MiB. Launch upkeep (orphan hosts, draft TTL, lone tmp recovery, cap totals) runs in idle slices after the splash, and host removal wipes the host's rows. No consumers yet.
* style(mobile): use a ternary for the cache row write
* fix(mobile): keep a lone draft tmp before reuse and settle every row read
* perf(mobile): reopen Source Control's Changes list from memory instead of rescanning
* fix(mobile): keep action errors through background status reads
* fix(mobile): let only explicit status reads clear Source Control's action error
* refactor(mobile): simplify the query layer and the Changes compare trigger
* fix(mobile): follow language switches in Changes' empty states
* revert(mobile): drop the details card on session tab taps
Reverts 852391da0f (#36395). The card popped up on every tab switch.
Tapping a tab switches tabs again, and the session title card goes back
to its own owner from #30389. Keeps later changes to these files: tab
press feedback, the compact top bar's + button, and #36866's keyboard
keep-focus for title taps.
* fix(mobile): hide branch and folder when the setting is off
The phone's workspace row always printed the branch or folder path, so the
Branch name / Branch / folder path toggle under Card display did nothing.
The shared card details now carry that rule, and the row reads it.
* fix(mobile): centre the chevron on Home list rows
* fix(mobile): scroll sheets taller than the screen
Content-sized sheets ride the keyboard by translation, but their height cap ignored that lift, so a tall sheet pushed its top off screen with no way to scroll back to it. The cap now gives back the keyboard lift. Sheets that scroll their own list can now shrink to the cap instead of clipping it, and the GitHub Pages picker no longer nests its list inside the drawer's ScrollView.
* feat(mobile): show each desktop's machine kind and model
The desktop now reports whether it is a laptop, desktop or server, and its
model, on status.get. The phone stores it with the other last-known host
fields and turns it into a line like "MacBook Pro · macOS".
* feat(mobile): edit a host from its screen and align the Automations bar
The host screen's menu gains Edit host and Network diagnostics. Edit host moves
onto the standard bar and gains Remove host. Automations uses the standard bar
with its switcher pinned under it, as Source Control does.
* feat(mobile): arrange Home and bring back stats and account usage
Home drops the Jump back in times, names the repo on every Jump back in row
(#37642), shows each desktop's machine kind, and adds stats and account usage
at the bottom. A new Arrange button reorders and hides sections, saved on the
phone.
* fix(mobile): keep the page bundle within budget and show the machine on the host screen
The edit route gets a web stub so its native-only removal flow stays out of
the page's chunks, and Automations keeps its own segment style. The host
screen header shows the machine line like Home does.
* fix(mobile): give the edit route's page stub its own module
A bare re-export of the catch-all route compiled to the same asset as
[...page], so the page manifest listed it twice and the desktop template's
inventory check refused the build.
* feat(mobile): turn Home into Feed, Usage, Automations and Settings tabs
Feed keeps agents (needs-you first, five then Show all), desktops with a
needs-you count, and Tasks; New workspace moves to a floating button.
Usage shows stats and per-desktop account usage, Automations a per-desktop
summary with on/off switches that opens the page for editing (new=1 opens
its editor). Settings becomes a tab. Arrange Home is removed: tabs replace
the long scrolling Home it was for.
* style(mobile): darken the accent blue slightly
* style(mobile): give the Home Tasks row the desktop tile icon
* style(mobile): condense the Home Feed
Smaller tiles, type and gaps; a desktop is two lines with its needs-you
count as an amber pill; the Tasks row drops its duplicate heading.
* style(mobile): drop the Last known prefix on Home desktop rows
* chore(mobile): bump to 0.0.63 and Android versionCode 27
* test(mobile): re-record Linear task recordings for the darker accent blue
* test(mobile): check the page Back stack on Tasks now that Edit host is native
* fix(mobile): pass the Usage tab's limit window to the shared usage helpers
* fix(mobile): type the Usage tab test to the account-slot limits and keep the tab under 400 lines
* feat(mobile): bring back the terminal key row and one live input
Every terminal tab, shell or agent, gets the dock from before #36166:
the scrolling key row (desktop width, Paste, the built-in keys, custom
keys, add) above one live field that types straight into the terminal,
with the attach and mic buttons beside it. The field keeps today's 3:1
outline and focus ring, and the keys use today's chips and press styles.
The Keys/Message toggle, the Message composer, the arrow pad, the ⋯
sheet on the row and the per-terminal mode preference go. A stored mode
from an older build is never read. Sticky ctrl/alt/shift and the More
keys sheet stay only behind the iPhone Duo rail, which shipped shells
still draw; the page reports no composer, so shipped shells grey their
input-mode key and newer shells hide it.
* refactor(mobile): delete the buffered terminal line
With one live input there is no way to write a terminal line first and
send it, so the per-terminal drafts and their storage, the Send path,
command-field dictation, draft restoration and the terminal setting for
autocomplete and autocorrect (which only applied to that line) go.
Dictation in a terminal always types into it live. Drafts and the setting
saved by older builds are never read.
* 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 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.
* 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 eight native Settings screens use the shared NavBar and grouped rows,
Source Control's tabs move into the bar's segmented control, and Accounts,
Troubleshoot, the custom key editor, the Tasks pickers and drawers and the
workspace details sheet drop their hand-rolled rows for ListRow.
* feat(mobile): polish press feedback, sheet motion, states, spacing and layout jumps
Every control presses and disables the same way, with one haptic rule.
Sheets follow the finger and settle without bounce, pickers stack instead of
swapping, and screens slide over a dimmed, shifted parent. Loading, empty,
error and offline states share one view with plain-language errors, rows and
labels line up on one grid, and screens stop jumping as data arrives.
* fix(mobile): settle voice, tasks and accounts states; keep host snapshot current
Voice settings with no paired desktop shows the connect state instead of
loading forever. A failed task settings read after a successful probe retries
hydration. The shared host-list snapshot drops on invalidation and ignores
stale passes. Accounts shows a re-pair state on auth failure, and busy
action-sheet rows keep their disabled state for screen readers.
Sheets slide on iOS's curve over their own height, ease when their content
changes, and paint a lighter surface with grouped rows and a drawn switch.
Create worktree, the model picker, the GitHub issue sheet, file preview,
Agent Session History and the task list take the redesign draft's layouts
at the app's smaller type sizes.
* feat(mobile): give every corner one of three shapes
Corners had drifted to 17 different radii, so a screen mixed 6 pt
buttons, 14 pt cards, 12 pt groups and 4 pt badges. Every corner now
comes from the shape of what it is:
- control (12): anything you tap — buttons, icon buttons, fields,
pickers, segmented controls, key caps.
- surface (16): anything that holds content — cards, groups, sheets,
banners, code boxes. Dialogs and the composer box use floating (24).
- pill: badges, chips, status pills, dots, avatars and the composer's
own round buttons.
- tile (10) for icon squares and mark (4) for inline code and check
squares; nested shapes take the parent's radius minus the gap.
Every rounded style also asks for the continuous (squircle) corner
curve; iOS draws it, Android and the web ignore it.
* fix(mobile): keep circles and pills on the circular corner curve
The continuous curve squares off a corner radius that reaches half the element's size, so the FAB, dots, avatars and the composer's round buttons stopped being round on iOS.
* fix(mobile): put the anchored detail card on the surface radius
* fix(mobile): keep the circular curve on unclipped bordered styles
React Native strokes an unclipped visible border on a circular path while it fills the background with the continuous curve, so the two edges drifted apart at the corners.
* feat(mobile): tap the session title to see its full name, path and host
A sideways phone caps the title at 160pt beside the tabs, so long worktree names were cut off with no way to read them. Tapping the title now opens a card under the top bar, built like desktop's tab hover card, with the full name, the worktree's path and the host. It fades and slides in over 150ms (fade only with Reduce Motion), and any tap outside closes it.
* fix(mobile): keep the worktree-name reset effect unchanged; type-only View import; lazy card animation value
* fix(mobile): preserve title card bounds and execution host identity
* test(mobile): complete title card platform mocks
* fix(mobile): separate detail card from title trigger
* style(mobile): apply pinned formatter wrapping
* fix(mobile): reopen session details during the closing animation
* style(mobile): wrap the session title imports
* Keep session title activation on one target through card exit
* Host session details above page navigation and retain input scope
* Keep the title target clear as its layout changes
* Clarify title-card types and observer cleanup for checks
* Keep mobile title-card requests and visual exits under one owner
* Make the existing mobile title an explicit web click target
* Keep mobile title details nonmodal through their visual exit
* Follow the active mobile palette in session title details
* Use array syntax for title card motion fixture
* Localize the mobile workspace details hint
* test(mobile): cover title card release to a higher sheet
* Refresh measured mobile web chunk envelope
Carries over #33221 and #33210. Replaces our #34484 Duo support with the community design, on top of main's rebuilt terminal key bar (#36166).
Fixes#33200
Co-authored-by: Takeshi Matsumoto <zytakeshi520@gmail.com>
Co-authored-by: Lars Keggenhoff <lars@kghf.io>
* feat(mobile): give every text input the chat composer's look
Adds shared MessageBox, TextField and ComposerSendButton built from the
composer's own styles, and moves chat, form, search, Tasks, PR, review and
commit inputs onto them. The commit message becomes multi-line, every input
is 16pt (no page zoom on focus), and focus rings are gone. The terminal and
browser typing docks are unchanged.
* feat(mobile): rebuild the terminal dock around Keys and Message modes
Agent tabs open in Message mode, where you write in the chat composer and
Send types it into the terminal. Shell tabs open in Keys mode, where each
letter goes straight to the terminal through the live field. A pick made on
an agent tab lasts until the agent quits. The shell/agent rule is the one
keyboard dismissal already used, now shared.
The key row carries sticky ctrl, alt and shift (tap for the next key,
double tap to lock) and one arrow pad (tap a side, or hold and drag to
repeat through the existing held-key repeat). Paste, attachments, the
display width, the remaining keys and custom keys move to a ⋯ sheet. Return
in the composer adds a line; Send sends.
* fix(mobile): read arrow-pad taps from the pad and keep every key reachable from ⋯
A touch on a chevron made the icon the touch target, so the side was
measured against the icon's box and most taps read as left. The ⋯ sheet
now leaves out only the keys the current row shows, so agent tabs get Tab
and shell tabs get Shift+Tab and Enter back.
* fix(mobile): keep the last changed files clear of a growing commit box
The file list's bottom inset was a fixed 196 pt, so a commit message several lines long hid the last rows behind the commit bar. The inset now follows the bar's measured height.
* test(mobile): give the file list test its bottom inset
* feat(mobile): localize tasks, new workspaces and source control
Moves the last ~670 strings (task lists and boards, issue and PR sources, the
new-workspace flow, and source control) to the translation API, with all ten
translations. What remains in the coverage baseline is reviewed non-copy: key
names, schema errors and log lines.
- Source control section titles recompute on a language change
* fix(mobile): align catalog terminology across screens
A whole-catalog pass per language reconciled terms that differed between batches
(host/desktop, dictation, transcripts, address form) with the glossaries and the
desktop catalogs. The page bundle verifier now reports catalog bytes separately.
* fix(mobile): keep fetched history and source error slugs language-independent
History rows are derived from the fetched page at render so relative times follow a
language switch; quality-gate follow-ups for the tasks and source control migration.
* fix(mobile): name Android notification channels in the UI language
* chore(mobile): drop catalog entries for removed source-control copy
* fix(mobile): re-localize the composer source hook on current main
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
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>
* 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>
* 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>
* 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>
* fix(mobile): show pasted pairing feedback without camera access
* test(mobile): drop unreachable revoke and mock-only listener tests
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): leave Agent Session History when resuming a session from it
Resuming always pushed the session screen, so Back from it landed on the
history screen again (A -> B -> A). History now learns it was opened from a
session (origin=session, as Source Control does): resuming into that same
workspace pops back to it, anything else replaces the history screen.
The history screen is a page in the shell, and the page could only ask the
shell to push. The navigate notify gains an optional replace flag; a page with
no in-page history asks the shell to replace its screen, and one with history
below pops its own entry and has the shell push. Older shells strip the flag
and push as before.
Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>
* fix(mobile): show the resumed terminal on the session that opened Agent Session History
Going back to the session screen below kept its old tab: the session pins its
own pick over the host's caller-scoped snapshot. History now hands the resumed
terminal to that session through the registry Source Control already uses for
opened diffs, renamed to session-screen-handlers since it carries both.
* refactor(mobile): positional replace flag on notifyNavigate, share the owned-session test fixture
* fix(mobile): keep MobileWebShellScreen under its line cap
---------
Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>
* feat(mobile): light and dark themes with desktop's palettes
Settings → Appearance offers System, Light and Dark. The shell passes the
resolved scheme to over-the-air pages in its init frame.
Fixes#22184
* fix(mobile): draw your chat messages in desktop's user bubble
Desktop uses a faint surface with a hairline border in both themes; the inverted fill went black in light.
* test(mobile): re-measure the page script sweep after the shared theme hooks
Every route now imports the theme hooks while the entry does not, so they emit one shared script (96 for the tree). Re-swept every route prefix; the worst route step is now 15.
* test(mobile): read themed stylesheets in the text-input font-size census
The census only unwrapped StyleSheet.create({...}); inputs styled through themedStyleSheet(() => ({...})) came back unread.
Classify substantially delayed connect/handshake deadlines as interrupted
instead of blaming endpoint reachability, retaining the existing close/retry
path and ordinary timeout errors. Limit sleep-clock compensation to observed
background segments and let fresh failures replace delayed evidence.
Remove the stale host-version fallback and discard captured versions when
report hydration crosses an observed reconnect. Independently reviewed;
128 focused tests, adjacent transport contracts and changed quality pass.
Adapted from community PR #24425.
Fixes#18829.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(mobile): the home screen's New Workspace opens the create sheet again
The native host-list route dropped the home screen's action=newWorktree when it built
the page's route, so the page opened as a plain list. The route now forwards it, and
the page names it back once applied so a reload does not reopen the sheet.
Reaches phones only with the next native build.
* test(mobile): drop the New Workspace tests that only re-check types and a null guard
* fix(mobile): Home's New Workspace opens the create sheet over Home
Replaces the action=newWorktree route hand-off. The quick action picks a
host (as Tasks does), then mounts the host's create sheet on Home; a
create opens the new workspace's session marked as just created.
The sheet controller becomes a plain visible/onClose component, and the
host screen's route-action state, the action route param and its builder
are deleted.
* fix(mobile): Home's create sheet seeds only proven worktree rows
Home's cold-start seed is a pathless resume row that the name suggester
cannot read, so the sheet now seeds from proven rows and is keyed by host.
The host screen gets one setter for the sheet's visibility and its ref.
* test(mobile): name the Home new-workspace test after the screen it renders
* proto(mobile): Home's New Workspace opens a native drawer hosting the page's create form
Home pushes /new-workspace/<host>, a react-native-screens formSheet over Home whose content is
MobileWebShellScreen for the page route /h/<host>/new-workspace. The page renders the shared
Create Workspace form with its drawers inline, so the form and its pickers fill the native
drawer. The shell's loading, download and failure states render inside the drawer. Prototype only.
* proto(mobile): the New Workspace drawer is the host screen's own sheet over a transparent modal
The native route is a transparentModal hosting the shell over Home; the page route renders the
host screen's NewWorktreeModal on a transparent page, so the drawer is the same component. While
the page loads, the shell shows a placeholder of that sheet (scrim, grabber, spinner or download
bar), and the page's sheet opens in place without its slide. A host that cannot serve the page
(old desktop, blocked, failed) opens the host list instead of a wall in a drawer.
* feat(mobile): Home's New Workspace opens the page's create drawer over a dimmed Home
A transparent native overlay route hosts the host picker and then the page's
/h/[hostId]/new-workspace route, which renders the same NewWorktreeModal as the
host screen's +. The native scrim matches the drawer backdrop and the page's
first drawer opens already dimmed, so Home stays dimmed throughout. Old desktops
without the route fall back to the host list.
* fix(mobile): one loading layer for Home's New Workspace overlay
The overlay route owns a single tappable loading layer (spinner, or the
download on a small card) that stays mounted from the pick until the page
paints, so the fade and spinner no longer restart per shell state. The shell
reports its load and shows the host list for anything it cannot open; the
picker hands the pick over at once and keeps sliding away. The page's first
drawer takes the dimmed backdrop once, and a gate's overlay pending view no
longer stacks a second scrim over a mounted stack. Native /h/[hostId]/new-workspace
redirects to the overlay. Drops the unused session-route params, the
clipboard grant, and pass-through tests.
* feat(mobile): New Workspace loads in a short drawer over an undimmed Home
While the page loads, the overlay shows one short native drawer in the shared
drawer chrome with a spinner or the download, and nothing dims Home: a tap
above it, a drag down or Back cancels. The page's real drawer then slides up
with its own backdrop exactly as on the host screen's +. Drops the native
scrim, the loading card and the already-dimmed backdrop handoff; the overlay's
WebView stays invisible until the page paints, since a fresh one draws opaque.
* feat(mobile): the New Workspace loading drawer dims Home like any drawer
The short loading drawer opens with the shared drawer backdrop and show
duration, and a tap on that backdrop, a drag down or Back cancels. It stays
in the screen's own window (a native Modal over the loading WebView stalls
the page) and goes in the frame the page paints: the shell reports its load
from a layout effect so the WebView shows and the drawer leaves together.
* fix(mobile): the page's first paint waits for a drawer opening with it
A drawer opening as its page screen mounts holds that screen's first-paint
report until its open animation ends, through a hold on the existing report
rather than a new bridge message. The New Workspace overlay's loading drawer
now stays until the create drawer is up, so a cold open no longer shows Home
undimmed between the two.
* refactor(mobile): the New Workspace overlay owns its exits; the loading drawer fades out
The shell no longer imports the feature: an overlay is a shell with
onOverlayLoad, which reports `unavailable` for what it cannot show, and the
overlay route opens the host list for every exit. HostProtocolGate takes one
transparentWhilePending flag. The loading drawer's drag handler follows a
stable cancel, and on paint the drawer fades out eased in, as the shell's page
cover does, because the WebView takes a frame or two to show the page after it
is revealed; removing it outright left Home undimmed for those frames.
* Revert "fix(mobile): the page's first paint waits for a drawer opening with it"
This reverts commit be0913b9d5.
* fix(mobile): Home's host picker leaves when there is no desktop to pick
An unreadable host catalog, or offered desktops that are all gone, left the
picker invisible inside the transparent overlay, holding a screen over Home
with nothing to dismiss it. The catalog read now resolves to no hosts on
failure, and an empty pick list goes back to Home.
* fix(mobile): Home's create drawer names SSH hosts as the host screen does
The page drawer opened from Home passed no host labels, so a project on an SSH
host showed its run target by raw host id. Reading the SSH target summaries
and display overrides moves into one function that both the host screen's
metadata fetch and the page drawer call.
Saving an endpoint after the user leaves can pop a second screen. Retire navigation ownership while allowing the endpoint save and client refresh to finish.