* 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.
* 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.
The longer-hyphen recovery path (#5222) reconstructed runs by writing a
value that differed from the native field text. After #7933 stores raw
field text and normalizes only on send/PTY, that recovery is unreachable
and any write-back would reintroduce dictation kill. Map each smart dash
to exactly "--" with a single-arg normalizer.
* 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>
* fix(mobile): avoid SF Mono fallback on iOS terminal
* test(mobile): cover touch iPadOS terminal font fallback
* refactor(mobile): share terminal font fallback tail across platforms
Dedup the identical fallback chain that the iOS/non-iOS branches each
repeated so the two platforms can only differ in the lead family and
cannot silently drift. Make the regression tests behavioral: assert the
resolved chain always terminates in the generic monospace (the real iOS
bug) and that both platforms share an identical tail.
Co-authored-by: Orca <help@stably.ai>
* test(mobile): anchor font-block extraction on font markers only
The VM-slice end boundary was an unrelated text-scale comment; re-anchor
it on the terminalFontFamily declaration so edits below the font block
cannot break the extraction.
Co-authored-by: Orca <help@stably.ai>
* chore(mobile): bump terminal-webview-html max-lines ratchet to match file size
The iOS-safe font selection block adds a few code lines to
terminal-webview-html.ts, pushing it to 1784. Bump the grandfathered
per-file ratchet to match, consistent with prior ratchet bumps.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* 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>
* fix(mobile): bundle terminal engine and show load errors instead of a blank pane
The mobile terminal WebView loaded xterm.js from cdn.jsdelivr.net at
runtime; old WebViews (< Chrome 85) fail to parse the modern bundle and
blocked-CDN networks fail to fetch it, and the resulting error was
silently dropped, leaving the pane permanently blank (#7030).
Bundle the engine into the app via exact-pinned npm deps + a postinstall
esbuild step (chrome74 target, guarded WeakRef/structuredClone/
replaceChildren shims) emitting a gitignored generated module, inline it
into the terminal document, and surface fatal engine failures as a
visible overlay with diagnostics and a Reload wired into the existing
resubscribe path. Non-fatal errors log without covering a live terminal.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): add a native watchdog so a dead terminal document can't stay silently blank
CodeRabbit round: if the webview document dies before the glue can post
anything (or the RN message bridge never comes up), no error message and
no native handler fires. Arm a 15s foreground-gated watchdog per document
generation that paints the fatal overlay when web-ready never arrives;
first fatal diagnostics win over later cascades. Extract the watchdog and
the public contract types to keep TerminalWebView under the line cap, and
document the SVG xmlns percent-encoding transform.
Co-authored-by: Orca <help@stably.ai>
* test(mobile): unmount TerminalWebView renderers so watchdog timers can't leak across tests
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
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.
* 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>
* 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>
* 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
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.
- 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.
* 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>
- 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.
* fix: improve mobile terminal glyph rendering
Align the mobile companion WebView terminal with the desktop xterm setup so Claude output renders correctly on iOS.
- Upgrade the embedded xterm CDN stack to the desktop beta line
- Load Unicode 11 before replaying terminal bytes
- Enable WebGL rendering with context-loss fallback
- Preserve Claude status dots as text presentation
- Add regression coverage for the mobile terminal setup
* Fix mobile status dot normalization edge cases
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
## 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
* 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>
Fix Android terminal text overflow and stale foldable viewport handling.
- Pin terminal WebView textZoom so Android system font scale cannot inflate xterm glyphs past measured cells.
- Refit mobile terminal viewport on window dimension changes for fold/unfold, rotation, and split-screen.
- Harden viewport refit fallback handling for stale async runs and updateViewport responses.
Fixes#4579
Bounds the mobile terminal WebView pre-ready write backlog, avoids Array.shift() in the embedded xterm write drain, disposes old xterm observers, and clears successful fallback timers.
* Improve mobile terminal streaming performance
Co-authored-by: Orca <help@stably.ai>
* Add mobile clear terminal action
Co-authored-by: Orca <help@stably.ai>
* Fix terminal connection test mock
Co-authored-by: Orca <help@stably.ai>
* WIP: mobile markdown tabs before rebase
Co-authored-by: Orca <help@stably.ai>
* Add mobile markdown editing
Co-authored-by: Orca <help@stably.ai>
* Harden mobile tab and markdown sync
Co-authored-by: Orca <help@stably.ai>
* Fix mobile terminal reconnect loading race
Co-authored-by: Orca <help@stably.ai>
* Polish mobile terminal keyboard behavior
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile markdown editor chrome
Co-authored-by: Orca <help@stably.ai>
* Move mobile markdown actions to top
Co-authored-by: Orca <help@stably.ai>
* Use app modals for markdown discard
Co-authored-by: Orca <help@stably.ai>
* Dismiss keyboard before markdown confirmations
Co-authored-by: Orca <help@stably.ai>
* Add mobile file explorer
Co-authored-by: Orca <help@stably.ai>
* Fix mobile file explorer type narrowing
Co-authored-by: Orca <help@stably.ai>
* Fix mobile files navigation param
Co-authored-by: Orca <help@stably.ai>
* Show mobile files connection wait state
Co-authored-by: Orca <help@stably.ai>
* Preview text files on mobile
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile file previews
Co-authored-by: Orca <help@stably.ai>
* Clarify unavailable mobile file types
Co-authored-by: Orca <help@stably.ai>
* Fix mobile subscription and preview review issues
Co-authored-by: Orca <help@stably.ai>
* Keep fallback terminals visible on mobile
Co-authored-by: Orca <help@stably.ai>
* Keep mobile terminal tap active
Co-authored-by: Orca <help@stably.ai>
* Preserve mobile terminal fallback order
Co-authored-by: Orca <help@stably.ai>
* Fix mobile session tab authority
Co-authored-by: Orca <help@stably.ai>
* Run mobile tests in mobile CI lane
Co-authored-by: Orca <help@stably.ai>
* Bump mobile app version to 0.0.7
Co-authored-by: Orca <help@stably.ai>
* Allow main window IPC wiring size
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>