mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
ec62243dedec0ba82b8ef393438eecd8f35fa0ef
123
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c5957e7d4d |
fix(mobile): notice a silent drop while dictating within seconds (#35834)
An overdue chunk reply now asks the connection for one decisive liveness probe instead of waiting for the idle watchdog, so the resend lands inside the host's grace. Native clients probe directly; the OTA page asks the shell with a new notify that older shells refuse with a diagnostic only. |
||
|
|
1a03d59ea4 |
fix(mobile): release retired route storage mirrors (#35586)
* fix(mobile): release retired route storage mirrors * fix(mobile): keep route hydration through unrelated writes and route changes Stamp writes per owned key so an unrelated write no longer voids a cold route's hydration into host-profile-unreadable, and retain the next route's keys before releasing the previous route's so shared host-wide keys stay mirrored. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
ccd0829e6f |
feat(mobile): localize app screens, settings, pairing and host lists (#35498)
Moves ~600 strings in the root screens, settings, pairing, onboarding, accounts, agent history, diagnostics, connection status, home and host workspace lists to the translation API, with all ten translations. - Connection verdicts carry an explicit `dialing` flag instead of matching the "…" ending of an English label - Update-failure copy and connection-path accessibility labels are built from data, not by editing translated text - Workspace status names stay the host's labels, as on desktop; transport errors stay English - The stale-key check keeps a plural family while any of its forms is referenced Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
12a7d4c13a |
feat(mobile): localization infrastructure and language setting (#35399)
* feat(mobile): add UI localization infrastructure Mobile follows the phone's language (or an in-app choice under Settings > Appearance) across the same 11 languages as desktop, switching live like the theme does. - i18next with desktop's translate(key, fallback, options) call shape; catalogs in mobile/src/i18n/locales load lazily, English fallbacks stay inline - Hermes lacks Intl.PluralRules: load the FormatJS polyfill with the 11 languages' rules - OTA pages get the shell's language through an optional bridge init field - iOS permission prompts localized; iOS and Android 13+ per-app language pickers - verify:mobile-localization and a coverage ratchet run in lint, check:changed and CI * fix(mobile): mount pages after the newest language request; tighten localization gates - Wait for the latest page language activation, not a superseded one - Keep literal fallbacks when a key also has a dynamic one - Count headerTitle as visible copy; select the mobile check when the runtime catalog generator changes * feat(mobile): repaint themed components on language changes Components that read the theme already subscribe for repaint; a language switch repaints them too, so labels built by helpers during render follow it. RPC recordings load the real i18next and JSON catalogs. * fix(mobile): scope the translate() gate to render bodies and memos; prune stale keys on sync Handlers and effects may call translate() when they run. sync:mobile-localization now removes keys no code references from every catalog. * fix(mobile): use node: imports in the locale config plugin; pass uiLocale in bridge hook test * chore(mobile): refresh the localization coverage baseline * fix(mobile): follow render callbacks in the translate() gate; keep used plural siblings on prune Also let anonymous default-exported screens count as components, flag any plural family a locale started, and mark JSON modules as ES modules in the recording loader. * fix(mobile): treat async and named local callbacks as handlers in the translate() gate * chore(mobile): refresh the localization coverage baseline * chore(mobile): refresh the localization coverage baseline --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e5b45ac90f |
fix(mobile): keep dictation recording through a reconnect and keep what was already said (#35338)
* fix(mobile): keep dictation recording through a reconnect The host tied a phone's dictation to its socket, so any reconnect cancelled it, and the phone ended the recording as soon as the connection dropped. The host now owns the session by device and waits a grace period for the phone to come back. The phone keeps recording through the reconnect and keeps the text transcribed so far if the connection does not return. Fixes #35211 * fix(mobile): bound dictation reconnect waits and release retired relay waiters * fix(mobile): resend an unsent dictation finish after a connection move and send the text so far only when it changes * fix(mobile): hold dictation requests through a suspended relay and bound the start wait * fix(mobile): recognise a suspended relay client across the page bridge * refactor(dictation): refresh one abandon timer, report text by version and share the reconnect wait * fix(mobile): resend dictation text each second and never wait offline for a cancel |
||
|
|
64344142fa |
fix(mobile): stop held terminal keys when you switch terminals or leave the app (#31712)
* fix(mobile): restore lifecycle-safe terminal accessory repeat The selection hook replaced the established repeat controller with unbounded timers, allowing held keys to cross terminal/client changes. Reintegrate the existing pinned sender, lifecycle cancellation and backpressure. * test(mobile): stop held terminal input after refusal or disconnect * test(mobile): give accessory-repeat harnesses the PTY modes and focus hook main now reads * fix(mobile): stop a held key when its strip unmounts and repeat through the current live-input mode * fix(mobile): type the accessory repeat sender's input * refactor(mobile): read app state live in the accessory repeat gate * test(mobile): pin the key bar's held press to the repeat controller's first send --------- Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> |
||
|
|
7d29cd0839 |
fix(mobile): recover host profile reads without discarding cached bundles (#34806)
* fix(mobile): retry unavailable host profiles without discarding cache * fix(mobile): cancel startup work on terminal shell failures via flow advance Merging main pushed use-mobile-web-shell-session.ts over max-lines; move the host-profile cancellation into the reducer so the flow-change cancel path from #34780 handles it. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e0420b3b58 |
fix(mobile): skip queued generation stages after cancellation (#34809)
* fix(mobile): skip queued generation stages after cancellation * test(mobile): fold canceled queued-stage case into download suite --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
d4b6e928ac |
fix(mobile): index reusable bundle assets once per generation (#34789)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
6d5db9a397 |
fix(mobile): cancel superseded shell download flows (#34780)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
4270c886d8 |
fix(mobile): release native picks after page teardown (#34760)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
1ac9be7605 |
fix(mobile): let native screens handle Android Back after handoff (#34766)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
9c094e16d2 |
fix(mobile): share microphone ownership across page sessions (#34762)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
d5dcc2cdf7 |
perf(mobile): keep terminal backlog draining linear (#34741)
* perf(mobile): drain terminal backlog without repeated array shifts * test(mobile): cover backlog reuse after mid-drain dispose --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
941f4ece37 | feat(mobile): support the iPhone Duo folded and unfolded, with controls in its side status bar (#34484) | ||
|
|
2a9d874e67 |
fix(mobile): leave Agent Session History when you resume a session from it (#34485)
* fix(mobile): leave Agent Session History when resuming a session from it Resuming always pushed the session screen, so Back from it landed on the history screen again (A -> B -> A). History now learns it was opened from a session (origin=session, as Source Control does): resuming into that same workspace pops back to it, anything else replaces the history screen. The history screen is a page in the shell, and the page could only ask the shell to push. The navigate notify gains an optional replace flag; a page with no in-page history asks the shell to replace its screen, and one with history below pops its own entry and has the shell push. Older shells strip the flag and push as before. Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com> * fix(mobile): show the resumed terminal on the session that opened Agent Session History Going back to the session screen below kept its old tab: the session pins its own pick over the host's caller-scoped snapshot. History now hands the resumed terminal to that session through the registry Source Control already uses for opened diffs, renamed to session-screen-handlers since it carries both. * refactor(mobile): positional replace flag on notifyNavigate, share the owned-session test fixture * fix(mobile): keep MobileWebShellScreen under its line cap --------- Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com> |
||
|
|
f87a746200 |
feat(mobile): light and dark themes with desktop's palettes (#34217)
* feat(mobile): light and dark themes with desktop's palettes Settings → Appearance offers System, Light and Dark. The shell passes the resolved scheme to over-the-air pages in its init frame. Fixes #22184 * fix(mobile): draw your chat messages in desktop's user bubble Desktop uses a faint surface with a hairline border in both themes; the inverted fill went black in light. * test(mobile): re-measure the page script sweep after the shared theme hooks Every route now imports the theme hooks while the entry does not, so they emit one shared script (96 for the tree). Re-swept every route prefix; the worst route step is now 15. * test(mobile): read themed stylesheets in the text-input font-size census The census only unwrapped StyleSheet.create({...}); inputs styled through themedStyleSheet(() => ({...})) came back unread. |
||
|
|
86f2f24ddc | feat(mobile): open PDFs from the file browser in Quick Look or the system viewer (#33771) | ||
|
|
de93f326ea |
test: remove low-value inventories, passthroughs and redundant cases (#33796)
* test: remove low-value inventories, passthroughs and redundant cases * style: format test suites after pruning redundant cases * test: remove obsolete workflow and flaky-test references * test: prune deleted builder test from Windows shim allowlist --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
7d6603aa17 |
[comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
cc2b6fe6a2 |
build(mobile): move to TypeScript 7 and Vitest 5, typecheck through the root tsrs runner (#33419)
- Bump mobile typescript to ^7.0.2 and vitest to 5.0.5; keep TypeScript 6 as typescript-api for the test-support code that walks the AST, mirroring the root. - Patch Expo CLI's tsconfig reader to fall back to typescript-api: TypeScript 7 has no compiler API, and Metro otherwise crashes reading tsconfig paths. - Register mobile/tsconfig.json and mobile/tsconfig.test.json with the root typecheck runner; locally they use incremental tsrs (Bun elsewhere), CI uses Bun like root CI. - Route the tests-typecheck ratchet through the same compiler selection. - Enforce root/mobile parity for typescript, typescript-api and vitest. |
||
|
|
01652255f0 |
test: strengthen hook, build, and mobile lifecycle coverage (#32968)
* test: strengthen hook, build, and mobile lifecycle coverage * fix: use relocated Jira types in issue user field --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
6882870850 |
test: strengthen hook delivery, CLI and mobile shell coverage (audit 9) (#32876)
* test: audit CLI environment, Jira and orchestration coverage * test: audit project setup routing and refusal coverage * test: audit hook delivery, config gates and mobile shell coverage * docs: retain a reachable audit tracker source commit --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
d91a3d5804 |
test: strengthen CLI, lifecycle, bundle and build coverage (audit 8) (#32777)
* test: audit repository visibility and session-search CLI coverage * test: verify service identities and public skill-sharing output * test: verify terminal retry commands and unsuccessful navigation attempts * test: exercise bulk-close refusal and printed legacy prompt receipts * test: audit host discovery and agent launch recovery identities * test: audit ACP replay, lifecycle, and hook delivery coverage * test: audit automation errors and build artifact guards * test: audit mobile bridge and bundle lifecycle coverage * docs: reconcile audit tracker with input-ownership changes --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
633da07af5 |
test: audit CLI dispatch and mobile host boundaries (#32576)
* test: verify independent CLI quoting and screenshot bytes * test: audit CLI registration and offline hook boundaries * test: record CLI automation fence review * test: audit computer CLI dispatch and exported files * test: record CLI enrollment and emulator contract review * test: audit file command path and navigation contracts * test: bracket login shutdown grace and fallback deadlines * test: keep managed server timeout expectations independent * test: record Linear CLI input and dispatch review * test: verify legacy completion dispatch boundary * test: replace copied config and browser controls * test: isolate gate refusal from inherited caller identity * test: exercise real mobile host admission and teardown boundaries * docs: reconcile audit entrypoints and refresh completed reviews * test: fix mobile audit React Doctor findings --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
385418e0db |
fix(mobile): the shell returns to a screen already on its stack (#32110)
* fix(mobile): the shell returns to a screen already on its stack
The page's own "back to hosts" chevron calls dismissTo('/') inside the page, which the page's
router hands to the shell as one navigate message. The shell pushed every such href, so leaving
a host from its page left [Home, Host, Home] with the old host page still mounted (WebView and
live subscriptions), and every round trip stacked another one. System Back then walked back
through stale host pages.
The shell now reads the navigation container's state first: an href that is already a screen
below this one (same path and query) is popped back to, and an href that is this screen is
dropped. Anything else is pushed as before. The pop names its navigator and source route
explicitly, because a screen's dispatch otherwise stamps its own key as the source and a parent
stack that cannot find it drops the pop.
The wide host-route dismissTo stays for the hrefs this rule does not cover: a host route whose
query differs from the one below (the missing-worktree bounce), and a cold deep link with no host
route below to return to. Overlay replace is unchanged, and so is the bridge message.
* fix(mobile): a param cleared to empty and a fragment still match a screen on the stack
The session screen clears paneKey with setParams({ paneKey: '' }), which leaves ?paneKey= on the
stack entry, so a later navigate to the plain session href found no match and pushed a duplicate.
Empty query values are now dropped when canonicalising, and the fragment is stripped since
expo-router keeps it as a param no href spells the same way.
|
||
|
|
5aa5467c27 |
feat(mobile): one download bar whose fill follows progress on the native driver (#31755)
* feat(mobile): one download bar whose fill follows progress on the native driver The page download's replies keep the JS thread busy, so React committed nothing until the end and both bars sat at zero, then jumped to full. The session now publishes each progress step to a small feed as it is dispatched, and one shared bar eases its fill to each value with Animated on the native driver (scaleX). The caption stays React-drawn. * fix(mobile): the download bar always moves on the native driver With reduced motion, the fill jumped through a JS-side setValue on a value that never became native, so on Fabric it only moved with React commits, which the download holds off. An unread preference also counted as reduced, so a freshly mounted bar took that path. Every step is now a native-driven timing, of zero length when motion is reduced, and the preference is read through a ref. * fix(mobile): the download bar starts at most one ease per 50 ms The feed fires per asset, often well under a millisecond apart. Starting a new 150 ms timing on each one replaced every ease before it drew a frame, so on iOS the fill stayed near empty until the last update. Now a callback inside the 50 ms window only records the latest fraction; the next callback past the window, or a trailing timer at its end, eases to it over 100 ms. Full lands at once. The effect cleanup clears the timer. |
||
|
|
ccc520ab09 |
feat(mobile): download only the page assets the cached generation lacks (#31769)
* feat(mobile): download only the page assets the cached generation lacks Before paging the bundle, the phone matches the new manifest against the host's active generation by content (sha256 and length). Matches are read from disk and hashed again while the changed assets come over the wire; one whose bytes no longer hash to the manifest is downloaded instead. Progress counts only the bytes that cross the wire. * test(mobile): put the older-build manifest note back on its describe * perf(mobile): copy unchanged page assets natively instead of reading and hashing them The reuse pass read every unchanged asset into JS and hashed it again, which kept the JS thread busy for most of an update. The cached generation was hash-verified when it was written, is never written again, and is what the page already opens as is, so matching assets are now copied natively into the staging tree and checked by size. A copy that fails or comes up short is downloaded instead; stage refuses a reused copy that is no longer whole. Path rules move to generation-asset-path.ts and the copying to generation-reuse.ts. * fix(mobile): copy reused page assets at stage, on the store's queue Copies made when the manifest was read sat in tmp for the whole download, where a second shell mount's sweep, or a second download of the same build, could take them away and fail the stage. The fetch now only checks that each reusable file is still whole (a native stat) and leaves it to the stage, which copies it from the generation active at that moment and checks its size. Copy destinations must not exist, as on both platforms. |
||
|
|
42da9f430f | refactor(mobile): group platform-seam analysis tools (#31845) | ||
|
|
f47f632bd7 |
feat(mobile): Home's New Workspace opens the desktop's create drawer over Home (native part ships with the next app build) (#31305)
* fix(mobile): the home screen's New Workspace opens the create sheet again
The native host-list route dropped the home screen's action=newWorktree when it built
the page's route, so the page opened as a plain list. The route now forwards it, and
the page names it back once applied so a reload does not reopen the sheet.
Reaches phones only with the next native build.
* test(mobile): drop the New Workspace tests that only re-check types and a null guard
* fix(mobile): Home's New Workspace opens the create sheet over Home
Replaces the action=newWorktree route hand-off. The quick action picks a
host (as Tasks does), then mounts the host's create sheet on Home; a
create opens the new workspace's session marked as just created.
The sheet controller becomes a plain visible/onClose component, and the
host screen's route-action state, the action route param and its builder
are deleted.
* fix(mobile): Home's create sheet seeds only proven worktree rows
Home's cold-start seed is a pathless resume row that the name suggester
cannot read, so the sheet now seeds from proven rows and is keyed by host.
The host screen gets one setter for the sheet's visibility and its ref.
* test(mobile): name the Home new-workspace test after the screen it renders
* proto(mobile): Home's New Workspace opens a native drawer hosting the page's create form
Home pushes /new-workspace/<host>, a react-native-screens formSheet over Home whose content is
MobileWebShellScreen for the page route /h/<host>/new-workspace. The page renders the shared
Create Workspace form with its drawers inline, so the form and its pickers fill the native
drawer. The shell's loading, download and failure states render inside the drawer. Prototype only.
* proto(mobile): the New Workspace drawer is the host screen's own sheet over a transparent modal
The native route is a transparentModal hosting the shell over Home; the page route renders the
host screen's NewWorktreeModal on a transparent page, so the drawer is the same component. While
the page loads, the shell shows a placeholder of that sheet (scrim, grabber, spinner or download
bar), and the page's sheet opens in place without its slide. A host that cannot serve the page
(old desktop, blocked, failed) opens the host list instead of a wall in a drawer.
* feat(mobile): Home's New Workspace opens the page's create drawer over a dimmed Home
A transparent native overlay route hosts the host picker and then the page's
/h/[hostId]/new-workspace route, which renders the same NewWorktreeModal as the
host screen's +. The native scrim matches the drawer backdrop and the page's
first drawer opens already dimmed, so Home stays dimmed throughout. Old desktops
without the route fall back to the host list.
* fix(mobile): one loading layer for Home's New Workspace overlay
The overlay route owns a single tappable loading layer (spinner, or the
download on a small card) that stays mounted from the pick until the page
paints, so the fade and spinner no longer restart per shell state. The shell
reports its load and shows the host list for anything it cannot open; the
picker hands the pick over at once and keeps sliding away. The page's first
drawer takes the dimmed backdrop once, and a gate's overlay pending view no
longer stacks a second scrim over a mounted stack. Native /h/[hostId]/new-workspace
redirects to the overlay. Drops the unused session-route params, the
clipboard grant, and pass-through tests.
* feat(mobile): New Workspace loads in a short drawer over an undimmed Home
While the page loads, the overlay shows one short native drawer in the shared
drawer chrome with a spinner or the download, and nothing dims Home: a tap
above it, a drag down or Back cancels. The page's real drawer then slides up
with its own backdrop exactly as on the host screen's +. Drops the native
scrim, the loading card and the already-dimmed backdrop handoff; the overlay's
WebView stays invisible until the page paints, since a fresh one draws opaque.
* feat(mobile): the New Workspace loading drawer dims Home like any drawer
The short loading drawer opens with the shared drawer backdrop and show
duration, and a tap on that backdrop, a drag down or Back cancels. It stays
in the screen's own window (a native Modal over the loading WebView stalls
the page) and goes in the frame the page paints: the shell reports its load
from a layout effect so the WebView shows and the drawer leaves together.
* fix(mobile): the page's first paint waits for a drawer opening with it
A drawer opening as its page screen mounts holds that screen's first-paint
report until its open animation ends, through a hold on the existing report
rather than a new bridge message. The New Workspace overlay's loading drawer
now stays until the create drawer is up, so a cold open no longer shows Home
undimmed between the two.
* refactor(mobile): the New Workspace overlay owns its exits; the loading drawer fades out
The shell no longer imports the feature: an overlay is a shell with
onOverlayLoad, which reports `unavailable` for what it cannot show, and the
overlay route opens the host list for every exit. HostProtocolGate takes one
transparentWhilePending flag. The loading drawer's drag handler follows a
stable cancel, and on paint the drawer fades out eased in, as the shell's page
cover does, because the WebView takes a frame or two to show the page after it
is revealed; removing it outright left Home undimmed for those frames.
* Revert "fix(mobile): the page's first paint waits for a drawer opening with it"
This reverts commit
|
||
|
|
92bbb380a6 |
Sidebar: land the switch-on fixes from the flip branch on main, switch still off (#31466)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip) The fixes found while the sidebar ran from main's feed, without turning that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's stop-gaps come along. - Main stops counting a closed tab's terminal once it was asked to stop and no pane shows it; a failed or refused stop takes the request back, and a host out of contact stays unverifiable. - Main fetches a paired server's rows with the transport's normal bound, so a server slower than 5 s still lists. - A deferred unread raise flips the folder row it was decided for, and a read while the owner check is out cancels it. - Phone: no worktree.ps flash before the feed; a host's last installed feed stands in while a fresh read is out; it highlights its own last-opened workspace and a fold never scrolls; projects file under their project group with the desktop's indent; the collapse spinner shows only after 500 ms; "Reopen to update" keeps the rows on screen. - e2e helpers and journeys for the switch-on suite. * fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises - A stop-and-wait that throws takes its stop request back, like one that fails. - A final stop the legacy relay still owes stays marked, since the relay retries it. - Pending unread raises are tracked only while one waits, so the map no longer grows. - The paired-server fallback poll test gives its third fetch a reply and asserts live. |
||
|
|
513c24bc16 |
fix(mobile): preserve unsent terminal commands across workspace navigation (#31314)
Persist unsent buffered terminal commands per paired host, workspace, execution host, and surface across navigation. Preserve newer typing during storage hydration and clear successfully sent drafts. Validation: draft/storage/bridge tests, signed iPhone simulator build, and all CI checks passed. |
||
|
|
cf7423c8f4 |
feat(mobile): redesign the chat composer with file attachments and inline skills (#30359)
* refactor(native-chat): move the slash picker builder and upload chunking to shared Mobile builds the same skill and command list as desktop. * feat(mobile): redesign the chat composer with file attachments and inline skills * fix(mobile): the composer field owns its input style The page's font-size census cannot follow a style passed in as a prop. |
||
|
|
aa07d5891a |
fix(mobile): break the transport and terminal-document import cycles, and gate cycles after mobile's install (#30290)
* fix(mobile): break the transport and terminal-document import cycles and gate them after mobile's install client-context re-exported the host-client hooks, which import client-context back: the re-export barrel is gone and importers (and their test mocks) name host-client-hooks directly. In the terminal document, the cell-metric getters move to a leaf cell-metrics module and select-mode entry/exit to selection-mode, so fit, transform, overlay and range no longer import each other. pr.yml lints imports before mobile's install, when mobile's tsconfig can't load, so Mobile verify now runs the same import rules over mobile after its install with zero warnings allowed. * fix(mobile): the browser-pane render harness imports useHostClient from host-client-hooks Its route source is a template string, so no typecheck saw the removed client-context re-export; the mobile web bundle check did. * fix(ci): the e2e typecheck ratchet counts the same with or without mobile's install Specs reach mobile/src/transport, whose expo-crypto and async-storage imports resolve only after mobile's install and then load React Native's globals (numeric timers, a required NODE_ENV), adding errors in unrelated specs. An empty stub pins both, so the static job reads 232 either way. |
||
|
|
90c1c0ac50 |
feat(mobile): draw the host Accounts screen from the desktop's page (#30020)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens * refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable * test(mobile): read the nullable page route refusal in its policy test * refactor(mobile): every build is the OTA shell; drop the native/OTA build switch The owner retired the native-screen fallback with no emergency native build, so EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro cache-key suffix and both release workflows' shell input go. Every host route mounts the shell; a route naming no screen the shell could open gets the existing refusal screen instead of a native screen. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): delete native screens the OTA shell no longer reaches With every build the OTA shell, these native siblings run nowhere: the native-executed closure (index.js plus every non-web route) does not reach them, and the page resolves their .web siblings. Each .web sibling takes the plain name, its web-overrides entry goes, and the native-only rich Markdown editor and Mermaid WebView generators and their outputs go with them. Kept: native siblings the node test suite still renders shared screens through (terminal WebView, browser frame path, clipboard, dictation, media picker, live input, snapshot budget, input styles). Claude-Session: https://claude.ai/code/session_6cea92f6 * test(mobile): drop the phone-preview comparison the deleted native sibling held Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind The host route files no longer switch, so their comments say what they do now: a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx duplicated index.tsx and nothing linked to it, so it and its stack entry go (the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print the bridge protocol on its own instead of a shell kind that has one value. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps MobileSelectableText was react-native's Text, so callers use Text. The rich Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are required now that the page is its only host; the WebView defaults go. Comments that described deleted native components are rewritten, and expo-image-manipulator and react-native-uitextview leave package.json and the lockfile. Claude-Session: https://claude.ai/code/session_6cea92f6 * feat(mobile): draw the host Accounts screen from the desktop's page Accounts was the last host screen the shell still drew natively, so its changes needed a phone build. It is now a page route like Tasks: the screen moves to src/accounts/MobileAccountsScreen.tsx, accounts.web.tsx renders it on the page, and accounts.tsx mounts the shell for it. What React Native Web needed: Alert.alert is a no-op there, so the reset confirmation, its outcome and a failed account switch are ConfirmModal drawers (ConfirmModal can now drop Cancel for a notice); Back goes through the route handoff, which pops the shell's stack; and the reset idempotency key is built from getRandomBytes, since randomUUID needs a secure context the iOS custom scheme is not. edit.tsx edits the phone's own pairing and never renders on the page, so the route manifest leaves it out and the page's host stack does not declare it. The script sweep is re-measured for the 14-route tree. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(accounts): answer a same-offer Codex reset retry with its recorded outcome; mobile mirrors the desktop The desktop keeps no client journal for a reset credit: the host's ledger owns the attempt. A remote client that lost a reply and retried the same offer under a new key was refused with "already attempted" (or "unknown outcome" while the provider call was unresolved), which is why the phone carried its own durable journal to replay the old key. The coordinator now answers a request for an offer an earlier key already claimed as that attempt: the settled outcome, the running attempt, or a resume of the pending one under its original key. A different offer for the account is still refused while an outcome is unknown, so nothing can spend twice. The reply shape is the one a same-key replay always had, so installed clients with their journal read it unchanged. With that, the Accounts page sends a fresh key per attempt like the desktop and keeps nothing on the phone: the AsyncStorage journal, its client singleflight and the retry-record warnings are deleted, and the journal-resume recording goes with them. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(accounts): resume an account's unresolved Codex reset when the retry names a newer offer Review follow-ups: - Host: after an unknown provider outcome the usage refresh moves the offer, so a retry names a newer one. The coordinator now answers it as the account's unresolved attempt (resumed under its own key and scope), as the desktop's consumeCurrent does, instead of refusing forever. One ledger lookup finds own key, offer claimant, then unresolved attempt. - Mobile: a reply is matched on target, account and account revision, not the offer, so the resumed attempt's scope is accepted. - The key is minted inside requestCodexResetCredit from randomV4Uuid, the session helper moved to src/platform/random-v4-uuid.ts. - RefreshControl goes; the header refresh stays. ConfirmModal's onConfirm is optional for an OK-only notice. - Stale journal comments and the mobile docs line are corrected. - The edit.tsx page exclusion is dropped from this PR; the script sweep is re-measured for 15 routes. - Root oxlint: escape the apostrophe; zod v4 strictObject. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): secure UUID fallback from expo-crypto; pin reset retry routing - randomV4Uuid never falls back to Math.random: without crypto.randomUUID it builds the v4 UUID from ExpoCrypto.getRandomBytes, as the old reset key did. Tests get Node's secure bytes for expo-crypto from the setup file, since the native module has no Node build. - Host tests: a new offer after a settled attempt spends again rather than replaying, and a retry naming a newer offer joins a resumed attempt in flight (one provider call, original key). Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * chore: restore main's bytes for files the merge's pre-commit format touched The merge commit's lint-staged run reformatted 27 files this PR does not change. Committed without the hook so they match origin/main exactly. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): stand in for expo-crypto with a Node alias, not a setup-file mock Anti-slop forbids module mocking outside test files. The vitest config now aliases expo-crypto to a faithful Node implementation of the names shared code reaches; a test file's own mock still wins. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): match the bridged-parity comments to the measured 795-golden tally Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
86f6258d54 |
refactor(mobile): delete native screens the OTA shell no longer reaches (#29979)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens * refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable * test(mobile): read the nullable page route refusal in its policy test * refactor(mobile): every build is the OTA shell; drop the native/OTA build switch The owner retired the native-screen fallback with no emergency native build, so EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro cache-key suffix and both release workflows' shell input go. Every host route mounts the shell; a route naming no screen the shell could open gets the existing refusal screen instead of a native screen. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): delete native screens the OTA shell no longer reaches With every build the OTA shell, these native siblings run nowhere: the native-executed closure (index.js plus every non-web route) does not reach them, and the page resolves their .web siblings. Each .web sibling takes the plain name, its web-overrides entry goes, and the native-only rich Markdown editor and Mermaid WebView generators and their outputs go with them. Kept: native siblings the node test suite still renders shared screens through (terminal WebView, browser frame path, clipboard, dictation, media picker, live input, snapshot budget, input styles). Claude-Session: https://claude.ai/code/session_6cea92f6 * test(mobile): drop the phone-preview comparison the deleted native sibling held Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind The host route files no longer switch, so their comments say what they do now: a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx duplicated index.tsx and nothing linked to it, so it and its stack entry go (the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print the bridge protocol on its own instead of a shell kind that has one value. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps MobileSelectableText was react-native's Text, so callers use Text. The rich Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are required now that the page is its only host; the WebView defaults go. Comments that described deleted native components are rewritten, and expo-image-manipulator and react-native-uitextview leave package.json and the lockfile. Claude-Session: https://claude.ai/code/session_6cea92f6 |
||
|
|
37f23b9445 |
refactor(mobile): every build is the OTA shell; drop the native/OTA build switch (#29969)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens * refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable * test(mobile): read the nullable page route refusal in its policy test * refactor(mobile): every build is the OTA shell; drop the native/OTA build switch The owner retired the native-screen fallback with no emergency native build, so EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro cache-key suffix and both release workflows' shell input go. Every host route mounts the shell; a route naming no screen the shell could open gets the existing refusal screen instead of a native screen. Claude-Session: https://claude.ai/code/session_6cea92f6 * refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind The host route files no longer switch, so their comments say what they do now: a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx duplicated index.tsx and nothing linked to it, so it and its stack entry go (the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print the bridge protocol on its own instead of a shell kind that has one value. Claude-Session: https://claude.ai/code/session_6cea92f6 |
||
|
|
b2419a58ad |
fix(mobile): show the update screen instead of native screens when the desktop has no page (#29956)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens * refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable * test(mobile): read the nullable page route refusal in its policy test |
||
|
|
2ff42fd37c |
fix(mobile): stop re-downloading a current OTA bundle on entry (#29860)
* fix(mobile): stop rewriting the cached manifest on a same-build hit A same-build cache hit rewrote manifest.json (write next, delete, move) while the native view was reading it. A read inside the gap reported generation-unreadable, which discarded the cache and redownloaded the same build. A matching build id now writes nothing to the generation directory; the fresh routes still apply to the current session. * refactor(mobile): drop code the manifest rewrite left behind Opens a same-build hit straight from session.cached, folds the manifest file name and parser back into the generation store, removes the unused moveFile adapter and fake hooks, and trims tests that passed on main. |
||
|
|
a622f9efd3 |
fix(mobile): stop "Opening workspace" spinning forever when the page never loads (#29274)
* fix(mobile): fail a shell document the view never finishes loading instead of spinning forever A load the WebView refuses or never commits reports nothing, which is how iOS 18 left the OTA workspace page on "Opening workspace" forever (#28626). Arm a document-load deadline whenever the view is given a document; expiry takes the existing document-load-failed recovery (drop the cache, refetch once, then the failure screen with Retry). * refactor(mobile): arm both shell document deadlines from one case |
||
|
|
27c0275e3a |
feat(mobile): log OTA shell lifecycle to the connection diagnostics (#28840)
* feat(mobile): log OTA shell lifecycle to the connection diagnostics Exported connection logs showed nothing about the web shell, so a phone stuck on "Opening workspace" could not say whether it stalled activating the download, loading the page, or waiting for its first paint. Record sparse shell milestones, page signals, one-shot 15s stall warnings and bridge diagnostics into the per-host connection log. Diagnostics only. Refs STA-10545, #28626 * fix(mobile): cut shell log volume and pinpoint each waiting screen Every page route mounts its own shell, so four lines per healthy open evicted connection history from the 200-entry log. A healthy open now writes one line, on paint, carrying source, build and elapsed. The 15s stall line now also covers "Checking host" (gate verdict and whether the cache read answered) and a download with no progress. The "page has not drawn" line counts the page's ready asks and whether the document loaded, telling a page that keeps refusing init from one that took it and died. Frames dropped with no bridge host, an unreadable host profile, post-failed error names and route-refused issues are now logged; path masking catches quoted, bracketed and Windows paths. The exported report header names the shell build and bridge protocol. Diagnostics only: no behaviour, UI or wire change. Refs STA-10545 * fix(mobile): dedupe flapping shell lines and mask spaced paths A host with no cache flapping between unreachable and dialling wrote "checking host" and "host unreachable" every cycle per mounted shell; each is now written once per host until a different shell line lands. Path masking keeps going across a space while the next word still holds a separator, and accepts a comma before the path. The paint line's elapsed time is renamed activatedMs, which is what it measures. A route-only session rebuild re-arms the stall timer. Refs STA-10545 * refactor(mobile): split the shell log module by responsibility shell-lifecycle-log.ts passed the 300-line lint cap. The line type, recorder and flapping dedupe move to shell-lifecycle-line-recorder.ts, bridge diagnostic lines to shell-bridge-log-lines.ts, and user-path masking to shell-log-path-masking.ts, with tests split to match. No behaviour change. Refs STA-10545 |
||
|
|
cd1ff506e1 |
feat(mobile): list the desktop's server projects on the phone's Tasks screen (#27741)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve restart and today's gap: a desktop's phone does not list the desktop's server workspaces. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): show SSH host health on the workspace row host badge Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): compose the phone-mirror topology and check the cold snapshot Slices need one composer for host, desktop and phone with a single teardown, so specs stop copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold start, which is what the phone builds its tab strip from. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name an unhealthy SSH host even in a single-host list The desktop card shows SSH status on any host count, so the phone now reads target summaries whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one target-to-host keying, and a hook-level test pins the reply-to-row wiring. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): keep phone-mirror teardown errors from masking test failures Teardown failures become a test attachment instead of replacing the real failure, and the cold-snapshot poll reports stream errors together with the PTY list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): clear stale SSH host badges and announce them without the dot A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps, so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox, Disconnected") instead of reading the middle dot. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(runtime): let a paired desktop register its phones as delegated mobile devices A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(runtime): move the mobile device revoke primitive below dispatch Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in a layer under it as a pure move; this drops the abstract method seam. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): keep a runtime grant until its delegated phones are revoked A failed child cleanup now leaves the parent in place so a retry can still find the children, and revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage, shares credential minting, and drops the timing-dependent census. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): fail a delegated sync whose dropped phone cannot be revoked Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors before minting anything, so the desktop retries instead of believing the server matches its list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): relay a paired phone's server-workspace requests through the desktop A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per (phone, server), signed in as the phone's delegated device; replies pass through untouched except stream ids. Hidden until the phone client ships: the relay capability is not advertised yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): type the single-host health fixtures inline so the test tsc passes Fixtures hoisted into consts widened their health maps to string, which the now-required hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the desktop relay contract in its entry module main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a later request syncs it again. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin the relay's configured-server adapter The production hosts adapter was only typechecked; this pins that it resolves configured servers by exact id only (never a name or a removed server), fences by pairing revision and runtime identity, and stops reporting retirements once unsubscribed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the repo-metadata goldens for SSH host health The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): tighten the desktop relay after elegance review Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request whose link ended while opening, passes failure codes through, relays only this desktop's own phones, and drops the unused host-state reader until S2 needs one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): bound relay frames and stop sticky relay verdicts Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on each new link so an in-place upgrade relays, answers a phone the server just declined without syncing again, and ends a link whose mirrored capability update cannot be queued. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run targeted desktop-owned calls on the desktop The phone tags every call on a workspace screen with its execution host, so a paired-desktop method carrying `runtime:<env>` now runs locally instead of failing 'forbidden'. The Mac stays the only reader of the routing table. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): list the desktop's configured servers and their last workspace rows for the phone The phone needs the same host list the desktop shows, including servers that are offline or too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): list server rows through one desktop fetch, with no side effect on listing The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the desktop's own fetch is both the live source and the cache, and stamping stays in main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows when a server hangs, drops a server's rows when it is removed or re-paired, and checks the server's identity after the fetch so a reply from before a re-pair is never served. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name a server workspace's host on the phone's calls A phone reaches a server's workspace through its desktop only if each call names the host; the direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete A server Orca manages on an SSH host now lists once under that host's name, or not at all while the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is deleting are left out at reply time, as a phone asking the server itself would see. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied the phone's list; rows are now pruned only once a server is no longer configured. Fetches are shared per server identity, so a poll after a re-pair asks the new server right away. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): list the paired desktop's server workspaces on the phone Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin that a desktop failing the hosts list leaves only its own rows The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave today's list with no server rows and no catalog error. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): mirror the desktop's shown hosts exactly, one host included Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a one-host list narrows the phone too instead of being read as a single-host focus. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): scope a server workspace at its route, never the desktop Every screen under a server workspace's route now reads one context and gets that server's client and a router that keeps pushes on it, refusing to fall back to the desktop; pins write to the row's owner like the Mac. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): never open an unreachable server workspace, and prove the relay in e2e A server workspace the phone can't reach now gets one explicit screen at its route and no row tap or action-sheet entry opens it; native routes read the route's host, a census pins every workspace route, and the e2e reads files and git on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): gate a server workspace on that server's own features A targeted status.get now relays to the server, so a server workspace's route provides that server's status to every screen under it and its action sheet, while whether the phone can reach the server stays the desktop's answer. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(ci): run the remote-runtime WebSocket opener test on Node Under the bun vitest project, `ws` resolves to Bun's built-in client, which ignores maxPayload, so the frame-cap test hung. The opener only ships on Node/Electron, so run its test in the Node runtime project like its siblings. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run a server workspace's SSH calls on that server A phone in a server workspace asks for SSH state before a file mutation, but the Mac answered about its own targets, so server-owned SSH repos failed. The desktop asks the server, so the ssh.* family now routes by target; an e2e pins files, uploads, commits and review on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): keep the server files/git e2e offline and baseline-git safe Review eligibility reached real gh/glab over the network and proved nothing beyond repoSlug; setup git now ignores the runner's config and avoids `init -b` (Git 2.25). The relay integration test is the routing oracle, so the census re-listing goes. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): sleep, wake and new workspaces on a desktop's servers from the phone The desktop renderer alone sleeps a server's workspace and holds its slept agents, so the phone now asks the desktop (two paired-desktop methods) instead of the headless server; the new-workspace sheet lists and creates on reachable servers the way the desktop's composer does. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): sleep and wake only a server the desktop has configured A phone's sleep or wake names a server by host id; the desktop now refuses one that is not in its own configured servers, read from its settings rather than any fetched workspace list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep server workspaces' repos, picks and sleeps on their own host A hung server no longer holds back the desktop's repos, a repo id shared by two hosts no longer swaps the pick, and a server row's optimistic sleep waits for the server's own list to confirm it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): give the server-workspaces test rows every Worktree field The tests-typecheck ratchet checks mobile test files; the fixture row was missing required Worktree members, so the file no longer typechecked. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): give the file preview the workspace router, back to 128 page assets The preview was the one workspace screen on the plain router, so the scoped router was reached by a route set of its own and esbuild split it into an extra chunk (129 assets); every workspace screen now uses it and it shares the route's chunk. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * revert(mobile): drop server sleep and wake from the phone for now The workspace-layout refactor deletes the notifier path these used; sleep and wake on a server come back later as server-side layout.sleep / layout.wake. A server row's Sleep behaves exactly as on feat/mobile-remote-workspaces. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): derive the phone allowlist from the routing table Adding a phone method now takes one edit (its route). A server-targeted call on a legacy non-E2EE socket is refused instead of running locally, and only execution-host methods enter the relay. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): drop unread freshness fields from server workspace lists The phone reads only the rows, so stale, fetchedAt, totalCount and truncated were dead wire weight. The desktop still serves a server's last rows while the server is unreachable. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): read only the rows of a server's workspace list The desktop no longer sends a freshness flag, so the phone's schema drops it. The relay capability list names the delegated-devices constant instead of repeating its literal, which also retires a now-circular type check. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): open server-workspace diffs and file taps on the phone's own screens A headless orca serve has no renderer, so files.openDiff / files.open answer renderer_unavailable and the Changes tap showed that raw code. On that refusal the phone now opens its review screen (git.diff) or file preview (files.read) instead. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): show a server workspace as disconnected while its server is down The phone read the desktop's connection state for a server workspace, so a stopped server kept a green dot and "Loading tabs" spun forever. The workspace route now narrows that state by the server's health from mobileRelay.hosts.list, reusing the existing disconnected session UI and refetching tabs when the server returns. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): name run targets and the desktop's own row as the host list does The Run on picker showed a server's raw environment id behind "Remote ·" and always a monitor glyph; it now takes the host list's badge labels and icon, as the desktop's picker does. Server rows also made the desktop row read "This computer" because its platform was only read for a mixed desktop catalog. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): drop Open in Session and preview new notes in server workspaces A server workspace's host has no editor tabs, so the review screen no longer offers Open in Session there, and a new markdown note whose files.open answers renderer_unavailable opens in the file preview instead of a raw error toast. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): share one desktop server-list feed between the host list and workspaces A server workspace polled mobileRelay.hosts.list beside the host list's own read, so the phone could ask the desktop twice per tick and the row badge and the screen could disagree. One feed per desktop client now owns that read; both readers join it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): read a server's reachability from the hosts list, not a dropped stale flag |
||
|
|
5f134d8f01 |
feat(mobile): show and use the desktop's server workspaces on the phone (#26501)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs
Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the
phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve
restart and today's gap: a desktop's phone does not list the desktop's server workspaces.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): show SSH host health on the workspace row host badge
Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): compose the phone-mirror topology and check the cold snapshot
Slices need one composer for host, desktop and phone with a single teardown, so specs stop
copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold
start, which is what the phone builds its tab strip from.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name an unhealthy SSH host even in a single-host list
The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): keep phone-mirror teardown errors from masking test failures
Teardown failures become a test attachment instead of replacing the real failure, and the
cold-snapshot poll reports stream errors together with the PTY list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): clear stale SSH host badges and announce them without the dot
A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(runtime): let a paired desktop register its phones as delegated mobile devices
A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(runtime): move the mobile device revoke primitive below dispatch
Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): keep a runtime grant until its delegated phones are revoked
A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked
Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): relay a paired phone's server-workspace requests through the desktop
A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop
now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per
(phone, server), signed in as the phone's delegated device; replies pass through untouched except
stream ids. Hidden until the phone client ships: the relay capability is not advertised yet.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): type the single-host health fixtures inline so the test tsc passes
Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs(mobile): state the desktop relay contract in its entry module
main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a
server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a
later request syncs it again.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): pin the relay's configured-server adapter
The production hosts adapter was only typechecked; this pins that it resolves configured servers
by exact id only (never a name or a removed server), fences by pairing revision and runtime
identity, and stops reporting retirements once unsubscribed.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): re-record the repo-metadata goldens for SSH host health
The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): tighten the desktop relay after elegance review
Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request
whose link ended while opening, passes failure codes through, relays only this desktop's own
phones, and drops the unused host-state reader until S2 needs one.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): bound relay frames and stop sticky relay verdicts
Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on
each new link so an in-place upgrade relays, answers a phone the server just declined without
syncing again, and ends a link whose mirrored capability update cannot be queued.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): run targeted desktop-owned calls on the desktop
The phone tags every call on a workspace screen with its execution host, so a
paired-desktop method carrying `runtime:<env>` now runs locally instead of
failing 'forbidden'. The Mac stays the only reader of the routing table.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): list the desktop's configured servers and their last workspace rows for the phone
The phone needs the same host list the desktop shows, including servers that are offline or
too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and
its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): list server rows through one desktop fetch, with no side effect on listing
The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the
desktop's own fetch is both the live source and the cache, and stamping stays in main.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers
The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows
when a server hangs, drops a server's rows when it is removed or re-paired, and checks the
server's identity after the fetch so a reply from before a re-pair is never served.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name a server workspace's host on the phone's calls
A phone reaches a server's workspace through its desktop only if each call names the host; the
direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for
replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete
A server Orca manages on an SSH host now lists once under that host's name, or not at all while
the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is
deleting are left out at reply time, as a phone asking the server itself would see.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch
Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied
the phone's list; rows are now pruned only once a server is no longer configured. Fetches are
shared per server identity, so a poll after a re-pair asks the new server right away.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): list the paired desktop's server workspaces on the phone
Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's
mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the
relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): pin that a desktop failing the hosts list leaves only its own rows
The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave
today's list with no server rows and no catalog error.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): mirror the desktop's shown hosts exactly, one host included
Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a
one-host list narrows the phone too instead of being read as a single-host focus.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): scope a server workspace at its route, never the desktop
Every screen under a server workspace's route now reads one context and
gets that server's client and a router that keeps pushes on it, refusing
to fall back to the desktop; pins write to the row's owner like the Mac.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): never open an unreachable server workspace, and prove the relay in e2e
A server workspace the phone can't reach now gets one explicit screen at its route
and no row tap or action-sheet entry opens it; native routes read the route's host,
a census pins every workspace route, and the e2e reads files and git on the server.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): gate a server workspace on that server's own features
A targeted status.get now relays to the server, so a server workspace's route
provides that server's status to every screen under it and its action sheet,
while whether the phone can reach the server stays the desktop's answer.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(ci): run the remote-runtime WebSocket opener test on Node
Under the bun vitest project, `ws` resolves to Bun's built-in client, which
ignores maxPayload, so the frame-cap test hung. The opener only ships on
Node/Electron, so run its test in the Node runtime project like its siblings.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): give the file preview the workspace router, back to 128 page assets
The preview was the one workspace screen on the plain router, so the scoped router
was reached by a route set of its own and esbuild split it into an extra chunk
(129 assets); every workspace screen now uses it and it shares the route's chunk.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile-relay): derive the phone allowlist from the routing table
Adding a phone method now takes one edit (its route). A server-targeted call on a
legacy non-E2EE socket is refused instead of running locally, and only
execution-host methods enter the relay.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile-relay): drop unread freshness fields from server workspace lists
The phone reads only the rows, so stale, fetchedAt, totalCount and truncated
were dead wire weight. The desktop still serves a server's last rows while
the server is unreachable.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): read only the rows of a server's workspace list
The desktop no longer sends a freshness flag, so the phone's schema drops it.
The relay capability list names the delegated-devices constant instead of
repeating its literal, which also retires a now-circular type check.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): show a server workspace as disconnected while its server is down
The phone read the desktop's connection state for a server workspace, so a stopped
server kept a green dot and "Loading tabs" spun forever. The workspace route now
narrows that state by the server's health from mobileRelay.hosts.list, reusing the
existing disconnected session UI and refetching tabs when the server returns.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): share one desktop server-list feed between the host list and workspaces
A server workspace polled mobileRelay.hosts.list beside the host list's own read, so
the phone could ask the desktop twice per tick and the row badge and the screen could
disagree. One feed per desktop client now owns that read; both readers join it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): read a server's reachability from the hosts list, not a dropped stale flag
|
||
|
|
13a75d3939 |
[CI] Reduce redundant test work and import existing helpers directly (#28365)
* [CI] Reduce redundant test work and import existing helpers directly * [CI] Preserve provider guards and pin the mobile browser test core * [CI] Fix CI test setup and verify retained contracts * [CI] Compare native media chunks without boxed byte arrays * [CI] Detect shifted native media reads with position-sensitive bytes |
||
|
|
3a011e48d1 |
fix(native-chat): load long chats on the phone by keeping every update within its limit (#28203)
* fix(native-chat): fit chat stream pages in one mobile bridge frame A long native chat failed to open on a phone with "the shell ended this stream (overflow)". The host sized each history page to ~1.94 MiB (half the relay cap), but the mobile app ends any stream whose event frame is over 640 KiB, so the opening snapshot of a long chat never arrived. Pages now fit that frame; older rows load through Load earlier as before. The page also words a stream the app ended in plain language instead of the protocol's reason. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): keep the outline on the relay page share; plainer end words Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): read the bridge frame cap from its shared definition Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(native-chat): bounded frames for the phone's chat stream A phone page now asks the host (when it advertises agent-session.bounded-frames.v1) to keep every agentSession.subscribe frame within the bridge's 640 KiB frame: - each row is cut to what the phone draws (tool output to its detail length unless it draws as a diff, tool inputs to the diff view's length) and then to one row's share of a page, its long strings cut to a common length; the row is marked `truncated` with its full size - history frames carry rows and metadata held to a fixed ceiling; the draft queue and the background-task roster go in frames of their own; the command list is not sent, since the phone reads it from options - the queue's admission bound counts whole cards and is sized to one frame, so a phone always gets every card whole Other subscribers get the frames they always did. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bounded frames review round 1 - the phone waits for the host's capabilities before opening the stream, so its opening frame is never sent unbounded - rows are bounded once, by the reader that sizes the page; the item form is an options field and always a function - one cut rule (`cutLongStrings`) serves the display trims and the fit - a side list rides an empty history batch rather than a second frame; commands changes send a bounded subscriber nothing - queue admission measures the candidate list once; a legacy queue of more cards than fit lists the ones it sends first Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bounded frames review round 2 - an over-budget legacy queue cuts its text to one common length and marks the cards it cut, and no longer names a next card it left out - one predicate says whether a subscriber is owed the `/` list - a lighter invariant fixture Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): type the shortening guards against named shapes Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound every phone's chat stream at the host The host now bounds frames for every phone (clientKind 'mobile') at the RPC boundary, so the subscribe param, the host capability and the phone's wait for the capability probe are gone. Phones of every version already ignore the stream's slash commands, and a phone reached through the desktop relay signs in as a phone too. - A live gap of more than one page reaches a phone as the newest page, so no publish outruns the bridge's unacked window; older rows load on scroll. - Queue admission sizes each card as wide as a failed send can make it. - Rows are bounded only as a page takes them; the unread row marker is gone and a shortened queue card is marked `truncated: true`. - A bounded subscriber no longer reads or tracks the slash commands. - The stream-end words are back to main's: `overflow` also covers a page that stopped acking, which "too large" would misstate. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): count every card in line and never shorten one Queue admission now counts every card the queue can publish again without passing admission: waiting and returned cards, and sent cards whose delivery is not yet accepted, which a refusal or a Stop puts back. The fixed per-card reserve for a failed send's words is gone. When a phone's list is over its frame room anyway (an older build's larger queue, or a card that grew after admission), the phone gets the cards it sends first, whole, and the rest once those leave. Cards are never shortened, so no app version can Edit a partial copy over the full draft, and the `truncated` card flag is gone. Bounded frames strip only the roster from an event; no event carries the other side lists. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound only phone frames; keep main's queue and page sizes Queue admission is main's again. The phone's list is bounded where it is sent: the cards it sends first, whole, so every frame fits for any queue size and no card is shortened. History pages keep main's size for every reader but phone subscribe frames. A reader is now its item form plus its page budget, so desktop pages, the outline and phone history replies (chunked by the app's bridge) are as on main, and only bounded frames use the bridge frame's share. Drops the queue's `nextQueuedMessageId` rewrite, which no phone reads, and a test that duplicated the prefix check. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound phone rows by size alone; final tidy Rows the journal already caps at a 16 KiB head gain little from a second, display-shaped cut, and the diff check behind it passed a character count as a line count. A row is now held to its frame share by shortening alone, and native-chat-diff is back to main's. Unexports file-local bounds, fixes a capability-era test comment, and adds tests for string shortening and a phone's stop-note refresh. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(native-chat): assert a phone's stop-note refresh pages Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(native-chat): run the bounded-frames suite in the Node runtime project It opens a real SQLite journal. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): bound legacy dispatch reasons on read Rows written before the write-side bound carry a provider's full error text, which every page and phone frame attaches with its submission. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
2b62a3db1a |
fix(mobile): drop the download bar glide so it always matches the MB counter (#28089)
The OTA download bar no longer glides: its width is bytes received over the total, the same number the MB caption shows, so the two always agree. On a fast local link the screen still only re-renders near the end of the download, because decrypting replies keeps the JS thread busy. The glide then started late and was cut off by "Opening workspace", leaving a ~10% bar next to "8.0 of 8.1 MB". At normal speeds, renders land every 100-250 ms since the native hashing in #28056, so the glide added only lag. Verified on the iOS simulator: at 1 MB/s bar and caption agree within 1 point in every screenshot, and uncapped they go from empty to full together. |
||
|
|
974b78d4e3 |
feat(mobile): show the OTA download as a progress bar that renders while downloading (#28056)
The OTA download screen now shows a progress bar over bytes received, labelled "Downloading the latest app" with a "from <host> · X.X of Y.Y MB" caption, instead of a spinner with raw file and byte counts. Related offline and failure copy says "the latest app" instead of "the workspace". The bar barely moved before because React never committed progress during the download. SHA-256 of each asset ran in JS (3.8 s of a 5.7 s download), and React Native's scheduler runs queued native callbacks ahead of normal-priority renders, forcing a render only after 5 s. Assets are now hashed with expo-crypto's native digest (~5 ms total), so progress renders every 100–250 ms at 1 MB/s. Verified on an Android emulator and the iOS simulator. The bar glides between bursts of progress (moving average of burst gaps, clamped 200–2000 ms) and is ShellWaitingFrame's indicator, so the label keeps the same position as on "Opening workspace". On an uncapped local link, decryption still keeps the JS thread busy and the bar can jump; that is left for a follow-up. |
||
|
|
7cb30b7f8f |
feat(mobile): let the OTA page send extra top-level request fields (#27815)
* feat(mobile): let the OTA page send extra top-level request fields A page request could only carry method, params and three fixed options, so any new top-level wire field needed a new app binary. Requests and subscribes now carry an optional verbatim `envelope`, gated by a new `rpcEnvelope` grant; nothing sets it yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): one envelope merge seam per transport Review follow-up: the direct tracker and registry now hand frames to a single sendFrame that adds the credential and envelope, like the relay; page refusals use one fixed code per error class and an envelope-only options bag posts no options. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): refuse a non-object envelope before treating it as empty null threw synchronously and [] or a Date read as empty and was dropped; the schema now runs first. The tracker tests settle their requests instead of leaving timers. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): type the direct socket's request as the wire request The typecheck now proves every direct request carries the transport fields, and the native-verb fence says why it drops an envelope. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
11cf21e492 | [CI] Remove duplicate terminal backlog test replays (#27503) | ||
|
|
6013d093e2 |
fix(mobile): one host sidebar on iPad, with the page owning the host area (#26761)
* feat(mobile): let the page own the wide host area, one sidebar only
On wide layouts a detail route's page session ran app/h/_layout.tsx inside
the detail pane, judged itself wide, and drew a second host sidebar beside
the native one.
- The page draws the host sidebar only when init carries the new optional
`ownsHostArea` fact, never from its own width.
- The page manifest's `/h/[hostId]` route declares `canOwnHostArea`.
- On wide layouts the native host route mounts one host-area page session.
A declaring bundle serves it with every served route's grants, so hops
stay in the page, and the native layout drops its sidebar. Without the
declaration, or without a bundle, the session answers native-route and
every wide detail route stays native.
- The owner is recorded per host so the layout and switches agree and a
relaunch does not flash.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): decide the wide host area per session, without a stored owner
Round 1 kept a per-host owner record that the native layout trusted. When
the record said the page owned the area, a detail route opened natively
(deep link, notification, handoff, narrow-to-wide with a detail pushed)
showed no sidebar at all.
- Drop the record. A wide session decides from the manifest it already
read: it is served only when the host route declares canOwnHostArea,
and it rebuilds when the layout class changes.
- The native layout hides its sidebar only on the host route, while that
host's page session is serving, as the shell screen reports through a
layout context. Detail routes keep the native sidebar, and a page there
draws none, so there is exactly one.
- Phones read and render nothing new.
- The host-area page can read and write every workspace-scoped key of its
host (chat tabs, live-input handles), as a per-workspace session does.
Claude-Session: lane-ipad-page-owns-host
* test(mobile): type the layout-class doubles rather than assert them
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): keep detail pages through a layout flip, guard capped keys
- A wide/narrow flip rebuilds a session only on the host route or under a
bundle without canOwnHostArea, where the answer changes. A detail page
under a declaring bundle keeps its document, scroll and draft.
- In the host-area page, a workspace key the 32-entry init cap leaves out
is named in storageOversize, so the page cannot replace the device's
chat-tab list from a default. The init frame bounds that list to the
page's schema; the host still refuses every key in the full list.
- A leaving host-area session clears only its own report.
Claude-Session: lane-ipad-page-owns-host
* refactor(mobile): host-area ownership from the reducer, storage read on demand
- The serving report is a focus-scoped boolean, so a host pushed over
another no longer leaves the first one's area unclaimed after the pop.
- The reducer takes `layout-changed {wide}`: it keeps the session when the
route view is unchanged and restarts it otherwise; `routeViewOf` returns
`ownsHostArea`.
- One web override, `usePageOwnsHostArea`; the native rule is inline in the
host layout.
- A host-area page reads a host workspace key from the shell on demand
(`native.storage.read`, granted to that session only) instead of init
enumerating the store.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): host-area page reads back its own workspace-key writes
A page read right after a page write of the same key is answered from the
page's own value, not from a shell that may not have the notify yet. A
refused read crosses as an error, never as an unset key.
Claude-Session: lane-ipad-page-owns-host
* refactor(mobile): read-your-writes from the shell's mirror, final review fixes
- The shell answers a workspace-key read from the mirror, which every
shell-side write updates synchronously (removals held as known-absent),
and reads the store only on a miss. The page shim's written-keys set
goes.
- `onLayoutChanged` compares the route view field by field; the session
is created with its layout and the layout effect fires on `wide` alone.
- The device verb dispatcher's type excludes the storage read.
- The sidebar matrix mounts the real shell screen to decide the report.
Claude-Session: lane-ipad-page-owns-host
* feat(mobile): host-area page takes the whole screen from the download on
The native sidebar steps aside once the manifest says the page owns the
host area: fetching, activating, ready and the failure screens after them
are full screen, as on a phone. It stays while the host is being checked.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): host-area workspace-key read survives a write during it
On a mirror miss the read went through the mirror's hydrate, which drops
its answer when any mirrored write lands meanwhile, so a stored key read
as unset right after a relaunch. A miss now reads the store directly, and
a store failure rejects rather than reading as unset.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): a wide detail session hands the host route to the shell
A session opened on its own under a page that can own the host area
kept a hop to the host route in-page (a missing worktree's notice, or
leaving the session), drawing a second host list beside the native
sidebar. Its pairs now leave the host route out, so the hop goes to the
shell, which opens the area-owning session. Phones keep the hop in-page.
Claude-Session: lane-ipad-page-owns-host
* test(mobile): a phone mount's layout dispatch leaves the session alone
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): leaving a wide detail session returns to the host area
The leave-session chevron and the missing-worktree bounce hand the host
route to the shell on a wide layout, which pushed it: a second host-area
session over the session, with Back reopening the session. On a wide
layout the shell now dismisses to this host's route instead, taking the
href's params, or replaces the session when nothing is below it. Phones
still push.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): a narrow-wide flip restarts a detail session's page
The route view comparison now counts the paired routes, so a detail
session under a page that can own the host area restarts on a flip
instead of keeping the narrow pairs, whose host-route pair let the page
draw a second host list beside the native sidebar.
Claude-Session: lane-ipad-page-owns-host
|