Commit Graph
239 Commits
Author SHA1 Message Date
Brennan BensonandOrca 47389990a5 fix(mobile): surface disconnected source-control states + keep the mobile WS fallback port stable (STA-1511) (#7855)
Co-authored-by: Orca <help@stably.ai>
2026-07-08 18:28:59 -07:00
60037d60ab feat(mobile): add explicit keyboard dismiss control to terminal command dock (#5917)
* feat(mobile): add explicit keyboard dismiss control to terminal command dock

Add a fixed Hide control at the left of the terminal command dock accessory
bar whenever the software keyboard is open (keyboardHeight > 0). Tapping it
clears any pending live-input focus timer, blurs the live and buffered command
inputs, and dismisses the keyboard without sending bytes, switching input mode,
or clearing typed text.

The dismiss behavior lives in a dedicated, unit-tested terminal-keyboard-dismiss
module rather than the customizable accessory-key path, so the escape hatch
cannot be hidden by user shortcut customization. Available on every platform
where the IME covers the app (iOS and Android).

* review: harden keyboard dismiss control per adversarial review

- document the load-bearing clear-before-blur order in dismissTerminalKeyboard
- cover the both-handles-missing case in unit tests (5/5)
- move the #5106 first-tap comment onto the accessory ScrollView and add a
  why-comment for the fixed Hide control
- add accessibilityRole=button and hitSlop to the Hide control for a larger,
  semantically-correct touch target

* fix(mobile): harden hide button visibility and scroll layout

* refactor(mobile): use stacked keyboard+chevron glyph for dismiss control

Replace the icon+'Hide' text with the iOS-native dismiss glyph (keyboard
with a chevron-down beneath it). Narrower in the accessory row, removes the
icon/word redundancy, and reads as distinct from the >> input-mode toggle.
Accessibility label/hint/role unchanged.

* fix(mobile): align keyboard dismiss accessory height

* test(mobile): align vitest transform with Vite 8

---------

Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-07-08 17:18:16 -07:00
Neil 54c4959830 perf(mobile): cap the scheduled-notification map + tap-dedup set (#7646)
scheduledNotificationsByHostAndNotificationId (mobile-notifications.ts)
retained one entry per scheduled desktop notification. The key embeds
notificationId, which carries a per-completion timestamp
(buildAgentNotificationId), so every agent-task-complete inserts a new,
never-reused key. Entries are removed only when the desktop sends a
matching dismiss — which a remote mobile user (not sitting at the
desktop) frequently never receives — so the module-level map grew for
the app's whole lifetime. Small per entry, but genuinely unbounded.

Fix: bound the map to the 256 most-recent SETTLED entries (never evict
one mid-schedule). A settled entry only retains a small identifier used
for later programmatic dismissal, which is unnecessary for long-past
completions, so eviction has no user-visible effect.

Also FIFO-cap RootLayout's handledNotificationIdsRef tap-dedup Set
(RootLayout never unmounts, so it otherwise grew one id per tapped
notification forever).

Test (red->green): with the cap at 1, scheduling a second notification
evicts the first, so a later dismiss for the evicted id is a no-op while
the retained one still dismisses; without the cap the old entry survives.
2026-07-06 21:28:09 -07:00
JinjingandOrca 430d4b9482 Show all worktrees across all hosts on mobile (#7500)
* Show all worktrees across all hosts on mobile

Avoid honoring desktop's host-filtering settings since mobile lacks the
UI to manage or unhide them. This prevents worktrees from being silently
hidden under certain host scopes.

Additionally, this removes worktree filtering based on repo metadata, which
previously caused worktrees to vanish when same-named repos on different
hosts collapsed to a single ID.

* fix(daemon): preserve promisify.custom type through wrapChildProcessApi

The windows-hidden-console-children test (from #7499, admin-merged with a
failing verify) failed tsgo: promisify(wrapped) resolved to its zero-arg
overload because the wrapper erased its argument to a bare variadic function
and the fake never statically carried promisify.custom. Preserve the wrapped
type via a generic overload (accurate: the wrapper copies the call signature
and symbols verbatim) and build the fake as a real CustomPromisify, so
promisify routes through the custom overload as it does in production.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-05 23:10:48 -07:00
Jinjing bdb6d144c3 Pin the new tab button outside the tab scroll view (#7292)
Move the "New tab" button out of ScrollView so that it remains visible
and reachable regardless of how far the tabs have been scrolled.
2026-07-03 19:38:24 -07:00
e8c2b79a93 Fix mobile live input keyboard and Korean IME (#7273)
* feat: 모바일 터미널 한글 미러 스텝 순수 모델 추가

* feat: 미러 델타 순서 보장용 send 체인 추가

* fix: 모바일 터미널 한글 입력을 미러 모델로 전환

* fix: 탭 상태 지연 중 한글 조합 상태 소실 방지

* fix: 미러 가드와 send 체인 리뷰 지적사항 반영

탭 상태 지연으로 활성 탭 타입이 일시적으로 null이 될 때 runMirrorStep의 stale-handle 가드가 조합 중 음절을 버리지 않도록 pending-clear 효과와 동일한 null 허용 패턴 적용. 테스트 하네스가 ref와 prop을 동일 소스에서 파생하도록 결합해 실제 경로의 lag 프레임을 검증. queueTerminalLiveMirrorSend의 previousSend await를 catch로 보호.

* refactor(mobile): drop dead queueTerminalLivePendingFlush orphaned by the mirror model

The mirror model migrated all live-input sends to queueTerminalLiveMirrorSend,
leaving queueTerminalLivePendingFlush referenced only by its own tests. Remove
the dead function and its three tests.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): expose live terminal keyboard target

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): refocus live keyboard after dismissal

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: realitsyourman <wongil@demodev.io>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 16:47:59 -07:00
Siddiqui Qamar f0278c116e fix(mobile): preserve terminal input mode on reentry (#7129)
Fixes #6972.\n\nPreserves mobile terminal buffered/live input mode across Android terminal re-entry and session refreshes. Includes follow-up hardening for pre-hydration preference edits and failed storage reads.
2026-07-02 23:22:30 -07:00
Brennan BensonandOrca c06507ee30 Open terminal file links on mobile (#7134)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 18:17:47 -07:00
WONGILandJinjing 4f0a141951 Fix mobile terminal Korean IME composition on Android (#7011)
* Fix Korean IME composition by deferring live terminal preedit

The mobile terminal capture field previously sent and cleared every TextInput change, which can break Hangul composition on Android keyboards. Introduce a small commit model and extracted live-input hook so composed text is flushed deliberately while ASCII remains immediate.

Constraint: React Native TextInput has no portable composition event for this path; the fix uses a bounded commit delay for likely IME text.

Rejected: Native-module IME integration | unnecessary for the confirmed JS dispatch/clear failure and higher maintenance risk.

Confidence: high

Scope-risk: moderate

Directive: Keep terminal.send payload shape and buffered command input unchanged; do not claim physical Samsung Keyboard QA without device evidence.

Tested: cd mobile && pnpm exec vitest run src/terminal/terminal-live-text-commit.test.ts src/terminal/terminal-live-input.test.ts src/terminal/terminal-text-input-normalization.test.ts src/terminal/terminal-keyboard-type.test.ts --reporter=verbose

Tested: cd mobile && pnpm exec tsc --noEmit

Tested: cd mobile && pnpm exec oxlint src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx

Not-tested: Physical Galaxy Fold7/Samsung Keyboard and Android emulator/Gboard QA were unavailable; device probes recorded no attached Android device.

* Preserve pending Korean IME text before mobile accessory controls

Accessory keys share the same pending live-input commit gate as TextInput keypress and submit paths, so control bytes cannot race ahead of composed Hangul.

Constraint: React Native mobile input does not expose portable composition events for Samsung/Gboard IME paths.

Rejected: Let accessory buttons keep sending directly | Direct sends can drop pending Hangul before Tab/Esc/Enter/Backspace reaches the PTY.

Confidence: high

Scope-risk: narrow

Directive: Keep all terminal control-byte paths behind the pending live-input flush/local-edit decision before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --cached --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA is still external-device only.

* Prevent stale IME timer flushes after mobile terminal teardown

Pending live-input timers now clear on hook unmount, and accessory Delete documents why it stays local without trimming pending IME text.

Constraint: React Native TextInput lacks a portable composition lifecycle, so pending IME text is guarded by a bounded timer that must not survive screen teardown.

Rejected: Use clearPendingLiveInputCommit during unmount | it would also touch React state/native props during teardown when only timer/ref cleanup is required.

Confidence: high

Scope-risk: narrow

Directive: Any delayed terminal input commit must have an owner-lifecycle cleanup path before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec vitest run src/terminal/terminal-live-text-commit.test.ts --reporter=verbose; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/use-terminal-live-input-commit.ts; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Use semantic accessory edits for mobile IME commits

Accessory Backspace/Delete now carry semantic local-edit intent from built-in keys instead of inferring intent from raw bytes, and submit handling is reconnected to the pure submit-sequence model.

Constraint: Custom terminal accessory keys may produce the same bytes as built-ins but should still flush pending IME text before sending rather than being silently treated as hidden-input edits.

Rejected: Classify local accessory edits by raw bytes | That couples future custom controls to current built-in byte encodings.

Confidence: high

Scope-risk: narrow

Directive: Keep semantic input intent separate from terminal byte payloads when pending IME text is present.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Respect IME flush failures before control input

Propagate terminal.send success from pending Korean IME text before sending Enter, Tab, or accessory bytes, while keeping custom no-pending accessory bytes on the original direct path.

Constraint: PR #7011 review required follow-up control bytes only after the pending composed text send actually succeeds.

Rejected: Treating send invocation as success | It can still reject or no-op when RPC state changed.

Confidence: high

Scope-risk: narrow

Directive: Keep pending IME flush paths async-success-aware before adding new terminal control inputs.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; targeted no-excuse clean for mobile/src/terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Serialize mobile IME flushes before live controls

Treat terminal.send as successful only when the RPC response is ok and the runtime send result is accepted, then route all live-input control sends through a shared in-flight pending-flush barrier.

Constraint: PR #7011 review found that resolved RPC promises and per-call sequencing were not enough to prove pending Hangul text reached the PTY before follow-up controls.

Rejected: Only awaiting each flush-then-send call | Repeatable accessory keys and no-pending sends can arrive while the first flush is still in flight.

Confidence: high

Scope-risk: moderate

Directive: Keep future mobile terminal control paths behind the pending-flush barrier whenever IME text may be in flight.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Queue current IME snapshots behind active flushes

Drain the pending snapshot captured by a control action after any already-active terminal send, and make accessory commit handling explicit so raw fallback is not encoded as an inverted boolean.

Constraint: Architecture review found the previous single-slot barrier could wait for an older flush while skipping newly pending Hangul text.

Rejected: Reusing the prior in-flight promise as the current flush result | It proves only an older snapshot, not the current pending buffer.

Confidence: high

Scope-risk: narrow

Directive: New mobile terminal control paths must distinguish allow-raw, handled, and suppress-raw outcomes explicitly.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Preserve accessory raw-send terminal targets

Capture the terminal handle at accessory keypress time and suppress raw fallback if the active live terminal changes while waiting for pending IME flushes.

Constraint: Independent review found raw accessory bytes could retarget to a different terminal after an async IME flush barrier.

Rejected: Re-reading activeHandleRef as the send target after await | It can point at a different terminal than the keypress belonged to.

Confidence: high

Scope-risk: narrow

Directive: Raw accessory fallback must use the keypress-time target and revalidate it after any await.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Document accessory flush barrier intent

Make the non-obvious raw accessory wait/suppress behavior explicit so future changes preserve IME-before-control ordering.

Constraint: CodeRabbit requested a why-comment for the send-now accessory branch.

Rejected: Leaving the barrier semantics implicit | The branch can otherwise look like unnecessary async defensive code.

Confidence: high

Scope-risk: narrow

Directive: Keep comments focused on why raw accessory bytes wait behind IME flushes.

Tested: targeted terminal vitest suite; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt check for changed file.

Not-tested: Physical Galaxy Fold7 Samsung keyboard.

* Preserve buffered accessory raw sends

Keep the stale-handle guard focused on the captured active terminal instead of live-input opt-in state, so buffered mode keeps existing accessory key behavior while async live-input waits still cannot retarget to another terminal.

Constraint: Buffered command input behavior must remain unchanged while fixing mobile Korean IME live input ordering.
Rejected: Requiring live-input enabled handles for raw accessory fallback | suppresses valid buffered-mode accessory sends.
Confidence: high
Scope-risk: narrow
Directive: Do not use live-input opt-in state as terminal liveness for raw accessory sends; validate captured target, active terminal tab, connection, and client instead.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep Hangul IME text pending until explicit flush

Avoid timer-driven PTY writes for Hangul candidates so paused Korean composition cannot leak intermediate jamo, while preserving the bounded settle timer for non-Hangul IME text. Also keep disabled live-input accessory fallback behind any existing pending flush barrier.

Constraint: React Native TextInput does not expose a portable composition lifecycle on this mobile surface.
Rejected: Fixed 150ms auto-flush for Hangul | can emit ㅎ or 하 if the user pauses mid-composition.
Confidence: high
Scope-risk: narrow
Directive: Treat Hangul candidates as pending until submit/control/accessory flush; do not reintroduce idle timer commits for Hangul without device-level composition evidence.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Gate dictation toast on accepted live send

Honor the async live-input sender contract so the mobile UI reports dictation insertion only after terminal.send is accepted.

Constraint: sendLiveTerminalInput now returns false for stale, disconnected, oversized, or rejected terminal sends.
Rejected: Toasting immediately after dispatch | reports success for sends that never reached the PTY.
Confidence: high
Scope-risk: narrow
Directive: Treat live-input UI success as terminal.send acceptance, not request dispatch.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check app/h/[hostId]/session/[worktreeId].tsx.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep accessory edits on Hangul pending path

Make accessory local edits reuse the Hangul-aware defer policy so built-in Backspace/Delete cannot reintroduce timer-driven Hangul PTY writes.

Constraint: Hangul IME candidates must remain pending until explicit submit/control/accessory flush.
Rejected: Reusing the non-Hangul 150ms settle timer for accessory local edits | can leak pending Hangul after Backspace/Delete.
Confidence: high
Scope-risk: narrow
Directive: Any future pending-text reschedule must use getTerminalLiveDeferredTextDelayMs instead of a hardcoded timer.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Prove Hangul live-input hook ordering

Add a direct hook-level regression so Android Korean IME fixes are covered at the orchestration boundary, not only by lower-level helpers.

Constraint: React Native mobile TextInput lacks portable composition lifecycle events in this path.

Rejected: Relying only on helper tests | misses hook-level pending flush and submit ordering.

Confidence: high

Scope-risk: narrow

Directive: Keep Hangul candidates pending until an explicit terminal action flushes them.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; no-excuse on terminal modules

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep accessory raw-send tests precise

Remove a duplicate raw-target assertion whose title implied disabled live-input behavior that is covered at the accessory commit boundary instead.

Constraint: Anti-slop cleanup must preserve existing Hangul/accessory behavior and stay within changed terminal tests.

Rejected: Keeping the duplicate disabled-input wording | it tests the same active-terminal predicate as the preceding case.

Confidence: high

Scope-risk: narrow

Directive: Test disabled live-input buffering in the accessory commit layer, not in the raw-target predicate helper.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Explain stale mobile terminal send gates

Document why async IME flush paths re-check terminal/client refs before sending raw bytes or reporting live-send success.

Constraint: CodeRabbit review requested short why comments for non-obvious stale-send safety gates.

Rejected: Leaving the gates undocumented | future edits could remove the stale-target suppression contract.

Confidence: high

Scope-risk: narrow

Directive: Keep async terminal sends guarded by current client, active handle, tab type, and connection state.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Run mobile IME hook tests through effects

Move the Hangul live-input hook regression from server rendering to react-test-renderer so effect cleanup and unmount timer cancellation are exercised.

Constraint: @testing-library/react-native imports React Native's Flow entry under this Vitest setup, so the narrow effect-running renderer is the compatible test surface.

Rejected: Keeping renderToString | it never runs useEffect cleanup and missed the pending timer cleanup path.

Rejected: Adding @testing-library/react-native directly | it failed before tests with React Native Flow syntax under the current Vitest transform.

Confidence: high

Scope-risk: narrow

Directive: Hook-level IME tests must use a renderer that runs effects when asserting pending flush cleanup.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep hook lifecycle tests quiet

Suppress only the react-test-renderer deprecation warning around the effect-running hook harness so real console errors still surface.

Constraint: CodeRabbit flagged React 19 renderer warning noise; @testing-library/react-native remains incompatible with the current Vitest/RN Flow transform path.

Rejected: Global console silencing | it would hide unrelated test failures.

Confidence: high

Scope-risk: narrow

Directive: Keep the renderer warning suppression scoped to this hook harness and pass all other console errors through.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* fix: flush pending mobile IME input before external sends

* fix: guard terminal command finished event dispatch

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-02 02:31:19 -07:00
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00
Brennan BensonandOrca a6f12f6120 Show the git primary action on mobile Source Control (#6659)
Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:30:46 -07:00
Jinjing 7caa582841 Show coding agent icons on mobile terminal tabs (#6792)
* Show coding agent icons on mobile terminal tabs

Move agent title decoration and terminal title parsing utilities
from the desktop renderer to shared code for reuse on mobile.

* Extract agent title stripping and terminal agent resolution to shared
* Implement mobile agent identity resolution and title-cleaning helpers
* Render agent icons on mobile terminal tabs when an agent is active
* Strip leading status glyphs from tab titles when showing an icon

* Suppress PTY resize on resume for mobile-driven terminals

Avoid reasserting the PTY size on resume if desktop resizing is
suppressed. This prevents overriding the intentional drift from
desktop dimensions for parked or mobile-driven terminals.
2026-06-29 16:06:27 -07:00
JinjingandOrca a8e6a91bcf fix: address review findings (#6710)
Co-authored-by: Orca <help@stably.ai>
2026-06-28 23:57:47 -07:00
Jinjing 31d35b14fc Fix mobile terminal close keyboard dismissal (#6681)
* fix: dismiss keyboard before tab actions

* fix: reset mobile tab sheet keyboard listener
2026-06-28 18:46:31 -07:00
Jinjing 93867ba05c fix: dismiss keyboard before tab actions (#6671) 2026-06-28 17:32:22 -07:00
Jinjing c50391df84 Support dictation and image attachments in mobile live input mode (#6603)
* Support dictation and image attachments in live terminal input mode

- Unify image attachment and voice dictation actions across both live
  and buffered terminal input views using a shared action bar.
- Route completed dictations directly to the active PTY (matching
  live keystroke semantics) when live mode is active, or append
  them to the input field in buffered mode.
- Add live terminal status headers to indicate mic activity and
  image upload progress.
- Include unit tests for the dictation routing logic.

* rm unused file
2026-06-28 01:20:00 -07:00
Jinjing 76331ff597 Create pr not working on mobile (#6523)
* Implement automated git preparation workflow for mobile PR creation

Introduce a structured hosted review intent preparation workflow to handle
staging, AI commit message generation, committing, and pushing changes
automatically before displaying the pull request composer on mobile.

- Map creation block reasons to descriptive user-facing validation errors
  (e.g., dirty working tree, default branch, detached head) to match desktop.
- Decouple hosted-review business logic into a dedicated service helper.
- Update source control runner hooks to handle the new preparation flow.

* Refactor mobile PR creation to run intent and open URL directly

Remove MobilePrComposeSheet and the local compose form, moving instead
to a direct PR creation workflow that matches the desktop experience.

- Add runMobileHostedReviewCreateIntent to handle the full prepare,
  push, and create sequence.
- Replace useMobileOpenPrSheetRunner with useMobileCreatePrRunner to
  trigger the creation workflow and directly open the created PR URL.
- Simplify state management by removing showPrSheet, prPrefill, and
  associated local compose sheets.

* Propagate git status and commit state on PR creation failure

Update `MobileHostedReviewCreateIntentOutcome` and the local change
commit helper to include optional `committed` and `status` fields in
their failure results.

This ensures that if PR preparation fails, callers still receive the
current repository status and know if their local changes have already
been committed.

* Add tests for mobile hosted review creation flow

Introduce unit tests for runMobileHostedReviewCreateIntent to verify
different scenarios of creating a hosted review on mobile, including:

- Successful flow including staging, committing, pushing, and creating
- Eligibility block handling (e.g., authentication requirements)
- Error reporting when creation fails after an automatic commit

* Block mobile PR creation on unresolved conflicts and refresh status

Prevent creating a hosted review on mobile when there are unresolved
merge conflicts. Also, return the latest git status on failures and
reload it in the UI to keep the source control screen in sync.

* Prefer fetched PR head SHA over cached status SHA for PR checks

On mobile, a create command can commit before opening the review,
meaning the fetched PR's head SHA is fresher than the route's cached
status SHA. Prioritizing the fetched PR head SHA ensures we fetch checks
for the most up-to-date commit.

* Fix mobile PR creation errors and validate branch presence

- Reject branch matches when the status branch is null or missing to
  prevent PR creation when the branch is lost.
- Display actual PR creation errors in the sidebar instead of silently
  ignoring them on failure.
- Trim leading and trailing whitespace from the base branch reference
  before persisting the worktree link.
2026-06-27 18:34:58 -07:00
Jinjing c0f4bdd020 Default first-seen mobile terminal tabs to direct input mode (#6509)
Automatically enable direct (live) terminal input for new terminal
handles on mobile, while allowing users to opt out back to buffered
input.

- Track defaulted handles to ensure list refreshes preserve manual
  buffered-mode choices.
- Prune tracked handles from live input sets when terminals are closed.
- Add unit tests for defaulting and pruning logic.
- Include a design doc detailing goals and implementation notes.
2026-06-27 12:12:46 -07:00
Brennan BensonandOrca 5fd9f0c62c Prevent mobile worktree selection from focusing desktop (#6461)
Co-authored-by: Orca <help@stably.ai>
2026-06-26 19:34:33 -07:00
Brennan BensonandOrca 55c0c74951 Fix mobile file previews (#6315)
Co-authored-by: Orca <help@stably.ai>
2026-06-26 12:10:58 -07:00
Brennan BensonandOrca c0fd54c341 Show Agent activity for mobile smart sort (#6347)
Co-authored-by: Orca <help@stably.ai>
2026-06-25 19:00:35 -07:00
Jinwoo HongandOrca 5d8617bc71 Fix mobile workspace parity (#6207)
Co-authored-by: Orca <help@stably.ai>
2026-06-23 17:07:28 -07:00
Jinjing 9bd439ccb4 Remove textContentType none from iOS terminal input (#6176)
- iOS `textContentType` overrides `autoComplete` and restricts keyboard
  layouts, preventing switching to non-Latin input methods (IMEs).
- Use `autoComplete="off"` instead to ensure the keyboard remains
  default and IME switching stays available.
- Update tests to assert these changes.
2026-06-23 10:34:32 -07:00
JinHyeok Jeong e253d46e72 Harden mobile session tab sync
Harden mobile session-tab snapshot reconciliation and terminal creation. Rejects stale mobile snapshots, tombstones locally closed tabs until the publisher catches up, rolls back half-created terminal tabs when their surface never appears, and scopes terminal-create idempotency by worktree.
2026-06-22 13:31:10 -07:00
423c2befe1 fix(mobile): keep iOS terminal inputs on the default keyboard (#5876)
* fix(mobile): keep iOS terminal inputs on the default keyboard

iOS treated keyboardType="ascii-capable" as an ASCII-only input surface,
which hides non-Latin keyboards (Zhuyin, Japanese, Korean) from the iOS
keyboard switcher, so terminal users could not switch away from English.

Use the system default keyboard for terminal inputs on every platform so
IMEs stay selectable, while keeping autoCorrect/spellCheck off so commands,
flags, and paths are not rewritten by the OS keyboard. The keyboard-type
helpers now return a single 'default' value; their dead per-platform
branching was removed.

Fixes #5525.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): tighten terminal keyboard comment

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-22 13:24:43 -07:00
Jinjing a459100d87 Introduce MobileWorkspaceNameInput with delayed auto-focus (#5968)
- Delays input focus by 220ms to ensure animating bottom drawers settle before the soft keyboard is requested, improving focus reliability on mobile.
- Replaces standard TextInputs with this component in tasks workspace creation and the NewWorktreeModal.
2026-06-21 23:51:00 -07:00
Jinwoo HongandOrca 32e6ab7655 Fix mobile workspace list parity (#6001)
Co-authored-by: Orca <help@stably.ai>
2026-06-21 15:11:55 -07:00
Jinjing 8a4f10b35c Refresh live worktree display name in mobile session (#5934)
Query the `worktree.show` RPC endpoint on screen focus and poll every
3 seconds to retrieve up-to-date worktree metadata. This ensures that
task-generated names and subsequent displayName updates are reflected,
rather than relying solely on the initial route parameter entry hint.
2026-06-20 16:40:34 -07:00
Jinjing 220dfe09f4 Recover iOS terminal on foreground and enforce tap URL length limit (#5903)
- Replay terminal scrollback when transitioning back to the foreground on iOS to fix blank xterm backing stores in WKWebView.
- Mark inactive mounted terminal buffers stale so they repaint on their next activation.
- Enforce a 2048-character maximum URL length for mobile terminal link tapping to align with desktop safety bounds.
2026-06-20 03:47:19 -07:00
Jinwoo HongandOrca 4f4d520149 fix: support Android terminal IME and scroll gestures (#5844)
Co-authored-by: Orca <help@stably.ai>
2026-06-19 15:27:53 -07:00
Jinwoo Hong f92f12eb79 Fix mobile pending terminal tabs and prepare 0.0.15
Fix mobile pending terminal tab hydration and bump Orca Mobile to 0.0.15.
2026-06-19 14:12:09 -07:00
Jinjing bc2f5145da Allow deleting downloaded speech models on desktop and mobile (#5794)
* Allow deleting downloaded speech models on desktop and mobile

Implement safe deletion of local speech models across desktop and
mobile platforms.

- Add a shared helper to coordinate STT service teardown, file deletion,
  and voice settings cleanup.
- Prevent deleting speech models that are currently in use or starting.
- Update desktop and mobile settings UIs to expose the delete action.

* Track multiple pending model deletions and update action disabling

- Replace the single pending delete ID string state with a Set of IDs on desktop settings to track multiple concurrently deleting models.
- Switch the desktop delete action to use the standard themed Button component.
- Disable all row interactions on the mobile voice model list when any model action is busy.
- Add unit test coverage verifying that individual row delete button disabled states behave correctly.
2026-06-19 12:31:11 -07:00
f534cd5bc3 fix(mobile): downscale oversized clipboard images so paste no longer fails (#5601)
* fix(mobile): downscale oversized clipboard images so paste no longer fails

Pasting a large clipboard image (high-res screenshot or photo) failed with
'Image too large to paste' because the re-encoded PNG exceeded the 24 MiB
base64 upload cap, with nothing shrinking it first.

Now an oversized image is downscaled to fit the budget before upload:
- computeMobileClipboardImageDownscale() picks target dimensions by area
  (~sqrt(budget/actual)), with a bounded retry loop since PNG size is nonlinear.
- prepareMobileClipboardImageBase64() drives the loop with an injected resizer,
  so the byte/dimension logic is unit-tested without native modules.
- The resizer stages the image to a temp file and hands ImageManipulator a
  file:// URI; the iOS native loader (Data(contentsOf:)) cannot decode large
  base64 data URIs, so a data URI made renderAsync throw.

Adds expo-image-manipulator and expo-file-system.

* fix(mobile): fail fast when resized clipboard image has no base64

Empty base64 from saveAsync would pass the downstream base64 check and
upload a corrupt image; throw instead so the paste surfaces an error.

Addresses CodeRabbit review on #5601.

* Fix mobile clipboard image resize cleanup

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-18 18:25:06 -07:00
Carlos Baraza d46349ce82 fix: improve mobile link modifier handling (#5597) 2026-06-18 16:48:43 -07:00
Jinwoo HongandOrca 06d674b35d Fix mobile folder workspace visibility (#5679)
Co-authored-by: Orca <help@stably.ai>
2026-06-17 23:18:34 -07:00
c9bd61376f feat(mobile): combine PR sidebar and checks parity (#5641)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-06-17 22:52:12 -07:00
Jinjing bd546b47d2 Redesign mobile driver overlay layout and clarify action copy (#5647)
Clarify the mobile-driving experience by updating outdated terminology and
making actions more precise. Generic "Mobile" references are replaced with
"Phone", and action buttons now explicitly distinguish between "this"
terminal and "all" terminals. Also introduces a modern card layout featuring
a smartphone icon badge and a live status indicator, complete with updated
locales, tests, and a standalone HTML preview.
2026-06-17 19:21:42 -07:00
gsxdsm c2acb3c11b fix(mobile): open the file tab when a terminal file path is tapped 2026-06-17 18:15:53 -07:00
Jinwoo Hong 83bee02b47 fix(mobile): add terminal link routing setting
## Summary

- add mobile Settings → Browser → Open terminal links
- route terminal HTTP(S)/OSC 8 link taps to either the streamed Orca browser or the phone browser
- keep existing file-path tap behavior intact

## Verification

- pnpm --dir mobile test
- pnpm --dir mobile lint
- pnpm --dir mobile exec tsc --noEmit
- git diff --check
2026-06-16 17:05:12 -07:00
21a01c7d6b Mobile: worktree-list sidebar for tablet/foldable layouts (#5505)
* Add worktree-list sidebar for mobile tablet/foldable layouts

On wide canvases (tablet/foldable, >=700pt) the per-host worktree list now
renders as a persistent left sidebar with the routed screens (terminal,
source control, review, accounts, tasks) shown in a detail pane to its
right — mirroring the desktop's sidebar + center layout. Phones keep the
existing single-pane stack navigation unchanged.

- Reuse the existing worktree-list screen as the sidebar via an `embedded`
  mode (props for hostId/action, mount-driven fetch since a sidebar is never
  the focused route, hide-sidebar control in place of the back button, and
  open-into-detail-pane navigation that replaces rather than stacks).
- The default host route renders an empty WorkspaceDetailPlaceholder on wide
  layouts (the list lives in the sidebar) and the full screen on phones, so
  exactly one instance mounts either way.
- Hide button collapses the sidebar to give the detail pane full width; an
  elevated reveal tab brings it back.
- Detail-pane screen transitions use `animation: 'none'` while the split is
  active so workspaces swap instantly instead of sliding and flashing the
  screen beneath.
- Drag the sidebar's right edge to resize (clamped 280-560pt, detail pane
  kept >=320pt, tap-transparent so list rows still work); width persists via
  AsyncStorage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address PR review feedback (#5505)

- Clamp the restored/persisted sidebar width against the current window and
  re-clamp when the window shrinks, so a width saved on a larger device can't
  starve the detail pane below MIN_DETAIL_WIDTH. Extract a shared
  clampSidebarToWindow helper reused by load, window-resize, and drag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Polish mobile tablet sidebar

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-16 16:50:38 -07:00
gsxdsm 50b324a8c8 Fix file loading on web and mobile remote clients 2026-06-16 14:39:25 -07:00
gsxdsm f47832cce0 fix(mobile): show usage bars for system-default login (#5499)
Show mobile account usage for Claude/Codex system-default logins when rate-limit data exists without Orca-managed accounts.\n\nReview fixes:\n- match inactive account usage to the runtime rateLimits payload shape\n- share usage-bar state so stale window data remains visible during transient fetch errors\n- add regression coverage for both cases
2026-06-16 13:29:45 -07:00
Jinwoo HongandOrca adf5ccbc92 feat(mobile): view image files from the Files tree (#5472)
Co-authored-by: Orca <help@stably.ai>
2026-06-15 22:30:05 -07:00
Jinwoo HongandOrca e3bf7d8614 feat(mobile): pickers, workspace parity, active-workspace focus, tap-to-open, source-control parity, artifact viewing (#5330)
Co-authored-by: Orca <help@stably.ai>
2026-06-15 22:02:51 -07:00
26121a4315 fix(mobile): respect Android device rotation lock instead of forcing rotation (#5403)
* release: v1.4.48-rc.0 [rc-slot:2026-06-05-15]

* release: v0.0.1-rc.0 [rc-slot:2026-06-06-03]

* fix(mobile): respect Android device rotation lock instead of forcing rotation

The app rotated on Android even when the system auto-rotate lock was on.
Expo's top-level `orientation: "default"` emits `android:screenOrientation="unspecified"`,
which auto-rotates on many devices regardless of the lock, and the
`react-native-screens` `orientation: 'all'` screenOption mapped to FULL_SENSOR,
which forces rotation outright.

react-native-screens exposes no orientation value that honors the rotation lock
(even 'default' calls setRequestedOrientation(UNSPECIFIED) at runtime, overriding
the manifest). The Android value that respects the lock is "fullUser", which must
come from the manifest. So:

- Add an `android-respect-rotation-lock` Expo config plugin that sets the main
  activity to `android:screenOrientation="fullUser"` (honors the lock; allows all
  orientations when unlocked, matching iOS).
- Remove the runtime `orientation` screenOption so the manifest value governs.

iOS behavior is unchanged (UISupportedInterfaceOrientations still lists all four).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 13:45:58 -07:00
cd31cd702d feat(mobile): add terminal text size (zoom) setting (#5388)
* release: v1.4.48-rc.0 [rc-slot:2026-06-05-15]

* release: v0.0.1-rc.0 [rc-slot:2026-06-06-03]

* feat(mobile): add terminal text size (zoom) setting

The mobile terminal fits the desktop's full column count to the phone
width with a CSS scale, which cancels out xterm's raw fontSize — so there
was no way to make text bigger or smaller. Add a persisted baseline zoom
multiplier the WebView applies on top of the fit.

- Settings → Terminal → "Text size": 50%–200% presets.
- Pinch-to-zoom in the terminal snaps to the same presets and persists.
- Per-device display preference; does not change the desktop terminal.
- Extracts the xterm WebView HTML into terminal-webview-html.ts to keep
  TerminalWebView.tsx within its max-lines budget.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(mobile): make terminal text size reflow columns instead of CSS scaling

Text size now drives the real xterm fontSize rather than a CSS transform
baseline. A larger cell means fewer columns fit the phone width, so the
existing measure -> terminal.updateViewport pipeline resizes the PTY and the
shell rewraps to the new width (and smaller sizes show more columns). Pinch
still snaps to a preset, but now changes the font size and reflows instead of
scaling pixels; the refit hook re-fits the PTY whenever the scale changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): restore terminal touch scrollback (undefined contentWiderThanViewport)

The single-finger touchmove handler gated horizontal panning on
contentWiderThanViewport(), which is never defined — so every one-finger
touchmove threw a ReferenceError before reaching the vertical-scroll code
below it, killing all touch scrolling including scrollback.

Replace the call with the inline overflow check clampPan() already uses
(scrollWidth * getTotalScale() > innerWidth). Vertical scroll now always runs;
two-finger pinch-to-zoom is unaffected. Merged two adjacent var decls to keep
the file under its max-lines cap without a disable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(mobile): guard terminal text scale viewport refit

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-15 13:14:58 -07:00
d17c22c826 feat(mobile): add terminal keyboard autocomplete toggle (#5348)
* release: v1.4.48-rc.0 [rc-slot:2026-06-05-15]

* release: v0.0.1-rc.0 [rc-slot:2026-06-06-03]

* feat(mobile): add terminal keyboard autocomplete toggle

Terminal command inputs disable autocorrect/suggestions and use a
password-class keyboard so the OS never rewrites commands, flags, or
paths. Some users want phone-style typing in the command bar.

Add a Settings → Terminal "Autocomplete & autocorrect" toggle
(persisted locally, default off). When on, the command bar enables
autoCorrect/spellCheck and uses the default keyboard so Android shows
its suggestion strip; iOS drops the ascii-capable restriction. The
direct keyboard-capture input is unchanged — it streams raw keystrokes
where suggestions cannot apply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): remount command input when autocomplete toggles

Android caches the IME inputType at mount, so flipping the toggle did
not switch suggestions on until the field remounted.

* fix(mobile): guard autocomplete pref loads against unmount and fast toggle

Address review feedback: the settings toggle's initial load could clobber
a fast user toggle, and the session focus-effect could setState after the
screen unfocused. Both now use a guard flag.

* test mobile terminal autocomplete preference

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-15 13:07:07 -07:00
Jinwoo Hong 618b5d3177 fix(mobile): retry handshake before latching auth-failed (#5200) (#5304) 2026-06-13 14:25:43 -07:00
c4fade90e7 Add mobile Review Changes workflow (#5313)
* Add mobile diff review

* Wrap mobile review notes prompt

* Fix mobile review unreviewed navigation

* Clear review completion when a refreshed diff invalidates a reviewed file

mergeMobileDiffReviewState invalidates a file's reviewed flag when its
diff identity changes, but left completedAt set — inconsistent with
markUnreviewed. Drop completedAt on invalidation and add a regression
test.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-13 14:24:26 -07:00
Jinwoo HongandOrca 71bfbbf840 Fix rich markdown image paste bloat (#5290)
Co-authored-by: Orca <help@stably.ai>
2026-06-12 16:48:09 -07:00