* Add Close Tabs to the Left and complete Close Others across tab menus and mobile long-press sheets
* Fold the per-sheet Close action into the bulk-close module (session route max-lines)
* fix(mobile): preserve pinned tabs during bulk close
---------
Co-authored-by: Tom de Bres <tomdebres@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Add closeBeforePress flag to Rename, Browser, and Refresh actions to
defer modal opening until the action sheet closes. Eliminates the race
condition that caused the mobile app to freeze when opening these modals.
An image send whose text+Enter RPC ended 'unknown' (ack loss / path
cutover) collapsed to accepted=true, so the terminal was never marked
stale. When the Enter truly never landed, the already-pasted image path
sat on the input line and glued onto the next plain-text message.
Propagate the send outcome through handleNativeChatSendWithOutcome and
mark the terminal input stale on any non-accepted outcome; the next send
heals with Ctrl+U (a no-op when the message did land). Chips still clear
on 'unknown' to avoid a double-send on retry.
* fix(mobile): show attached images in native (rich) chat
Attaching an image in the mobile native chat did nothing visible — it reused
the terminal attach flow, which pastes a bracketed host path into the hidden
terminal, so there was no composer preview and nothing in the transcript.
Give native chat the desktop model instead:
- pick + upload shows a removable thumbnail chip in the composer (no early paste)
- on submit, images ride along: Ctrl+U clear -> bracketed paste(s) -> settle ->
text + Enter (idempotent on retry)
- the optimistic echo carries the local preview URIs and the message renderer
draws image-ref blocks as real thumbnails when the URI is loadable, so the
sent photo appears in the conversation immediately
- image-only echoes reconcile by ordinal against user turns after their tail
(ignores agent replies / paginated history / the 'unknown' ack-loss path)
Terminal chat attach is unchanged (both flows consolidated behind
useMobileSessionImageAttachments). Adds unit coverage for pick+upload, the
ride-along byte order, chip render/remove, and echo reconciliation.
* test(mobile): interactive native-chat image proof (real hooks, click-driven)
Replace the hand-fed component render with an interactive harness that mounts
the real MobileNativeChatComposer/Message + useMobileNativeChatImageAttachments +
drafts under react-native-web and drives the actual flow via clicks. Only the two
OS boundaries are faked: the photo picker and the paired-host RPC socket.
Screenshots (mobile/docs/native-chat-image-attachment/) are produced by real
clicks, not props:
- attach -> real upload pipeline -> chip appears, nothing pasted yet
- send -> real ride-along emits Ctrl+U clear, bracketed image paste, text+Enter
(shown in the live byte trace) and the sent bubble renders the photo thumbnail
* fix(mobile): scope native-chat image attachments by active tab
Images are now scoped to the tab that initiated the pick, so switching tabs
during upload cannot ride an image into another terminal. Chips stay with
their original tab, and only the active scope's images send with text.
Improved error handling with user-facing toast messages for disconnection
and send failures.
* test(mobile): add image attachment tab-scoping and error tests
Add comprehensive test coverage for tab-scoped attachment behavior,
error handling when transport fails or lease is gated, and edge cases
like attaching images during an in-flight send. Extract baseArgs and
update helpers to reduce boilerplate across test cases.
* fix(mobile): show attached images in native rich chat
Images attached in the mobile native (rich) chat now display as:
- Removable composer chips while composing
- Thumbnails in the sent user bubble after sending (desktop parity)
Implements proper image echo reconciliation by distinguishing
image-source marker turns from text echoes, so an image send isn't
cleared by an unrelated text echo. Adds scope isolation to prevent
chips and drafts from leaking between tabs, and detects tab switches
during the image-paste settle window to abort the send.
Fixes Android tap-target positioning for the image removal badge and
clears stale terminal input after failed pastes to avoid gluing
fragments onto the next message.
* rm stubs
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* fix(mobile): keep native chat from resizing the covered terminal PTY
Native chat reads the agent transcript stream and never renders the
terminal grid, but two paths still pushed phone dimensions into the
covered PTY, reflowing the desktop terminal for no benefit:
- The covered lease-only subscribe carried the cached viewport, and
handleMobileSubscribe phone-fits the PTY whenever a viewport is
present. The lease now omits the viewport so the host keeps the
desktop baseline and late-binds on return to the terminal tab.
- useTerminalViewportRefit measured the still-mounted WebView under
the chat overlay and sent terminal.updateViewport on rotation,
keyboard, text-scale, reconnect, and iOS-resume triggers. Refits
are now suppressed while native chat covers the active terminal;
the triggers already mark the viewport stale, and the
return-to-terminal resubscribe re-measures.
* fix(mobile): harden native-chat resize suppression
Update desktop experimental settings, mobile settings/onboarding, i18n
(en/zh/ja/ko/es), and user-visible error strings. Keep internal APIs and
identifiers as nativeChat.
* fix(mobile): gate dictation setup polling
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): fence a stale dictation refresh against a newer setPolling intent
An in-flight setup read resolving 'keep polling' after an explicit setPolling(false)
wrote polling=true and rescheduled, resurrecting a poll the caller had just stopped.
Snapshot a pollingRevision when each read starts and only apply its result if no
explicit setPolling superseded it mid-flight — so a late true can't restart a stopped
poll (nor a late false cancel a restart).
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* perf(mobile): coalesce overlapping home requests
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): queue a trailing follow-up for triggers during an in-flight read
Single-flight returned the in-flight promise to any trigger that arrived mid-read,
so a distinct refresh requested while a slow read was on the wire was silently
answered by the older response and never re-read the latest state (UI could stay
one refresh cycle stale). Coalesce mid-flight triggers into exactly one trailing
follow-up (latest params win) whose fresh result is delivered to those callers.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): keep quick-commands button steady while capabilities load
The tab-row quick-commands button only rendered once the capability probe
resolved true, so it popped in after the row was already visible (and
vanished during reconnect re-probes). Render it whenever support is not
confirmed absent and disable it until the probe settles — pre-quick-commands
hosts strip agentPrompt, so the action (not the button) must wait for
confirmation. Confirmed-unsupported hosts still hide it entirely.
* fix(mobile): explain unsupported quick commands on tap instead of hiding
Per feedback on the disabled/hidden states: the button now always renders
and stays tappable. Tapping against a desktop that confirmed no support
shows "Desktop update required for quick commands" (mirroring the browser
streaming copy); tapping while the capability probe is still resolving says
to try again in a moment. The sheet still opens only once support is
confirmed, since pre-quick-commands hosts strip agentPrompt.
* docs(pr): add QA screenshots for quick-commands button states
* test(mobile): lock quick-commands button stability
Add a focused source-contract test for the always-mounted tab action and confirmed-support sheet gate. Keep the non-obvious safety comment concise, and remove PR screenshots now hosted as GitHub user attachments.
* test(mobile): structurally guard quick-command action mount
* perf(mobile): gate host polling on foreground
The mobile host screen ran two 3s polls (routed + embedded), each firing worktree.ps
AND repo.list, with no foreground/background gate — so a connected phone kept pinging
every 3s (worktree.ps is a full multi-repo process scan) plus a radio wakeup, including
brief background windows while the socket stays parked.
Consolidate both into one startHostWorktreeRefresh lifecycle and AppState-gate the
interval so BOTH polls stop while backgrounded and refresh immediately on foreground
return. worktree.ps keeps its 3s cadence while foregrounded (it carries live agent
status/preview/unread that no push event replaces). repo.list stays on the interval as
an AppState-gated, self-throttling (REPO_METADATA_REFRESH_MS=60s) convergence safety-net
— desktop Settings repo edits notify only the renderer, not the runtime clientEvents
stream, so it can't be made purely event-driven without going stale — and additionally
gets a reposChanged/worktreesChanged fast-path and reconnect-replay refetch.
Verified in a deps-installed mobile checkout: full mobile suite 2232 pass, typecheck,
oxlint (within the frozen max-lines budget), and oxfmt --check all clean.
Co-authored-by: Orca <help@stably.ai>
* chore(mobile): drop stale fetchRepoMetadata dep from the reconnect effect
Address CodeRabbit nitpick: the reconnect effect no longer calls fetchRepoMetadata
(that refetch moved into startHostWorktreeRefresh), so it shouldn't remain in the
effect's dependency array.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(mobile): mount ProtocolBlockScreen when protocol compat is blocked
ProtocolBlockScreen existed since PR #1440 but was never mounted: on a
'blocked' compat verdict the only output was a console.warn, so a future
MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION bump would have silently shown a
broken host UI instead of the update screen.
Add HostProtocolGate — a choke point in app/h/_layout.tsx above every
/h/[hostId] route — that consumes useHostStatusGates and replaces the
blocked host's entire UI (sidebar + detail stack) with ProtocolBlockScreen.
The host list and other hosts stay usable; the screen's own 'Back to
hosts' escape hatch routes to '/'. Both block reasons render their
respective CTAs (mobile-too-old → App Store, desktop-too-old → GitHub
Releases). Compat logic stays in the src/shared mirror contract — no fork.
* fix(mobile): fence incompatible host routes efficiently
* fix(mobile): route Android updates to releases
* fix(mobile): retry session capability probe so tab-row actions survive relay cutover
The session screen learned host capabilities (quick commands, browser
screencast, agent history, query-reply input) from a single status.get
fired when the screen connected. Over relay, a relay-to-direct transport
cutover rejects every in-flight request while connState stays
'connected', and a request timeout does the same — so one transient
failure latched the capability flags false (or left them null on an
ok:false reply) and the quick-commands tab-row button stayed hidden
until the screen was remounted.
Replace the one-shot probe with startRuntimeCapabilityProbe: retry
promptly after a cutover (the replacement transport is already
authenticated) and with capped exponential backoff on other failures,
until a probe lands or the effect is cleaned up. Also export the
cutover-error predicate from stable-logical-rpc-client and reuse it in
worktree-create-capability instead of a local copy.
* fix(mobile): reset runtime gates before capability reprobe
* Access the Floating Workspace from mobile (#8405)
Surface the desktop Floating Workspace (the global, repo-less scratchpad
of terminal tabs under the synthetic `global-floating-terminal` id) on the
mobile app so a Claude session left running there is reachable from a phone.
Adds a terminal-icon button to the mobile host header (phone + tablet
sidebar) that opens the existing Session screen for the floating id. The
sentinel already had host-side RPC support (#5946: local runtime, homedir
cwd, explicit-id fast paths in session.tabs.*); this wires up the mobile
surface and gates it on a new `floatingWorkspaceEnabled` status flag so the
entry hides on hosts that predate it or where the feature is disabled.
The Session screen learns an `isFloatingWorkspaceRoute` flag (mirroring the
existing `folder:` route pattern) that hides repo-backed surfaces — Files,
Source Control, PR/checks, agent history — skips the diff-comment and GitHub
probes, routes terminal URL taps to the phone browser, and limits the New
Tab drawer to terminals + agents (browser/markdown creation resolves a real
worktree host-side and stays desktop-only). useLiveWorktreeName
short-circuits for the sentinel so it no longer polls worktree.show forever.
Extracted the host status.get gating into a useHostStatusGates hook to keep
the host screen under the max-lines ratchet.
* Harden mobile Floating Workspace routing
* Fix mobile host gate reuse race
* Harden floating mobile session polling
* fix(mobile): harden floating workspace route reuse
* fix(mobile): skip floating workspace repo lookup
* fix(mobile): clarify floating workspace header action
* fix(github): load PR diffs for Enterprise remotes
* fix(github): encode PR content paths by segment
* Fix PR review actions failing on GitHub Enterprise remotes
- Threads GitHub host identity (not just owner/repo) through the client,
work-item-details, issues, and RPC layers so gh commands target the
correct Enterprise server instead of silently falling back to github.com
- Adds a shared github-api-repository helper to resolve/host-qualify repo
identity consistently across REST, GraphQL, and CLI shorthand calls
- Scopes the gh rate-limit breaker and singleton rate-limit snapshot by
host/runtime so a github.com block or probe can't affect GHES or WSL
- Coalesces concurrent host-auth probes and paginates PR file fetching
beyond 100 results
- Propagates `host` through renderer PR caches, checks-panel keys, and
preload IPC types so Enterprise and github.com data never collide
* Route gh host qualification through runner options instead of argv sniff
Move GHES/GH_HOST resolution from parsing --hostname/--repo out of gh argv to an explicit options.host passed through ghExecFileAsync, since SSH-backed repos spawn gh with no cwd and argv sniffing couldn't reliably detect the target host. The runner now injects --hostname and qualifies --repo/-R at spawn time from options.host, and rate-limit scoping/guards use the same explicit host instead of inferring it. Also adds a shared githubRepoIdentityKey helper to keep cache/store keys consistent with the new host-aware repository identity.
* Fix gh CLI GHES host pinning and rate-limit scope leaks
- Pin `--host` on every gh call site so a process-level GH_HOST can't
silently redirect requests, and qualify `-R`/`-R=` repo shorthand
alongside the existing `--repo=` handling.
- Check the target scope for an active rate-limit block before each
WSL/native or host fallback retry, not just on the initial attempt,
so a blocked scope can't be hit again through a fallback path.
- Compute idempotency once per call instead of re-deriving it after
fallback reassigns args.
* Fix GitHub Enterprise host identity loss across PR/work-item paths
- Thread `host` through mobile PR RPC params, IPC work-item lookups, and
RPC schemas so GHES identity survives the renderer/mobile/main boundary
instead of silently falling back to a same-named github.com repo.
- Qualify `--repo`/`-R` args for github.com too (not just GHES), since
gh resolves bare shorthand against a process-level GH_HOST that can
redirect pinned github.com commands.
- Cache `getOriginGitHubApiRepository` to avoid a per-call uncached
`git remote get-url` round trip on connection-backed repos.
- Add a local-fork fallback in `getWorkItemDetails` so PRs living on a
base repo (not visible via the origin slug) still resolve via cwd.
- Centralize the github.com-vs-GHES host predicate in
`isDefaultGitHubHost` so cache keys, quota scoping, and identity
checks can't drift out of sync.
* Make repository identity host-aware across all GitHub surfaces
Generalize the auth-gated enterprise resolver to any remote and build a
cached hosted-identity family (origin/issue/candidates/source) on top of
it, then migrate every github.com-only consumer: Tasks listing/counting,
branch-to-PR discovery, push targets, fork upstream, issue operations,
Projects, web links, avatars, and PR-link facts. Scope the rate-limit
breaker probe per runtime:host and classify WSL UNC cwds correctly.
Co-authored-by: Orca <help@stably.ai>
* Fix expected slug to include host field in GitHub PR link test
Updates the smart-source paste-intent test fixture to match the
repository slug shape that now carries a `host` field, keeping GHES
host identity intact through the paste-intent parsing path.
* Surface per-host gh auth state for GitHub Enterprise
diagnoseGhAuth accepts the host a surface needs credentials for, scopes
the account/scope diagnosis to that host, and reports whether gh has any
login there; GhAuthErrorHelp renders host-qualified login/refresh
commands so an unauthenticated GHES host stops masquerading as a
github.com scope problem. Also fixes the mobile paste-intent expectation
for host-carrying parsed links.
Co-authored-by: Orca <help@stably.ai>
* Bound GHES identity caches and preserve non-default ports in host identity
Cap the origin-repo and host-auth caches like ownerRepoCache; keep ports
from remote/link URLs so GHES on a non-default port is a distinct
identity; make positional github.com slugs explicit against GH_HOST;
compare work-item sources by host-aware identity key; bail cwd-less
branch lookups when no repository candidate resolved; thread host
through the renderer work-item slug lookup.
Co-authored-by: Orca <help@stably.ai>
* Thread GitHub host through issue detail requests
Incorporates ghes-issue-host-support (ed6bb96ef): one hosted issue
repository identity is resolved before the details fan-out so comments,
timeline, participants, and mention lookups cannot drift across hosts,
with SSH guards so unresolved issue/PR repositories never fall through
to gh's default host.
Co-authored-by: Orca <help@stably.ai>
* Scope remaining GitHub rate-limit accounting
* Resolve typed PR lookups across hosted repository candidates
getWorkItem's PR path probes upstream-then-origin hosted candidates
instead of origin alone, so fork checkouts resolve the base repo's PR
with the right host; issue detail resolution reuses the up-front hosted
identity and keeps the SSH unresolved-host guards.
Co-authored-by: Orca <help@stably.ai>
* Refactor GitHub repository execution setup
* Carry host on smart-submit link intents
Co-authored-by: Orca <help@stably.ai>
* Carry the project host on GitHub item dialog origins
Co-authored-by: Orca <help@stably.ai>
* Keep GHES web ports but drop SSH transport ports in host identity
Supersedes PR #9118 on this branch: http(s) remote ports identify the
Enterprise web/API endpoint and are preserved, while ssh/git transport
ports (including ssh.github.com:443) never leak into gh's host identity.
Replaces the ssh.github.com:443 special case with the structural
protocol split and ports the PR's parsing test suite.
Co-authored-by: Orca <help@stably.ai>
* Support GitHub Enterprise diffs and mutations with host-scoped caches
Parse GitHub host identity from work-item URLs and carry it through PR/issue mutations, labels, and assignments. Bound rate-limit and scope-probe caches (1024 and 512 entries) to prevent unbounded growth when interacting with multiple GHES instances. Normalize repository identity keys to include host so github.com and GHES slugs don't collide in cache and equality checks.
* Support GitHub Enterprise diffs and mutations with host-scoped caches
- Carry host identity through PR mutations and reads so fork PRs on
different GHES instances don't collide in cache or state tracking.
- Validate host authentication before routing requests to unconfigured
Enterprise servers; ambient credentials must never reach untrusted hosts.
- Scope rate-limit guards and spend tracking per host so GHES quota stays
independent from github.com quota.
- Respect explicit --hostname arguments in gh CLI calls ahead of GH_HOST or
ambient defaults, so breaker state follows the actual request target.
- Detect implicit WSL runtimes from UNC paths for consistent host auth and
execution-options scoping across mobile and desktop clients.
* Support GitHub Enterprise work-item diffs with host-scoped execution
Enterprise PRs must use their selected host consistently across diff, comments,
and file-content loads. Validate repository slugs before authenticated execution
to prevent path-injection via renderer overrides. Scope project browsing cache
and rate-limit tracking by host to prevent cross-host pollution. Use parsed
URLs as authoritative over ambient hosts for project resolution.
* Support GitHub Enterprise work-item diffs with host-scoped execution
Preserve host identity on PR/issue work items throughout the mutation and diff
pipeline so Enterprise instances (including ported endpoints like
github.acme.test:8443) can execute mutations without ambiguity. Rate-limit gh
commands by the pre-qualified --repo host, cache auth state per ported host,
and surface Enterprise hosts in project metadata and error messages.
* fix(review): drop dead rateLimitGuard/noteRateLimitSpend re-export
Both callers (project-view.ts, mutations.ts) moved to the host-scoped
repositoryRateLimitGuard/noteRepositoryRateLimitSpend; the bucket-only
re-export in internals.ts had zero importers left.
Co-authored-by: Orca <help@stably.ai>
* fix(ci): split Enterprise host work-item tests under max-lines
Move GHES/SSH host-routing cases out of work-item-details.test.ts so
the suite stays within the 800-line test max-lines budget.
* test(github): align mocks with host-scoped repository resolution
- Route origin repository resolution through getOwnerRepoForRemote, not getOwnerRepo, to match production path
- Pin github.com host on origin results so host-less fixtures pass host gate in resolveGitHubApiRepository
- Add generation-based invalidation to prevent stale slug-cache writes from in-flight resolutions
- Fix ref-sync race in ProjectPicker: use useLayoutEffect so committed tree owns browse cache key
- Defer handledCrossRepoUrlRef assignment in SmartWorkspaceNameField until resolution succeeds
- Update Enterprise host routing: found work items must not silently fall back to default host when unresolved
- Normalize GHES avatar URLs: accept explicit port 443 as canonical form, not a fallback trigger
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Add a mobile native-chat opt-in so users pick terminal vs chat once
Mirror the notifications one-time opt-in for the native-chat default view.
After pairing, a full-screen modal (modeled on notification-opt-in) lets the
user choose whether supported agent sessions open in the terminal or in native
chat, then persists the choice to the existing orca:defaultSessionView key.
- Expose readDefaultSessionViewPreference() (tri-state; absent key = undecided)
so the gate can prompt exactly once; loadDefaultSessionView() is unchanged.
- shouldPresentSessionViewOptIn() gates the screen; the home focus effect shows
it after the notification opt-in.
- Settings -> Native chat toggle (already shipped) remains the recovery path.
* fix(mobile): preserve onboarding flow after pairing
* refine mobile session view opt-in copy
* Unify mobile onboarding prompts
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.
Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.
Area: mobile. 11 files changed, 339 insertions(+), 1137 deletions(-).
Co-authored-by: Orca <help@stably.ai>
* feat(mobile): add Quick Commands (terminal + agent-prompt presets)
Brings the desktop Terminal Quick Commands feature to mobile: saved
agent-prompt or terminal-command presets that launch a new terminal tab.
Entry point sits in the session tab strip next to the "+" new-terminal
button (with a divider) — quick commands spawn a tab, so they live with
tab creation, mirroring desktop's tab-bar split button.
- Launcher button + Quick Commands bottom sheet (search, This project /
Global groups, run/edit/delete rows, add row).
- Add/Edit sheet mirroring desktop TerminalQuickCommandDialog: Label,
Action toggle (Terminal Command | Agent Prompt), Agent select, Prompt /
Command Text, Advanced (Append Enter, Scope Global/Project), validation
and save-failure feedback.
- Launch reuses handleCreateTerminal (extended with enter + toast copy):
agent prompts launch the agent then deliver the prompt; terminal
commands run the (Enter-appended) command text.
- Expose terminalQuickCommands over the remote/mobile RPC surface
(getClientSettings/updateClientSettings allowlists, RuntimeStore type,
and the strict SettingsUpdate zod schema).
- Mirror the agent-prompt support predicate mobile-side (stdin-after-start
agents are unsupported) with a parity test guarding drift from desktop.
- Mock server: sample quick commands + settings.update handler for QA.
* fix(mobile): harden quick command execution
* fix(mobile): harden quick command persistence and launch
* test(mobile): preserve unexpected quick command errors
* fix(mobile): harden quick command launch performance
* fix(runtime): reject malformed quick command updates
* refactor(mobile): reuse shared quick-command logic instead of mirroring
The mobile quick-commands mirror was built on a false premise — that
runtime-importing src/shared/terminal-quick-commands breaks the RN bundle
/ Vitest. It doesn't: tui-agent-config → orca-cli-command-name is a pure
leaf with no module-load Node APIs (verified via probe + bundle-graph).
- Mobile now reuses the canonical desktop helpers (action/agent/scope/
matchesRepo/support/flatten) directly from src/shared; only genuinely
mobile-specific pieces (agent-branded labels, native row truncation,
the launch plan) stay local.
- Multiline runnable terminal commands now flatten via the shared
flattenTerminalQuickCommand (";"-join) — unity with desktop, so a
command saved on one runs identically on the other.
- Drop the MOBILE_TUI_AGENT_PROMPT_COMMAND_UNSUPPORTED mirror + its parity
test; use the shared supportsTerminalAgentQuickCommand predicate.
- Export the shared MAX_QUICK_COMMAND_* length caps for reuse.
* fix(mobile): protect quick command data boundaries
* fix(mobile): enforce quick command limits
* fix(mobile): make quick command updates atomic
* fix(mobile): keep quick command filters recoverable
* fix(mobile): use filled play icon for quick commands
* Revert "fix(mobile): use filled play icon for quick commands"
This reverts commit 169bf053b0.
* fix(mobile): gate quick commands on host capability
* feat(native-chat): add mobile default-view toggle for native chat
Adds a per-device "Native chat" setting on mobile (Settings → Native chat)
that controls whether supported agent sessions open in the native chat view
or the raw terminal. Default stays terminal; flipping it on makes eligible
sessions render as native chat, mirroring the desktop default-view control.
- New orca:defaultSessionView preference (loadDefaultSessionView/save).
- Per-tab chat set refactored into a tri-state override map so a session can
be pinned to terminal or chat regardless of the default; the legacy array
format migrates to chat overrides on load.
- useMobileSessionViewMode resolves each tab as override ?? default and
reloads the default on focus so a Settings change applies without remount.
- Toggle/action-sheet builders take an isTabChatView predicate.
* chore(skills): refresh skill bundle manifest for v1.4.144-rc.2
The v1.4.144-rc.2 release bump left resources/skills/current-manifest.json
pointing at rc.1, so verify:skill-bundle-manifest fails on any branch that
reaches it. Regenerated (appVersion only; no skill content changed) to
unblock CI. Unrelated to the native-chat toggle in this PR.
* fix(native-chat): harden mobile view preferences
* fix(native-chat): harden default view persistence
* fix(native-chat): serialize mobile view persistence
* fix(native-chat): reconcile failed mobile view saves
* fix(native-chat): fail closed on unreadable view overrides
* fix(native-chat): honor fail-closed view races
* Add mobile image-diff previews via shared data-URI builder
- Extracts a `buildImageDataUri` helper (src/shared/image-data-uri.ts) shared by
the desktop ImageViewer and mobile, so both trim whitespace-wrapped base64 and
skip non-previewable mimes (e.g. application/pdf) the same way.
- Adds mobile-diff-image-preview.ts to render binary git.diff results (add/modify/
delete) as images instead of falling back to "Binary preview unavailable".
- Extracts resolveMobileFileTabDoc to consolidate the session file-tab loading
logic (diff/image/html/text) out of the route file for testability.
* Fix stale binary image fallback for empty modified diffs and relay reads
- mobileDiffImageDataUri now distinguishes a true deletion (modified
side absent) from a modify whose binary bytes arrived empty
(relay/size-cap cases), returning null instead of the stale
pre-change image
- readWorkingDiffFile passes the file path to bufferToBlob so relay
working-tree reads can detect previewable image extensions instead
of always reporting empty binary content
- add mobile-file-tab-doc.test.ts covering diff/image/binary/text
resolution paths
* Regenerate skill bundle manifest for 1.4.144-rc.2
Co-authored-by: Orca <help@stably.ai>
* fix(review): trim comments to AGENTS.md's one/two-line why-only rule
Comments in mobile-diff-image-preview.ts and mobile-file-tab-doc.ts ran
3-6 lines and narrated mechanism instead of stating only the non-obvious
reason, per AGENTS.md's "Code Comments: Document the Why, Briefly" rule.
Co-authored-by: Orca <help@stably.ai>
* Distinguish read failures from true deletions in binary diff results
- Working-tree stat/readFile errors and relay reads previously collapsed
onto the same empty-content signal as a genuine deletion, letting
previewers fall back to stale original bytes on a failed read.
- Add modifiedDeleted/missing flags through status.ts, git-handler-ops,
and git-working-file-read so only proven deletions trigger the
original-bytes fallback; failed reads now return null.
- Tighten buildImageDataUri to accept only image/* mimes instead of
special-casing application/pdf.
* fix(relay): expect missing:false on index blob maxBuffer overflow
readBlobAtIndex now returns a missing flag so staged deletions are
distinct from size-capped binary reads; update the overflow test.
* Allow opening deleted files to show pre-delete text or image diffs
Deleted files can now be opened to view their pre-delete content via
git.diff (including images via modifiedDeleted). Only unresolved conflicts
remain unopenable. Centralizes the canOpen rule in canOpenMobileGitStatusEntry()
to keep opener guards consistent across the mobile source control UI.
---------
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): add native chat view across mobile
* fix(native-chat): address review findings and CodeRabbit threads
Correctness:
- Restore an independent initial readSession seed and surface initial-drain
errors as snapshot frames so the chat view can never strand on 'loading'
- Pair mobile tool results to calls by ordinal FIFO (parallel calls no longer
misgraft results); clear a pending ask only when its own call resolves
- Show a new streaming reply immediately (same-turn suppression, not length)
- Delegate mobile noise filtering to the shared harness-injected classifier
- Admit soft-leaving mobile clients in beginMobileInputFloor (parity with
mobileTookFloor) so grace-window writes aren't dropped
- Self-heal a stale 'working' status once this turn's reply lands
- Catch RPC rejections in mobile file-open helpers; guard sanitizeToolInput
key collisions; settle web/runtime transports on unrecognized first frames
and forward snapshot errors
Perf:
- Throttle the mobile streaming bubble (50ms) so per-part status frames stop
re-parsing the whole accumulated markdown
- Short-circuit markdown path detection on dot-less or oversized runs
(quadratic backtracking guard)
UX/minor:
- Wire hold-mode dictation through the native chat composer
- Allow scoped-package (@) paths in file-path detection
- Move caret after mid-text autocomplete insertion; index-prefixed ask option
keys; single scroll-to-end effect; bounded wait + toast when image attach
races a resubscribe; count-based pending reconciliation; cache-hit search
cancels stale debounce; chat-tab toggle wins over in-flight preference load
- Share shouldStepNativeChatAskAnswer between desktop and mobile; import
block guards/source priority from shared instead of local copies
- Defensive non-positive transcript limits; test strengthening (TTL expiry,
post-unsubscribe stale frame, lease readiness, filtered console.error)
* refactor(native-chat): share desktop/mobile chat logic in src/shared
Extract the parity-mirrored native-chat modules into shared implementations
both surfaces re-export: ask parsing (registry, parseAskFromStatus,
extractPendingAsk, formatAskAnswer), answer stepping offsets/scheduler, diff
detection/parsing, harness-noise filtering, tool fold/pair/split, and tool
summaries. Removes the hand-synced copies and their stale Metro comments.
Divergence reconciliations take the safer side of each: diffs truncate at
120 lines/32KB everywhere (desktop previously unbounded), tool-run summaries
cap at 3 parts with bounded-depth previews, nameless tool calls are skipped,
and basenames split on both separators.
Also: settle and kill every sibling quick-open pass when one reaches
maxResults (main rg/git and relay git; relay rg already did) so a capped
search cannot leave a scan walking a huge tree; fold window-bounding into
the shared merger's applyAppend; localize the web 'Pair a host' snapshot
error.
* fix(native-chat): address CodeRabbit follow-ups on shared modules
- Attachment lease gate re-checks connection/target/tab after the bounded
wait, so a tab/host switch or disconnect mid-wait can't send into a stale
terminal; a moved-away target drops silently like the pre-wait guard and
only an unrecovered lease surfaces the toast. Adds hook tests.
- extractPendingAsk parses transcript tool-calls through the same
registered-parser + canonical-shape fallback as live status, so a custom
question tool that rendered live survives reconnect/replay.
- Direct unit tests for the shared ask parser (FIFO ordering, fallback,
malformed payloads) and tool-summary bounded preview (depth/collection
caps, circular refs, basename/command branches).
* fix(native-chat): treat initialLimit 0 as a valid empty window
Both engine guards used truthiness, so an explicit zero limit skipped the
bounded tail reader and fell back to an unbounded incremental read. Latent
only (every caller clamps positive), hardened for consistency with the
tail reader's non-positive-limit handling.
* fix(mobile): native-chat composer lock UX + send-failure feedback
- Distinguish input-lock reasons: transport 'disconnected' shows Reconnecting…
instead of mislabeling a reconnect as locked-by-another-client
- Guard the composer lock behind a 600ms hold so connState blips / lease
hand-offs don't flicker the placeholder; unlock stays instant
- Surface a rejected send inline above the composer (a bottom toast hides
behind the keyboard); auto-dismisses after 4s
- waiting-session hint invites the first message instead of implying the
agent is still starting
* test(mobile): sync answer-send pacing test to the 500ms advance buffer
Missed in merge 8fe3c391c, which carried main's NATIVE_CHAT_ADVANCE_BUFFER_MS
300->500 (#8568) into the shared stepping module that mobile derives from.
* fix(mobile): restore terminal stream after chat cold start
* fix(native-chat): harden retries, optimistic sends, and file scans
* fix(mobile): deliver AskUserQuestion answers by option number (STA-1860)
Port #8840's fix to the mobile native chat: the Ask card now tracks
per-question option INDICES (+ free text) and the answer-send hook drives
Claude's arrow-navigate selector with buildAskAnswerKeys keystroke groups —
option numbers, next-tab arrows, Enter — paced one selector step apart, instead
of pasting label text that the selector ignores (which silently committed the
default option). Non-Claude agents keep the pasted-label path via the
selection-based formatAskAnswer.
Backcompat: keystrokes are built client-side and written through the EXISTING
terminal.send passthrough with enter:false — the same contract the permission
card already uses — so an older desktop runtime (SSH/relay included) replays
them verbatim; no RPC/contract change in either update order. Free text is
newline-sanitized because terminal.send has no paste framing.
Drops the now-unused formatCompleteAskAnswer from the shared module.
* fix native chat send and runtime races
* fix mobile native chat formatting
* fix(native-chat): mobile empty state matches desktop copy
Mobile showed a single generic line ('Send a message to get started') where
desktop shows a titled two-line empty state naming the agent ('Start a chat with
Claude' + 'Ask Claude to inspect code, explain output, or make a change.'). Align
them from one source of truth so they can't drift again:
- Extract the agent-type label map + formatAgentTypeLabel to
src/shared/agent-type-label.ts (desktop re-exports; mobile imports).
- Add src/shared/native-chat-empty-state.ts with the canonical English copy;
desktop uses it as its i18n fallbacks (localization unchanged — en/es/ja/ko/zh
keys still win), mobile substitutes the agent label and renders it directly
(mobile ships English only).
- Mobile: render title + subtitle for waiting-session AND ready-but-empty (both
are 'start a chat'), error copy for errors; keep the loading spinner.
Live-verified on the iOS sim against a pn-dev of this branch. typecheck node/web
+ mobile tsc clean; 30 mobile + 428 desktop/shared native-chat tests green.
* style: oxfmt the empty-state parity test (line wrap)
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(mobile): edit saved host endpoints
* fix(mobile): reject ambiguous numeric host addresses
* fix(mobile): label edit host inputs
* fix(mobile): make host edit save atomic and remove superseded mutators
Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.
Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (#8789) so this lands
without reverting the mobile terminal restore fix.
Co-authored-by: Orca <help@stably.ai>
* Simplify save-race comment and reword host-removed error message
- Trims the redundant comment explaining the savingRef race guard down
to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
clearer phrasing, updating the matching test expectation.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Re-pairing a desktop that was already paired created a duplicate host card (STA-1840). Pairing now resolves the durable host identity by the desktop's pinned publicKeyB64 and reuses the existing id/name, collapses any already-stored duplicates for that key, clears stale relay overlays on a direct-only re-pair, fails closed on unreadable storage, and closes the host's client on pairing success so a reused id reconnects on the newly-paired endpoint. Mobile only. Full mobile suite (1,719 tests) + typecheck/lint/format pass.
* fix(mobile): harden terminal height refit (follow-up to #8647)
Addresses review feedback on #8647:
- Defer height refits while the keyboard is visible and coalesce every
skipped layout change into one correction after the keyboard closes,
via a pure reducer. Prevents an over-fit that settles with the keyboard
up from surviving (on iOS the edge-to-edge keyboard doesn't change the
frame height on close, so there was no later event to re-trigger it).
- Drive height layout callbacks imperatively (notifyTerminalFrameHeight)
instead of setState, so height-only layout bursts no longer re-render
SessionScreen.
- Cache the updateViewport capability (method_not_found -> unsupported):
old desktops now get one unsupported probe then legacy resubscribe,
instead of one probe per refit. Reconnect resets the cache so an
upgraded desktop is re-detected.
No server schema or subscription-protocol changes; desktop-first stays
compatible.
Tests: 703 mobile terminal/session pass; tsc, oxlint, formatting clean.
* fix(mobile): re-check keyboard when a deferred height refit fires
Close a race in the keyboard-deferral: a height refit deferred at
keyboard-close arms a 150ms debounce timer, and if the keyboard reopens
inside that window the timer still fired and reflowed the PTY mid-keystroke.
The timer callback now re-consults the reducer (new `refit-committed`
event) when the armed refit is height-originated: if the keyboard is
visible again it re-defers (pending) instead of reflowing, and runs on the
next keyboard close. Scoped via a height-originated flag so width/rotation
and the forced reconnect/foreground re-asserts stay unguarded and always run.
Tests: reducer coverage for the reopen-during-debounce re-defer + a wiring
assertion; 705 mobile terminal/session pass; tsc, oxlint clean.
* fix(mobile): re-fit terminal PTY when the frame height settles
A freshly-created agent terminal fits its PTY to rows = floor(frameHeight
/ cellHeight) before the accessory/live-input dock has laid out, so the
frame is briefly too tall and the PTY gets too many rows. Claude/Codex
pin their input box to the bottom of the grid, so those extra bottom rows
— the input box and status lines — render behind the dock and you can't
see what you're typing. Leaving and re-entering the workspace worked
around it by re-measuring against the settled layout.
The refit hook previously re-fit only on width changes and deliberately
ignored height-only changes, so the over-fit was never corrected. Track
the measured frame height and re-fit on its change too, mirroring the
width path. Safe because Expo SDK 55's edge-to-edge IME overlays instead
of resizing, so the frame height doesn't change on keyboard toggle and
the PTY is never reflowed while typing; the refit's row-count guard makes
sub-row jitter a no-op.
* fix(mobile): guard height refit against IME resize; test the decision
Address review on #8647:
- Extract shouldRefitOnFrameHeightChange (pure) and gate the height refit on
keyboard-visible, so an IME that resizes the window (Android adjustResize)
can never reflow the PTY while typing — no longer relies on the edge-to-edge
no-resize assumption alone.
- Add a behavioral test for the decision helper (height transition, same-value
no-op, keyboard-open skip) instead of only source-string assertions.
- Trim the added comments to 1-2 lines per AGENTS.md.
Take-over of #8605 (issue #8591). Ships #8498 (worktree resync + pull-to-refresh + cache write-through) and #8129 (idempotent notification replay on reconnect). Fixes the original PR's field mismatch (seq vs notificationSeq) and adds the missing notifications.getMissedSince mobile RPC allowlist entry. #6784 and #4500 held back to avoid conflicting with the relay work (#8536). Co-authored-by: Brandon Bennett (@branben).
* feat(mobile): show usage reset countdown on accounts screen
Surface the rate-limit reset time ("5h resets in 3h 54m · 7d resets in
6d 7h") under the usage bars on the mobile accounts screen, matching the
desktop status-bar tooltip copy. The resetsAt timestamps already arrive
in the accounts.subscribe snapshot; this only adds the presentation.
Claude-Session: https://claude.ai/code/session_01FvjvCsc9QoyQALqvxkvDqQ
* docs(mobile): JSDoc for new usage reset selectors
Claude-Session: https://claude.ai/code/session_01FvjvCsc9QoyQALqvxkvDqQ
* refactor(mobile): per-bar reset countdown instead of combined line
Drop the redundant "5h/7d" prefixes — each countdown now renders under
its own bar ("Resets in 3h 54m"), matching the desktop tooltip copy
exactly.
Claude-Session: https://claude.ai/code/session_01FvjvCsc9QoyQALqvxkvDqQ
* Extract shared reset-countdown formatter for desktop and mobile
- Move duration/countdown formatting out of tooltip.tsx into
src/shared/rate-limit-reset-format.ts so mobile's account-usage-state
can reuse it instead of a duplicated copy (with tests).
- Re-export formatResetCountdown from tooltip.tsx to avoid touching
existing import paths.
- Resend the pairing deep link once more in start-emulator.mjs since
the first can arrive before the Expo app's JS router is ready.
---------
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* feat(mobile): start a workspace from a branch, issue/PR, or Linear ticket
Unify mobile workspace creation with desktop. The "+" Create Workspace
modal now has a primary "Start from" field that opens a tabbed search
drawer (Branch · GitHub · GitLab · Linear), letting a user start a
workspace from an existing/new git branch, a GitHub issue/PR, a GitLab
issue/MR, or a Linear ticket — in addition to the default blank workspace.
No new backend is required: the search RPCs (github.listWorkItems,
gitlab.listWorkItems, linear.searchIssues/listIssues, repo.searchRefs) and
the worktree.create linked-item params were already used by the mobile
Tasks screen. This surfaces them in the create flow, reusing the existing
pure modules (buildTaskWorkspaceCreateParams, shouldResolveHostedReviewStartPoint,
filterAvailableTaskProviders).
Details:
- New pure modules: workspace-source-selection, use-workspace-source-search,
source-workspace-create, worktree-create-retry, blank-workspace-create
(the blank/retry path extracted from the modal for reuse + line budget).
- New UI: WorkspaceSourcePickerDrawer (+ row) and SetupHookTrustDrawer
(extracted from the modal).
- Older paired desktops (missing the mobile.tasks.v1 capability) degrade to
Branch + Blank only; GitLab/Linear tabs appear only when available.
- GitHub/GitLab sources pin their repo; switching repos resets the source.
PR/MR sources resolve their base branch at create time; SSH repos gate
search until connected (Linear search is repo/SSH-independent).
* fix(mobile): hydrate settings/trust before availability probes settle
Review fixes for #7985: setTrustedOrcaHooks/setRuntimeSettings no longer
wait on status.get/preflight.check/linear.status (a first-open
preflight.check can take seconds, widening the spurious setup-trust
re-prompt window). Also adds param-parity tests for createBlankWorkspace
and a GitLab MR base-resolve test.
* feat(mobile): match desktop's Smart source picker exactly
Rework the mobile create-workspace source picker to be a faithful port of
desktop's Smart picker instead of the earlier divergent "Start from" drawer.
The mobile field is now the workspace-name input AND the source search, with the
exact desktop tabs — Smart · GitHub · Linear · GitLab · Branch · Name. "Smart"
fans out across GitHub + GitLab + Linear + branches, prepends a "Use '<name>'"
row, and resolves pasted URLs / #123 / STA-42 to exact items (with a cross-repo
switch prompt). Selecting a source shows a pill and moves the editable name into
Advanced. The invented "Blank workspace" concept is removed — the neutral state
is just a typed/empty name (blank submit still yields a creature name).
DRY: the pure desktop logic (smart-workspace-source-results, -command-value,
github-links, gitlab-links, work-item-link-query-bounds, github-work-item-identity)
moves to src/shared/new-workspace/ with re-export shims at the old renderer paths,
so both renderer and mobile share one implementation. composer-branch-selection
and workspace-name were already shared and are reused directly.
Two read-only lookup RPCs are allowlisted for mobile so pasted GitLab URLs and
cross-repo GitHub URLs resolve to exact items (github.workItemByOwnerRepo,
gitlab.workItemByPath).
New mobile modules are split for max-lines: use-mobile-composer-source (selection
state + desktop-parity handlers, PR/MR base resolve), use-smart-workspace-source
+ smart-source-fan-out/-search-requests/-paste-intent (RPC orchestration),
composer-linked-work-item / work-item-lookup-text / mobile-smart-source-modes
(pure logic), and SmartWorkspaceSourceField/Drawer/Row + SmartWorkspaceAdvancedFields.
Replaces WorkspaceSourcePickerDrawer/Row, workspace-source-selection,
use-workspace-source-search, and MobileWorkspaceNameInput.
Reviewed by three adversarial agents + re-reviewed after fixes: GitHub search now
returns issues AND PRs (not issues-only), Linear defaults to assigned, create-branch
preserves slashy names, cross-repo PR base resolves against the item's own repo,
displayName is suppressed for user-edited names, and the smart-mode GitHub fan-out
respects availability. tsc/oxlint/max-lines-ratchet clean; 1328 mobile tests pass.
* fix(mobile): keep smart source drawer fully visible
* refactor: share workspace creation behavior across clients
* fix: address workspace creation review findings
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Prod-release-scan P1+P2 from v1.4.137-rc.1 mobile host-remove.
P1: Host remove could orphan a SecureStore pairing token with no Settings
retry when BOTH the durable pending-queue write failed AND the native delete
rejected/stalled. recordCleanupIntent swallowed the queue-write failure, so
the only recovery handle for the failed keychain delete was silently lost.
Now scheduleHostCredentialCleanup keeps a session-scoped in-memory fallback
handle when the durable write fails, so Settings still surfaces the pending
cleanup and offers a retry; confirmNativeCleanup clears the fallback if the
native delete later lands. removeHost stays non-blocking on the keychain
(freeze fix intact).
P2 (updateLastConnected): the fire-and-forget `void updateLastConnected(...)`
call site threw on unreadable storage, producing an unhandled rejection.
updateLastConnected now swallows unreadable-storage failures internally since
it's a best-effort timestamp.
P2 (soft-read): loadPendingHostCredentialCleanup now reports storageUnreadable
instead of pretending the queue is empty, and Settings surfaces a
"couldn't check cleanup status — retry to be safe" affordance rather than
hiding the section when the durable queue can't be read.
Tests: dual-fault fallback + no-clobber, storageUnreadable reporting,
fallback self-heal on late delete success, and updateLastConnected non-throw.
* Add host removal lifecycle safeguards and credential cleanup retry UI
- Sequence host removal so metadata commits before the client socket
closes, avoiding a stranded host when storage fails, and add a
cancellable open-registry to stop races between host-client opens
and closes/unmounts.
- Queue AsyncStorage host-list mutations (rename/removal/lastConnected)
to prevent concurrent writers from clobbering each other's changes.
- Track keychain credential cleanups that fail or time out as durable
pending intents, surfaced with a manual retry affordance in Settings.
* Fix host removal error handling to reopen confirm dialog and alert user
Previously a failed host removal silently closed the confirm dialog,
leaving the host listed with no feedback and no easy retry path. Now
the confirm modal reopens and an alert surfaces the failure so the
user can retry.
* test: reconcile settings tests with universal right-click paste and promoted worktree symlinks
Merging main surfaced two semantic conflicts against this branch's tests:
- #8322 exposed right-click paste on every platform, so the settings
navigation metadata now indexes it even when only the terminal host is
Windows. Update the stale assertion accordingly.
- #8318 promoted APFS worktree shared paths by dropping the
experimentalWorktreeSymlinks gate, so WorktreeSymlinksSection now always
mounts inside RepositoryPane and reads window.api.fs. Stub a minimal
renderer fs bridge in the pane test, matching the AppearancePane pattern.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Fix stale terminal panes after backgrounding by retrying foreground reco
- Foreground recovery was skipping the replay when resume landed mid-reconnect
(socket typically dies after 60-80s backgrounded), leaving WKWebView panes
blank until a manual tab switch. Recovery now returns a 'deferred' outcome
and the session screen retries it once connState flips back to connected.
- Fix a related race where a newly created tab's web-ready subscribe could be
skipped if a lagging session-tab snapshot reset activeHandleRef before the
subscribe fired; track the intended active handle separately.
* Fix stale pending terminal handle outliving a failed create
Clear pendingActiveTerminalHandleRef when terminal creation returns
no handle, since web-ready subscribe logic gates on this ref being
active and would otherwise see a stale value.
* Fix mobile terminal query reply authority
* fix(terminal): harden mobile query reply handoffs
* fix(terminal): exclude passive mobile query responders
* fix(terminal): gate mobile query replies on host capability
Older hosts strip terminal.send's inputKind (zod drops unknown keys), so a
forwarded xterm reply would land as ordinary floor-taking shell input. Hosts
now advertise terminal.query-reply-input.v1 via status.get and mobile drops
replies unless the host advertises it (pre-fix behavior). Also documents the
bounded desktop-to-mobile handoff double-reply residual.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): advance snapshot seq across recovery snapshots
The pending-overflow recovery loop trims buffered output against
recovery.seq while query replay and boundary strips kept using the
initial snapshot seq. Unreachable under today's control flow (no await
separates the initial-overflow consume from the loop), but the stale
seq would silently drop covered query replies if that ordering ever
changes. Track the seq that actually covered the buffered chunks.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): recover terminal state after iOS resume
* Refactor terminal record merge to extract snapshot-reconciliation helper
Split the inline merge logic in mergeTerminalRecordsByCurrentOrder into a
named mergeTerminalSnapshotWithKnownRecord function for clarity, preserving
the existing behavior of keeping the last known theme when a snapshot omits it.
* Redesign mobile search field as a shared, raised component
- Extract MobileSearchField from duplicated Search icon + TextInput + clear
button markup in worktree list and tasks screens into a reusable component
- Give the field a raised bgRaised shell with focus/disabled states so it
reads as a tappable control instead of blending into panel chrome
- Fix delayed autoFocus via InteractionManager + timeout so the keyboard
reliably appears after the search bar opens
- Preserve per-screen clear behavior (preset/query fallback for GitHub,
project-view filter) via configurable showClear/onClear props
* Simplify GitHub project search state checks and fix stuck clear button
- Extract `isGithubProjectSearch` to dedupe repeated `provider === 'github' && githubMode === 'project'` checks
- Fix showClear so an explicit empty applied override doesn't leave the clear button visible forever