* feat(mobile): floating answer card, queued messages above it
Port of stablyai/orca-private#724 (e1a794e6), plus:
- keep orca #40820's filled Steer pill on the lit head row
- let the open queue list shrink and scroll under a height limit so
the Show less row stays whole at 320pt on a short screen
* test(mobile): pin the short-screen queue shrink at narrow width and on the box
* fix(mobile): scroll a failed queued message into view and keep the lit Steer pill's press
- A failure that opens the queue scrolls its row into view, so on a short
screen the reason the list opened is visible above Show less.
- The pressed opacity comes after the lit fill on the Steer pill.
- Test the lit pill (orca #40820) and the failure scroll.
* fix(mobile): place a failed queued row only from its fresh height and let stale requests lapse
- The failure scroll waits for the failed row to lay out again, then settles
on the known heights after 300ms, so a stale height or an unresolved
request never moves the list later.
- A deferred show expires after 1s and on a queue change; the inline
editor's reveal scrolls at once again.
* refactor(mobile): keep the failure-scroll request as the queue list's own state
React Doctor reads a hook-returned setter called during render as a prop
callback; the list now owns the request with useState and hands it to the hook.
* fix(mobile): address review threads on the queued list
- Fold again for a later prompt: an open choice made in one placement no
longer comes back in the next.
- Scroll a message the person queues into a folded list into view through
the same deferred show as a failure.
- Lift from the row only in the plain presentation; a settings row keeps
its grip gesture.
* fix(mobile): let a choice made in the render a prompt arrives win over the placement reset
* fix(mobile): count an open inline editor's height when placing a later queued row
* test(mobile): note why the inline-editor test stub is cast
Holding Cmd/Ctrl swapped the round Send/Stop button for a wide labeled pill,
pushing the model picker, context ring and mic left. The preview now keeps
Send's round button and only swaps its icon (CornerDownRight for steer,
ListEnd for queue); the label stays as the aria-label. With Steer queued,
the head queued card's Steer button also fills as the primary.
The phone composer had the same pill and now follows the same rule.
* feat(mobile): show only the open tab, with a tab count and a sheet of every tab
The session bar shows the open tab at full width, then a tab count cell and a + cell. The count opens a sheet of every tab grouped Needs you / Working / Everything else; tapping the tab opens its actions; swiping it steps between tabs; + opens a new tab sheet with agents as tiles. The header subtitle shows the branch. Brings back #40592 on current main.
* fix(mobile): don't switch tabs when a bar swipe is interrupted
* 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>
* feat(mobile): pick the PR glyph from PR state
Workspace rows always drew the open-PR glyph and only changed its color,
and the PR tab's state badge had no glyph. Both now show merged, closed and
draft glyphs, from one prStateIcon mapping that the details sheet and task
rows also use.
* feat(mobile): show the PR state on the session header's source-control button
The button always drew a branch glyph. When the worktree's catalog row has a
linked PR it now draws that PR's state glyph in its state colour, read from
the host list's worktree.ps query in its own component.
* fix(mobile): read the header's PR state from the catalog cache only
Opening a session must not request worktree.ps before its first paint, so the
source-control button reads what the host list last loaded instead of
fetching it.
Reverts #40592 (12e809c755). The session bar goes back to the scrolling strip of tab cells, the + menu goes back to the action sheet, and the header subtitle shows the tab count again.
* Show Source Control menu rows on one line with a short status
* Keep Push and Force Push clickable; force push shows remote-only commits it replaces
* Match Force Push description to remote-only commits; list review blockers exhaustively
* Show short statuses for every blocked review action
* feat(mobile): show the open tab as one pill with a tabs sheet
The phone copied desktop's tab strip, which fits about three tabs cut to
~95 pt and hides a tab that needs you behind an edge dot. The bar now shows
only the tab you're in, at full width, with the tab count. Tapping it opens
a sheet of every tab grouped by what needs you, with each tab's state and
last activity; swiping it steps to the next tab. The + sheet shows agents as
a grid above Terminal, Browser and Markdown note, and the header names the
branch instead of the tab count.
* feat(mobile): pair the tab count with + and open tab actions from the pill
The count and its chevron inside the pill read like a menu and hid the
"another tab needs you" dot. The count now shares a capsule with +, the
way phone browsers put the tab-count button beside new tab, and tapping
the pill opens the tab's own actions.
* feat(mobile): give the tab count its own button beside +
The shared capsule made each half a small target. The count is now a
round nav button like +, lined up under the header's buttons.
* feat(mobile): draw the tab bar as the strip's square cells
The rounded pill and round buttons read as a different control from the
strip people know. The open tab is now the strip's raised, underlined cell
from the screen edge, with the count and + as square cells in the same
row, which also gives them 46x44 pt targets.
* fix(mobile): keep a hairline after the tab bar's last cell in the top bar
* 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): create a worktree per repository from an empty folder workspace
A folder workspace with no attached worktrees lists one card per
repository in its project group, as desktop's empty state does. Tapping
one creates a worktree named after the folder, attached to it, in place;
a taken name or a setup choice opens the new-workspace composer with the
repo, the name and the folder parent filled in. Shown only when the host
creating it supports exact-name creation.
* chore(mobile): merge the folder repo read's execution-host imports
* fix(mobile): folder creates keep their repository until the folder shows them
Every create from a folder's empty state, direct or through the
composer, holds its repository from admission until the folder lists
the new worktree, so a lost reply, a remount or a reopened form can't
start a second one. The composer and toasts belong to the folder
screen, stay on the folder's host, and say nothing once it's left. A
relayed server's repository read holds no desktop event stream, and a
failed read can be retried.
* test(mobile): a folder create resent after a lost reply carries the same mutation id
* fix(mobile): folder creates keep their name, and a left or replaced form shows and holds nothing
* fix(mobile): pause folder rows under the create form; a lost folder create is checked, never resent as another
* fix(mobile): a folder card never resends a form's create, and one form owns the folder screen at a time
* fix(mobile): a folder setup choice resends only its own unconfirmed create, and the newest form silences older ones
* test(mobile): declare the submit hook's folder launch in its recording fixture
* fix(mobile): a create refused on a resend after a lost reply tries no other name and stays unconfirmed
* fix(mobile): a replayed refusal after an agent-launch fallback stops the retry, and the composer says it may have been created
* test(mobile): type the replayed refusal reply in the retry test
* ci: retrigger checks
* fix(mobile): localize the folder and uncertain-create messages in the workspace composer
* feat(mobile): smart rail keys for the terminal agent's state, with Shift/Ctrl/Alt latches
* fix(mobile): cancel key presses only when the key row really moves
* fix(mobile): keep Smart rail on through Reset Defaults and finish two-write keys
* fix(mobile): only a finger scrolling the key row cancels its presses
* fix(mobile): hold the smart group until the tap's press lands, and drop clicks on keys that moved
* refactor(mobile): simplify the smart rail touch hook and layout read
* chore(mobile): allowlist the Shift, Ctrl and Alt key names
* feat(mobile): Codex async question keys on the smart rail
* fix(mobile): drop the Codex Always key, whose key differs by approval
Codex binds "don't ask again" to p for a command-prefix rule and a for session and file approvals (0.136 through 0.166). The permission hook doesn't say which one is on screen.
* 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
* feat(mobile): keep commit, PR and review drafts across kills
* refactor(mobile): build commit, PR and review drafts on the shared draft fields, and clear only the text that was sent
* fix(mobile): keep a toggle made during Write with AI, the edit note while its sheet closes, and a newer PR edit after an earlier save
* test(mobile): wait for the saved review note row instead of sleeping
* perf(mobile): keep a hash of the PR text an edit was typed against, not a second copy
* feat(mobile): keep Tasks and new-workspace drafts across kills
Task comments (per item and project row), the New issue title and body,
and the New workspace note now live in device-store draft rows. They come
back after the form closes or the app is killed, and only into an empty
field. A comment's setter stays bound to its item, so a send that settles
after the user moved on clears the right row.
* fix(mobile): rebuild comment sends when the draft's item changes
* fix(mobile): key the workspace note by host and keep cleared fields cleared
* fix(mobile): clear only the note a finished create sent
* fix(mobile): count clearing an empty draft field as the user's choice
* perf(mobile): cap field drafts, read sheet drafts on open, retry a failed read once
* fix(mobile): retry a failed draft read after a pause and keep New issue text typed during a create
* fix(mobile): save draft text again once a failed store read can be retried
* fix(mobile): remove a stored draft when an unread field is cleared
Unread rows show the desktop's bell, swipe right toggles read, swipe
left clears a finished agent, and the long-press and options menus get
Mark read/unread, Unread only, Mark all read and Clear finished. Writes
go through agentActivity.setRead/clear and show optimistically until the
desktop's list confirms them.
* 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