* 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>
A keyboard's mic button switches to a separate voice IME, which closes the keyboard and opens the voice panel about 250 ms later. The page read every Android keyboard close as Back and blurred the field at once, so the voice IME's text had nowhere to go. Blur only once the keyboard stays closed for a second; a reopen within that window keeps focus.
* feat(mobile): announce dictation to VoiceOver through the shell
* feat(mobile): speak dictation from the host, so leaving the session and the pill's toasts are announced too
* fix(mobile): say dictation outcomes where they are known, and re-read a repeated live message
* fix(mobile): leave a setup refusal to its setup sheet instead of reading the raw code
* fix(mobile): say cancelled when a kept recording is discarded
* feat(speech): spell dictionary terms in dictation, with repo terms and Whisper prompt
* test(speech): pin host repo terms for phone dictation
* fix(speech): keep symbol terms whole, accept null setup params, resolve SSH repos
* refactor(speech): key transcript words once and type phone terms at the reply schema
* refactor(speech): merge speech-types imports in the speech IPC
* feat(mobile): cancel dictation in place and show which device is transcribing
* fix(mobile): give each composer its own dictation cancel target
* fix(mobile): keep the dictating composer row on one line
* refactor(mobile): share the dictation mic face and measure the cancel target per platform
* feat(dictation): remove filler words from text the phone recognizes itself
Phones that dictate with their own recognizer (Automatic or This phone)
never send text through the computer, so its filler pass never saw it.
The page's on-device speech adapter now runs the same shared text
pipeline on the phone's live and final text, in the language of the
locale the recognizer started in, and honours the computer's Remove
Filler Words switch, which the host now reports in its dictation setup.
An older host that doesn't report it leaves the switch on, its default.
* test(mobile): expect both commas to go around a mid-sentence filler on the phone too
* 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
Count a key changed twice in one write once, keep the per-bucket estimate an upper bound, and redo a seed pass that missed files at the next foreground instead of trusting it as exact.
The page store refuses a bad key or an over-cap value before posting anything, keeps its calls in call order and inside the bridge's in-flight cap, and checks deferred slots. The shell reads only the keys that surely fit the reply budget, so no value is decrypted twice.
* fix(mobile): finish Android dictation on a call, alarm or leaving the app
A transient audio-focus loss (call, alarm, assistant) left the mic recording
silence, and pausing the activity closed the mic only to reopen it on resume,
splicing a silent gap into the dictation. Both now end the recording and emit
an interruption, so dictation finishes and inserts what was heard. The
dictation hook also finishes when the app goes to the background, which
covers iOS and shells without this native change.
* fix(mobile): keep a dictation that is waiting on the Android mic prompt
* fix(mobile): keep Android recording state consistent across the focus and JS threads
* refactor(mobile): name the Android pause path for what it does and stop retaking focus on resume
* fix(mobile): keep what the open mic heard when leaving during a pending host start
* fix(mobile): close the open mic at once when leaving during a pending host start
* fix(mobile): a second page capture end waits for the stop already in flight
* fix(mobile): show the phone speech download size and drop a cancelled session's late text
* fix(mobile): show the Android language download size too
* fix(mobile): keep a recognizer error raised while its start resolves, and read the download size aloud
* feat(mobile): define the on-device speech contract for the native audio module and page bridge
* feat(mobile): dictate on the phone first and replay to the computer when the phone fails
* refactor(mobile): type the on-device speech module without a cast
* fix(mobile): point dictation's finish ref at the committed callback
* test(mobile): wait on the takeover request instead of a fixed sleep
* fix(mobile): release each dictation request's own share of a replayed backlog
* fix(mobile): let the computer's text replace the phone's after a fallback
Each chunk RPC holds one of the page bridge's 60 desktop request slots until
the host answers, through a reconnect too, so the page drained the microphone
only every 500 ms. Cap chunk sends at four per dictation and merge audio
captured meanwhile into the next request, then drain every 150 ms.
Slash and mention suggestion menus in the phone chat composer stay reachable above the on-screen keyboard while typing.
Co-authored-by: Guderlilian <73724370+qq2144@users.noreply.github.com>
* perf(mobile): stop live input and keyboard height re-rendering the whole session
* refactor(mobile): drop the web soft-keyboard state hook nothing reads
* refactor(mobile): read the session keyboard lift from the shared occlusion hook
* test(mobile): give the command dock render probe a live input store
* feat(mobile): copy, save and share an image from the chat image viewer
* refactor(mobile): pass image viewer actions in so diagrams skip them
* fix(mobile): name the image verb native.image.act and serialize image actions
* fix(mobile): queue image actions per page and skip SVG links
* refactor(mobile): share the cache-folder helper and table the viewer's image actions
* fix(mobile): keep the toast's live region mounted so screen readers hear it
* test(mobile): re-record agent history goldens for the always-mounted toast region
* test(mobile): import the declared routes in the moved routes test
* 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
* Open large pasted text and attached text in structured chats
* Read pending pasted text before its host route resolves
* Pass structured attachment eligibility from the owning chat
* Preserve composer and Markdown state when inspecting text attachments
* fix: preserve attachment identity and cancel deferred reads
* test: consolidate phone attachment rendering contracts
* Scope attachment previews to visible chats and share route indexes
* test: consolidate duplicate attachment preview coverage
* perf: share remaining workspace catalog derivations
* feat(chat): open text attachments from structured goal objectives
* fix(chat): reuse preview owner and bind app hooks
* refactor(chat): trim duplicate tests and unused capability initialization
* test(chat): share routing scope mount fixture
* Fix routing performance attribution and folder row imports
* Share attachment capability through its domain module
* Align attachment fixtures and warm routing contracts with current gates
* Preserve relay connection timeout default for ordinary requests
* Share image classification ownership and repair integration fixtures
* Repair real attachment integration fixture boundaries
* Expose e2e compiler diagnostics and prune stale read allowance
* Type the recall fixture row ref as a div
* Keep text attachment capability in its existing capability owner
* Keep image MIME bindings in the media type owner
* Own private bridge response completion at the request boundary
* Complete bridge fixture return and retention types
* Localize native chat text attachment preview controls
* Complete attachment preview localization at presentation boundaries
* Preserve legacy preview locale identity and complete localization controls
* Fix typed mobile host witness lookup
* Commit attachment page edits and reset mobile page scroll
* test(chat): remove duplicate preview localization coverage
* fix(mobile): apply page input font and footer safe area
* fix(chat): commit edited page before admitted navigation
* fix(chat): retain field readiness on pending commit
* fix: preserve attachment field focus until control activation
* fix: restore attachment preview styles and canonical close
* Bind mobile input font role to the central theme
* Type font source fixture cases as tuples
* Attribute inherited owner filter work and qualify folder fixtures
* Keep owner filter fixture input types source-derived
* test: retain attachment capability assertion import
* test: focus attachment coverage on important regressions
* Replace text attachment paging with bounded scrolling and trim tests
* Preserve literal phone attachment whitespace and trim unused test meter
* Keep scroll view refs and measurements out of render
* Keep preview collection independent of native clipboard initialization
* Give the phone scroll fixture coherent measured DOM geometry
* Commit attachment scroll extent before restoring its anchor
* Preserve text viewport geometry across bounded reads
* Unify bounded text scrolling around the viewport
* Preserve the live text anchor across viewport resizing
* Measure committed text viewport bounds from the host
* Preserve live attachment position across native client extent changes
* Use the actual first source block in the resize regression fixture
* Reuse subscription handle type
* Keep attachment effects keyed to their existing triggers
* Keep attachment rows within the committed track
* Allow the full attachment renderer contract to settle
* Open text attachment previews in a large modal
* Preserve attachment modal header layout with primitive-owned styles
* fix(mobile): keep the keyboard open when a tab tap opens its details card
Tracked in STA-13199.
* fix(mobile): keep the keyboard open when the session title opens its details card
The session title opens the same details card as a tab, but sat outside every
keep-focus container, so its tap moved focus off a focused composer or terminal
input and closed the soft keyboard. Attach the existing keep-focus helper to the
title button so the tap leaves the field focused, as tab taps already do.
* fix(mobile): keep the keep-focus helper safe where there is no DOM
The session title now attaches the keep-focus helper, and the title renders in
renderers without a DOM, where checking against HTMLElement throws. Check that
HTMLElement exists first, as other mobile code already does.
* test(mobile): use a type-only namespace import for the react-native mock type
* fix(mobile): save iPhone library photos as JPEG named by their bytes
The iOS photo picker handed back the asset's HEIC bytes as-is, and the host
named every pasted image .png, so agents read a HEIC file as a PNG and failed.
Ask the picker for the compatible representation (JPEG), and name pasted
images by their signature so the extension always matches the bytes.
* fix(mobile): keep unknown pasted bytes on the .png name
A .heic name falls outside the shared image MIME table, so chat previews
and agent prompts would drop the image. The picker now sends JPEG, so
leave HEIC detection out.
* refactor(mobile): keep the pasted image extension list module-private
The free-slot probe fills every desktop slot, so end()'s final chunk forward
is refused by construction. Since #36922 reserved native slots, that final
read now succeeds and its forward is refused, failing Mobile verify under Bun.
Desktop RPCs and on-device native verbs shared the bridge's 64 in-flight request slots on both page and shell. Unanswered desktop calls could hold all of them, refusing native.media.pick and native.media.release; refused releases stranded staged handles until expiry, so later picks hit native_media_handle_cap. Desktop calls now stop at 60 so native verbs always have the last 4.
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>
* 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>
* 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
* fix(mobile): keep dictated words through interruptions, busy taps and failed inserts
A call, Siri or mic dropout now finishes the dictation instead of
discarding it, a tap while transcribing no longer cancels, a transcript
its target refuses is copied to the clipboard with a toast, and host
dictation error codes show as plain sentences.
* test(mobile): share one session dictation harness for refusals and kept transcripts
* refactor(mobile): share the lost-session dictation sentence and keep speechless helpers private
* fix(mobile): keep a dictated terminal transcript when the live send throws
* fix(mobile): release the dictation audio session on stop and fail clearly without iOS voice processing
Stopping dictation only muted the native input, so the iOS voiceChat session
and Android audio focus stayed held until the screen unmounted and other apps'
audio stayed ducked. End now tears the engine down; the next open rebuilds it.
iOS tearDown stops I/O before deactivating and notifies other apps.
The iOS engine returned from setup before installing the mic tap when voice
processing could not be enabled, so recording reported success and captured
nothing. Setup now throws, so initialize fails and the phone shows the
microphone error.
* test(mobile): expect dictation stop to tear the audio engine down
* test(mobile): drop the duplicate release-throw case from native capture tests