Merge pull request #143 from nwparker/cloud/sync-upstream-main

Merge stablyai/orca main into cloud/integration (+ health fixes, gate/sync script updates)
This commit is contained in:
Neil
2026-11-09 18:48:01 -08:00
committed by GitHub
2403 changed files with 68857 additions and 71927 deletions
+32 -257
View File
@@ -2,193 +2,42 @@
Thanks for contributing to Orca.
## Before You Start
- Found a security vulnerability? Do not open a public issue or PR; report it privately as described in [`SECURITY.md`](./SECURITY.md).
- Keep changes scoped to a clear user-facing improvement, bug fix, or refactor.
- Orca targets macOS, Linux, and Windows. Every change must stay compatible with all three platforms unless the code is explicitly guarded by a runtime platform check.
- For keyboard shortcuts, use runtime platform checks in renderer code and `CmdOrCtrl` in Electron menu accelerators.
- For shortcut labels, show `⌘` and `⇧` on macOS, and `Ctrl+` and `Shift+` on Linux and Windows.
- For file paths, use Node or Electron path utilities such as `path.join`.
- Orca must work against local repositories, remote servers, and SSH worktrees. Do not assume a process, file, credential, shell, or network path exists only on the local machine.
- Orca supports many CLI agents, integrations, and git providers. Keep generic behavior provider-neutral; guard integration-specific logic behind explicit checks.
- Keep changes well-engineered and performant: follow existing architecture, avoid unnecessary work in hot paths, clean up owned resources, and use concrete module names.
- For UI work, follow [`docs/STYLEGUIDE.md`](../docs/STYLEGUIDE.md), use the tokens and shadcn primitives it specifies, and verify polished behavior across platforms, light/dark mode, and SSH latency.
Found a security vulnerability? Do not open a public issue or PR; report it privately as described in [`SECURITY.md`](./SECURITY.md).
## Local Setup
Install Node 24, pnpm, and the Bun version in [`config/.bun-version`](../config/.bun-version).
`pnpm test` runs Vitest on Bun, with Node workers for runtime contracts such as SQLite,
native PTYs, socket liveness, and V8 memory behavior. `pnpm test:node` runs the same suites
entirely on Node. Builds and dependency installation still use Node and pnpm.
The server, its workers and its packaged dependencies target Node 24. The compatibility
launcher and CLI still target Node 18; the launcher hands off to bundled Node 24 before
loading server code. WSL guest programs and host discovery retain their Node 18 minimum.
Keep Node 24 APIs and server imports out of these host compatibility paths. Running an
unpackaged server directly requires Node 24 or newer.
Use the pnpm version in `packageManager` in [`package.json`](../package.json): `corepack enable`
needs Corepack 0.34.5 or newer to start the pinned pnpm (`npm install -g corepack@latest`). On
Windows it writes shims next to `node.exe` and fails with `EPERM` outside an elevated shell when
Node is in `C:\Program Files\nodejs`; run `npm install -g pnpm@<that version>` instead.
`pnpm install` compiles native modules such as `node-pty` with `node-gyp` and loads them in
Electron, so a fresh machine also needs:
- **macOS**: Xcode Command Line Tools (`xcode-select --install`).
- **Windows**: Python 3 and Visual Studio Build Tools with the "Desktop development with C++"
workload.
- **Linux**: a C++ toolchain, Python 3, and the libraries Electron loads even on a headless
machine, plus `xvfb`, which the `*.electron.test.ts` files in `pnpm test` use when `DISPLAY` is
unset. On Ubuntu 24.04+ and Debian 13+:
```bash
sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb libgtk-3-0 libnss3 libgbm1 libasound2t64
```
Older releases name the last package `libasound2`.
Install Node 24, the pnpm version in `packageManager` in [`package.json`](../package.json), and the Bun version in [`config/.bun-version`](../config/.bun-version). `pnpm install` compiles native modules, so you also need a C++ toolchain and Python 3 (Xcode Command Line Tools on macOS, Visual Studio Build Tools on Windows, `build-essential` and Electron's libraries on Linux).
```bash
pnpm install
pnpm -C mobile install --frozen-lockfile # mobile/ has its own lockfile; build and lint need it
pnpm dev
```
`mobile/` is a separate pnpm project with its own lockfile, so the root `pnpm install` leaves its
dependencies uninstalled. `pnpm build` (`build:mobile-web` fails without them) and the lint
passes that cover `mobile/` need them. Install them before building or linting, and again
when `mobile/pnpm-lock.yaml` changes:
`pnpm dev` in a linked Git worktree uses its own profile and ports, so instances in different worktrees run side by side.
```bash
pnpm -C mobile install --frozen-lockfile
```
### Dev Profiles and Parallel Instances
`pnpm dev` in a linked Git worktree uses its own userData profile, renderer port, and remote
debugging (CDP) port, so dev instances in different worktrees run side by side. The main checkout
keeps the shared `orca-dev` profile. Ports are derived from the worktree path and move to the next
free port when taken: renderer ports start at 15200 and CDP ports at 9333.
On startup the runner prints one stdout line and writes the same JSON to `out/dev-instance.json`:
```text
ORCA_DEV_INSTANCE {"userData":".../orca-dev-wt/1d3830d4d3","profile":"worktree","rendererPort":15339,"rendererUrl":"http://localhost:15339","cdpPort":9513,"cdpUrl":"http://127.0.0.1:9513","repoRoot":"...","runnerPid":4242}
```
| Option | Effect |
| ----------------------------------------------------------------------- | ---------------------------------------------------------- |
| `pnpm dev --shared-profile` or `ORCA_DEV_PROFILE=shared` | Use the shared `orca-dev` profile |
| `pnpm dev --worktree-profile` or `ORCA_DEV_PROFILE=worktree` | Use a per-worktree profile, including in the main checkout |
| `ORCA_DEV_USER_DATA_PATH=<dir>` | Use an explicit profile directory |
| `REMOTE_DEBUGGING_PORT=<port>`, `ORCA_DEV_RENDERER_PORT=<port>` | Pin a port |
| `pnpm dev --seed-profile[=<dir>]` or `ORCA_DEV_SEED_PROFILE=<dir or 1>` | Seed an empty profile on first launch |
Per-worktree profiles live in `orca-dev-wt/<hash>` next to `orca-dev` (`~/Library/Application
Support` on macOS, `%APPDATA%` on Windows, `$XDG_CONFIG_HOME` or `~/.config` on Linux). The
automatic storage cleanup removes them once their worktree is gone and nothing still runs from
them. `config/scripts/orca-dev.mjs` and the `orca-dev` terminal wrapper target the profile this
checkout's `pnpm dev` used.
Seeding copies a slim set of state from another profile: projects, workspaces, settings, SSH
targets, and the Orca account session. Terminal sessions, PTY leases, automations, caches, logs,
and lock files are not copied, and the source profile is only read. The default source is
`orca-dev`, or the installed app's profile when `orca-dev` is empty. Seed from `orca-dev` when you
need the signed-in account session: dev builds cannot decrypt secrets the installed app encrypted.
```bash
pnpm dev:seed-profile [--from=<profile dir>] [--to=<profile dir>] [--reset]
pnpm dev:fresh [--keep] [--seed-from[=<profile dir>]] # temporary profile, removed on exit
```
### Targeting Local Tests
Use a test file or directory when you know the relevant suites. For dependency-based
feedback, Vitest can select tests affected by your edits:
```bash
pnpm test:changed src/main/runtime --maxWorkers=2
pnpm test --changed=origin/main --maxWorkers=2
pnpm test src/shared/automations/scheduling/automation-cron-input-validation.test.ts --maxWorkers=2
```
`test:changed` considers staged, unstaged, and untracked files. `--changed=origin/main`
also includes committed changes since the branch's merge base. Update that ref when
you need a newer baseline. Both accept the same path filters and options as `pnpm test`.
When Git reports no changed files at all, the launcher exits before starting Vitest.
Affected tests use Vitest's static import graph. Run suites explicitly for computed
dynamic imports, fixture files, subprocess inputs, or other dependencies outside that
graph. Local affected tests do not replace CI.
Use `pnpm test`, not raw `vitest`: only it keeps the transform cache that makes repeat runs fast.
Most unit files share a worker and its module cache with the files before them. After each file
the worker undoes mocks, fake timers, stubs, env, cwd, and leftover timers, listeners, and globals.
Files using `vi.doMock`, `vi.resetModules`, a DOM environment, or direct `globalThis` writes keep
their own worker. If a file passes alone but fails after others, add `// @orca-isolated-worker`
to it; `ORCA_VITEST_ISOLATE_EVERY_FILE=1` isolates every file to confirm a leak.
To reduce main-process rebuild CPU and memory when you do not need its source maps,
set `ORCA_DEV_MAIN_SOURCEMAPS=0` before `pnpm dev`. In Bash/zsh:
```bash
ORCA_DEV_MAIN_SOURCEMAPS=0 pnpm dev
```
In PowerShell, use `$env:ORCA_DEV_MAIN_SOURCEMAPS='0'` before `pnpm dev`.
Remove the variable when debugging the main process with source maps. Renderer and
preload settings are unaffected; production builds always keep their hidden main maps.
`pnpm dev` serves the renderer with Vite's bundled dev mode: a few bundled chunks instead of
thousands of module requests, so the first render and full reloads are faster. While a rebuild is
pending, a newly opened pop-out, space, or web window may briefly get placeholder HTML. If the
renderer misbehaves, set `ORCA_DEV_BUNDLED=0` (PowerShell: `$env:ORCA_DEV_BUNDLED='0'`) and report it.
Local installs use pnpm's global virtual store: each package version is one slot
under `~/pnpm/store` that every worktree links to, instead of a full `node_modules` per worktree.
An existing worktree converts on its next `pnpm install`, and an older branch without the setting
converts back the same way. CI always uses the normal layout. On Windows, pnpm links with
junctions when you lack the symlink privilege, so no administrator rights or Developer Mode are
needed. Things to know:
- Keep `~/node_modules` (and any other `node_modules` above the store) out of your home
directory. Node resolves upward from the store slot, so a stray one there supplies
dependencies the package never declared and hides missing-dependency bugs; `pnpm install` warns
when it finds one.
- Native addons are never rebuilt in place in a shared slot: each CPU gets its own build
beside it (see [`native-build-entry.cjs`](../config/scripts/native-build/node-addons/native-build-entry.cjs)), so
cross-arch packaging is safe for other worktrees.
- `ORCA_PNPM_GLOBAL_VIRTUAL_STORE=0` opts out. Keep it set
in your shell: `pnpm run` re-checks the install and relinks a worktree whose layout no longer
matches.
Ordinary installs include native optional dependencies for the current OS and CPU only.
Before a cross-architecture build (including `pnpm build:mac`, which produces both x64 and
arm64 artifacts by default), run `pnpm install:release` to add the other CPU's variants.
Linux `pnpm build`, `pnpm build:native`, and Linux packaging require a running Docker
daemon to build the clipboard helper against Ubuntu 20.04 rather than the host's
newer libraries. A verified cached helper can be reused without Docker. For native
Wayland development, run `pnpm build:native` before `pnpm dev` to make text copying
available. See the [clipboard build guide](../native/wayland-clipboard/README.md#building-and-testing).
macOS and Windows builds do not use this helper or require Docker for it.
See [`docs/development.md`](../docs/development.md) for per-OS packages, Corepack and Windows notes, dev profiles and seeding, cross-architecture and Linux (Docker) builds, and troubleshooting.
## Branch Naming
Use a clear, descriptive branch name that reflects the change.
Use a clear, descriptive branch name that reflects the change, such as `fix/ctrl-backspace-delete-word`, `feat/shift-enter-newline`, or `chore/update-contributor-guide`. Avoid vague names like `test`, `misc`, or `changes`.
Good examples:
## Code Guidelines
- `fix/ctrl-backspace-delete-word`
- `feat/shift-enter-newline`
- `chore/update-contributor-guide`
- Keep changes scoped to a clear user-facing improvement, bug fix, or refactor.
- Orca targets macOS, Linux, and Windows. Guard platform-specific code with runtime checks, use `CmdOrCtrl` in Electron menu accelerators, show `⌘`/`⇧` labels on macOS and `Ctrl+`/`Shift+` elsewhere, and build paths with `path.join`. See [cross-platform support](../docs/agents-md-refs/cross-platform-support.md).
- Orca must work against local repositories, remote servers, and SSH worktrees. Do not assume a process, file, credential, shell, or network path exists only on the local machine.
- Keep generic behavior provider-neutral across CLI agents, integrations, and git providers; guard integration-specific logic behind explicit checks.
- Follow existing architecture, avoid unnecessary work in hot paths, clean up owned resources, and use concrete module names.
- For UI work, follow [`docs/STYLEGUIDE.md`](../docs/STYLEGUIDE.md) and verify behavior across platforms, light/dark mode, and SSH latency.
Avoid vague names like `test`, `misc`, or `changes`.
### Type Declarations: Prefer `.ts` Over `.d.ts`
## Before Opening a PR
Put project-owned types in `.ts` files. `.d.ts` is reserved for ambient shims (e.g., `env.d.ts`). `skipLibCheck: true` applies to our own `.d.ts` files too, so an unresolved type reference there silently becomes `any`. CI enforces this for `src/preload/` and `src/shared/`.
Run the same checks that CI runs, after the `mobile/` install from [Local Setup](#local-setup):
## Checks Before Opening a PR
Run the same checks CI runs, after the `mobile/` install above:
```bash
pnpm lint
@@ -197,102 +46,28 @@ pnpm test
pnpm build
```
`pnpm lint` runs independent checks in a bounded pool. Use
`pnpm lint --maxWorkers=2 --timings` to limit concurrent checks and print their durations.
For faster repeat typechecks use `pnpm tc` (or `pnpm tc:node`, `pnpm tc:web`, `pnpm tc:cli`); run `pnpm tc:node` after editing shared tests.
For repeated local typechecks, use `pnpm tc` (or `pnpm tc:node`, `pnpm tc:web`,
and `pnpm tc:cli`). On Apple Silicon macOS and x64/arm64 Linux, these use the pinned
`tsrs` compiler with incremental caches. Keep `.cache/typecheck/` between checks;
delete it to force a fresh check. Caches are separate for each project, compiler
version, and worktree. At most two projects run together, subject to the memory budget.
Windows, Intel macOS, and other unsupported platforms use pinned Bun automatically.
Pass `--compiler=bun` to explicitly use Bun, or `--compiler=native` to use TypeScript.
CI uses `pnpm typecheck:ci`, which explicitly selects Bun and its CI concurrency.
Shared tests are checked by the
Node project; shared production code is checked in all three environments.
Run `pnpm tc:node` after editing shared tests, or `pnpm tc` to check everything.
Add a test when it would catch a regression of your behavior change or bug fix, not for shallow coverage; see [test anti-patterns](../docs/agents-md-refs/test-antipatterns.md). If your change affects UI or interaction behavior, verify it on the platforms it could impact.
Add high-quality tests for behavior changes and bug fixes. Prefer tests that would actually catch a regression, not shallow coverage that only exercises the happy path.
### Targeting Local Tests
If your change affects UI or interaction behavior, verify it on the platforms it could impact.
Use `pnpm test`, not raw `vitest`: only it keeps the transform cache that makes repeat runs fast.
## Type Declarations: Prefer `.ts` Over `.d.ts`
```bash
pnpm test src/shared/automations/scheduling/automation-cron-input-validation.test.ts --maxWorkers=2
pnpm test:changed src/main/runtime --maxWorkers=2 # staged, unstaged, and untracked changes
pnpm test --changed=origin/main --maxWorkers=2 # also commits since the merge base
```
Project-owned type declarations belong in `.ts` files. `.d.ts` is reserved for ambient shims (e.g., `env.d.ts`, `vite/client.d.ts`). TypeScript's `skipLibCheck: true` setting applies globally, including to our own `.d.ts` files, which means any unresolved type reference in a `.d.ts` silently becomes `any` at its call sites. Write your types in `.ts` files so the compiler actually checks them.
CI enforces this for `src/preload/` and `src/shared/`.
Affected tests follow Vitest's static import graph, so run suites explicitly for dynamic imports, fixtures, or subprocess inputs. Local affected tests do not replace CI. If a file passes alone but fails after others, add `// @orca-isolated-worker` to it.
## Pull Requests
Each pull request should follow [`.github/pull_request_template.md`](./pull_request_template.md). In particular:
Fill in every section of [`.github/pull_request_template.md`](./pull_request_template.md): an ELI5, what changed and why, a linked issue (required for outside contributors), a before and after (screenshots or videos) for any UI or behavior change (only if there is truly no visual or interaction change, write exactly `N/A` and briefly say why), how you tested it, the risk, and under Review a short summary from your AI coding agent covering cross-platform, SSH/remote, agent and integration compatibility, performance, UI quality, and security. Keep each PR small and focused on one topic.
- if you are an outside contributor, link the issue your PR addresses
- open with an ELI5 of the change (plain language paragraph; the PR title is the one-liner)
- explain what changed and why, and stay focused on a single topic when possible
- for any UI or interaction change, attach **before and after** screenshots (or short videos); if there is no visual or interaction change, write `N/A` and briefly explain why
- include high-quality tests when behavior changes or bug fixes warrant them
- include a brief code review summary from your AI coding agent that explicitly checks cross-platform compatibility, SSH/remote/local compatibility, supported agent and integration compatibility, performance risk, UI quality when applicable, and basic security risk
- mention any platform-specific, remote/SSH-specific, agent-specific, integration-specific, or git-provider-specific behavior and testing notes
- **Include your X (Twitter) handle** in the PR template Author section — we shout out contributors when we merge features on [@orca_build](https://x.com/orca_build).
Version bumps, tags, and releases are maintainer-managed. Do not include release version changes unless a maintainer asks. Maintainers cut releases with the [Cut Release](https://github.com/stablyai/orca/actions/workflows/release-cut.yml) workflow.
## Release Process
## Getting Help
Version bumps, tags, and releases are maintainer-managed. Do not include release version changes in a normal contribution unless a maintainer asks for them.
### Cutting a release (maintainers)
All releases are cut from the **Cut Release** GitHub Actions workflow. There is no local `pnpm release:*` script — running releases locally is too easy to get wrong (dirty tree, wrong branch, stale main).
**To cut a release:**
1. Open [Actions → Cut Release](../../actions/workflows/release-cut.yml).
2. Click **Run workflow** and pick:
- **kind**: one of `rc`, `patch`, `minor`, `major`.
- **ref**: the branch, tag, or SHA to build from. Defaults to `main`.
3. Run it.
The workflow resolves the next version from GitHub Releases, bumps `package.json`, tags, pushes, and runs the multi-platform build + publish inline.
**How the next version is chosen:**
All stable kinds (`patch`, `minor`, `major`) are computed off the latest _stable_ release, ignoring any RCs in between.
- `kind=rc` + last tag was stable (e.g. `v1.3.14`) → `v1.3.15-rc.0`.
- `kind=rc` + active RC series (e.g. `v1.3.15-rc.2`) → `v1.3.15-rc.3`.
- `kind=patch` + latest stable `v1.3.14` → `v1.3.15` (regardless of any intermediate RCs).
- `kind=minor` + latest stable `v1.3.14` → `v1.4.0`.
- `kind=major` + latest stable `v1.3.14` → `v2.0.0`.
**Safety guarantees:**
- Stable releases are refused if the new version isn't strictly greater than the latest published stable. This is the only rule `electron-updater` actually needs — it compares semver within the `latest` channel, so a regressing stable is the one thing that breaks auto-update for fresh installs.
- Complete RC draft releases created by the release workflow are published before cutting a new tag only when the draft tag was built from the current release ref. Stale drafts are skipped so fixes cut a fresh RC instead of exposing old artifacts.
- If the latest RC tag exists but is still draft-only or missing its GitHub Release, the workflow resumes that tag only when it was built from the current release ref. Otherwise the next RC number is cut.
- RC numbering also considers release commits on `main`, so deleting a stale tag does not let a later cut reuse the same RC number.
- Off-main releases (when `ref` is not the tip of `main`) only push the tag. `main` is never mutated from a non-main ref, so you can safely release an older commit without polluting history.
- When `ref` is the tip of `main`, the workflow tries to fast-forward the version-bump commit onto `main`. If main requires pull requests, it leaves main unchanged and pushes only the release tag. Other push errors still stop the cut.
**Common scenarios:**
- **Normal release:** `kind=patch`, `ref=main`.
- **"A bad commit just landed on main, release the commit before it":** `kind=patch`, `ref=<good-sha>`. `main` is left alone; the tag points at the good SHA. Fix forward on `main` afterward.
- **One-off RC for a feature branch:** `kind=rc`, `ref=<branch-or-sha>`. Produces an RC tag that does not touch `main`.
- **Minor or major bump:** `kind=minor` or `kind=major`.
## Release Channels
The public Homebrew cask tracks stable desktop releases:
```bash
brew install --cask stablyai/orca/orca
```
Release candidates use a separate cask token:
```bash
brew install --cask stablyai/orca/orca@rc
```
The two casks conflict because both install `Orca.app`. Switch channels with a
normal `brew uninstall --cask` followed by the install for the other channel.
Do not use `--zap` unless you intentionally want to remove local Orca state.
Ask questions on [Discord](https://discord.gg/fzjDKHxv8Q), or open an [issue](https://github.com/stablyai/orca/issues/new/choose) for bugs and feature requests.
@@ -19,7 +19,6 @@ on:
# Contracts without the .performance suffix. Keep in sync with config/vitest.performance.config.ts;
# without these a rename lands green and only breaks the next nightly.
- 'src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts'
- 'src/renderer/src/components/terminal-pane/pty-connection/startup/saved-resume-cancellation.test.ts'
- 'src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-terminal-writer-acquire.test.ts'
- 'src/shared/agent-launch/permissions/tui-agent-permission-choice.test.ts'
- 'src/main/runtime/rpc/methods/projects/repo.test.ts'
@@ -33,7 +32,6 @@ on:
- 'src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts'
- 'src/main/claude-usage/scanner-incremental-append.test.ts'
- 'src/main/claude-usage/store-checkpoint-persistence.test.ts'
- 'src/renderer/src/components/native-chat/surfaces/integration/NativeChatResolvedView.streaming-input.test.tsx'
permissions:
contents: read
-1
View File
@@ -1111,7 +1111,6 @@ jobs:
src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts
src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-adapter-compatibility.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts
src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts
+1
View File
@@ -99,6 +99,7 @@ docs/**
!docs/bug-reproductions/
!docs/bug-reproductions/**
!docs/STYLEGUIDE.md
!docs/development.md
!docs/styleguide/
!docs/styleguide/**
!docs/agents-md-refs/
+2
View File
@@ -272,6 +272,8 @@ _Or via a package manager:_
brew install --cask stablyai/orca/orca
# Upgrade an existing install
brew upgrade --cask stablyai/orca/orca
# Release candidates (conflicts with the stable cask; uninstall it first)
brew install --cask stablyai/orca/orca@rc
# Arch Linux (AUR) — or stably-orca-git to build from source
yay -S stably-orca-bin
@@ -0,0 +1,36 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
// Why: import.meta.url, not dirname — electron-vite's config bundle rewrites only the former.
const REPO_ROOT = fileURLToPath(new URL('../..', import.meta.url))
export const UNCOMPILED_LIST = 'config/react-compiler-uncompiled-files.txt'
// Hand-kept list plus every file still in a hazard ratchet; compiled, these would run differently.
export const UNCOMPILED_FILE_LISTS = [
UNCOMPILED_LIST,
'config/render-path-translate-baseline.txt',
'config/react-compiler-hazard-baseline.txt'
]
/** Repo-relative paths from a list or count baseline: `[count ]path[#detail][ # note]`. */
export function parseListedFiles(text) {
return text
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'))
.map((line) =>
line
.replace(/^\d+\s+/, '')
.split('#')[0]
.trim()
)
}
export function readListedFiles(list, root = REPO_ROOT) {
return parseListedFiles(readFileSync(resolve(root, list), 'utf8'))
}
export function reactCompilerExcludedFiles(root = REPO_ROOT) {
return [...new Set(UNCOMPILED_FILE_LISTS.flatMap((list) => readListedFiles(list, root)))]
}
+1 -21
View File
@@ -1,25 +1,5 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import type { Options } from '@vitejs/plugin-react'
// Hand-kept list plus every file still in a hazard ratchet; compiled, these would run differently.
const UNCOMPILED_FILE_LISTS = [
'config/react-compiler-uncompiled-files.txt',
'config/render-path-translate-baseline.txt',
'config/react-compiler-hazard-baseline.txt'
]
function listedFiles(list: string): string[] {
return readFileSync(resolve(list), 'utf8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'))
.map((line) => line.replace(/^\d+\s+/, '').split('#')[0])
}
export function reactCompilerExcludedFiles(): string[] {
return [...new Set(UNCOMPILED_FILE_LISTS.flatMap(listedFiles))]
}
import { reactCompilerExcludedFiles } from './react-compiler-uncompiled-files.mjs'
export function isReactCompilerEnabled(): boolean {
return process.env.ORCA_REACT_COMPILER !== '0'
+14 -19
View File
@@ -20,9 +20,12 @@ const {
assertPackagedNativeVariantsInstalled,
assertProcessHostOutputBuilt,
createPackagedRuntimeNodeModuleResources,
normalizeElectronArchitecture,
prunePackagedRuntimeNodeModules,
readPackage,
verifyPackagedMainRuntimeDeps
} = require('./packaged-runtime-node-modules.cjs')
const { copyPackagedTranscribeCpp } = require('./packaged-transcribe-cpp.cjs')
const {
assertNoWindowsExecutablesInRuntimeNodeModules
} = require('./packaged-windows-only-runtime-files.cjs')
@@ -171,19 +174,6 @@ const commonExtraResources = [
nativeChatVisualsResource,
emojiShortcodeDatasetResource
]
// Native speech addons need real disk paths; copy only the artifact target’s variant.
const macSpeechNativeResource = {
from: 'node_modules/sherpa-onnx-darwin-${arch}',
to: 'node_modules/sherpa-onnx-darwin-${arch}'
}
const linuxSpeechNativeResource = {
from: 'node_modules/sherpa-onnx-linux-${arch}',
to: 'node_modules/sherpa-onnx-linux-${arch}'
}
const winSpeechNativeResource = {
from: 'node_modules/sherpa-onnx-win-x64',
to: 'node_modules/sherpa-onnx-win-x64'
}
// electron-builder replaces these defaults when `depends` is configured; retain
// Electron's loader requirements alongside Orca's headless-host dependencies.
const debElectronRuntimeDependencies = [
@@ -329,8 +319,8 @@ module.exports = {
'!resources/native-chat-visuals/**',
// extraResources already ships plugin bytes; exclude source duplicates.
'!resources/plugins/launch/**',
// Platform extraResources selects speech natives; asar would duplicate it and ship other variants.
'!node_modules/sherpa-onnx*{,/**/*}',
// afterPack copies the target's speech engine to resources/node_modules; asar would duplicate it and ship other variants.
'!node_modules/{transcribe-cpp,@transcribe-cpp,koffi,@koromix}{,/**/*}',
// CLI shim belongs beside resources/bin/orca.exe; unpacking source creates a second .cmd without its executable (#7351).
'!resources/win32{,/**/*}'
],
@@ -450,6 +440,13 @@ module.exports = {
writeMacBuildCompatibility(resourcesDir, { version, commit, architecture })
}
stampPackagedCliVersion(resourcesDir, context.packager.appInfo.version)
// Before the prune, which drops the copied packages' type declarations too.
copyPackagedTranscribeCpp(
readPackage,
resourcesDir,
context.electronPlatformName,
normalizeElectronArchitecture(context.arch)
)
prunePackagedRuntimeNodeModules(resourcesDir, context.electronPlatformName, context.arch)
assertNoWindowsExecutablesInRuntimeNodeModules(resourcesDir, context.electronPlatformName)
// Gate native glibc floor after pruning foreign CPU variants; GLIBC_2.34 once broke Ubuntu 20.04 startup (#9902).
@@ -553,7 +550,6 @@ module.exports = {
extraResources: [
...commonExtraResources,
...windowsRuntimeResources,
winSpeechNativeResource,
{
from: 'resources/win32/bin/orca.cmd',
to: 'bin/orca.cmd'
@@ -610,7 +606,8 @@ module.exports = {
rank: 'Alternate'
}))
],
// Bundled ONNX Runtime (speech) needs macOS 13.4 (#22690); the update-manifest gate reads this from Info.plist.
// Set for ONNX Runtime (#22690, #39135), now gone; transcribe-cpp needs 11.0, Electron 12.0. Kept until
// Kelvin decides. The update-manifest gate reads this from Info.plist.
minimumSystemVersion: '13.4',
icon: 'resources/build/icon.icns',
entitlements: 'resources/build/entitlements.mac.plist',
@@ -633,7 +630,6 @@ module.exports = {
extraResources: [
...commonExtraResources,
...createPackagedRuntimeNodeModuleResources('darwin'),
macSpeechNativeResource,
{
from: 'resources/darwin/bin/orca',
to: 'bin/orca'
@@ -698,7 +694,6 @@ module.exports = {
extraResources: [
...commonExtraResources,
...createPackagedRuntimeNodeModuleResources('linux'),
linuxSpeechNativeResource,
{
from: 'native/wayland-clipboard/.build/${arch}/orca-wayland-clipboard',
to: 'bin/orca-wayland-clipboard'
+2 -7
View File
@@ -147,7 +147,6 @@
2 test-no-real-time-wait:src/main/agent-hooks/server/testing/http/server-transport-interference.test.ts
1 test-no-real-time-wait:src/main/agent-hooks/server/testing/main-agent/server-turn-ended-at.test.ts
1 test-no-real-time-wait:src/main/agent-hooks/server/testing/main-agent/server-turn-started-at.test.ts
5 test-no-real-time-wait:src/main/agent-hooks/wsl-relay/wsl-hook-relay-manager.test.ts
1 test-no-real-time-wait:src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-process.test.ts
2 test-no-real-time-wait:src/main/ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-scan-scope.test.ts
1 test-no-real-time-wait:src/main/antigravity/windows-hook-json-post.test.ts
@@ -265,7 +264,6 @@
4 test-no-real-time-wait:src/renderer/src/components/activity/activity-auto-mark-read-loop.react185.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/editor/DiagramViewerModal.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-stale-analysis-work-budget.test.tsx
1 test-no-real-time-wait:src/renderer/src/components/native-chat/drafts/native-chat-composer-draft-lifecycle.test.tsx
4 test-no-real-time-wait:src/renderer/src/components/native-chat/drafts/native-chat-composer-draft-store.test.ts
1 test-no-real-time-wait:src/renderer/src/components/terminal-pane/layout/split/resolve-split-cwd.test.ts
2 test-no-real-time-wait:src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme-child-stdin.node-pty.test.ts
@@ -458,7 +456,6 @@
1 test-no-wall-clock:src/cli/runtime/status-caller.test.ts
2 test-no-wall-clock:src/main/agent-hooks/installation/scripts/managed-hook-timeout.test.ts
2 test-no-wall-clock:src/main/agent-hooks/installation/scripts/posix-hook-json-stdin-reader.test.ts
1 test-no-wall-clock:src/main/agent-hooks/server/testing/providers/codex/server-codex-capacity.test.ts
1 test-no-wall-clock:src/main/agent-hooks/server/testing/http/server-endpoint-file-lifecycle.test.ts
3 test-no-wall-clock:src/main/agent-hooks/server/testing/structured-ingress/server-ingest-structured-status.test.ts
4 test-no-wall-clock:src/main/agent-hooks/server/testing/identity/server-launch-resume-identity.test.ts
@@ -636,12 +633,11 @@
2 test-no-wall-clock:src/renderer/src/components/dashboard/useLiveDashboardSnapshot.test.ts
1 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/slots/NativeChatMessageList.opening-send.test.tsx
1 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/slots/NativeChatMessageList.queued-turn-order.test.tsx
3 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/turns/NativeChatMessageList.turn-indicator.test.tsx
2 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/turns/NativeChatMessageList.turn-indicator.test.tsx
3 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/turns/NativeChatResolvedView.turn-status.test.tsx
1 test-no-wall-clock:src/renderer/src/components/native-chat/session/structured/delivery/NativeChatMessageList.unsent-message.test.tsx
3 test-no-wall-clock:src/renderer/src/components/native-chat/surfaces/integration/NativeChatMessageList.test.tsx
2 test-no-wall-clock:src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx
1 test-no-wall-clock:src/renderer/src/components/settings/accounts/providers/GrokAccountsSection.test.tsx
3 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx
1 test-no-wall-clock:src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.lineage-agent-expansion-coupling.test.tsx
2 test-no-wall-clock:src/renderer/src/components/task-page/jira/task-page-jira-cache-selectors.test.ts
@@ -692,7 +688,6 @@
2 test-timeout-needs-reason:src/main/acp/journal/acp-structured-broken-stream.test.ts
4 test-timeout-needs-reason:src/main/agent-hooks/server/testing/http/server-transport-interference.test.ts
1 test-timeout-needs-reason:src/main/agent-hooks/wsl-relay/testing/wsl-hook-relay-live.integration.test.ts
1 test-timeout-needs-reason:src/main/agent-hooks/wsl-relay/wsl-hook-relay-manager.test.ts
1 test-timeout-needs-reason:src/main/ai-vault/parse-cache/session-parse-cache-large-scan.test.ts
1 test-timeout-needs-reason:src/main/antigravity/native-macos-credentials.real.test.ts
1 test-timeout-needs-reason:src/main/antigravity/posix-hook-invocation.test.ts
@@ -707,7 +702,7 @@
1 test-timeout-needs-reason:src/main/browser/cookies/browser-cookie-import-validated-partition.electron.test.ts
1 test-timeout-needs-reason:src/main/browser/guest-lifecycle/find/browser-find-frame-traversal.electron.test.ts
1 test-timeout-needs-reason:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity-cross-context.electron.test.ts
2 test-timeout-needs-reason:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity.electron.test.ts
1 test-timeout-needs-reason:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity.electron.test.ts
3 test-timeout-needs-reason:src/main/claude-usage/scanner-incremental-reader.test.ts
1 test-timeout-needs-reason:src/main/claude/claude-agent-sdk-exit-proof.test.ts
1 test-timeout-needs-reason:src/main/claude/claude-background-run-isolation-real-cli.test.ts
+1 -1
View File
@@ -12,7 +12,7 @@
3 src/renderer/src/components/LinearIssueTextEditor.tsx
3 src/renderer/src/components/LinearIssueWorkspace.tsx
5 src/renderer/src/components/LinearItemDrawer.tsx
1 src/renderer/src/components/NativeChatResumeOnRestartGroups.tsx
1 src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.tsx
1 src/renderer/src/components/activity/ActivityPrototypePage.tsx
1 src/renderer/src/components/activity/activity-scope-filter-controls.tsx
2 src/renderer/src/components/activity/activity-thread-actions.ts
@@ -15,7 +15,6 @@ src/main/managed-server/migration/cutover/orcad-migration-delta-move.ts
src/main/managed-server/migration/linking/orcad-migration-repository-link.test.ts
src/main/ssh/migration-source/orcad-migration-terminal-gate.test.ts
src/main/ssh/migration-source/orcad-migration-terminal-gate.ts
src/main/ssh/host-server/ssh-host-server-on-connect.test.ts
src/main/ssh/host-server/ssh-host-server-on-connect.ts
src/main/ssh/targets/ssh-target-orcad-preflight.ts
src/preload/api-types.ts
@@ -14,4 +14,3 @@ tests/e2e/cross-version-wire/legacy-relay-bridge.unit.test.ts
tests/e2e/helpers/legacy-relay-docker-host.ts
src/main/managed-server/install/remote-install-model.ts
src/main/ssh/system-transport/ssh-system-transport.integration.test.ts
src/main/ssh/migration-source/orcad-migration-bridged-panes.test.ts
+4 -3
View File
@@ -3,9 +3,10 @@
# This is a RATCHET: counts may only go DOWN. Layout moves to one runtime owner; do not add
# writers. Prune after removing writers: pnpm check:layout-writer-ratchets --prune
1 src/main/ipc/pty/ipc/leaf-move.ts
1 src/main/ipc/pty/pane/fresh-pane-binding.ts
1 src/main/ipc/pty/pane/pane-owner-replacement.ts
3 src/main/ipc/pty/pane/stable-owner.ts
2 src/main/ipc/pty/spawn-commit-binding.ts
1 src/main/ipc/pty/spawn-commit-binding.ts
1 src/main/ipc/renderer-shutdown-checkpoint.ts
1 src/main/ipc/runtime-environments/runtime-environment-removal-cleanup.ts
1 src/main/ipc/runtime-environments/runtime-environment-session-reconcile.ts
@@ -32,6 +33,7 @@
2 src/main/persistence/migrating-orcad-catalog/orcad-destination-client-state.ts
1 src/main/persistence/migrating-orcad-catalog/orcad-destination-workspace-session.ts
3 src/main/persistence/migrating-orcad-catalog/orcad-dormant-state-records.ts
1 src/main/persistence/migrating-orcad-catalog/orcad-migration-source-retention.ts
2 src/main/persistence/migrating-orcad-catalog/orcad-source-client-subtraction.ts
1 src/main/persistence/migrating-orcad-catalog/orcad-source-dormant-state.ts
1 src/main/persistence/migrating-orcad-catalog/orcad-source-scrollback-state.ts
@@ -53,11 +55,11 @@
2 src/main/pty/scrollback/terminal-scrollback-snapshots.ts
1 src/main/runtime/browser/client-pages/client-hosted-browser-page-persistence.ts
1 src/main/runtime/client/runtime-client-state.ts
1 src/main/runtime/session-tabs/headless-editor-tabs.ts
4 src/main/runtime/orchestration/terminal-authority/runtime-legacy-worker-terminal-recovery-persistence.ts
2 src/main/runtime/session-graph/session-graph-promotion.ts
2 src/main/runtime/session-lifecycle/projection/apply-mobile-session-tab-navigation.ts
2 src/main/runtime/session-lifecycle/projection/build-headless-mobile-session-browser-tabs.ts
1 src/main/runtime/session-tabs/headless-editor-tabs.ts
2 src/main/runtime/session-tabs/headless-tab-group-moves.ts
6 src/main/runtime/session-tabs/headless-tab-properties.ts
2 src/main/runtime/session-tabs/headless-terminal-tab-titles.ts
@@ -72,5 +74,4 @@
3 src/main/runtime/terminal/surface/mobile-session-layout-projection.ts
1 src/main/runtime/workspaces/session-hosts/get-runtime-id.ts
1 src/main/runtime/workspaces/session-hosts/runtime-workspace-session-controller.ts
1 src/main/persistence/migrating-orcad-catalog/orcad-migration-source-retention.ts
1 src/main/ssh/targets/ssh-target-id-migration.ts
+5 -2
View File
@@ -283,6 +283,7 @@
1 src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-subscribe.ts
1 src/main/ipc/filesystem/watcher-process/rebinding-file-watch.ts
1 src/main/ipc/filesystem/watcher-process/watcher-event-root-path-rewrite.ts
1 src/main/ipc/floating-workspace-directory.ts
1 src/main/ipc/macos-keyboard-layout-snapshot.ts
1 src/main/ipc/notification-authorization-status.ts
1 src/main/ipc/pty/spawn-pane-env.ts
@@ -497,9 +498,10 @@
10 src/main/speech/openai-api-key-store.ts
2 src/main/speech/speech-model-download-cleanup.ts
1 src/main/speech/speech-model-download-transport.ts
1 src/main/speech/speech-model-files.ts
2 src/main/speech/speech-model-http-download.ts
1 src/main/speech/stt-worker-model-config.ts
2 src/main/speech/stt-worker-paths.ts
1 src/main/speech/speech-vad-model.ts
2 src/main/speech/stt-gpu-failure-marker.ts
2 src/main/sqlite/harden-database-files.ts
1 src/main/sqlite/row-cache/keyed-json-row-cache.ts
2 src/main/sqlite/row-cache/legacy-json-source.ts
@@ -610,6 +612,7 @@
7 src/shared/serve-bundle-park.ts
1 src/shared/serve-user-data-path.ts
3 src/shared/source-scan/source-tree-scan.ts
2 src/shared/speech-recognizer-package.ts
5 src/shared/startup-command-staging.ts
1 src/shared/terminal/pty/native-process/pty-process-ceiling-state.ts
8 src/shared/windows-breakaway-launcher.ts
@@ -307,6 +307,27 @@
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/terminal/session/MobileSessionLatchKeys.tsx",
"kind": "object-property:label",
"text": "Shift",
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/terminal/session/MobileSessionLatchKeys.tsx",
"kind": "object-property:label",
"text": "Ctrl",
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/terminal/session/MobileSessionLatchKeys.tsx",
"kind": "object-property:label",
"text": "Alt",
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/tasks/mobile-smart-source-modes.ts",
"kind": "object-property:label",
@@ -14,7 +14,7 @@
"import/export": "warn",
"import/named": "warn",
"import/namespace": "warn",
"import/no-cycle": ["warn", { "maxDepth": 3 }],
"import/no-cycle": ["warn", { "ignoreTypes": true }],
"import/no-duplicates": "warn",
"import/no-empty-named-blocks": "warn",
"import/no-self-import": "warn"
+4 -39
View File
@@ -15,6 +15,7 @@ const {
} = require('./scripts/native-build/windows/windows-pe-machine.cjs')
const { adoptPackagedNativeBuildEntries } = require('./packaged-native-build-entries.cjs')
const { runtimePackageExcludeFilter } = require('./packaged-windows-only-runtime-files.cjs')
const { missingTranscribeCppPackages } = require('./packaged-transcribe-cpp.cjs')
const {
NATIVE_BUILD_PACKAGES,
TRANSIENT_NATIVE_BUILD_ENTRY_FILTER
@@ -81,7 +82,6 @@ const PACKAGED_MAIN_REQUIRED_FILES = [
const PACKAGED_MAIN_SOURCE_RE = /^out\/main\/.+\.js$/
const TYPE_DECLARATION_ARTIFACT_RE = /\.d\.(?:c|m)?ts(?:\.map)?$/
const JS_SOURCE_MAP_ARTIFACT_RE = /\.(?:c|m)?js\.map$/
const VERSIONED_ONNXRUNTIME_DYLIB_RE = /^libonnxruntime\.\d[\d.]*\.dylib$/
const NODE_BUILTINS = new Set([
...builtinModules,
@@ -518,31 +518,6 @@ function prunePackagedRuntimeTypeAndSourceMapArtifacts(resourcesDir) {
pruneMatchingFiles(nodeModulesDir, isPrunableTypeOrSourceMapArtifact)
}
function prunePackagedSherpaOnnx(resourcesDir, electronPlatformName) {
if (electronPlatformName !== 'darwin') {
return
}
const nodeModulesDir = join(resourcesDir, 'node_modules')
if (!existsSync(nodeModulesDir)) {
return
}
for (const entry of readdirSync(nodeModulesDir, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith('sherpa-onnx-darwin-')) {
continue
}
const packageDir = join(nodeModulesDir, entry.name)
const packageEntries = readdirSync(packageDir)
const hasVersionedOnnxRuntime = packageEntries.some((filename) =>
VERSIONED_ONNXRUNTIME_DYLIB_RE.test(filename)
)
if (hasVersionedOnnxRuntime) {
// Why: darwin sherpa-onnx binaries link to the versioned ONNX Runtime
// install name; the unversioned dylib is a duplicate fallback copy.
rmSync(join(packageDir, 'libonnxruntime.dylib'), { force: true })
}
}
}
function prunePackagedZodSources(resourcesDir) {
// Why: Zod's src tree is TypeScript source only selected by the @zod/source
// condition; packaged runtime import/require paths resolve to built JS.
@@ -556,17 +531,7 @@ function assertPackagedNativeVariantsInstalled(electronPlatformName, electronArc
const isInstalled = (name) => existsSync(join(nodeModulesDir, name, 'package.json'))
const missing = []
const rootOptionalDependencies =
JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8')).optionalDependencies ?? {}
// Why win32 is always x64: winSpeechNativeResource packages sherpa-onnx-win-x64 for every
// Windows target (there is no sherpa-onnx-win-arm64; it runs under emulation).
const sherpaName =
electronPlatformName === 'win32'
? 'sherpa-onnx-win-x64'
: `sherpa-onnx-${electronPlatformName}-${architecture}`
if (sherpaName in rootOptionalDependencies && !isInstalled(sherpaName)) {
missing.push(sherpaName)
}
missing.push(...missingTranscribeCppPackages(readPackage, electronPlatformName, architecture))
// Why prefix, not equality: linux variants carry a libc suffix (watcher-linux-x64-glibc),
// mirroring what prunePackagedParcelWatcher keeps.
@@ -634,7 +599,6 @@ function prunePackagedRuntimeNodeModules(resourcesDir, electronPlatformName, ele
// Why before the filename walk: zod/src is deleted wholesale, so walking it first is wasted work.
prunePackagedZodSources(resourcesDir)
prunePackagedRuntimeTypeAndSourceMapArtifacts(resourcesDir)
prunePackagedSherpaOnnx(resourcesDir, electronPlatformName)
}
function pruneMatchingFiles(directory, shouldPrune) {
@@ -656,13 +620,14 @@ module.exports = {
createPackagedRuntimeNodeModuleResources,
findAsarEntry,
isPackagedExternalSpecifier,
normalizeElectronArchitecture,
normalizeNodePtyWindowsArch,
packageNameFromSpecifier,
prunePackagedNodePty,
prunePackagedParcelWatcher,
prunePackagedRuntimeNodeModules,
prunePackagedRuntimeTypeAndSourceMapArtifacts,
prunePackagedSherpaOnnx,
prunePackagedZodSources,
readPackage,
verifyPackagedMainRuntimeDeps
}
+97
View File
@@ -0,0 +1,97 @@
const { cpSync, existsSync } = require('node:fs')
const { execFileSync } = require('node:child_process')
const { join } = require('node:path')
const TRANSCRIBE_CPP_PACKAGES = require('../src/shared/transcribe-cpp-packages.json')
// transcribe.dll needs MSVC runtime >= 14.40 (older ones crash in std::mutex); the system copy may be older.
const WINDOWS_CRT_DLLS = ['msvcp140.dll', 'vcruntime140.dll', 'vcruntime140_1.dll']
const MIN_WINDOWS_CRT_VERSION = [14, 40]
/**
* The packages the speech host loads transcribe-cpp from, in load order: the ESM binding, koffi
* (its FFI), koffi's prebuilt addon, and the target's native libraries. Each resolves from the
* previous one's directory, as Node does at runtime. Null where the target ships no transcribe-cpp.
*/
function transcribeCppRuntimePackages(readPackage, electronPlatformName, architecture) {
const native = TRANSCRIBE_CPP_PACKAGES[`${electronPlatformName}-${architecture}`]
if (!native) {
return null
}
const binding = readPackage('transcribe-cpp')
const koffi = readPackage('koffi', binding.packageDir)
const koffiAddon = readPackage(
`@koromix/koffi-${electronPlatformName}-${architecture}`,
koffi.packageDir
)
const nativeLibraries = readPackage(native, binding.packageDir)
return [binding, koffi, koffiAddon, nativeLibraries]
}
function readFileVersion(dll) {
const version = execFileSync(
'powershell.exe',
['-NoProfile', '-Command', `(Get-Item -LiteralPath '${dll}').VersionInfo.FileVersion`],
{ encoding: 'utf8' }
).trim()
return version.split(/[.,\s]+/).map((part) => Number.parseInt(part, 10))
}
function copyWindowsCrt(nativeDir) {
const systemDir = join(process.env.SystemRoot || 'C:\\Windows', 'System32')
for (const name of WINDOWS_CRT_DLLS) {
const source = join(systemDir, name)
if (!existsSync(source)) {
throw new Error(
`Packaging transcribe-cpp needs ${source} (Visual C++ 2015-2022 x64 runtime).`
)
}
const [major, minor] = readFileVersion(source)
if (!Number.isFinite(major) || !Number.isFinite(minor)) {
throw new Error(`Cannot read the file version of ${source}.`)
}
const [minMajor, minMinor] = MIN_WINDOWS_CRT_VERSION
if (major < minMajor || (major === minMajor && minor < minMinor)) {
throw new Error(
`${source} is ${major}.${minor}; transcribe-cpp needs ${MIN_WINDOWS_CRT_VERSION.join('.')}+. ` +
'Install the latest Visual C++ 2015-2022 x64 redistributable on the build host.'
)
}
// App-local beside transcribe.dll, which loads its dependencies from its own directory.
cpSync(source, join(nativeDir, name))
}
}
/** Copy transcribe-cpp into the slice's resources/node_modules, outside asar where koffi can dlopen it. */
function copyPackagedTranscribeCpp(readPackage, resourcesDir, electronPlatformName, architecture) {
// beforePack's assertPackagedNativeVariantsInstalled already failed any real build missing these;
// only an afterPack driven without it (packaging tests) gets here without them.
if (missingTranscribeCppPackages(readPackage, electronPlatformName, architecture).length > 0) {
return
}
const packages = transcribeCppRuntimePackages(readPackage, electronPlatformName, architecture)
if (!packages) {
return
}
for (const { name, packageDir } of packages) {
const target = join(resourcesDir, 'node_modules', ...name.split('/'))
cpSync(packageDir, target, { recursive: true, dereference: true })
}
if (electronPlatformName === 'win32') {
copyWindowsCrt(join(resourcesDir, 'node_modules', ...packages[3].name.split('/')))
}
}
/** The transcribe-cpp packages a target needs that this install lacks (install:release adds other CPUs). */
function missingTranscribeCppPackages(readPackage, electronPlatformName, architecture) {
try {
transcribeCppRuntimePackages(readPackage, electronPlatformName, architecture)
return []
} catch (error) {
return [String(error.message ?? error)]
}
}
module.exports = {
copyPackagedTranscribeCpp,
missingTranscribeCppPackages
}
+1 -1
View File
@@ -206,7 +206,7 @@
1 src/renderer/src/components/mobile/WindowsFirewallNotice.tsx
2 src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts
1 src/renderer/src/components/mobile/use-mobile-pairing-generation.ts
1 src/renderer/src/components/native-chat-restart-runtimes.ts
1 src/renderer/src/components/native-chat-recovery/offers/native-chat-restart-runtimes.ts
1 src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
8 src/renderer/src/components/native-chat/attachments/chips/use-native-chat-composer-attachments.ts
2 src/renderer/src/components/native-chat/attachments/intake/use-native-chat-resolved-path-attachments.ts
+1 -1
View File
@@ -1,5 +1,5 @@
# Grandfathered code React Compiler would run differently (<file>#<function>:<hazard>).
# Generated; never hand-edit. Lower after a fix: pnpm check:react-compiler-compat-ratchet --prune
1 src/renderer/src/components/agent/agent-launch-picker-drafts.ts#useAgentPickerSetup:discarded-subscription
1 src/renderer/src/components/native-chat-restart-runtimes.ts#useNativeChatRestartRuntimes:discarded-subscription
1 src/renderer/src/components/native-chat-recovery/offers/native-chat-restart-runtimes.ts#useNativeChatRestartRuntimes:discarded-subscription
1 src/renderer/src/components/right-sidebar/source-control/ai/use-source-control-action-agent-choice.ts#useSourceControlActionAgentChoice:store-read-in-memo-block
File diff suppressed because one or more lines are too long
+10 -8
View File
@@ -27,6 +27,7 @@
3 src/renderer/src/lib/workspace/persistence/workspace-session-staged-browser-tabs.ts
1 src/renderer/src/lib/workspace/persistence/workspace-session-unified-tabs.ts
17 src/renderer/src/lib/workspace/persistence/workspace-session.ts
13 src/renderer/src/runtime/browser/creation/testing/web-runtime-browser-creation-placement-test-rig.ts
3 src/renderer/src/runtime/hosted-terminals/recovery/adoption/web-session-terminal-recovery-snapshot-validation.ts
1 src/renderer/src/runtime/hosted-terminals/recovery/surfaces/web-session-terminal-orphan-recovery-surface.ts
2 src/renderer/src/runtime/hosted-terminals/recovery/surfaces/web-session-terminal-orphan-topology.ts
@@ -35,15 +36,14 @@
10 src/renderer/src/runtime/sync-runtime-graph/mobile-session-group-projection.ts
2 src/renderer/src/runtime/sync-runtime-graph/mobile-session-snapshots.ts
2 src/renderer/src/runtime/sync-runtime-graph/sync-key.ts
13 src/renderer/src/runtime/browser/creation/testing/web-runtime-browser-creation-placement-test-rig.ts
1 src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session-snapshot.ts
3 src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-client-owned-tab-placement.ts
1 src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts
8 src/renderer/src/runtime/web-session-tabs-sync/apply-active-state.ts
18 src/renderer/src/runtime/web-session-tabs-sync/apply-final-patch.ts
2 src/renderer/src/runtime/web-session-tabs-sync/apply-preparation-groups.ts
5 src/renderer/src/runtime/web-session-tabs-sync/apply-terminal-records.ts
4 src/renderer/src/runtime/web-session-tabs-sync/apply-worktree-records.ts
1 src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session-snapshot.ts
3 src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-client-owned-tab-placement.ts
2 src/renderer/src/runtime/web-session-tabs-sync/session-tabs-inventory-absence.ts
10 src/renderer/src/store/slices/browser/cleanup/browser-close-actions.ts
2 src/renderer/src/store/slices/browser/create-browser-slice.ts
@@ -57,7 +57,7 @@
9 src/renderer/src/store/slices/editor/checks/check-run-details-actions.ts
6 src/renderer/src/store/slices/editor/closing/close-file-action.ts
10 src/renderer/src/store/slices/editor/closing/recently-closed-editor-tabs.ts
3 src/renderer/src/store/slices/editor/file-ids/hydrated-editor-file-ids.ts
1 src/renderer/src/store/slices/editor/file-ids/hydrated-editor-file-ids.ts
2 src/renderer/src/store/slices/editor/file-ids/hydrated-editor-file-selection.ts
1 src/renderer/src/store/slices/editor/file-ids/open-file-path-rekey.ts
2 src/renderer/src/store/slices/editor/file-ids/rekey-open-files-action.ts
@@ -82,7 +82,7 @@
4 src/renderer/src/store/slices/tabs/tab-group-state.ts
9 src/renderer/src/store/slices/tabs/tabs-close-actions.ts
14 src/renderer/src/store/slices/tabs/tabs-create-actions.ts
23 src/renderer/src/store/slices/tabs/tabs-drag-preview-actions.ts
22 src/renderer/src/store/slices/tabs/tabs-drag-preview-actions.ts
11 src/renderer/src/store/slices/tabs/tabs-drop-actions.ts
8 src/renderer/src/store/slices/tabs/tabs-empty-workspace.ts
4 src/renderer/src/store/slices/tabs/tabs-focus-actions.ts
@@ -91,11 +91,13 @@
1 src/renderer/src/store/slices/tabs/tabs-hydration.ts
8 src/renderer/src/store/slices/tabs/tabs-label-actions.ts
10 src/renderer/src/store/slices/tabs/tabs-move-actions.ts
9 src/renderer/src/store/slices/tabs/tabs-reconciliation.ts
4 src/renderer/src/store/slices/tabs/tabs-reconciliation-batch.ts # split from tabs-reconciliation.ts (9 -> 2 + 4)
2 src/renderer/src/store/slices/tabs/tabs-reconciliation.ts
4 src/renderer/src/store/slices/tabs/tabs-session-actions.ts
1 src/renderer/src/store/slices/tabs/tabs-split-ratio-actions.ts
5 src/renderer/src/store/slices/tabs/tabs-surface.ts
24 src/renderer/src/store/slices/tabs/worktree-layout-lifecycle.ts
19 src/renderer/src/store/slices/tabs/worktree-layout-lifecycle.ts
3 src/renderer/src/store/slices/tabs/worktree-terminal-tab-respawn.ts # moved unchanged out of worktree-layout-lifecycle.ts (24 -> 19 + 3)
5 src/renderer/src/store/slices/worktrees/session/set-active-worktree.ts
2 src/renderer/src/store/slices/worktrees/session/worktree-slice-lookups.ts
6 src/renderer/src/store/terminals/create-terminal-slice.ts
@@ -112,7 +114,7 @@
10 src/renderer/src/store/terminals/terminal-tab-creation.ts
7 src/renderer/src/store/terminals/terminal-tab-navigation.ts
3 src/renderer/src/store/terminals/terminal-tab-presentation.ts
4 src/renderer/src/store/terminals/terminal-tab-title-batch.ts
2 src/renderer/src/store/terminals/terminal-tab-title-batch.ts
5 src/renderer/src/store/terminals/workspace-terminal-hydration-patch.ts
2 src/renderer/src/store/terminals/workspace-terminal-hydration.ts
2 src/renderer/src/store/terminals/workspace-terminal-reconnect.ts
@@ -24,6 +24,13 @@ import {
routePathnameFromKey
} from '../../mobile-web-app/mobile-web-app-route-manifest.mjs'
import { MOBILE_WEB_PAGE_ROUTES } from './mobile-web-page-routes.mjs'
import {
ROUTE_ENTRY_NAMESPACE,
entryStaticClosure,
isScriptOutput,
lazyModulesToAnchor,
sharedModulesToMerge
} from './mobile-web-app-chunk-grouping.mjs'
const projectDir = fileURLToPath(new URL('../../../..', import.meta.url))
const mobileDir = join(projectDir, 'mobile')
@@ -134,7 +141,7 @@ const HAIRLINE_PLUGIN_NAME = 'orca-hairline-device-pixel'
const ENTRY_CHUNK_NAME = 'entry'
const ROUTE_MANIFEST_MODULE = /web-entry[\\/]route-manifest(\.ts)?$/
const ROUTE_ENTRY_NAMESPACE = 'orca-route-entry'
const LAZY_ENTRY_NAMESPACE = 'orca-lazy-entry'
// The entry's static imports as written, minus the manifest: a route importing it would be an output cycle.
async function entryImportAnchors() {
@@ -160,8 +167,14 @@ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// Keep typed route-manifest file for TS/Metro resolution; replace only its body for web.
// Routes import the entry's imports first, so esbuild keeps those modules in one chunk instead of one per route subset.
// Routes also import every module in sharedModules, which esbuild then puts in one chunk all routes load.
function routeManifestPlugin(manifestSource, routes, sharedModules) {
// Lazy modules in anchoredLazyModules (path to export names) get the same imports, so they don't split those chunks either.
function routeManifestPlugin(manifestSource, routes, sharedModules, anchoredLazyModules) {
let anchors
const anchoredImports = async () => {
anchors ??= entryImportAnchors()
const shared = sharedModules.map((module) => `import ${JSON.stringify(module)}\n`).join('')
return `${await anchors}${shared}`
}
return {
name: ROUTE_MANIFEST_PLUGIN_NAME,
setup(build) {
@@ -170,26 +183,43 @@ function routeManifestPlugin(manifestSource, routes, sharedModules) {
loader: 'js',
resolveDir: mobileDir
}))
if (routes.length === 0) {
return
}
const routeImport = new RegExp(
`^(?:${routes.map(({ module }) => escapeRegExp(module)).join('|')})$`
)
build.onResolve({ filter: routeImport }, (args) =>
args.kind === 'dynamic-import'
? { path: args.path, namespace: ROUTE_ENTRY_NAMESPACE }
: undefined
)
build.onLoad({ filter: /.*/, namespace: ROUTE_ENTRY_NAMESPACE }, async (args) => {
anchors ??= entryImportAnchors()
const shared = sharedModules.map((module) => `import ${JSON.stringify(module)}\n`).join('')
return {
contents: `${await anchors}${shared}export { default } from ${JSON.stringify(args.path)}\n`,
if (routes.length > 0) {
const routeImport = new RegExp(
`^(?:${routes.map(({ module }) => escapeRegExp(module)).join('|')})$`
)
build.onResolve({ filter: routeImport }, (args) =>
args.kind === 'dynamic-import'
? { path: args.path, namespace: ROUTE_ENTRY_NAMESPACE }
: undefined
)
build.onLoad({ filter: /.*/, namespace: ROUTE_ENTRY_NAMESPACE }, async (args) => ({
contents: `${await anchoredImports()}export { default } from ${JSON.stringify(args.path)}\n`,
loader: 'js',
resolveDir: dirname(entryPoint)
}))
}
if (anchoredLazyModules.size === 0) {
return
}
build.onResolve({ filter: /.*/ }, async (args) => {
if (args.kind !== 'dynamic-import' || args.pluginData === LAZY_ENTRY_NAMESPACE) {
return undefined
}
const { path, errors } = await build.resolve(args.path, {
kind: args.kind,
importer: args.importer,
resolveDir: args.resolveDir,
pluginData: LAZY_ENTRY_NAMESPACE
})
return errors.length === 0 && anchoredLazyModules.has(path)
? { path, namespace: LAZY_ENTRY_NAMESPACE }
: undefined
})
build.onLoad({ filter: /.*/, namespace: LAZY_ENTRY_NAMESPACE }, async (args) => ({
contents: `${await anchoredImports()}export { ${anchoredLazyModules.get(args.path).join(', ')} } from ${JSON.stringify(args.path)}\n`,
loader: 'js',
resolveDir: dirname(entryPoint)
}))
}
}
}
@@ -234,7 +264,11 @@ const hairlineDevicePixelPlugin = {
}
/** Split out so a test can read the options MOBILE_WEB_APP_SHIMS claims, without a build. */
export function mobileWebAppBuildOptions(routes, sharedModules = []) {
export function mobileWebAppBuildOptions(
routes,
sharedModules = [],
anchoredLazyModules = new Map()
) {
return {
// Fixed so no absolute path of this checkout can reach the output.
absWorkingDir: mobileDir,
@@ -267,7 +301,12 @@ export function mobileWebAppBuildOptions(routes, sharedModules = []) {
zod: MOBILE_ZOD_PACKAGE
},
plugins: [
routeManifestPlugin(renderMobileWebAppRouteManifest(routes), routes, sharedModules),
routeManifestPlugin(
renderMobileWebAppRouteManifest(routes),
routes,
sharedModules,
anchoredLazyModules
),
lucideBarrelPlugin,
hairlineDevicePixelPlugin
],
@@ -311,66 +350,6 @@ export function mobileWebAppBuildOptions(routes, sharedModules = []) {
}
}
// Budget entry plus transitive static imports, stopping at dynamic imports; entry-only limits let shared initial chunks grow unseen.
export function entryStaticClosure(metafile, entryOutputPath) {
const reached = new Set([entryOutputPath])
for (const current of reached) {
for (const imported of metafile.outputs[current]?.imports ?? []) {
if (imported.kind !== 'import-statement' || reached.has(imported.path)) {
continue
}
reached.add(imported.path)
}
}
return reached
}
// esbuild cuts a chunk per set of routes sharing a module; one this small costs more as a request than as code every route loads.
const MERGED_SHARED_CHUNK_MAX_BYTES = 16 * 1024
// Small route-shared chunks whose static imports already load at boot or merge too, so merging adds only their own bytes.
// ESM only: a bare import runs a CommonJS module's body at route load, not where its importer first needs it.
function sharedModulesToMerge(metafile, entryOutputPath) {
const { inputs, outputs } = metafile
const boot = entryStaticClosure(metafile, entryOutputPath)
const routeReached = new Set(
Object.entries(outputs)
.filter(([, output]) => output.entryPoint?.startsWith(`${ROUTE_ENTRY_NAMESPACE}:`))
.flatMap(([path]) => [...entryStaticClosure(metafile, path)])
)
const merged = new Set(
Object.entries(outputs)
.filter(
([path, output]) =>
isScriptOutput(path) &&
!output.entryPoint &&
!boot.has(path) &&
routeReached.has(path) &&
output.bytes <= MERGED_SHARED_CHUNK_MAX_BYTES &&
Object.keys(output.inputs).every((input) => inputs[input]?.format === 'esm')
)
.map(([path]) => path)
)
const importsUnmerged = (path) =>
outputs[path].imports.some(
({ kind, path: imported }) =>
kind === 'import-statement' && !boot.has(imported) && !merged.has(imported)
)
for (let dropped = true; dropped;) {
dropped = false
for (const path of merged) {
if (importsUnmerged(path)) {
merged.delete(path)
dropped = true
}
}
}
return [...merged]
.flatMap((path) => Object.keys(outputs[path].inputs))
.sort()
.map((input) => resolve(mobileDir, input))
}
// Hash final output bytes leaf-first, rewriting dependency names before hashing importers.
// esbuild hashes checkout-dependent input paths; renaming makes manifest hashes/import URLs agree and avoids redownloading identical bundles across checkouts.
export function renameOutputsByContent(metafile, outputFiles) {
@@ -438,8 +417,6 @@ export function routeChunkNames(metafile, routes, renamed) {
)
}
const isScriptOutput = (path) => path.endsWith('.js')
// appDir is a seam for the tests, which bundle a scratch route tree; production always uses mobile/app.
// Page closure includes ancestor layouts as entry modules, not just route imports.
export async function mobileWebAppRouteClosure(routeModule) {
@@ -532,10 +509,12 @@ function entryOutput(metafile) {
// The page's scripts as shipped: a first build finds the small shared chunks, a second merges them.
export async function buildMobileWebAppScripts(routes) {
const split = await esbuild.build(mobileWebAppBuildOptions(routes))
const sharedModules = sharedModulesToMerge(split.metafile, entryOutput(split.metafile))
return sharedModules.length === 0
const entryOutputPath = entryOutput(split.metafile)
const sharedModules = sharedModulesToMerge(split.metafile, entryOutputPath)
const anchoredLazyModules = lazyModulesToAnchor(split.metafile, entryOutputPath)
return sharedModules.length === 0 && anchoredLazyModules.size === 0
? split
: esbuild.build(mobileWebAppBuildOptions(routes, sharedModules))
: esbuild.build(mobileWebAppBuildOptions(routes, sharedModules, anchoredLazyModules))
}
export async function bundleMobileWebApp({ appDir = defaultAppDir } = {}) {
@@ -599,7 +578,9 @@ export function resolveMobileWebPageRoutes(routeKeys, declared = MOBILE_WEB_PAGE
export async function buildMobileWebAppBundle({
appDir,
outDir = defaultOutDir,
pageRoutes = MOBILE_WEB_PAGE_ROUTES
pageRoutes = MOBILE_WEB_PAGE_ROUTES,
// Only the shipped tree pays for brotli-11; tests and verify's scratch builds read raw assets.
brotli = false
} = {}) {
const [
desktopVersion,
@@ -645,7 +626,8 @@ export async function buildMobileWebAppBundle({
written: [indexAsset, ...written],
desktopVersion,
protocolWindow,
routes: resolveMobileWebPageRoutes(routeKeys, pageRoutes)
routes: resolveMobileWebPageRoutes(routeKeys, pageRoutes),
brotli
})
return {
manifest,
@@ -670,7 +652,7 @@ if (isDirectInvocation(import.meta.url, process.argv[1])) {
}
try {
const { manifest, outDir, routeKeys, entryStaticBytes, chunkCount } =
await buildMobileWebAppBundle()
await buildMobileWebAppBundle({ brotli: true })
console.log(
`[build-mobile-web-app-bundle] OK — ${String(routeKeys.length)} route(s), ` +
`${String(chunkCount)} chunk(s), ${String(entryStaticBytes)} bytes before the first route, ` +
@@ -0,0 +1,102 @@
import { realpathSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const mobileDir = fileURLToPath(new URL('../../../../mobile', import.meta.url))
export const ROUTE_ENTRY_NAMESPACE = 'orca-route-entry'
export const isScriptOutput = (path) => path.endsWith('.js')
// Budget entry plus transitive static imports, stopping at dynamic imports; entry-only limits let shared initial chunks grow unseen.
export function entryStaticClosure(metafile, entryOutputPath) {
const reached = new Set([entryOutputPath])
for (const current of reached) {
for (const imported of metafile.outputs[current]?.imports ?? []) {
if (imported.kind !== 'import-statement' || reached.has(imported.path)) {
continue
}
reached.add(imported.path)
}
}
return reached
}
// esbuild cuts a chunk per set of routes sharing a module; one this small costs more as a request than as code every route loads.
const MERGED_SHARED_CHUNK_MAX_BYTES = 16 * 1024
// Small route-shared chunks whose static imports already load at boot or merge too, so merging adds only their own bytes.
// ESM only: a bare import runs a CommonJS module's body at route load, not where its importer first needs it.
export function sharedModulesToMerge(metafile, entryOutputPath) {
const { inputs, outputs } = metafile
const boot = entryStaticClosure(metafile, entryOutputPath)
const routeReached = new Set(
Object.entries(outputs)
.filter(([, output]) => output.entryPoint?.startsWith(`${ROUTE_ENTRY_NAMESPACE}:`))
.flatMap(([path]) => [...entryStaticClosure(metafile, path)])
)
const merged = new Set(
Object.entries(outputs)
.filter(
([path, output]) =>
isScriptOutput(path) &&
!output.entryPoint &&
!boot.has(path) &&
routeReached.has(path) &&
output.bytes <= MERGED_SHARED_CHUNK_MAX_BYTES &&
Object.keys(output.inputs).every((input) => inputs[input]?.format === 'esm')
)
.map(([path]) => path)
)
const importsUnmerged = (path) =>
outputs[path].imports.some(
({ kind, path: imported }) =>
kind === 'import-statement' && !boot.has(imported) && !merged.has(imported)
)
for (let dropped = true; dropped;) {
dropped = false
for (const path of merged) {
if (importsUnmerged(path)) {
merged.delete(path)
dropped = true
}
}
}
return [...merged]
.flatMap((path) => Object.keys(outputs[path].inputs))
.sort()
.map((input) => resolve(mobileDir, input))
}
// A lazy module sharing boot code gives those modules a new importer set, so esbuild would split the boot chunks;
// anchor it like a route. Modules boot imports lazily (routes, catalogs) stay as they are: anchoring them is an output cycle.
// esbuild's runtime helper chunk has no inputs and every chunk imports it, so self-contained engines (mermaid) keep their bytes.
export function lazyModulesToAnchor(metafile, entryOutputPath) {
const { outputs } = metafile
const boot = entryStaticClosure(metafile, entryOutputPath)
const bootLoadsLazily = new Set(
[...boot].flatMap((path) =>
outputs[path].imports
.filter(({ kind }) => kind === 'dynamic-import')
.map(({ path: imported }) => imported)
)
)
const sharesBootModules = ({ imports }) =>
imports.some(
({ kind, path }) =>
kind === 'import-statement' &&
boot.has(path) &&
Object.keys(outputs[path].inputs).length > 0
)
return new Map(
Object.entries(outputs)
.filter(
([path, output]) =>
output.entryPoint &&
!boot.has(path) &&
!bootLoadsLazily.has(path) &&
sharesBootModules(output)
)
.map(([, output]) => [realpathSync(resolve(mobileDir, output.entryPoint)), output.exports])
)
}
@@ -15,7 +15,11 @@ const PIXEL_PNG = Buffer.from(
'base64'
)
export async function writeMobileWebBundleFixtureTree({ outDir, body = 'fixture' }) {
export async function writeMobileWebBundleFixtureTree({
outDir,
body = 'fixture',
brotli = false
}) {
const [desktopVersion, protocolWindow] = await Promise.all([
readDesktopVersion(),
readProtocolWindow()
@@ -49,6 +53,7 @@ export async function writeMobileWebBundleFixtureTree({ outDir, body = 'fixture'
image
],
desktopVersion,
protocolWindow
protocolWindow,
brotli
})
}
@@ -3,10 +3,13 @@ import { readFile, mkdir, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { promisify } from 'node:util'
import { brotliCompress, constants as zlibConstants } from 'node:zlib'
const { darkBuildMarkers } = createRequire(import.meta.url)('../../../dark-build-switches.cjs')
const projectDir = fileURLToPath(new URL('../../../..', import.meta.url))
const brotliCompressAsync = promisify(brotliCompress)
export const MOBILE_WEB_BUNDLE_SCHEMA_VERSION = 1
export const MOBILE_WEB_BUNDLE_ENTRYPOINT = 'index.html'
@@ -101,6 +104,31 @@ export async function readDesktopVersion() {
return packageJson.version
}
// Mirrors mobileWebBundleBrotliPath in src/shared/mobile-web-bundle/manifest-contract.ts.
export function mobileWebBundleBrotliPath(assetPath) {
return `${assetPath}.br`
}
// Largest first so the slowest file starts first on zlib's thread pool; keep only copies that shrink.
async function compressWithBrotli(written) {
const largestFirst = [...written].sort((left, right) => right.byteLength - left.byteLength)
const copies = await Promise.all(
largestFirst.map((asset) =>
brotliCompressAsync(asset.bytes, {
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY,
[zlibConstants.BROTLI_PARAM_SIZE_HINT]: asset.byteLength
}
})
)
)
return new Map(
largestFirst.flatMap((asset, index) =>
copies[index].byteLength < asset.byteLength ? [[asset.path, copies[index]]] : []
)
)
}
// Single manifest producer keeps builder/contract/packaging guard shape aligned.
export async function writeMobileWebBundleTree({
outDir,
@@ -109,10 +137,22 @@ export async function writeMobileWebBundleTree({
protocolWindow,
// Empty route list makes shell render every screen natively.
routes = [],
// Whole-file brotli copies for phones that inflate natively.
brotli = false,
env = process.env
}) {
const brotliCopies = brotli ? await compressWithBrotli(written) : new Map()
const assets = written
.map(({ path, sha256, byteLength, contentType }) => ({ path, sha256, byteLength, contentType }))
.map(({ path, sha256, byteLength, contentType }) => {
const copy = brotliCopies.get(path)
return {
path,
sha256,
byteLength,
contentType,
...(copy === undefined ? {} : { brotliByteLength: copy.byteLength })
}
})
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
const manifest = {
schemaVersion: MOBILE_WEB_BUNDLE_SCHEMA_VERSION,
@@ -133,6 +173,9 @@ export async function writeMobileWebBundleTree({
for (const asset of written) {
await writeFile(join(outDir, asset.path), asset.bytes)
}
for (const [path, copy] of brotliCopies) {
await writeFile(join(outDir, mobileWebBundleBrotliPath(path)), copy)
}
await writeFile(join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
// Keep switch metadata outside manifest so compiled page bytes/buildId remain unchanged.
for (const marker of darkBuildMarkers(env)) {
@@ -102,13 +102,14 @@ export const MOBILE_WEB_PAGE_ROUTES = [
'native.audio.read',
'native.audio.stop'
],
// Optional media.name falls back to the file type; terminalKeyRail falls back to the page's row; the image viewer drops its actions without native.image.act; dictation uses the computer without native.speech.act.
// Optional media.name falls back to the file type; terminalKeyRail falls back to the page's row; the image viewer drops its actions without native.image.act; dictation uses the computer without native.speech.act; without native.a11y.announce only the page's live region speaks.
optionalGrants: [
'externalNavigation',
'native.media.name',
'native.pdf.view',
'native.image.act',
'native.speech.act',
'native.a11y.announce',
'terminalKeyRail',
DEVICE_STORE_GRANT
]
+32 -139
View File
@@ -102,9 +102,7 @@
"config/scripts/locale-catalog/locale-repair-catalog-missing-leaves.test.mjs": 159,
"config/scripts/locale-catalog/locale-translation-policy-ko-round5.test.mjs": 154,
"config/scripts/locale-catalog/locale-translation-policy.es-pr.test.mjs": 91,
"config/scripts/locale-catalog/locale-translation-policy.es-round5.test.mjs": 153,
"config/scripts/locale-catalog/locale-translation-policy.ja-relocalization.test.mjs": 116,
"config/scripts/locale-catalog/locale-translation-policy.ja-round5.test.mjs": 209,
"config/scripts/locale-catalog/locale-translation-policy.test.mjs": 189,
"config/scripts/localization/source-extraction/localization-extraction-call-syntax.test.mjs": 317,
"config/scripts/localization/extraction-policy/localization-extraction-change-scope.test.mjs": 732,
@@ -313,7 +311,6 @@
"src/cli/automation-owner-conflict-recovery.test.ts": 448,
"src/cli/automation-runs-paging.test.ts": 569,
"src/cli/browser/browser-empty-values.test.ts": 313,
"src/cli/browser/browser.test.ts": 431,
"src/cli/codex-command-classification.test.ts": 65,
"src/cli/command-suggestion-budget.test.ts": 132,
"src/cli/command-suggestion-destructive-families.test.ts": 226,
@@ -331,7 +328,6 @@
"src/cli/handlers/automation-owner-fencing.test.ts": 529,
"src/cli/handlers/computer-action-validation.test.ts": 317,
"src/cli/handlers/computer-state-formatting.test.ts": 337,
"src/cli/handlers/connector.test.ts": 781,
"src/cli/handlers/core.test.ts": 194,
"src/cli/handlers/data-account-commands.test.ts": 431,
"src/cli/handlers/emulator.test.ts": 303,
@@ -348,21 +344,13 @@
"src/cli/handlers/orchestration-legacy-read-only.test.ts": 526,
"src/cli/handlers/orchestration-lifecycle-json-rejection.test.ts": 555,
"src/cli/handlers/orchestration-lifecycle-rejection.test.ts": 781,
"src/cli/handlers/orchestration-migration.test.ts": 536,
"src/cli/handlers/orchestration-request-show-cli.test.ts": 581,
"src/cli/handlers/orchestration-send-receipt-warnings.test.ts": 565,
"src/cli/handlers/orchestration-task-create-cli.test.ts": 652,
"src/cli/handlers/orchestration-task-list-brief.test.ts": 494,
"src/cli/handlers/orchestration-windows-ask-cli.test.ts": 801,
"src/cli/handlers/orchestration-worker-cli.test.ts": 706,
"src/cli/handlers/orchestration-worker-show-wait-cli.test.ts": 748,
"src/cli/handlers/orchestration.test.ts": 543,
"src/cli/handlers/orchestration/agent-view.test.ts": 511,
"src/cli/handlers/orchestration/concise-list-row-size.test.ts": 492,
"src/cli/handlers/orchestration/mutation-request.test.ts": 4146,
"src/cli/handlers/orchestration/older-runtime-run-binding.test.ts": 547,
"src/cli/handlers/orchestration/terminal-identity-codex-filtered.test.ts": 409,
"src/cli/handlers/orchestration/worker-list-run-scope.test.ts": 495,
"src/cli/handlers/orchestration/worker-output.test.ts": 79,
"src/cli/handlers/orchestration/worker-start-implicit-coordinator.test.ts": 522,
"src/cli/handlers/plugin-dev-watch.test.ts": 942,
@@ -374,13 +362,10 @@
"src/cli/handlers/profile-state-recovery-admission.test.ts": 2149,
"src/cli/handlers/profile-state.test.ts": 1045,
"src/cli/handlers/repo-visibility.test.ts": 419,
"src/cli/handlers/search.test.ts": 578,
"src/cli/handlers/serve-restore.test.ts": 120,
"src/cli/handlers/serve-service.test.ts": 200,
"src/cli/handlers/skill-guide-host-gate.test.ts": 596,
"src/cli/handlers/skill-sharing.test.ts": 423,
"src/cli/handlers/speech.test.ts": 454,
"src/cli/handlers/terminal-daemon.test.ts": 426,
"src/cli/handlers/terminal-set-pane-title.test.ts": 623,
"src/cli/handlers/terminal-split.test.ts": 452,
"src/cli/handlers/terminal-switch-paired-reveal.test.ts": 487,
@@ -394,7 +379,6 @@
"src/cli/index-omitted-host-scope-selectors.test.ts": 6719,
"src/cli/index-orchestration.test.ts": 659,
"src/cli/index-terminal-commands.test.ts": 466,
"src/cli/index-vm-recipe-doctor.test.ts": 459,
"src/cli/index-workspace-owner-choice-scaling.test.ts": 10154,
"src/cli/index-workspace-owner-choice.test.ts": 1072,
"src/cli/index-worktree-create-agent.test.ts": 582,
@@ -417,7 +401,6 @@
"src/cli/plugin-authoring/plugin-test-node.test.ts": 110,
"src/cli/plugin-authoring/plugin-test-runner.test.ts": 6256,
"src/cli/plugin-authoring/plugin-type-files-symlink.test.ts": 743,
"src/cli/plugin-authoring/plugin-type-files.test.ts": 372,
"src/cli/plugin-authoring/plugin-worker-panel-template.test.ts": 1469,
"src/cli/plugin-dev-watch/plugin-dev-watch-approval.test.ts": 544,
"src/cli/plugin-dev-watch/plugin-dev-watch-check.test.ts": 427,
@@ -456,7 +439,6 @@
"src/cli/runtime/serve-update-supervisor-gate.test.ts": 73,
"src/cli/runtime/status-caller.test.ts": 488,
"src/cli/runtime/status.test.ts": 1406,
"src/cli/runtime/terminal-prompt-mutation-recovery-plugin-denied.test.ts": 125,
"src/cli/runtime/transport-framing.test.ts": 194,
"src/cli/runtime/transport.test.ts": 462,
"src/cli/runtime/types.test.ts": 62,
@@ -468,17 +450,14 @@
"src/cli/skills-reference-selector.test.ts": 528,
"src/cli/skills.test.ts": 386,
"src/cli/specs/account.test.ts": 95,
"src/cli/specs/bundled-guide-flags.test.ts": 246,
"src/cli/specs/cli-command-names-parity.test.ts": 205,
"src/cli/specs/profile-state.test.ts": 102,
"src/cli/terminal-format.test.ts": 84,
"src/cli/terminal-list-host-scope-format.test.ts": 127,
"src/cli/terminal-read-screen.test.ts": 660,
"src/cli/worktree-ps-program-status.test.ts": 65,
"src/cli/worktree-ps-terminal-verdict.test.ts": 60,
"src/cli/worktree-selector-caller-host.test.ts": 107,
"src/cli/worktree-selector-wsl-posix-path.test.ts": 134,
"src/cli/wsl-distro-cli-refusal.test.ts": 74,
"src/main/acp/process/acp-agent-connection.test.ts": 407,
"src/main/acp/discovery/acp-at-rest-commands.test.ts": 1400,
"src/main/acp/transport/acp-json-rpc-peer.test.ts": 195,
@@ -551,55 +530,39 @@
"src/main/agent-hooks/installation/configuration/managed-toml-ownership.test.ts": 52,
"src/main/agent-hooks/installation/scripts/posix-hook-json-stdin-reader.test.ts": 3479,
"src/main/agent-hooks/server/testing/presence/server-agent-presence.test.ts": 1257,
"src/main/agent-hooks/server/testing/tool-events/server-agent-tool-plugin-hooks.test.ts": 717,
"src/main/agent-hooks/server/testing/identity/server-ai-vault-liveness.test.ts": 643,
"src/main/agent-hooks/server/testing/providers/amp/server-amp-normalization.test.ts": 607,
"src/main/agent-hooks/server/testing/providers/antigravity/server-antigravity-resume-identity.test.ts": 820,
"src/main/agent-hooks/server/testing/providers/claude/server-claude-blocked-prompt.test.ts": 681,
"src/main/agent-hooks/server/testing/permissions/server-claude-child-permission-lifecycle.test.ts": 681,
"src/main/agent-hooks/server/testing/providers/claude/server-claude-config-dir-observation.test.ts": 696,
"src/main/agent-hooks/server/testing/interruptions/server-claude-helper-kill.test.ts": 10279,
"src/main/agent-hooks/server/testing/interruptions/server-claude-helper-recovery.test.ts": 34523,
"src/main/agent-hooks/server/testing/providers/claude/server-claude-normalization.test.ts": 683,
"src/main/agent-hooks/server/testing/task-wakeup/server-claude-owed-notification-expiry.test.ts": 877,
"src/main/agent-hooks/server/testing/permissions/server-claude-permission-tool-failure.test.ts": 674,
"src/main/agent-hooks/server/testing/permissions/server-claude-permission-visibility.test.ts": 765,
"src/main/agent-hooks/server/testing/identity/server-claude-session-continuation.test.ts": 675,
"src/main/agent-hooks/server/testing/http/server-claude-statusline.test.ts": 679,
"src/main/agent-hooks/server/testing/task-wakeup/server-claude-task-wakeup-lifecycle.test.ts": 5451,
"src/main/agent-hooks/server/testing/interruptions/server-claude-terminal-interrupt.test.ts": 802,
"src/main/agent-hooks/server/testing/authority/server-closed-tab-suppression.test.ts": 895,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-capacity.test.ts": 671,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-interrupt-hook.test.ts": 679,
"src/main/agent-hooks/server/testing/normalization/server-provider-normalization.test.ts": 741,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-review-turn-end.test.ts": 860,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-side-thread.test.ts": 847,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-subagent-transcript.test.ts": 706,
"src/main/agent-hooks/server/testing/providers/codex/server-codex-turn-interruption.test.ts": 716,
"src/main/agent-hooks/server/testing/transcripts/server-transcript-result-retry.test.ts": 1125,
"src/main/agent-hooks/server/testing/identity/server-copilot-resume-identity.test.ts": 729,
"src/main/agent-hooks/server/testing/providers/cursor/server-cursor-normalization.test.ts": 645,
"src/main/agent-hooks/server/testing/http/server-endpoint-file-lifecycle.test.ts": 801,
"src/main/agent-hooks/server/testing/providers/grok/server-grok-background-status.test.ts": 723,
"src/main/agent-hooks/server/testing/providers/grok/server-grok-cancel.test.ts": 692,
"src/main/agent-hooks/server/testing/providers/grok/server-grok-discovery.test.ts": 723,
"src/main/agent-hooks/server/testing/identity/server-hermes-resume-identity.test.ts": 941,
"src/main/agent-hooks/server/testing/http/server-hook-http-ingest.test.ts": 719,
"src/main/agent-hooks/server/testing/identity/server-hook-session-binding.test.ts": 2674,
"src/main/agent-hooks/server/testing/presence/server-http-owner-probe-lifecycle.test.ts": 1088,
"src/main/agent-hooks/server/testing/presence/server-idle-process-owner.test.ts": 1014,
"src/main/agent-hooks/server/testing/remote-ingress/server-ingest-remote-codex-question.test.ts": 656,
"src/main/agent-hooks/server/testing/remote-ingress/server-ingest-remote-codex-transcript.test.ts": 647,
"src/main/agent-hooks/server/testing/remote-ingress/server-ingest-remote.test.ts": 653,
"src/main/agent-hooks/server/testing/structured-ingress/server-ingest-structured-state-clock.test.ts": 723,
"src/main/agent-hooks/server/testing/structured-ingress/server-ingest-structured-status.test.ts": 697,
"src/main/agent-hooks/server/testing/terminal-ingress/server-ingest-terminal-status.test.ts": 673,
"src/main/agent-hooks/server/testing/interruptions/server-interrupt-inference-guards.test.ts": 685,
"src/main/agent-hooks/server/testing/interruptions/server-interrupt-inference-resurrection.test.ts": 759,
"src/main/agent-hooks/server/testing/persistence/server-last-status-child-held-main-agent.test.ts": 937,
"src/main/agent-hooks/server/testing/persistence/server-last-status-hydrate-confirmation.test.ts": 713,
"src/main/agent-hooks/server/testing/persistence/server-last-status-hydrate-validation.test.ts": 737,
"src/main/agent-hooks/server/testing/persistence/server-last-status-lead-boundary.test.ts": 768,
"src/main/agent-hooks/server/testing/persistence/server-last-status-main-agent-fact.test.ts": 813,
"src/main/agent-hooks/server/testing/persistence/server-last-status-restored-children.test.ts": 818,
"src/main/agent-hooks/server/testing/persistence/server-last-status-write.test.ts": 745,
@@ -607,13 +570,10 @@
"src/main/agent-hooks/server/testing/main-agent/server-main-agent-turn-verdicts.test.ts": 1716,
"src/main/agent-hooks/server/testing/providers/muse/server-muse-session-log-poll.test.ts": 2772,
"src/main/agent-hooks/server/testing/identity/server-nested-agent-boundaries.test.ts": 657,
"src/main/agent-hooks/server/testing/identity/server-nested-agent-consumers.test.ts": 660,
"src/main/agent-hooks/server/testing/identity/server-nested-agent-model.test.ts": 665,
"src/main/agent-hooks/server/testing/identity/server-nested-same-provider-owner.test.ts": 708,
"src/main/agent-hooks/server/testing/persistence/server-observation-provenance.test.ts": 672,
"src/main/agent-hooks/server/testing/authority/server-omp-retired-ingress.test.ts": 641,
"src/main/agent-hooks/server/testing/authority/server-omp-retirement-ack.test.ts": 676,
"src/main/agent-hooks/server/testing/providers/opencode/server-opencode-tui-identity.test.ts": 843,
"src/main/agent-hooks/server/testing/identity/server-opencode-writer-observation.test.ts": 681,
"src/main/agent-hooks/server/testing/authority/server-pane-authority.test.ts": 762,
"src/main/agent-hooks/server/testing/terminal-ingress/server-process-lifetime-status.test.ts": 852,
@@ -624,7 +584,6 @@
"src/main/agent-hooks/server/testing/authority/server-removed-worktree-foreign-authority.test.ts": 764,
"src/main/agent-hooks/server/testing/authority/server-removed-worktree-status.test.ts": 687,
"src/main/agent-hooks/server/testing/replay/server-replay-evidence-clock.test.ts": 643,
"src/main/agent-hooks/server/testing/persistence/server-restored-agent-status.test.ts": 694,
"src/main/agent-hooks/server/testing/authority/server-retired-pane-new-turn.test.ts": 666,
"src/main/agent-hooks/server/testing/http/server-startup-lifecycle.test.ts": 718,
"src/main/agent-hooks/server/testing/http/server-startup-prompt-control.test.ts": 697,
@@ -637,7 +596,6 @@
"src/main/agent-hooks/server/testing/http/server-transport-interference.test.ts": 1814,
"src/main/agent-hooks/server/testing/main-agent/server-turn-ended-at.test.ts": 990,
"src/main/agent-hooks/server/testing/main-agent/server-turn-started-at.test.ts": 898,
"src/main/agent-hooks/server/program-status/hook-precedence.test.ts": 45,
"src/main/agent-hooks/server/testing/replay/spool.test.ts": 999,
"src/main/agent-hooks/server/testing/identity/terminal-handle-row-identity.test.ts": 669,
"src/main/agent-hooks/installation/commands/windows-batch-hook-launcher.test.ts": 108,
@@ -645,15 +603,12 @@
"src/main/agent-hooks/installation/commands/windows-hook-payload-delivery.test.ts": 345,
"src/main/agent-hooks/installation/commands/windows-native-hook-exe-hosts.test.ts": 262,
"src/main/agent-hooks/installation/commands/windows-powershell-hook-launcher.test.ts": 44,
"src/main/agent-hooks/wsl-relay/wsl-guest-plugin-install.test.ts": 64,
"src/main/agent-hooks/wsl-relay/wsl-hook-relay-idle-stop.test.ts": 1235,
"src/main/agent-hooks/wsl-relay/testing/wsl-hook-relay-live.integration.test.ts": 2231,
"src/main/agent-hooks/wsl-relay/wsl-hook-relay-manager.test.ts": 3176,
"src/main/agent-hooks/wsl-relay/wsl-hook-relay-reattach.test.ts": 1280,
"src/main/agent-hooks/wsl-relay/wsl-hook-relay-recovery.test.ts": 1529,
"src/main/agent-hooks/wsl-relay/wsl-hook-relay-sentinel.test.ts": 62,
"src/main/agent-hooks/wsl-relay/wsl-opencode-settings-lifetime.test.ts": 1951,
"src/main/agent-hooks/wsl-relay/wsl-opencode-settings.test.ts": 1364,
"src/main/agent-launch/agent-launch-account-env.test.ts": 958,
"src/main/agent-launch/agent-launch-executor-cancel.test.ts": 1455,
"src/main/agent-launch/agent-launch-mode-settings.test.ts": 4041,
@@ -941,8 +896,6 @@
"src/main/browser/cookies/browser-google-cookie-clear-target.test.ts": 6235,
"src/main/browser/element-grab/browser-grab-payload.test.ts": 55,
"src/main/browser/guest-lifecycle/devtools/browser-guest-devtools-dock.test.ts": 57,
"src/main/browser/guest-lifecycle/input/shortcuts/browser-guest-shortcut-plugin-frame.test.ts": 280,
"src/main/browser/guest-lifecycle/input/shortcuts/browser-guest-shortcut-precedence.test.ts": 411,
"src/main/browser/guest-lifecycle/input/browser-guest-target-url.test.ts": 50,
"src/main/browser/guest-lifecycle/input/browser-guest-wheel-zoom-scroll.test.ts": 45,
"src/main/browser/client-hosts/leases/browser-host-client-identity.test.ts": 97,
@@ -950,24 +903,18 @@
"src/main/browser/identity/mode/browser-identity-mode-store.test.ts": 95,
"src/main/browser/guest-lifecycle/prompts/browser-javascript-dialogs.test.ts": 54,
"src/main/browser/guest-lifecycle/manager/viewport/browser-manager-annotation-bridge.test.ts": 603,
"src/main/browser/guest-lifecycle/manager/navigation/browser-manager-auth-user-agent.test.ts": 555,
"src/main/browser/guest-lifecycle/manager/downloads/browser-manager-client-hosted-downloads.test.ts": 670,
"src/main/browser/guest-lifecycle/manager/downloads/browser-manager-destroyed-guest-downloads.test.ts": 589,
"src/main/browser/guest-lifecycle/manager/registration/browser-manager-destroyed-guest.test.ts": 633,
"src/main/browser/guest-lifecycle/manager/devtools/browser-manager-devtools-retirement.test.ts": 551,
"src/main/browser/guest-lifecycle/manager/downloads/browser-manager-downloads.test.ts": 592,
"src/main/browser/guest-lifecycle/manager/grab/browser-manager-grab-capture.test.ts": 516,
"src/main/browser/guest-lifecycle/manager/grab/browser-manager-grab-mode.test.ts": 567,
"src/main/browser/guest-lifecycle/manager/grab/browser-manager-grab-selection.test.ts": 584,
"src/main/browser/guest-lifecycle/manager/registration/browser-manager-guest-unload.test.ts": 534,
"src/main/browser/guest-lifecycle/manager/browser-manager-guest-visibility.test.ts": 543,
"src/main/browser/guest-lifecycle/manager/navigation/browser-manager-load-failure-replay.test.ts": 549,
"src/main/browser/guest-lifecycle/manager/registration/browser-manager-page-prompt-routing.test.ts": 560,
"src/main/browser/guest-lifecycle/manager/popups/browser-manager-popup-child.test.ts": 503,
"src/main/browser/guest-lifecycle/manager/popups/browser-manager-popup-routing.test.ts": 575,
"src/main/browser/guest-lifecycle/manager/viewport/browser-manager-tab-identity.test.ts": 619,
"src/main/browser/guest-lifecycle/manager/viewport/browser-manager-viewport-override.test.ts": 597,
"src/main/browser/guest-lifecycle/manager/viewport/browser-manager-viewport-ownership.test.ts": 550,
"src/main/browser/network/execution-routes/browser-network-deferred-socket.test.ts": 101,
"src/main/browser/network/execution-routes/browser-network-execution-route.test.ts": 272,
"src/main/browser/network/tunnels/browser-network-tunnel-client.test.ts": 127,
@@ -4171,7 +4118,6 @@
"src/main/ssh/connection/ssh-connection-reconnect-ladder.test.ts": 570,
"src/main/ssh/connection/ssh-connection-sftp-namespace.test.ts": 1290,
"src/main/ssh/connection/ssh-connection-sftp-wire.test.ts": 481,
"src/main/ssh/targets/ssh-connection-store-orcad-ownership.test.ts": 324,
"src/main/ssh/targets/ssh-connection-store-route-dedupe.test.ts": 350,
"src/main/ssh/targets/ssh-connection-store.test.ts": 377,
"src/main/ssh/connection/ssh-connection-system-transport.test.ts": 458,
@@ -4189,7 +4135,6 @@
"src/main/ssh/legacy-relay/bridge/legacy-relay-client.test.ts": 109,
"src/main/ssh/legacy-relay/bridge/legacy-relay-hidden-output.test.ts": 179,
"src/main/ssh/legacy-relay/bridge/legacy-relay-target-bridge.test.ts": 167,
"src/main/ssh/migration-source/orcad-migration-bridged-panes.test.ts": 322,
"src/main/ssh/legacy-relay/endpoints/ssh-host-relay-census-for-target.test.ts": 53,
"src/main/ssh/legacy-relay/endpoints/ssh-host-relay-windows-census.test.ts": 232,
"src/main/ssh/legacy-relay/endpoints/ssh-host-relay-windows-inventory.test.ts": 178,
@@ -4219,7 +4164,6 @@
"src/main/managed-server/migration/transfers/orcad-migration-snapshot-coordinator.test.ts": 309,
"src/main/ssh/migration-source/orcad-migration-source-fence.test.ts": 2000,
"src/main/persistence/migrating-orcad-catalog/orcad-migration-source-retention.test.ts": 2297,
"src/main/ssh/migration-source/orcad-migration-ssh-relay-source.test.ts": 1903,
"src/main/managed-server/migration/cutover/orcad-migration-stuck-host-profiles.test.ts": 2402,
"src/main/ssh/migration-source/orcad-migration-terminal-gate.test.ts": 63,
"src/main/managed-server/install/orcad-remote-deploy.test.ts": 537,
@@ -4645,7 +4589,6 @@
"src/packages/process-host/src/run-process-windows-tree-kill.test.ts": 81,
"src/packages/process-host/src/run-process.test.ts": 9406,
"src/packages/process-host/src/spawn-observer.test.ts": 357,
"src/packages/process-host/src/spawn-process-stdio.test.ts": 311,
"src/packages/process-host/src/windows-command-line.test.ts": 73,
"src/packages/process-host/src/windows-tree-kill.test.ts": 81,
"src/packages/process-host/src/wsl-distro-allowlist.test.ts": 55,
@@ -4698,12 +4641,11 @@
"src/renderer/src/components/JiraIssueWorkspace.optimistic-edit.test.tsx": 2340,
"src/renderer/src/components/JiraIssueWorkspace.status-queue.test.tsx": 1852,
"src/renderer/src/components/JiraIssueWorkspace.transition-source.test.tsx": 1843,
"src/renderer/src/components/NativeChatRemoteResume.test.tsx": 8774,
"src/renderer/src/components/NativeChatResumeOnRestartGroups.selection.test.tsx": 2633,
"src/renderer/src/components/NativeChatResumeOnRestartGroups.test.tsx": 3472,
"src/renderer/src/components/native-chat-recovery/integration/NativeChatRemoteResume.test.tsx": 8774,
"src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.selection.test.tsx": 2633,
"src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.test.tsx": 3472,
"src/renderer/src/components/NewWorkspaceComposerCard.quick-file-drop.test.tsx": 4557,
"src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx": 1301,
"src/renderer/src/components/NotificationCardStack.test.tsx": 272,
"src/renderer/src/components/QuickOpen.selection.test.tsx": 1046,
"src/renderer/src/components/SelectedTextCopyMenu.test.tsx": 329,
"src/renderer/src/components/TerminalSearch.test.tsx": 458,
@@ -4721,15 +4663,12 @@
"src/renderer/src/components/activity/activity-scope-filter.test.ts": 734,
"src/renderer/src/components/activity/activity-search-query.test.tsx": 2286,
"src/renderer/src/components/activity/activity-terminal-portal-publication-loop.react185.test.tsx": 222,
"src/renderer/src/components/activity/activity-thread-grouping.status-order.test.ts": 759,
"src/renderer/src/components/activity/activity-thread-list-pane-collapsible.test.tsx": 3688,
"src/renderer/src/components/activity/activity-thread-list-pane.multi-select.test.tsx": 4699,
"src/renderer/src/components/activity/activity-thread-list-pane.virtualization.test.tsx": 4863,
"src/renderer/src/components/activity/useActivityUnreadCount.freshness.test.tsx": 1990,
"src/renderer/src/components/activity/useActivityUnreadCount.test.ts": 1633,
"src/renderer/src/components/agent-session-continuation/agent-session-continuation-selection.test.ts": 56,
"src/renderer/src/components/agent-tab-shortcut-launch.test.ts": 369,
"src/renderer/src/components/agent/AgentLaunchPickerMenu.language.test.tsx": 2115,
"src/renderer/src/components/agent/AgentLaunchPickerMenu.search.test.tsx": 2090,
"src/renderer/src/components/artifacts/ArtifactCollection.test.tsx": 1833,
"src/renderer/src/components/artifacts/ArtifactPreview.test.tsx": 474,
@@ -4741,14 +4680,12 @@
"src/renderer/src/components/automations/AutomationRunsTable.test.tsx": 668,
"src/renderer/src/components/automations/AutomationSchedulePicker.test.ts": 431,
"src/renderer/src/components/automations/AutomationTimeField.test.tsx": 654,
"src/renderer/src/components/automations/AutomationsListPanel.test.tsx": 2441,
"src/renderer/src/components/automations/AutomationsPage.behavior.test.tsx": 3389,
"src/renderer/src/components/automations/AutomationsPage.edit-destination.test.tsx": 2932,
"src/renderer/src/components/automations/AutomationsPage.run-navigation.test.tsx": 3056,
"src/renderer/src/components/automations/ExternalAutomationManagers.test.tsx": 852,
"src/renderer/src/components/automations/automation-captured-owner.test.ts": 379,
"src/renderer/src/components/automations/automation-create-projects.test.ts": 42,
"src/renderer/src/components/automations/automation-detail-tab-navigation.test.ts": 201,
"src/renderer/src/components/automations/automation-host-cache-controller.test.ts": 411,
"src/renderer/src/components/automations/automation-host-cache-error.test.ts": 391,
"src/renderer/src/components/automations/automation-host-cache-health.test.ts": 52,
@@ -4764,14 +4701,11 @@
"src/renderer/src/components/automations/automation-host-recovery.test.ts": 51,
"src/renderer/src/components/automations/automation-host-scale-gates.test.ts": 638,
"src/renderer/src/components/automations/automation-host-scheduler.test.ts": 439,
"src/renderer/src/components/automations/automation-host-status-descriptors.test.ts": 231,
"src/renderer/src/components/automations/automation-legacy-schedule-editing.test.ts": 449,
"src/renderer/src/components/automations/automation-list-focus-recovery.test.ts": 48,
"src/renderer/src/components/automations/automation-list-keyboard-navigation.test.ts": 61,
"src/renderer/src/components/automations/automation-list-row-identity.test.ts": 626,
"src/renderer/src/components/automations/automation-list-search.test.ts": 75,
"src/renderer/src/components/automations/automation-list-view.test.ts": 257,
"src/renderer/src/components/automations/automation-project-groups.test.ts": 54,
"src/renderer/src/components/automations/automation-run-context.test.ts": 58,
"src/renderer/src/components/automations/automation-run-history-keyboard-navigation.test.ts": 434,
"src/renderer/src/components/automations/automation-run-open-target.test.ts": 442,
@@ -4795,7 +4729,6 @@
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.failure-remount.test.tsx": 3085,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx": 4575,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx": 6541,
"src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx": 497,
"src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx": 1124,
"src/renderer/src/components/browser-pane/annotate/browser-annotation-output.test.ts": 74,
"src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx": 1007,
@@ -4813,7 +4746,6 @@
"src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.retention-props.test.tsx": 3808,
"src/renderer/src/components/browser-pane/assemble-chrome/use-browser-chrome-tool-fold.test.tsx": 307,
"src/renderer/src/components/browser-pane/assemble-chrome/use-browser-page-find-shortcuts.test.tsx": 1874,
"src/renderer/src/components/browser-pane/browser-client-hosted-download-notices.test.tsx": 1020,
"src/renderer/src/components/browser-pane/browser-client-page-attachment-scan-budget.test.ts": 464,
"src/renderer/src/components/browser-pane/browser-client-page-metadata-publisher.test.ts": 315,
"src/renderer/src/components/browser-pane/browser-client-page-profile-staging.test.ts": 434,
@@ -4829,9 +4761,7 @@
"src/renderer/src/components/browser-pane/history/use-browser-history-shortcut.test.tsx": 1990,
"src/renderer/src/components/browser-pane/host-guest/agent-input-focus-guard.test.ts": 228,
"src/renderer/src/components/browser-pane/host-guest/browser-automation-visibility.test.ts": 45,
"src/renderer/src/components/browser-pane/host-guest/browser-focus.test.ts": 180,
"src/renderer/src/components/browser-pane/host-guest/browser-guest-eviction-veto.test.ts": 1724,
"src/renderer/src/components/browser-pane/host-guest/browser-guest-paint-policy.test.ts": 2035,
"src/renderer/src/components/browser-pane/host-guest/browser-page-evicted-guest-recovery.test.ts": 1839,
"src/renderer/src/components/browser-pane/host-guest/browser-page-guest-recovery.test.ts": 515,
"src/renderer/src/components/browser-pane/host-guest/browser-page-registration-ownership.test.ts": 2197,
@@ -5205,15 +5135,15 @@
"src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx": 691,
"src/renderer/src/components/mobile/paired-mobile-devices.test.ts": 192,
"src/renderer/src/components/mobile/use-mobile-page-paired-devices.test.ts": 475,
"src/renderer/src/components/native-chat-launch-resume-decision.test.tsx": 1844,
"src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx": 3261,
"src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts": 1954,
"src/renderer/src/components/native-chat-resume-failure-guidance.test.ts": 240,
"src/renderer/src/components/native-chat-resume-finished-run.test.tsx": 4173,
"src/renderer/src/components/native-chat-resume-on-restart-grouping.test.ts": 117,
"src/renderer/src/components/native-chat-resume-result-view.test.tsx": 2723,
"src/renderer/src/components/native-chat-resume-run.test.ts": 65,
"src/renderer/src/components/native-chat-resume-skipped-progress.test.tsx": 3009,
"src/renderer/src/components/native-chat-recovery/offers/native-chat-launch-resume-decision.test.tsx": 1844,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-bulk-progress.test.tsx": 3261,
"src/renderer/src/components/native-chat-recovery/offers/native-chat-resume-failed-chat-watch.test.ts": 1954,
"src/renderer/src/components/native-chat-recovery/failures/native-chat-resume-failure-guidance.test.ts": 240,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-finished-run.test.tsx": 4173,
"src/renderer/src/components/native-chat-recovery/tree/native-chat-resume-on-restart-grouping.test.ts": 117,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-result-view.test.tsx": 2723,
"src/renderer/src/components/native-chat-recovery/runs/native-chat-resume-run.test.ts": 65,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-skipped-progress.test.tsx": 3009,
"src/renderer/src/components/native-chat/messages/rows/NativeChatAgentMessageCard.test.tsx": 3700,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatBackgroundTaskRun.test.tsx": 242,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatBackgroundTasksStatus.test.tsx": 782,
@@ -5225,7 +5155,7 @@
"src/renderer/src/components/native-chat/attachments/chips/NativeChatImageAttachmentPreview.test.tsx": 2818,
"src/renderer/src/components/native-chat/media/visuals/NativeChatInlineVisual.link-placement.test.tsx": 2477,
"src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx": 2881,
"src/renderer/src/components/native-chat/usage/NativeChatInterruptedContinue.test.tsx": 2719,
"src/renderer/src/components/native-chat/viewport/scroll/NativeChatJumpToLatest.test.tsx": 477,
"src/renderer/src/components/native-chat/markdown/code/NativeChatMarkdown.code-wrap.test.tsx": 3123,
"src/renderer/src/components/native-chat/media/images/NativeChatMarkdown.image-preview.test.tsx": 4346,
@@ -5242,7 +5172,6 @@
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatMessageList.test.tsx": 4252,
"src/renderer/src/components/native-chat/conversation/turns/NativeChatMessageList.turn-indicator.test.tsx": 4070,
"src/renderer/src/components/native-chat/conversation/slots/NativeChatMessageList.turn-membership.test.tsx": 3527,
"src/renderer/src/components/native-chat/conversation/turns/NativeChatMessageList.turn-status.test.tsx": 3370,
"src/renderer/src/components/native-chat/session/structured/delivery/NativeChatMessageList.unsent-message.test.tsx": 3069,
"src/renderer/src/components/native-chat/viewport/scroll/NativeChatMessageList.visual-scroll.test.tsx": 6691,
"src/renderer/src/components/native-chat/viewport/virtualization/NativeChatMessageList.windowing.test.tsx": 20958,
@@ -5270,9 +5199,7 @@
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.host-outage.test.tsx": 3552,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.notice-card.test.tsx": 2911,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.refusal-notices.test.tsx": 3014,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.retained-draft.test.tsx": 3736,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.test.tsx": 4459,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.wsl-host-stopped.test.tsx": 3657,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx": 583,
"src/renderer/src/components/native-chat/tools/lines/NativeChatToolLine.test.tsx": 3544,
"src/renderer/src/components/native-chat/tools/lines/NativeChatToolOutput.test.tsx": 410,
@@ -5337,7 +5264,6 @@
"src/renderer/src/components/native-chat/queue/editing/native-chat-queued-edit-draft.test.ts": 649,
"src/renderer/src/components/native-chat/viewport/rail/native-chat-rail-outline-parity.test.ts": 497,
"src/renderer/src/components/native-chat/viewport/scroll/native-chat-reader-scroll-input.test.tsx": 311,
"src/renderer/src/components/native-chat/paste/native-chat-remote-image-paste.test.tsx": 1834,
"src/renderer/src/components/native-chat/attachments/uploads/native-chat-resolved-path-ownership.test.ts": 1749,
"src/renderer/src/components/native-chat/viewport/virtualization/native-chat-row-height-estimate.test.ts": 293,
"src/renderer/src/components/native-chat/drafts/native-chat-sent-prompt-history.test.ts": 217,
@@ -5347,7 +5273,6 @@
"src/renderer/src/components/native-chat/pane/routing/native-chat-split-flow.test.ts": 1916,
"src/renderer/src/components/native-chat/composer/submit/native-chat-structured-send-composition-clear.test.tsx": 2098,
"src/renderer/src/components/native-chat/subagents/native-chat-subagent-handoff-copy-budget.test.ts": 362,
"src/renderer/src/components/native-chat/subagents/native-chat-subagent-section-work-runs.test.ts": 278,
"src/renderer/src/components/native-chat/pane/routing/native-chat-tab-directory.test.ts": 304,
"src/renderer/src/components/native-chat/native-chat-task-list-state.test.ts": 62,
"src/renderer/src/components/native-chat/native-chat-thread-goal-rows.test.ts": 50,
@@ -5586,7 +5511,6 @@
"src/renderer/src/components/settings/chat/ChatSettingsSection.test.tsx": 2718,
"src/renderer/src/lib/skill-runtime-command.test.ts": 1670,
"src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx": 1025,
"src/renderer/src/components/settings/accounts/providers/GrokAccountsSection.test.tsx": 623,
"src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.test.tsx": 309,
"src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.test.tsx": 7498,
"src/renderer/src/components/settings/developer-permissions/LocalNetworkConnectionTest.test.tsx": 592,
@@ -5607,7 +5531,6 @@
"src/renderer/src/components/settings/ssh/SshPane.test.tsx": 1699,
"src/renderer/src/components/settings/ssh/targets/SshTargetForm.test.tsx": 1014,
"src/renderer/src/components/settings/ssh/targets/SshTargetList.test.tsx": 2093,
"src/renderer/src/components/settings/tasks/TaskSourceProviderCard.test.tsx": 467,
"src/renderer/src/components/settings/tasks/TaskSourceSimpleSetup.test.tsx": 527,
"src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts": 549,
"src/renderer/src/components/settings/terminal/appearance/TerminalContrastSetting.test.tsx": 798,
@@ -5760,7 +5683,6 @@
"src/renderer/src/components/sidebar/worktree-list/rows/repo-header-ssh-isolation.test.tsx": 2945,
"src/renderer/src/components/sidebar/worktree-list/rows/section-header-create-host.test.tsx": 2393,
"src/renderer/src/components/sidebar/worktree-list/viewport/hard-scroll-up.test.ts": 66,
"src/renderer/src/components/sidebar/worktree-list/viewport/header-folder-composition.test.tsx": 3725,
"src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.test.ts": 293,
"src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.test.tsx": 269,
"src/renderer/src/components/sidebar/worktree-list/drag/worktree-manual-order-store-write.test.ts": 2144,
@@ -5852,7 +5774,6 @@
"src/renderer/src/components/task-page/gitlab/use-task-page-gitlab-loading.test.tsx": 1504,
"src/renderer/src/components/task-page/jira/Content.board-owner.test.tsx": 2582,
"src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx": 1122,
"src/renderer/src/components/task-page/jira/TaskPageJiraStatusFilters.test.tsx": 923,
"src/renderer/src/components/task-page/jira/task-page-jira-board-detail-integration.test.tsx": 2192,
"src/renderer/src/components/task-page/jira/task-page-jira-board-transitions.test.ts": 69,
"src/renderer/src/components/task-page/jira/task-page-jira-board-view.test.tsx": 2287,
@@ -5881,9 +5802,6 @@
"src/renderer/src/components/terminal-pane/layout/close/RunningTerminalCloseDialog.test.tsx": 2303,
"src/renderer/src/components/terminal-pane/session-fork/TerminalAgentSessionForkDialog.test.tsx": 266,
"src/renderer/src/components/terminal-pane/titles/TerminalPaneHeaderOverlay.test.tsx": 1104,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/TerminalPaneRecoveryPortals.test.tsx": 670,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/TerminalRemoteRuntimeReconnectBanner.test.tsx": 812,
"src/renderer/src/components/terminal-pane/pty-transport/recovery/ssh/TerminalSshReconnectOverlay.test.tsx": 2354,
"src/renderer/src/components/terminal-pane/account-restart/detached/account-restart-queue-progress.test.ts": 1988,
"src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-attention-dispatch.test.ts": 301,
"src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-completion-replay-guard.test.ts": 318,
@@ -5957,7 +5875,6 @@
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/pty-connection-post-dispose-restore-termination.test.ts": 1215,
"src/renderer/src/components/terminal-pane/pty-connection/agent-identity/pty-connection-process-exit-row-retire.test.ts": 10626,
"src/renderer/src/components/terminal-pane/pty-connection/host-ownership/pty-connection-owner-hydration-backend-retention.test.ts": 4181,
"src/renderer/src/components/terminal-pane/pty-connection/startup/pty-connection-queued-startup-consume.test.ts": 4181,
"src/renderer/src/components/terminal-pane/pty-connection/reattach/pty-connection-reattach-binding.test.ts": 5240,
"src/renderer/src/components/terminal-pane/pty-connection/input/pty-connection-remote-new-terminal-typeahead.test.ts": 1864,
"src/renderer/src/components/terminal-pane/pty-connection/reattach/pty-connection-remote-snapshot-image-title.test.ts": 1754,
@@ -5965,7 +5882,6 @@
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/pty-connection-restored-baseline-shortfall.test.ts": 1902,
"src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts": 19652,
"src/renderer/src/components/terminal-pane/pty-connection/spawn/pty-connection-spawn-deferred-sleep.test.ts": 6918,
"src/renderer/src/components/terminal-pane/pty-connection/startup/pty-connection-split-cwd-resolution.test.ts": 2139,
"src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-visibility-and-exit.test.ts": 18950,
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/pty-connection-visible-pane-output-pause-latch.test.ts": 1639,
"src/renderer/src/components/terminal-pane/pty-connection/reattach/deferred-session-reattach-newer-daemon-hold.test.ts": 2570,
@@ -5974,12 +5890,10 @@
"src/shared/terminal/protocol/escape/terminal-synchronized-output-socket-splits.test.ts": 92,
"src/renderer/src/components/terminal-pane/pty-connection/output/foreground-renderer-risk-scan-tail.test.ts": 111,
"src/renderer/src/components/terminal-pane/pty-connection/spawn/hidden-agent-resume-deferral.test.ts": 55,
"src/renderer/src/components/terminal-pane/pty-connection/startup/in-place-resume-hold-wiring.test.ts": 2484,
"src/renderer/src/components/terminal-pane/pty-connection/startup/main-pane-resume-banner.test.ts": 59,
"src/renderer/src/components/terminal-pane/pty-connection/spawn/main-pane-start-wait.test.ts": 76,
"src/renderer/src/components/terminal-pane/pty-connection/serialization/pane-serializer-register.test.ts": 123,
"src/renderer/src/components/terminal-pane/pty-connection/host-ownership/pty-kitty-file-reader.test.ts": 297,
"src/renderer/src/components/terminal-pane/pty-connection/startup/saved-resume-cancellation.test.ts": 1882,
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/snapshot-covered-terminal-queries.test.ts": 150,
"src/renderer/src/components/terminal-pane/pty-connection/output/terminal-input-echo-delay.test.ts": 132,
"src/renderer/src/components/terminal-pane/pty-connection/host-ownership/unserved-ssh-pane-bridge.test.ts": 70,
@@ -6007,7 +5921,6 @@
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-pi-coalesce.test.ts": 683,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-reattach-admission.test.ts": 1636,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-recycled-pty-incarnation.test.ts": 787,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-spawn-errors.test.ts": 1218,
"src/renderer/src/components/terminal-pane/input/focus/regular-terminal-focus-ownership.test.ts": 208,
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/remote-hidden-output-restore-outcomes.test.ts": 2794,
"src/renderer/src/components/terminal-pane/pty-connection/output-restore/remote-hidden-output-restore-unavailable-banner.repro.test.ts": 2355,
@@ -6074,7 +5987,6 @@
"src/renderer/src/components/terminal-pane/emulation/terminal-capability-replies.test.ts": 131,
"src/renderer/src/components/terminal-pane/clipboard/terminal-clipboard-paste.test.ts": 76,
"src/renderer/src/components/terminal-pane/parking/retention/terminal-cold-park-timer-rearm.test.tsx": 701,
"src/renderer/src/components/terminal-pane/pty-transport/delivery/terminal-delivery-watchdog.test.ts": 111,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-handler.test.ts": 638,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-image-path.test.ts": 61,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-internal-handler.test.ts": 800,
@@ -6347,8 +6259,8 @@
"src/renderer/src/hooks/useMacTccAttributionSeveredNotice.test.tsx": 2358,
"src/renderer/src/hooks/useMetadataListRequest.test.tsx": 266,
"src/renderer/src/hooks/useModalReturnFocus.test.tsx": 1938,
"src/renderer/src/hooks/usePrimarySelectionPaste.terminal-native-paste.test.tsx": 388,
"src/renderer/src/hooks/usePrimarySelectionPaste.test.tsx": 411,
"src/renderer/src/hooks/clipboard/primary-selection/usePrimarySelectionPaste.terminal-native-paste.test.tsx": 388,
"src/renderer/src/hooks/clipboard/primary-selection/usePrimarySelectionPaste.test.tsx": 411,
"src/renderer/src/hooks/useReadOnScreenAttention.away.test.ts": 2420,
"src/renderer/src/hooks/useReadOnScreenAttention.clock-skew.test.ts": 2093,
"src/renderer/src/hooks/useReadOnScreenAttention.floating-panel.test.ts": 2157,
@@ -6422,9 +6334,9 @@
"src/renderer/src/lib/agent-session/resume/history/ai-vault-session-resume-preparation.test.ts": 408,
"src/renderer/src/lib/agent-session/history/title-sync/ai-vault-tab-title-sync-inputs.test.ts": 476,
"src/renderer/src/lib/agent-session/history/title-sync/ai-vault-tab-title-sync.test.ts": 925,
"src/renderer/src/lib/app-command-dispatch.test.ts": 64,
"src/renderer/src/lib/app-keyboard-router.test.ts": 187,
"src/renderer/src/lib/app-menu-paste.test.ts": 229,
"src/renderer/src/lib/app-commands/app-command-dispatch.test.ts": 64,
"src/renderer/src/lib/keyboard/app-keyboard-router.test.ts": 187,
"src/renderer/src/lib/clipboard/menu/app-menu-paste.test.ts": 229,
"src/renderer/src/lib/automation-session-reuse.test.ts": 70,
"src/renderer/src/lib/automation-terminal-ownership.test.ts": 1795,
"src/renderer/src/lib/bounded-generation-map.test.ts": 35,
@@ -6440,7 +6352,7 @@
"src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.test.ts": 75,
"src/renderer/src/lib/claude-account-switch-sessions.test.ts": 782,
"src/renderer/src/lib/claude-session-account-move.test.ts": 1816,
"src/renderer/src/lib/clipboard-image-copy.test.ts": 243,
"src/renderer/src/lib/clipboard/images/clipboard-image-copy.test.ts": 243,
"src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts": 526,
"src/renderer/src/lib/cmd-j-linear-issue-intent.test.ts": 42,
"src/renderer/src/lib/code-host-error-copy.test.ts": 297,
@@ -6449,8 +6361,7 @@
"src/renderer/src/lib/agent-session/accounts/codex/codex-session-restart.test.ts": 2039,
"src/renderer/src/lib/codex-stale-pane-account-identity.test.ts": 1823,
"src/renderer/src/lib/codex-stale-pane-sweep.test.ts": 1817,
"src/renderer/src/lib/comment-body-line-count.test.ts": 84,
"src/renderer/src/lib/comment-body-submit-state.test.ts": 55,
"src/renderer/src/lib/source-control/review/comments/comment-body-submit-state.test.ts": 55,
"src/renderer/src/lib/composer-submit-cancellation.test.ts": 41,
"src/renderer/src/lib/workspace/ownership/connection-context.test.ts": 1817,
"src/renderer/src/lib/crash-diagnostics.test.ts": 176,
@@ -6461,22 +6372,20 @@
"src/renderer/src/lib/workspace/creation/work-item/direct-work-item-setup.test.ts": 2072,
"src/renderer/src/lib/browser/documents/doc-preview-grants.test.ts": 2094,
"src/renderer/src/lib/editor/file-operations/editor-file-operation-owner.test.ts": 2848,
"src/renderer/src/lib/workspace/activation/initial-surface/empty-workspace-default-agent-chat.test.ts": 1895,
"src/renderer/src/lib/emulator-error-copy.test.ts": 415,
"src/renderer/src/lib/ensure-hooks-confirmed.test.ts": 799,
"src/renderer/src/lib/simulator/tabs/ensure-simulator-tab-behavior.test.ts": 3601,
"src/renderer/src/runtime/hosts/platform/execution-host-facts.test.ts": 1796,
"src/renderer/src/lib/feature-education-telemetry.test.ts": 52,
"src/renderer/src/lib/feedback-image-attachments.test.ts": 557,
"src/renderer/src/lib/browser/documents/file-preview-link-open.test.ts": 2311,
"src/renderer/src/lib/file-search-selection.test.ts": 51,
"src/renderer/src/lib/find-query-bounds.test.ts": 45,
"src/renderer/src/lib/finished-agent-resume-resurrection.test.ts": 1872,
"src/renderer/src/lib/focus-terminal-tab-surface.split-focus.test.ts": 331,
"src/renderer/src/lib/foreground-terminal-tabs.test.ts": 55,
"src/renderer/src/lib/github-links.test.ts": 76,
"src/renderer/src/lib/github-source-runtime-context.test.ts": 62,
"src/renderer/src/lib/gitlab-links.test.ts": 56,
"src/shared/new-workspace/github-links.test.ts": 76,
"src/renderer/src/lib/work-items/github/github-source-runtime-context.test.ts": 62,
"src/shared/new-workspace/gitlab-links.test.ts": 56,
"src/renderer/src/lib/hook-command-delayed-delivery.test.ts": 1904,
"src/renderer/src/lib/host-chat-program.test.ts": 2419,
"src/renderer/src/lib/host-mirror-handle-gap-drain.test.ts": 1706,
@@ -6484,11 +6393,10 @@
"src/renderer/src/lib/host-mirrored-pane-resume-replay.test.ts": 1814,
"src/renderer/src/lib/browser/navigation/http-link-completion.test.ts": 1760,
"src/renderer/src/lib/browser/navigation/http-link-routing-store.test.ts": 2211,
"src/renderer/src/lib/image-blob-png.test.ts": 253,
"src/renderer/src/lib/ime-composition-keyboard-event.test.ts": 274,
"src/renderer/src/lib/ime-text-field.test.tsx": 739,
"src/renderer/src/lib/text-input/ime/ime-composition-keyboard-event.test.ts": 274,
"src/renderer/src/lib/text-input/ime/ime-text-field.test.tsx": 739,
"src/renderer/src/lib/keyboard-layout/layout-base-character.test.ts": 48,
"src/renderer/src/lib/large-text-control-paste.test.ts": 220,
"src/renderer/src/lib/text-input/paste/large-text-control-paste.test.ts": 220,
"src/renderer/src/lib/agent-session/launch/background/launch-agent-background-session-launch-parity.test.ts": 2792,
"src/renderer/src/lib/agent-session/launch/background/launch-agent-background-session.test.ts": 1040,
"src/renderer/src/lib/agent-session/launch/new-tab/launch-agent-in-new-tab-main-surface.test.ts": 1821,
@@ -6505,11 +6413,10 @@
"src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts": 194,
"src/renderer/src/lib/lazy-with-retry.preload.test.tsx": 208,
"src/renderer/src/lib/lazy-with-retry.test.ts": 282,
"src/renderer/src/lib/linear-board-drag-payload.test.ts": 58,
"src/renderer/src/lib/work-items/linear/linear-issue-context-snapshot.test.ts": 55,
"src/renderer/src/lib/work-items/linear/linear-board-drag-payload.test.ts": 58,
"src/renderer/src/lib/work-items/linear/linear-issue-workspace-attachment.test.ts": 99,
"src/renderer/src/lib/link-source-local-open-owner.test.ts": 433,
"src/renderer/src/lib/linked-work-item-context.test.ts": 64,
"src/renderer/src/lib/work-items/linked-work-item-context.test.ts": 64,
"src/renderer/src/lib/editor/monaco/monaco-editor-gutter-disposal.test.ts": 295,
"src/renderer/src/lib/monaco-languages/markdown-heading-outline.test.ts": 70,
"src/renderer/src/lib/monaco-languages/monarch-embed-entry-recursion.test.ts": 697,
@@ -6593,14 +6500,14 @@
"src/renderer/src/lib/pane-manager/xterm-write-callback-guard.test.ts": 53,
"src/renderer/src/lib/pane-spawn-deferral-drain.test.ts": 61,
"src/renderer/src/lib/parked-terminal-host-hydration.test.ts": 274,
"src/renderer/src/lib/paste-payload-metadata.test.ts": 53,
"src/renderer/src/lib/text-input/paste/paste-payload-metadata.test.ts": 53,
"src/renderer/src/lib/path-head-elision.test.ts": 53,
"src/renderer/src/lib/path.test.ts": 55,
"src/renderer/src/lib/plugin-command-keybindings.test.ts": 1909,
"src/renderer/src/lib/plugin-command-replacements.test.ts": 95,
"src/renderer/src/lib/plugin-icons/plugin-icon-svg.test.ts": 231,
"src/renderer/src/lib/plugin-monaco-token-rules.test.ts": 51,
"src/renderer/src/lib/pr-bot-author-overrides.test.ts": 349,
"src/renderer/src/lib/source-control/review/pr-bot-author-overrides.test.ts": 349,
"src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts": 48,
"src/renderer/src/lib/project-clone-url-prefill.test.ts": 61,
"src/renderer/src/lib/project-host-workspace-target.jira-mapping.test.ts": 76,
@@ -6662,8 +6569,8 @@
"src/renderer/src/lib/task-provider-error-copy.test.ts": 363,
"src/renderer/src/lib/terminal-app-theme.test.ts": 301,
"src/renderer/src/lib/terminal-input-activity-coalescing.test.ts": 43,
"src/renderer/src/lib/text-control-paste-ownership.test.ts": 217,
"src/renderer/src/lib/text-control-paste.test.ts": 222,
"src/renderer/src/lib/text-input/paste/text-control-paste-ownership.test.ts": 217,
"src/renderer/src/lib/text-input/paste/text-control-paste.test.ts": 222,
"src/renderer/src/lib/text-generation-error-copy.test.ts": 466,
"src/renderer/src/lib/typing-latency/echo-instrumentation.test.ts": 145,
"src/renderer/src/lib/typing-latency/renderer-long-tasks.test.ts": 75,
@@ -6744,8 +6651,6 @@
"src/renderer/src/runtime/hosted-terminals/streams/remote-runtime-terminal-stale-stream-frames.test.ts": 234,
"src/renderer/src/runtime/hosted-terminals/streams/remote-runtime-terminal-stall-recovery.test.ts": 826,
"src/renderer/src/runtime/hosts/updates/remote-server-restart-wait.test.ts": 49,
"src/renderer/src/runtime/browser/client-hosting/restored-client-hosted-browser-host-attach.test.ts": 43,
"src/renderer/src/runtime/browser/client-hosting/restored-client-hosted-browser-host-restart-attach.test.ts": 474,
"src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts": 64,
"src/renderer/src/runtime/files/reads/runtime-directory-page-client.test.ts": 268,
"src/renderer/src/runtime/rpc/environment-revisions/runtime-environment-pairing-refresh.test.ts": 109,
@@ -6756,7 +6661,6 @@
"src/renderer/src/runtime/files/reads/runtime-file-range-client.test.ts": 444,
"src/renderer/src/runtime/files/mutations/runtime-file-write-disk-precondition.test.ts": 478,
"src/renderer/src/runtime/source-control/git/runtime-git-client.test.ts": 513,
"src/renderer/src/runtime/provider-accounts/sign-in/runtime-host-account-sign-in-client.test.ts": 487,
"src/renderer/src/runtime/hosts/connections/runtime-host-contact-regained.test.ts": 413,
"src/renderer/src/runtime/jira/runtime-jira-image-client.test.ts": 214,
"src/renderer/src/runtime/jira/runtime-jira-payload-stream.test.ts": 192,
@@ -6922,7 +6826,6 @@
"src/renderer/src/store/slices/editor/git/editor-git-status-reconciliation.test.ts": 870,
"src/renderer/src/store/slices/editor/tabs/editor-layout-owner-records.test.ts": 2006,
"src/renderer/src/store/slices/editor/markdown/editor-markdown-link-activation.test.ts": 953,
"src/renderer/src/store/slices/editor/markdown/editor-markdown-view-state.test.ts": 801,
"src/renderer/src/store/slices/editor/git/editor-open-diff.test.ts": 1987,
"src/renderer/src/store/slices/editor/tabs/editor-open-link-placement.test.ts": 1987,
"src/renderer/src/store/slices/editor/tabs/editor-preview-tab-setting.test.ts": 1952,
@@ -6931,7 +6834,6 @@
"src/renderer/src/store/slices/editor/git/editor-remote-branch-actions.test.ts": 935,
"src/renderer/src/store/slices/editor/hydration/editor-session-duplicate-restore.test.ts": 1931,
"src/renderer/src/store/slices/editor/focus/editor-set-active-file-guard.test.ts": 895,
"src/renderer/src/store/slices/editor/tabs/editor-state-worktree-purge-leak.test.ts": 1740,
"src/renderer/src/store/slices/editor/tabs/editor-tab-layout-owner.test.ts": 1883,
"src/renderer/src/store/slices/editor/tabs/editor-tab-placement.test.ts": 1837,
"src/renderer/src/store/slices/editor/tabs/editor-tab-shared-project-owner.test.ts": 954,
@@ -6950,7 +6852,6 @@
"src/renderer/src/store/github/pull-requests/github-pr-branch-direct-refresh-scope.test.ts": 667,
"src/renderer/src/store/github/pull-requests/github-pr-branch-fallback-results.test.ts": 658,
"src/renderer/src/store/github/pull-requests/github-pr-branch-linked-pr-divergence.test.ts": 662,
"src/renderer/src/store/github/checks/github-pr-checks-fetch.test.ts": 766,
"src/renderer/src/store/github/comments/github-pr-comment-read-order.test.ts": 570,
"src/renderer/src/store/github/comments/github-pr-comments.test.ts": 746,
"src/renderer/src/store/github/pull-requests/github-pr-host-scope.test.ts": 1030,
@@ -6959,7 +6860,6 @@
"src/renderer/src/store/github/pull-requests/github-pr-request-lifetime.test.ts": 1458,
"src/renderer/src/store/github/projects/github-project-view-tables.test.ts": 656,
"src/renderer/src/store/github/repositories/github-repo-lookup-index.test.ts": 51,
"src/renderer/src/store/github/review-threads/github-review-thread-actions.test.ts": 574,
"src/renderer/src/store/github/review-threads/github-review-thread-rollback.test.ts": 663,
"src/renderer/src/store/github/task-records/github-task-record-retries.test.ts": 758,
"src/renderer/src/store/github/task-records/github-task-records.test.ts": 807,
@@ -6969,7 +6869,6 @@
"src/renderer/src/store/github/work-items/github-work-items-twin-cache-host.test.ts": 672,
"src/renderer/src/store/slices/hosted-review/hosted-review-request-lifetime.test.ts": 110,
"src/renderer/src/store/slices/hosted-review/hosted-review.test.ts": 320,
"src/renderer/src/store/repos/issue-source-preference-twin.test.ts": 1858,
"src/renderer/src/store/slices/jira/jira-status-filter-cache.test.ts": 1770,
"src/renderer/src/store/slices/jira/jira-web-client-status.test.ts": 1901,
"src/renderer/src/store/slices/jira/jira.test.ts": 312,
@@ -7047,7 +6946,6 @@
"src/renderer/src/store/slices/tabs/tabs-unread-and-focus.test.ts": 1801,
"src/renderer/src/store/slices/tabs/structured-chat-close-owner.test.ts": 1866,
"src/renderer/src/store/slices/tabs/tabs-reconciliation-empty-groups.test.ts": 1703,
"src/renderer/src/store/terminals/terminal-input-activity-store-write.test.ts": 1726,
"src/renderer/src/store/terminals/terminal-layout-pty-ownership.test.ts": 1734,
"src/renderer/src/store/terminals/terminal-pane-detach-agent-identity.test.ts": 1627,
"src/renderer/src/store/terminals/terminal-pane-detach-agent-retention.test.ts": 2846,
@@ -7060,7 +6958,6 @@
"src/renderer/src/store/terminals/terminals-hydration-canonical-pty-overlap.test.ts": 1946,
"src/renderer/src/store/terminals/terminals-hydration-canonical-rows.test.ts": 1680,
"src/renderer/src/store/slices/ui/ui-agent-send-target.test.ts": 760,
"src/renderer/src/store/slices/ui/ui-feature-interactions.test.ts": 648,
"src/renderer/src/store/slices/ui/ui-host-filter-sync.test.ts": 597,
"src/renderer/src/store/slices/ui/ui-host-workspace-statuses.test.ts": 1695,
"src/renderer/src/store/slices/ui/ui-modal-slot-dismissal.test.ts": 662,
@@ -7084,7 +6981,6 @@
"src/renderer/src/store/slices/settings/worktree-visibility-settings-write.test.ts": 307,
"src/renderer/src/store/slices/worktrees/teardown/worktrees-always-force-delete.test.ts": 1611,
"src/renderer/src/store/slices/worktrees/teardown/worktrees-background-removal.test.ts": 2011,
"src/renderer/src/store/slices/worktrees/create/worktrees-create-base-status.test.ts": 1252,
"src/renderer/src/store/slices/worktrees/create/worktrees-create-captured-host.test.ts": 1148,
"src/renderer/src/store/slices/worktrees/listing/worktrees-fetch-listing-merge.test.ts": 1311,
"src/renderer/src/store/slices/worktrees/listing/worktrees-fetch-owner-routing.test.ts": 2038,
@@ -7113,7 +7009,6 @@
"src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-origins.test.ts": 1218,
"src/renderer/src/store/slices/worktrees/session/worktrees-terminal-reference-source-race.test.ts": 1063,
"src/renderer/src/store/slices/workspace-attention/worktrees-unread-state.test.ts": 1704,
"src/renderer/src/store/slices/worktrees/session/worktrees-workspace-selection-state.test.ts": 1170,
"src/renderer/src/store/slices/worktrees/host-scoped-row-merges.test.ts": 1805,
"src/renderer/src/store/slices/worktrees/listing/detected-worktree-meta.test.ts": 318,
"src/renderer/src/store/slices/worktrees/listing/stale-listing-purges-created-worktree.test.ts": 1828,
@@ -7182,7 +7077,6 @@
"src/shared/agent-cli-install-dir-fallback.test.ts": 130,
"src/shared/agent-launch/startup/agent-command-line-intent.test.ts": 95,
"src/shared/agent-status/title/agent-detection.test.ts": 188,
"src/shared/agent-hook-listener/transport/agent-hook-endpoint-file.test.ts": 58,
"src/shared/agent-hook-listener/transport/agent-hook-endpoint-temp-cleanup.test.ts": 55,
"src/shared/agent-hook-listener/normalization/agent-hook-listener-claude-elicitation.test.ts": 229,
"src/shared/agent-hook-listener/normalization/agent-hook-listener-codex-approval-ownership.test.ts": 258,
@@ -8342,7 +8236,6 @@
"tests/e2e/git-history-tooltip-wrap.spec.ts": 22900,
"tests/e2e/git-no-upstream-polling-churn.spec.ts": 22600,
"tests/e2e/github-cli-stall-repro.spec.ts": 18200,
"tests/e2e/github-created-issue-start-prefill.spec.ts": 43100,
"tests/e2e/github-url-smart-input-transition.spec.ts": 58200,
"tests/e2e/golden-agent-tui-launch.spec.ts": 27300,
"tests/e2e/golden-core-flows.spec.ts": 91100,
@@ -53,7 +53,6 @@ export const UNIT_EXCLUDE = [
'src/main/browser/client-pages/renderer/browser-client-page-renderer-lifecycle.electron.test.ts',
'src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-adapter-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts',
'src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts',
@@ -1,24 +1,43 @@
#!/usr/bin/env bash
# Gate for merging into cloud/integration (no CI on nwparker/orca-private): run from your branch's clone.
# Gate for merging into cloud/integration (no CI on nwparker/orca-private). Run from your branch, before merging:
# config/scripts/cloud-integration/premerge-check.sh
# Merges the current private/cloud/integration into a throwaway worktree with your branch, then requires
# typecheck, the full lint suite (pre-commit hooks only lint staged files) and the tests your branch changed.
# pnpm typecheck, the full pnpm lint suite (pre-commit hooks only lint staged files), and vitest for the tests your
# branch touched: changed *.test.ts(x) plus the sibling tests of changed sources (foo.ts -> foo.test.ts, foo.*.test.ts),
# and each touched cloud/apps/* package's own suite.
# Prints "premerge OK" only when everything passes. Nothing here reads /tmp state. On macOS (no flock) wrap heavy runs
# as /tmp/vdrv/heavy.sh 'config/scripts/cloud-integration/premerge-check.sh'.
set -euo pipefail
branch=$(git rev-parse --abbrev-ref HEAD)
git fetch -q private +cloud/integration:refs/remotes/private/cloud/integration
base=private/cloud/integration
dir=$(mktemp -d "${TMPDIR:-/tmp}/cloud-premerge-XXXX")
trap 'git worktree remove --force "$dir" >/dev/null 2>&1 || true' EXIT
git worktree add -q --detach "$dir" HEAD
cd "$dir"
git merge -q --no-edit private/cloud/integration
git merge -q --no-edit "$base"
pnpm install --frozen-lockfile >/dev/null
pnpm typecheck
pnpm lint
tests=$(git diff --name-only --diff-filter=AM private/cloud/integration...HEAD -- 'src/*.test.ts' 'src/*.test.tsx' | tr '\n' ' ')
if [[ -n "$tests" ]]; then
tests=()
while IFS= read -r file; do
case "$file" in
*.test.ts | *.test.tsx) if [[ -f "$file" ]]; then tests+=("$file"); fi ;;
*.ts | *.tsx)
stem=${file%.*}
for candidate in "$stem".test.ts "$stem".test.tsx "$stem".*.test.ts "$stem".*.test.tsx; do
if [[ -f "$candidate" ]]; then tests+=("$candidate"); fi
done
;;
esac
done < <(git diff --name-only --diff-filter=AMR "$base"...HEAD -- 'src/*')
if ((${#tests[@]} > 0)); then
tests=($(printf "%s\n" "${tests[@]}" | sort -u)) # repo paths have no spaces
node config/scripts/native-build/node-addons/ensure-native-runtime.mjs --runtime=node >/dev/null
npx vitest run --config config/vitest.config.ts $tests
npx vitest run --config config/vitest.config.ts "${tests[@]}"
fi
for app in $(git diff --name-only private/cloud/integration...HEAD -- cloud/apps | cut -d/ -f1-3 | sort -u); do
(cd "$app" && npx vitest run)
for app in $(git diff --name-only "$base"...HEAD -- cloud/apps | cut -d/ -f1-3 | sort -u); do
if [[ -d "$app" ]]; then (cd "$app" && npx vitest run); fi
done
echo "premerge OK: $branch merges cleanly into cloud/integration with typecheck, lint and tests green."
echo "premerge OK: $branch merges cleanly into cloud/integration with typecheck, lint and ${#tests[@]} targeted test file(s) green."
@@ -1,8 +1,9 @@
#!/usr/bin/env bash
# Move cloud/integration (on `private` = nwparker/orca-private) forward. Other workstreams branch from it and merge
# into it, so it is NEVER rebuilt or force-pushed: this script only merges new commits from its sources.
# stablyai/orca main (origin)
# + nwparker/orca-private nwparker/accounts-billing, nwparker/vercel-driver (until they are retired)
# stablyai/orca main ($UPSTREAM, default origin; set UPSTREAM=stably in clones that name it so)
# + PRIVATE_BRANCHES on nwparker/orca-private (none by default: accounts-billing and vercel-driver are retired;
# accounts-billing was rewritten after its content landed here, so merging its old tips would re-add history)
# + open upstream PR branches stacked in while under review: nwparker/agent-host-sign-in (#40228),
# nwparker/agent-sign-in-followups (#40283, stacked on #40228), nwparker/agent-api-keys (#40303).
# Once a PR lands, main brings it; drop it from STACKED. A source force-pushed (rebased) still merges: resolve to
@@ -12,40 +13,44 @@
# src/renderer/src/components/billing, src/shared/orca-billing.ts, billing keys in en.json) keep accounts-billing;
# vercel-driver carries an older billing snapshot. Elsewhere keep both sides.
#
# Usage (clone of stablyai/orca as origin, remote `private`; never an Orca worktree):
# Usage (clone of stablyai/orca, remote `private`). Nothing here reads /tmp. In a worktree where cloud/integration is
# checked out elsewhere, set LOCAL_BRANCH to a scratch branch name and push it with HEAD:cloud/integration.
# config/scripts/cloud-integration/sync-cloud-integration.sh
# on conflict: resolve, `git add -A && git commit --no-edit`, rerun (already-merged sources are skipped)
# then: pnpm install --frozen-lockfile && pnpm typecheck && pnpm lint && targeted vitest
# git push private cloud/integration # plain fast-forward push; if rejected, rerun (someone merged)
# (or run config/scripts/cloud-integration/premerge-check.sh)
# git push private "$LOCAL_BRANCH":cloud/integration # fast-forward only; if rejected, rerun (someone merged)
set -euo pipefail
PRIVATE_BRANCHES=(${PRIVATE_BRANCHES:-nwparker/accounts-billing nwparker/vercel-driver})
UPSTREAM=${UPSTREAM:-origin}
LOCAL_BRANCH=${LOCAL_BRANCH:-cloud/integration}
PRIVATE_BRANCHES=(${PRIVATE_BRANCHES:-})
STACKED=(${STACKED:-nwparker/agent-host-sign-in nwparker/agent-sign-in-followups nwparker/agent-api-keys})
git remote get-url private >/dev/null 2>&1 || git remote add private https://github.com/nwparker/orca-private.git
git config rerere.enabled true
git config rerere.autoupdate true
git fetch -q origin main
for b in "${STACKED[@]}"; do git fetch -q origin "+$b:refs/remotes/origin/$b"; done
for b in "${PRIVATE_BRANCHES[@]}"; do git fetch -q private "+$b:refs/remotes/private/$b"; done
git fetch -q "$UPSTREAM" main
for b in "${STACKED[@]}"; do git fetch -q "$UPSTREAM" "+$b:refs/remotes/$UPSTREAM/$b"; done
for b in ${PRIVATE_BRANCHES[@]+"${PRIVATE_BRANCHES[@]}"}; do git fetch -q private "+$b:refs/remotes/private/$b"; done
git fetch -q private +cloud/integration:refs/remotes/private/cloud/integration
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
echo "Working tree has changes; commit or set them aside first." >&2; exit 1
fi
# Keep local unpushed commits: fast-forward or merge the remote tip in, never reset onto it.
if git rev-parse -q --verify refs/heads/cloud/integration >/dev/null; then
git checkout -q cloud/integration
if git rev-parse -q --verify "refs/heads/$LOCAL_BRANCH" >/dev/null; then
git checkout -q "$LOCAL_BRANCH"
git merge --no-edit -q private/cloud/integration
else
git checkout -q -b cloud/integration private/cloud/integration
git checkout -q -b "$LOCAL_BRANCH" private/cloud/integration
fi
for ref in origin/main "${PRIVATE_BRANCHES[@]/#/private/}" "${STACKED[@]/#/origin/}"; do
for ref in "$UPSTREAM/main" ${PRIVATE_BRANCHES[@]+"${PRIVATE_BRANCHES[@]/#/private/}"} "${STACKED[@]/#/$UPSTREAM/}"; do
git merge-base --is-ancestor "$ref" HEAD && continue
git merge --no-ff --no-edit -m "Merge $ref into cloud/integration" "$ref" || {
echo "Conflict merging $ref. Resolve (policy above), git add -A && git commit --no-edit, rerun." >&2
exit 1
}
done
echo "cloud/integration at $(git rev-parse --short HEAD), $(git rev-list --count private/cloud/integration..HEAD) new commit(s) over private. Verify, then: git push private cloud/integration"
echo "cloud/integration at $(git rev-parse --short HEAD), $(git rev-list --count private/cloud/integration..HEAD) new commit(s) over private. Verify, then: git push private $LOCAL_BRANCH:cloud/integration"
@@ -1,6 +1,7 @@
const { createHash } = require('node:crypto')
const { readFileSync, readdirSync, statSync } = require('node:fs')
const { join, resolve } = require('node:path')
const { brotliDecompressSync } = require('node:zlib')
const { darkBuildMarkerFileNames } = require('../../../dark-build-switches.cjs')
@@ -98,6 +99,12 @@ function parseManifest(bundleDir) {
if (typeof asset.contentType !== 'string' || asset.contentType.length === 0) {
throw failure(`manifest asset ${asset.path} has no contentType`)
}
if (asset.brotliByteLength !== undefined) {
assertInteger(asset.brotliByteLength, `assets[${asset.path}].brotliByteLength`)
if (asset.brotliByteLength >= asset.byteLength) {
throw failure(`manifest asset ${asset.path} declares a brotli copy no smaller than itself`)
}
}
}
if (!manifest.assets.some((asset) => asset.path === manifest.entrypoint)) {
throw failure(`manifest entrypoint ${manifest.entrypoint} is not one of its assets`)
@@ -132,13 +139,33 @@ function listBundleFiles(directory, prefix = '') {
return found
}
// Mirrors mobileWebBundleBrotliPath in src/shared/mobile-web-bundle/manifest-contract.ts.
function brotliPath(asset) {
return `${asset.path}.br`
}
function brotliCopies(manifest) {
return manifest.assets.filter((asset) => asset.brotliByteLength !== undefined)
}
function statSize(bundleDir, path) {
try {
return statSync(join(bundleDir, path)).size
} catch (error) {
throw failure(
`manifest lists ${path}, which is missing from ${bundleDir} (${error.code ?? error.message}). ${REMEDY}`
)
}
}
// Reject unlisted on-disk assets: content-addressed stale files survive interrupted builds and would ship unreachable/unverified in asar.
function assertNoUnlistedFiles(bundleDir, manifest) {
// A dark build's marker is accounted for here; packaging refuses it through assertNotDarkBuild.
const listed = new Set([
'manifest.json',
...darkBuildMarkerFileNames(),
...manifest.assets.map((asset) => asset.path)
...manifest.assets.map((asset) => asset.path),
...brotliCopies(manifest).map((asset) => brotliPath(asset))
])
const strays = listBundleFiles(bundleDir).filter((path) => !listed.has(path))
if (strays.length > 0) {
@@ -155,14 +182,7 @@ function assertMobileWebBundleBuilt(bundleDir = MOBILE_WEB_BUNDLE_DIR) {
assertNoUnlistedFiles(bundleDir, manifest)
for (const asset of manifest.assets) {
const assetPath = join(bundleDir, asset.path)
let size
try {
size = statSync(assetPath).size
} catch (error) {
throw failure(
`manifest lists ${asset.path}, which is missing from ${bundleDir} (${error.code ?? error.message}). ${REMEDY}`
)
}
const size = statSize(bundleDir, asset.path)
if (size !== asset.byteLength) {
throw failure(
`${asset.path} is ${String(size)} bytes on disk, manifest says ${String(asset.byteLength)}. ${REMEDY}`
@@ -175,6 +195,20 @@ function assertMobileWebBundleBuilt(bundleDir = MOBILE_WEB_BUNDLE_DIR) {
)
}
}
// Phones verify only the inflated bytes, so a copy that inflates to anything else fails every download.
for (const asset of brotliCopies(manifest)) {
const path = brotliPath(asset)
const size = statSize(bundleDir, path)
if (size !== asset.brotliByteLength) {
throw failure(
`${path} is ${String(size)} bytes on disk, manifest says ${String(asset.brotliByteLength)}. ${REMEDY}`
)
}
const inflated = brotliDecompressSync(readFileSync(join(bundleDir, path)))
if (createHash('sha256').update(inflated).digest('hex') !== asset.sha256) {
throw failure(`${path} does not inflate to ${asset.path}. ${REMEDY}`)
}
}
console.log(
`[verify-packaged-mobile-web-bundle] OK — buildId ${manifest.buildId}, ` +
`${String(manifest.assets.length)} asset(s), ${String(manifest.totalBytes)} bytes`
@@ -2,6 +2,7 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { brotliCompressSync } from 'node:zlib'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { writeMobileWebBundleFixtureTree } from '../../build/mobile-web-app/mobile-web-bundle-fixture-tree.mjs'
@@ -10,11 +11,11 @@ const { assertMobileWebBundleBuilt } = require('./verify-packaged-mobile-web-bun
const electronBuilderConfig = require('../../../electron-builder.config.cjs')
const { darkBuildMarkers } = require('../../../dark-build-switches.cjs')
async function withBundle(run) {
async function withBundle(run, { brotli = false } = {}) {
const scratch = await mkdtemp(join(tmpdir(), 'orca-mobile-web-guard-'))
const bundleDir = join(scratch, 'mobile-web')
try {
const { manifest } = await writeMobileWebBundleFixtureTree({ outDir: bundleDir })
const { manifest } = await writeMobileWebBundleFixtureTree({ outDir: bundleDir, brotli })
await run({ bundleDir, manifest })
} finally {
await rm(scratch, { recursive: true, force: true })
@@ -65,6 +66,25 @@ describe('assertMobileWebBundleBuilt', () => {
})
})
it('accepts the brotli copies the manifest lists, and fails one that inflates to other bytes', async () => {
await withBundle(
async ({ bundleDir, manifest }) => {
const asset = manifest.assets.find((entry) => entry.brotliByteLength !== undefined)
expect(() => assertMobileWebBundleBuilt(bundleDir)).not.toThrow()
const forged = brotliCompressSync(Buffer.from('not the asset'))
await writeFile(join(bundleDir, `${asset.path}.br`), forged)
await rewriteManifest(bundleDir, (rewritten) => {
rewritten.assets.find((entry) => entry.path === asset.path).brotliByteLength =
forged.byteLength
})
expect(() => assertMobileWebBundleBuilt(bundleDir)).toThrow(
`${asset.path}.br does not inflate to ${asset.path}`
)
},
{ brotli: true }
)
})
it('refuses an asset path that escapes the bundle directory', async () => {
await withBundle(async ({ bundleDir }) => {
await rewriteManifest(bundleDir, (manifest) => {
@@ -3,147 +3,6 @@ import { describe, expect, it } from 'vitest'
import { repairTranslatedValue } from './locale-translation-policy.mjs'
describe('locale-translation-policy ko round 5', () => {
it('fixes Korean round 5 review, integration, and search keyword regressions', () => {
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.workspace.status.409528031f',
enValue: 'Review',
localeValue: '검토',
locale: 'ko'
})
).toBe('리뷰')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.workspace.status.6c1efa2cf8',
enValue: 'In review',
localeValue: '검토 중',
locale: 'ko'
})
).toBe('리뷰 중')
expect(
repairTranslatedValue({
key: 'auto.components.TaskPage.524f095d55',
enValue: 'Needs review',
localeValue: '검토 필요',
locale: 'ko'
})
).toBe('리뷰 필요')
expect(
repairTranslatedValue({
key: 'auto.components.onboarding.IntegrationsStep.277f30eb34',
enValue:
'Linear, GitLab, Bitbucket, Azure DevOps, Gitea, and Jira live in Settings > Integrations.',
localeValue:
'Linear, GitLab, Bitbucket, Azure DevOps, Gitea 및 Jira는 설정 > 통합에 있습니다.',
locale: 'ko'
})
).toBe('Linear, GitLab, Bitbucket, Azure DevOps, Gitea 및 Jira는 설정 > 연동에 있습니다.')
expect(
repairTranslatedValue({
key: 'auto.components.settings.general.search.244e3fb4c8',
enValue: 'Install the Orca skill so agents know to use the Orca CLI.',
localeValue: '에이전트가 Orca CLI 사용 방법을 알 수 있도록 Orca 기술을 설치합니다.',
locale: 'ko'
})
).toBe('에이전트가 Orca CLI를 사용하도록 Orca 스킬을 설치하세요.')
expect(
repairTranslatedValue({
key: 'auto.components.editor.MarkdownPreview.322afab6ff',
enValue: 'Review notes',
localeValue: '메모 검토',
locale: 'ko'
})
).toBe('리뷰 노트')
expect(
repairTranslatedValue({
key: 'auto.components.right.sidebar.source.control.repo.icon.ecf63ec3ef',
enValue: 'Launch',
localeValue: '시작하다',
locale: 'ko'
})
).toBe('실행')
expect(
repairTranslatedValue({
key: 'auto.components.GitHubItemDialog.485609c4f2',
enValue: 'check #',
localeValue: '확인하다 #',
locale: 'ko'
})
).toBe('체크 #')
expect(
repairTranslatedValue({
key: 'auto.lib.automation.templates.maintenance.prompt',
enValue:
'Check for stuck work, stale generated files, failing validation, and anything that needs human attention. Report only actionable issues.',
localeValue:
'작업 중단, 오래 생성된 파일, 유효성 검사 실패 및 사람의 주의가 필요한 모든 사항을 확인하세요. 실행 가능한 문제만 보고하세요.',
locale: 'ko'
})
).toBe(
'작업 중단, 오래 생성된 파일, 유효성 검사 실패 및 사람의 주의가 필요한 모든 사항을 확인하세요. 실행 가능한 이슈만 보고하세요.'
)
expect(
repairTranslatedValue({
key: 'auto.components.GitLabItemDialog.02cbe2de44',
enValue: 'Pipeline',
localeValue: '파이프라인',
locale: 'ko'
})
).toBe('파이프라인')
})
// Why: #12113 — brand names stay Latin, but generic workflow nouns keep their Korean.
it('keeps brand names English and generic workflow terms translated', () => {
expect(
repairTranslatedValue({
key: 'auto.components.feature.wall.BrowserAnimatedVisual.04096318ab',
enValue: 'Terminal 1',
localeValue: '터미널 1',
locale: 'ko'
})
).toBe('터미널 1')
expect(
repairTranslatedValue({
key: 'auto.components.skills.SkillsPage.38e0951c3a',
enValue: 'Agent Skills',
localeValue: '에이전트 스킬',
locale: 'ko'
})
).toBe('에이전트 스킬')
expect(
repairTranslatedValue({
key: 'auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef',
enValue: 'Markdown',
localeValue: '가격 인하',
locale: 'ko'
})
).toBe('Markdown')
expect(
repairTranslatedValue({
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.9623a5107d',
enValue: 'Unpushed commits',
localeValue: '푸시되지 않은 커밋',
locale: 'ko'
})
).toBe('푸시되지 않은 커밋')
expect(
repairTranslatedValue({
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.0b1766738a',
enValue: 'Repo',
localeValue: '레포',
locale: 'ko'
})
).toBe('레포')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
enValue: 'Local project, Git repo, or folder with many repos',
localeValue: '로컬 프로젝트, Git 저장소 또는 저장소가 많은 폴더',
locale: 'ko'
})
).toBe('로컬 프로젝트, Git 저장소 또는 저장소가 많은 폴더')
})
it('re-glues Korean particles after Latin terms without gluing content words', () => {
// A wrong space before the 에 particle is re-glued onto the Latin term.
expect(
@@ -1,65 +0,0 @@
import { describe, expect, it } from 'vitest'
import { repairTranslatedValue } from './locale-translation-policy.mjs'
describe('locale-translation-policy es round 5', () => {
// Why: #12113 — brand names stay Latin, but generic workflow nouns keep their Spanish.
it('keeps brand names English and generic workflow terms translated', () => {
expect(
repairTranslatedValue({
key: 'auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef',
enValue: 'Markdown',
localeValue: 'Reducción',
locale: 'es'
})
).toBe('Markdown')
expect(
repairTranslatedValue({
key: 'auto.components.TaskPage.7f3f7b4c18',
enValue: 'Description (optional, markdown)',
localeValue: 'Descripción (opcional, rebaja)',
locale: 'es'
})
).toBe('Descripción (opcional, markdown)')
expect(
repairTranslatedValue({
key: 'auto.components.right.sidebar.source.control.primary.action.ed93b4f14f',
enValue: 'Commit',
localeValue: 'Comprometerse',
locale: 'es'
})
).toBe('Commit')
expect(
repairTranslatedValue({
key: 'auto.components.right.sidebar.GitHistoryPanel.cf7cad58d2',
enValue: 'No commits yet',
localeValue: 'Aún no hay compromisos',
locale: 'es'
})
).toBe('Aún no hay commits')
expect(
repairTranslatedValue({
key: 'auto.components.right.sidebar.SourceControl.b94112eb9e',
enValue: 'Commit message',
localeValue: 'mensaje de confirmación',
locale: 'es'
})
).toBe('mensaje de confirmación')
expect(
repairTranslatedValue({
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.0b1766738a',
enValue: 'Repo',
localeValue: 'repositorio',
locale: 'es'
})
).toBe('repositorio')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
enValue: 'Local project, Git repo, or folder with many repos',
localeValue: 'Proyecto local, repositorio de Git o carpeta con muchos repositorios',
locale: 'es'
})
).toBe('Proyecto local, repositorio de Git o carpeta con muchos repositorios')
})
})
@@ -6,16 +6,6 @@ const ja = (enValue, localeValue, key = 'auto.test.relocalization') =>
repairTranslatedValue({ key, enValue, localeValue, locale: 'ja' })
describe('locale-translation-policy ja relocalization', () => {
it('heals generic terms the brand revert left in Latin mid-sentence', () => {
expect(ja('Close terminal?', 'この terminal を閉じますか?')).toBe('このターミナルを閉じますか?')
expect(ja('New Terminal', '新規 Terminal')).toBe('新規ターミナル')
expect(ja('Stop the agent?', 'この agent を停止しますか?')).toBe('この Agent を停止しますか?')
expect(ja('Nothing to commit.', 'commit するものはありません。')).toBe(
'コミットするものはありません。'
)
expect(ja('Open the repo', 'repo を開く')).toBe('リポジトリを開く')
})
it('leaves the second word of a two-word command in Latin', () => {
expect(ja('a commit hook or git commit fails', 'コミットフックまたは git commit が失敗')).toBe(
'コミットフックまたは git commit が失敗'
@@ -36,21 +26,6 @@ describe('locale-translation-policy ja relocalization', () => {
expect(ja('Run git commit -m', 'git commit -m を実行')).toBe('git commit -m を実行')
})
it('keeps commands and identifiers in English so they still run', () => {
expect(shouldPreserveEnglishValue('pnpm install')).toBe(true)
expect(shouldPreserveEnglishValue('glab auth login')).toBe(true)
expect(shouldPreserveEnglishValue("gh pr list --json number -q '.[0].number'")).toBe(true)
expect(shouldPreserveEnglishValue('--model sonnet')).toBe(true)
expect(shouldPreserveEnglishValue('localhost, 127.0.0.1, *.internal')).toBe(true)
expect(shouldPreserveEnglishValue('stale-agent-row-{{value0}}')).toBe(true)
expect(shouldPreserveEnglishValue('text-foreground')).toBe(true)
expect(shouldPreserveEnglishValue('{basePrompt}')).toBe(true)
expect(shouldPreserveEnglishValue('{firstPrompt}')).toBe(true)
expect(shouldPreserveEnglishValue('{assistantMessage}')).toBe(true)
expect(shouldPreserveEnglishValue('/pricing')).toBe(true)
expect(shouldPreserveEnglishValue('/signup')).toBe(true)
})
it('repairs translated code examples back to executable values', () => {
expect(ja('--model sonnet', '--モデルソネット')).toBe('--model sonnet')
expect(
@@ -68,78 +43,10 @@ describe('locale-translation-policy ja relocalization', () => {
expect(shouldPreserveEnglishValue('e.g. {{value0}}')).toBe(false)
})
it('normalizes Japanese typography and settled terminology', () => {
expect(ja('Loading…', '読み込み中...')).toBe('読み込み中…')
expect(ja('Merge conflict', 'マージ紛争')).toBe('マージ競合')
// Why: MT inserts a Microsoft-style separator space that Japanese compounds do not use.
expect(ja('Status bar', 'ステータス バー')).toBe('ステータスバー')
expect(ja('Missing credentials', '資格情報がありません')).toBe('認証情報がありません')
expect(ja('Unknown', '未知')).toBe('不明')
expect(ja('Restart the server', 'サーバを再起動')).toBe('サーバーを再起動')
expect(ja('Open the browser', 'ブラウザーを開く')).toBe('ブラウザを開く')
})
it('preserves translated Japanese Skills labels', () => {
expect(
ja(
'Filter by agent',
'エージェントで絞り込み',
'auto.components.skills.SkillsPage.filterProvider'
)
).toBe('エージェントで絞り込み')
})
it('preserves the native chat subagent label', () => {
expect(ja('Subagent', 'サブエージェント', 'components.native-chat.tool.row.subagent')).toBe(
'サブエージェント'
)
})
it('leaves ranges and token samples out of the ellipsis rule', () => {
expect(ja('Compare main...HEAD', 'main...HEAD を比較')).toBe('main...HEAD を比較')
})
it('spaces the Latin terms the phrase fixes write back in', () => {
expect(ja('Clear cookies', 'クッキーを消去')).toBe('Cookie を消去')
expect(ja('Safely fast-forward the branch', 'ブランチを安全に早送りします')).toBe(
'ブランチを安全に fast-forward します'
)
})
it('uses ホスト for host, and leaves サーバー alone when English says server', () => {
expect(ja('Host path', 'サーバーパス')).toBe('ホストパス')
expect(ja('Enter a path that exists on the selected host.', 'ランタイムサーバー上のパス')).toBe(
'ホスト上のパス'
)
// Why: host and server are deliberately distinct wherever the English uses both.
expect(ja('Save the server host', 'サーバーを保存')).toBe('サーバーを保存')
})
it('restores Git vocabulary and brand names the MT localized', () => {
expect(ja('Branch upstream', 'ブランチの上流')).toBe('ブランチの upstream')
expect(ja('origin has commits', '起源にコミットがあります')).toBe('origin にコミットがあります')
expect(ja('Discard all staged changes?', '段階的な変更を破棄しますか?')).toBe(
'ステージ済みの変更を破棄しますか?'
)
expect(ja('Create Hermes automation', 'エルメスオートメーションを作成')).toBe(
'Hermes オートメーションを作成'
)
expect(ja('Delete the worktree', 'worktree を削除')).toBe('ワークツリーを削除')
})
it('keeps class strings, refs, and fixture filenames in English', () => {
for (const value of [
'size-4 text-muted-foreground',
'text-amber-700 dark:text-amber-300',
'origin/main',
'upstream/main',
'login.spec.ts',
'bastion.example.com'
]) {
expect(shouldPreserveEnglishValue(value)).toBe(true)
}
})
it('preserves a selector that has no declaration block', () => {
for (const value of [
'div.pricing-grid > div.card.starter:nth-of-type(1) > a.cta',
@@ -1,157 +0,0 @@
import { describe, expect, it } from 'vitest'
import { repairTranslatedValue } from './locale-translation-policy.mjs'
describe('locale-translation-policy ja round 5', () => {
it('fixes inline, launch, action, MR, and status chip regressions', () => {
expect(
repairTranslatedValue({
key: 'auto.components.GitHubItemDialog.6e43a16435',
enValue: 'Inline',
localeValue: '列をなして',
locale: 'ja'
})
).toBe('インライン')
expect(
repairTranslatedValue({
key: 'auto.components.repo.repo.icon.ecf63ec3ef',
enValue: 'Launch',
localeValue: '打ち上げ',
locale: 'ja'
})
).toBe('起動')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.workspace.status.2c19d1db33',
enValue: 'Play',
localeValue: '遊ぶ',
locale: 'ja'
})
).toBe('再生')
expect(
repairTranslatedValue({
key: 'auto.components.TaskPage.8396825a14',
enValue: 'Action',
localeValue: 'アクション',
locale: 'ja'
})
).toBe('操作')
expect(
repairTranslatedValue({
key: 'auto.components.TaskPage.456b8512da',
enValue: 'MergeRequest',
localeValue: 'マージリクエスト',
locale: 'ja'
})
).toBe('MR')
expect(
repairTranslatedValue({
key: 'auto.components.github.PRFilterDropdowns.7f1ba66c3e',
enValue: 'Reviewed by',
localeValue: 'レビュー者',
locale: 'ja'
})
).toBe('レビュアー')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.workspace.status.caebe3c10f',
enValue: 'Conductor Review',
localeValue: '指揮者のレビュー',
locale: 'ja'
})
).toBe('Conductor レビュー')
expect(
repairTranslatedValue({
key: 'auto.components.github.pr.merge.state.bf5e4c6c92',
enValue: 'Blocked',
localeValue: 'ブロック',
locale: 'ja'
})
).toBe('ブロック中')
expect(
repairTranslatedValue({
key: 'auto.components.settings.IntegrationsPane.027440e1cb',
enValue: 'Merge requests, issues, todos, and pipelines via the',
localeValue: 'リクエスト、イシュー、ToDo、パイプラインをマージします。',
locale: 'ja'
})
).toBe('MR、Issue、ToDo、パイプラインは')
expect(
repairTranslatedValue({
key: 'auto.components.GitLabItemDialog.98718490e4',
enValue: 'MR title is required.',
localeValue: 'MRの肩書きは必須です。',
locale: 'ja'
})
).toBe('MR タイトルは必須です。')
expect(
repairTranslatedValue({
key: 'auto.components.settings.terminal.search.d5e6c7fab1',
enValue: 'font features',
localeValue: 'フォントの特徴',
locale: 'ja'
})
).toBe('フォント特性')
expect(
repairTranslatedValue({
key: 'auto.components.GitHubItemDialog.f05d237049',
enValue: 'Add a project first',
localeValue: 'まずプロジェクトを追加してください',
locale: 'ja'
})
).toBe('まずプロジェクトを追加')
})
// Why: #12113 — brand names stay Latin, but generic workflow nouns keep their Japanese.
it('keeps brand names English and generic workflow terms translated', () => {
expect(
repairTranslatedValue({
key: 'auto.components.status.bar.WorkspaceSpaceManagerPanel.e9528a89b3',
enValue: 'Terminals',
localeValue: '端末',
locale: 'ja'
})
).toBe('ターミナル')
expect(
repairTranslatedValue({
key: 'auto.components.skills.SkillsPage.38e0951c3a',
enValue: 'Agent Skills',
localeValue: 'Agent のスキル',
locale: 'ja'
})
).toBe('Agent のスキル')
expect(
repairTranslatedValue({
key: 'auto.components.tab.bar.TabBar.3d5d6c960d',
enValue: 'New Markdown',
localeValue: '新規マークダウン',
locale: 'ja'
})
).toBe('新規 Markdown')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.local.base.ref.suggestion.toast.commits',
enValue: 'commits',
localeValue: 'コミット',
locale: 'ja'
})
).toBe('コミット')
expect(
repairTranslatedValue({
key: 'auto.components.mobile.slides.WorktreeListSlide.22971156df',
enValue: 'Repo',
localeValue: 'リポ',
locale: 'ja'
})
).toBe('リポ')
expect(
repairTranslatedValue({
key: 'auto.components.sidebar.add.repo.local.start.actions.fb4fc5380e',
enValue: 'Local project, Git repo, or folder with many repos',
localeValue:
'ローカル プロジェクト、Git リポジトリ、または多数のリポジトリを含むフォルダー',
locale: 'ja'
})
).toBe('ローカルプロジェクト、Git リポジトリ、または多数のリポジトリを含むフォルダー')
})
})
@@ -43,9 +43,6 @@ const COMPAT_SLOT_GLIBC_FLOOR = glibcFloorProfile(
[1, 3, 7]
)
// Lazy speech-worker sherpa prebuilt requires GCC11 GLIBCXX_3.4.29; exempt C++ floor only, retain glibc gate because it is not launch-critical.
const LIBSTDCXX_FLOOR_EXEMPT = /(?:^|[/\\])sherpa-onnx/
// VER_FLG_WEAK needs may resolve null and use fallback; they must not raise required floor.
const VER_FLG_WEAK = 0x2
@@ -118,15 +115,9 @@ function isLibstdcxxNode(name) {
return name.startsWith('GLIBCXX_') || name.startsWith('CXXABI_')
}
// Gate load-time requirements; exempt sherpa C++ needs only, never its glibc needs.
function findFloorViolations(needs, filePath = '', glibcFloor = DESKTOP_GLIBC_FLOOR) {
const exemptLibstdcxx = LIBSTDCXX_FLOOR_EXEMPT.test(filePath)
return needs.filter(
(need) =>
!need.weak &&
isVersionNodeAboveFloor(need.name, glibcFloor) &&
!(exemptLibstdcxx && isLibstdcxxNode(need.name))
)
// Gate load-time requirements; weak needs can resolve to null and fall back.
function findFloorViolations(needs, glibcFloor = DESKTOP_GLIBC_FLOOR) {
return needs.filter((need) => !need.weak && isVersionNodeAboveFloor(need.name, glibcFloor))
}
// Ubuntu20.04 keeps openpty/forkpty in libutil until glibc2.34 merge: require DT_NEEDED even when newer libc compat aliases satisfy link-time floor.
@@ -402,7 +393,7 @@ function verifyLinuxGlibcFloor(rootDir, options = {}) {
const { versionNeeds, neededLibraries, isStatic } = readDynamicInfo(filePath, objdumpPath)
const isMuslTarget = isMuslTemplatePayload(filePath, neededLibraries, versionNeeds)
// Remote musl payloads use their host's C++ runtime, not Ubuntu's libstdc++ or libutil.
const floorViolations = findFloorViolations(versionNeeds, filePath, glibcFloor).filter(
const floorViolations = findFloorViolations(versionNeeds, glibcFloor).filter(
(need) => !isMuslTarget || !isLibstdcxxNode(need.name)
)
// Inspect dynamic symbols only if relocated provider is absent from DT_NEEDED; healthy case avoids extra objdump.
@@ -47,6 +47,7 @@ const RATCHET_INPUTS = {
'check-render-path-translate-ratchet.mjs': ['src/renderer/src/'],
'check-repo-host-read-ratchet.mjs': ['src/cli/', 'src/main/', 'src/shared/'],
'check-runtime-electron-ratchet.mjs': ['src/main/', 'src/cli/', 'src/shared/'],
'check-tab-map-seam-ratchet.mjs': ['src/renderer/src/'],
'check-unproven-pty-retirement-ratchet.mjs': ['src/main/'],
'check-workspace-record-read-ratchet.mjs': ['src/renderer/src/'],
'check-worktree-id-composer-ratchet.mjs': ['src/', 'mobile/src/']
@@ -39,6 +39,7 @@ export const LINT_STEPS = [
'check:mobile-shared-dependency-versions',
'check:ts-nocheck-ratchet',
'check:workspace-record-read-ratchet',
'check:tab-map-seam-ratchet',
'check:unproven-pty-retirement-ratchet',
'check:daemon-blocking-call-ratchet',
'check:main-sync-fs-ratchet',
@@ -13,20 +13,6 @@ const checks = [
args: ['test', '--package-path', 'native/computer-use-macos'],
enabled: process.platform === 'darwin'
},
{
name: 'Linux provider Python syntax',
command: 'python3',
args: [
'-c',
[
'import ast, pathlib',
'source=pathlib.Path("native/computer-use-linux/runtime.py").read_text(encoding="utf-8")',
'ast.parse(source)',
'print("syntax-ok")'
].join(';')
],
enabled: true
},
{
name: 'Linux snapshot renderer tests',
command: 'python3',
@@ -27,6 +27,10 @@ import {
renderLiveReadLines,
renderStoreReadLines
} from './react-compiler-hazards.mjs'
import {
UNCOMPILED_LIST,
readListedFiles
} from '../../../../build-plugins/react-compiler-uncompiled-files.mjs'
// Runs React Compiler (analysis only, nothing is written) over the renderer so code that compiles
// today keeps compiling, and code that would change behavior once compiled can't be added.
@@ -34,8 +38,6 @@ import {
const SCAN_ROOT = 'src/renderer/src'
const BAILOUT_BASELINE = 'config/react-compiler-bailout-baseline.txt'
const HAZARD_BASELINE = 'config/react-compiler-hazard-baseline.txt'
// The build leaves these files uncompiled, so their hazards cannot run.
const UNCOMPILED_LIST = 'config/react-compiler-uncompiled-files.txt'
const COMMAND = 'pnpm check:react-compiler-compat-ratchet'
const NOT_SHIPPED =
/(?:^|\/)__(?:mocks|fixtures|tests)__\/|\.(?:test|spec|bench)\.[cm]?tsx?$|[.-]test-|[.-]fixtures?\.tsx?$/
@@ -101,18 +103,31 @@ function addTo(map, key, value) {
map.set(key, [...(map.get(key) ?? []), value])
}
// The build leaves these files uncompiled, so their hazards cannot run.
function readUncompiledFiles(root) {
const file = path.join(root, UNCOMPILED_LIST)
if (!fs.existsSync(file)) {
return new Set()
return fs.existsSync(path.join(root, UNCOMPILED_LIST))
? new Set(readListedFiles(UNCOMPILED_LIST, root))
: new Set()
}
// Why: the build matches the exact id, so `\`, `../`, a directory or another case would silently compile.
function isCanonicalFile(root, rel) {
try {
const file = path.join(root, rel)
return (
!rel.includes('\\') &&
path.posix.normalize(rel) === rel &&
fs.statSync(file).isFile() &&
fs.realpathSync.native(file) === path.join(fs.realpathSync.native(root), rel)
)
} catch {
return false
}
return new Set(
fs
.readFileSync(file, 'utf8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'))
)
}
/** Listed paths that name no renderer file; a renamed file would silently compile. */
export function findStaleUncompiledFiles(uncompiled, exists) {
return [...uncompiled].filter((rel) => !rel.startsWith(`${SCAN_ROOT}/`) || !exists(rel))
}
/** Virtualizer hooks plus every hook that returns one, followed through wrappers of wrappers. */
@@ -210,12 +225,14 @@ async function scanFile(rel, text, result) {
export async function scanRenderer(root = process.cwd()) {
const sources = [...readTrackedSources(root, [SCAN_ROOT], isScannedPath)]
const uncompiled = readUncompiledFiles(root)
const result = {
files: 0,
compiled: 0,
diagnostics: new Map(),
hazards: new Map(),
uncompiled: readUncompiledFiles(root),
uncompiled,
staleUncompiled: findStaleUncompiledFiles(uncompiled, (rel) => isCanonicalFile(root, rel)),
virtualizerHooks: collectVirtualizerHooks(sources),
liveReaders: collectLiveReaders(sources)
}
@@ -341,8 +358,15 @@ export async function main(root = process.cwd(), arg = undefined) {
)
return 0
}
for (const rel of scan.staleUncompiled) {
console.error(
`::error::${UNCOMPILED_LIST}: ${rel} is not a renderer file. ` +
'Point it at the renamed file, or remove it once the file is gone.'
)
}
const touched = listTouchedFiles(root)
const ok = [
scan.staleUncompiled.length === 0,
check(root, 'bailout(s)', BAILOUT_BASELINE, bailouts, scan.diagnostics, touched),
check(root, 'hazard(s)', HAZARD_BASELINE, hazards, scan.hazards, touched)
].every(Boolean)
@@ -0,0 +1,39 @@
import { describe, expect, it } from 'vitest'
import { parseListedFiles } from '../../../../build-plugins/react-compiler-uncompiled-files.mjs'
import { findStaleUncompiledFiles } from './check-react-compiler-compat-ratchet.mjs'
describe('uncompiled file list', () => {
it('reads paths from list, count and hazard lines, dropping notes', () => {
const text = [
'# header',
'src/renderer/src/a.tsx',
'src/renderer/src/b.tsx # why it stays uncompiled',
'2 src/renderer/src/c.ts',
'1 src/renderer/src/d.ts#useD:store-read-in-memo-block',
''
].join('\n')
expect(parseListedFiles(text)).toEqual([
'src/renderer/src/a.tsx',
'src/renderer/src/b.tsx',
'src/renderer/src/c.ts',
'src/renderer/src/d.ts'
])
})
it('reports listed paths that no longer name a renderer file', () => {
const files = new Set([
'src/renderer/src/kept.tsx',
'src/renderer/src/notification-test-delivery.ts'
])
expect(
findStaleUncompiledFiles(
new Set([
'src/renderer/src/kept.tsx',
'src/renderer/src/notification-test-delivery.ts',
'src/renderer/src/renamed.tsx'
]),
(rel) => files.has(rel)
)
).toEqual(['src/renderer/src/renamed.tsx'])
})
})
@@ -320,6 +320,24 @@ function isSameNameCopy(property, name) {
return ts.isPropertyAccessExpression(value) && value.name.text === name
}
// The transition seam names the fields it tracks in `const SEAM_*_FIELDS = [...] as const` lists; names, not writes.
function isSeamFieldNameConstant(literal) {
let list = literal.parent
if (!ts.isArrayLiteralExpression(list)) {
return false
}
let declaration = list.parent
while (ts.isAsExpression(declaration) || ts.isSatisfiesExpression(declaration)) {
declaration = declaration.parent
}
return (
ts.isVariableDeclaration(declaration) &&
ts.isIdentifier(declaration.name) &&
/^SEAM_[A-Z_]+_FIELDS$/.test(declaration.name.text) &&
(declaration.parent.flags & ts.NodeFlags.Const) !== 0
)
}
/** Layout writes in one file under one ratchet's rules. */
export function countLayoutWrites(sourceText, ratchet, fileName = 'file.tsx') {
const names = [...ratchet.writerNames, ...ratchet.assignedFields]
@@ -392,7 +410,8 @@ export function countLayoutWrites(sourceText, ratchet, fileName = 'file.tsx') {
node.parent.expression.expression.text === 'Object' &&
node.parent.expression.name.text === 'hasOwn'
) &&
!ts.isLiteralTypeNode(node.parent)
!ts.isLiteralTypeNode(node.parent) &&
!isSeamFieldNameConstant(node)
) {
count += 1
}
@@ -0,0 +1,380 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { pathToFileURL } from 'node:url'
import ts from 'typescript-api'
// The tab-map transition seam (store/keyed-record-transitions.ts) only works if its writers annotate and its
// consumers enumerate whole maps only on named cold paths; this pins both for the repaired modules, nothing wider.
const R = 'src/renderer/src/'
const STATUS = `${R}components/native-chat/session/status/`
/**
* Repaired consumers: the only functions allowed to walk a whole map (`coldPaths`), which callers
* may reach a cold helper (`helpers`), and the only values they may import, so a new walking helper
* can't arrive through an import unreviewed.
*/
export const CONSUMERS = new Map([
[
`${STATUS}structured-agent-session-tabs.ts`,
{
coldPaths: new Set(['rebuildFromSnapshot']),
helpers: { rebuildFromSnapshot: ['advance'] },
imports: new Set(['structuredAgentSessionPaneKey', 'isStructuredTab', 'readTabMapChanges'])
}
],
[
`${STATUS}StructuredAgentSessionStatusBridge.tsx`,
{
coldPaths: new Set(['reconcileFromSnapshot']),
helpers: {
getStructuredAgentSessionTabs: ['reconcileFromSnapshot'],
reconcileFromSnapshot: ['reconcile']
},
imports: new Set([
'useEffect',
'projectStatus',
'getStructuredAgentSessionLaunchLifecycle',
'subscribeStructuredAgentLaunchStatus',
'getStructuredAgentSessionLaunchFailedAt',
'useAppStore',
'structuredAgentSessionTargetForHost',
'getStructuredAgentSessionStatusFeed',
'structuredAgentSessionStatusFeedKey',
'getStructuredAgentSessionTabs',
'getStructuredAgentSessionTabsInWorkspace',
'readTabMapChanges',
'createTabOwnerCache',
'ownerInputChanged',
'ownerInputs',
'createStructuredSessionMembership',
'sameSemanticTab'
])
}
],
[
`${STATUS}structured-agent-session-status-membership.ts`,
{
coldPaths: new Set(),
helpers: { getStructuredAgentSessionTabs: [] },
imports: new Set(['structuredAgentSessionPaneKey'])
}
]
])
const ENUMERATING_CALLS = new Set(['keys', 'values', 'entries'])
/** Shared tab-map writers wired to the seam: a raw record literal for these fields bypasses provenance. */
export const WRITERS = [
'src/renderer/src/runtime/web-session-tabs-sync/scoped-patch-annotation.ts',
'src/renderer/src/runtime/web-session-tabs-sync/store-patch.ts',
'src/renderer/src/runtime/workspace-layout/frames/workspace-layout-frame-sync.ts',
'src/renderer/src/store/slices/browser/pages/browser-page-state-actions.ts',
'src/renderer/src/store/slices/editor/file-ids/hydrated-editor-file-ids.ts',
'src/renderer/src/store/slices/editor/hydration/restored-editor-owner-in-place.ts',
'src/renderer/src/store/slices/editor/hydration/restored-editor-owner-transition.ts',
'src/renderer/src/store/slices/editor/markdown/markdown-preview-actions.ts',
'src/renderer/src/store/slices/editor/opening/open-file-mutations.ts',
'src/renderer/src/store/slices/ssh/ssh-target-cleanup.ts',
'src/renderer/src/store/slices/tabs/rename-worktree-layout.ts',
'src/renderer/src/store/slices/tabs/tab-group-state.ts',
'src/renderer/src/store/slices/tabs/tabs-close-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-create-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-drag-preview-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-drop-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-focus-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-group-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-host-mirroring.ts',
'src/renderer/src/store/slices/tabs/tabs-label-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-move-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-reconciliation.ts',
'src/renderer/src/store/slices/tabs/tabs-session-actions.ts',
'src/renderer/src/store/slices/tabs/tabs-split-ratio-actions.ts',
'src/renderer/src/store/slices/tabs/worktree-layout-lifecycle.ts',
'src/renderer/src/store/slices/tabs/worktree-terminal-tab-respawn.ts',
'src/renderer/src/store/slices/worktrees/session/set-active-worktree.ts',
'src/renderer/src/store/slices/worktrees/session/worktree-identity-rename-state.ts',
'src/renderer/src/store/slices/worktrees/session/worktree-slice-lookups.ts',
'src/renderer/src/store/slices/worktrees/teardown/record-key-omission.ts',
'src/renderer/src/store/terminals/terminal-tab-close.ts',
'src/renderer/src/store/terminals/terminal-tab-creation.ts',
'src/renderer/src/store/terminals/terminal-tab-navigation.ts',
'src/renderer/src/store/terminals/terminal-tab-title-batch.ts',
'src/renderer/src/store/terminals/workspace-terminal-hydration-patch.ts',
'src/renderer/src/store/terminals/workspace-terminal-hydration.ts',
'src/renderer/src/store/terminals/workspace-terminal-reconnect.ts',
'src/renderer/src/store/workspace-layout/frames/workspace-layout-frame-store-patch.ts'
]
export const SEAM_FIELDS = new Set([
'tabsByWorktree',
'unifiedTabsByWorktree',
'terminalLayoutsByTabId',
'ptyIdsByTabId',
'lastKnownRelayPtyIdByTabId',
'runtimePaneTitlesByTabId',
'pendingStartupByTabId',
'pendingReconnectPtyIdByTabId',
'sleepingAgentSessionsByPaneKey',
'layoutByWorktree',
'groupsByWorktree',
'activeGroupIdByWorktree'
])
function parse(file, text) {
return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
}
function line(source, node) {
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
}
function enclosingFunctionNames(node) {
const names = []
for (let current = node.parent; current; current = current.parent) {
if (ts.isFunctionDeclaration(current) && current.name) {
names.push(current.name.text)
} else if (
(ts.isArrowFunction(current) || ts.isFunctionExpression(current)) &&
ts.isVariableDeclaration(current.parent) &&
ts.isIdentifier(current.parent.name)
) {
names.push(current.parent.name.text)
}
}
return names
}
function valueImports(source) {
const imported = []
for (const statement of source.statements) {
const clause = ts.isImportDeclaration(statement) ? statement.importClause : null
if (!clause || clause.isTypeOnly) {
continue
}
if (clause.name) {
imported.push(clause.name)
}
const bindings = clause.namedBindings
if (bindings && ts.isNamespaceImport(bindings)) {
imported.push(bindings.name)
} else if (bindings) {
imported.push(
...bindings.elements.filter((element) => !element.isTypeOnly).map((e) => e.name)
)
}
}
return imported
}
function isHelperReference(node, helpers) {
if (!ts.isIdentifier(node) || !Object.hasOwn(helpers, node.text)) {
return false
}
const parent = node.parent
const declares =
((ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent)) &&
parent.name === node) ||
(ts.isPropertyAccessExpression(parent) && parent.name === node) ||
(ts.isPropertyAssignment(parent) && parent.name === node) ||
ts.isImportSpecifier(parent)
return !declares
}
export function consumerViolations(file, text, { coldPaths, helpers, imports }) {
const source = parse(file, text)
const violations = []
for (const name of valueImports(source)) {
if (!imports.has(name.text)) {
violations.push(
`${file}:${line(source, name)} imports ${name.text}; repaired consumers import only reviewed helpers`
)
}
}
const visit = (node) => {
let what = null
let allowed = coldPaths
if (
ts.isPropertyAccessExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'Object' &&
ENUMERATING_CALLS.has(node.name.text)
) {
// Any reference, called or aliased (`const keys = Object.keys`).
what = `Object.${node.name.text}`
} else if (isHelperReference(node, helpers)) {
// Calls, aliases (`const resync = reconcileFromSnapshot`) and callbacks alike.
what = `${node.text}()`
allowed = new Set(helpers[node.text])
} else if (ts.isForInStatement(node)) {
what = 'for…in'
}
if (what && !enclosingFunctionNames(node).some((name) => allowed.has(name))) {
violations.push(`${file}:${line(source, node)} ${what} outside a named cold path`)
}
if (
ts.isFunctionDeclaration(node) &&
node.name?.text === 'rebuildFromSnapshot' &&
node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
) {
violations.push(`${file}:${line(source, node)} rebuildFromSnapshot must stay module-private`)
}
ts.forEachChild(node, visit)
}
visit(source)
return violations
}
function isRawRecord(expression) {
if (ts.isObjectLiteralExpression(expression)) {
return expression.properties.some((property) => ts.isSpreadAssignment(property))
}
return (
ts.isCallExpression(expression) &&
ts.isPropertyAccessExpression(expression.expression) &&
ts.isIdentifier(expression.expression.expression) &&
expression.expression.expression.text === 'Object' &&
['fromEntries', 'assign'].includes(expression.expression.name.text)
)
}
// Only an explicit cold-path mark certifies a record built outside a construction helper or draft.
const ANNOTATED_ARGUMENT = {
markFullResync: 0
}
function enclosingScope(node) {
let current = node.parent
while (current && !ts.isFunctionLike(current) && !ts.isSourceFile(current)) {
current = current.parent
}
return current
}
/** The value a local name holds at `use`: its declaration's initializer, followed through aliases. */
function bindingSource(name, scope, use, seen = new Set()) {
if (seen.has(name)) {
return null
}
seen.add(name)
let found = null
const visit = (node) => {
if (node.pos >= use.pos) {
return
}
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
found = node.initializer ?? null
} else if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isIdentifier(node.left) &&
node.left.text === name
) {
found = node.right
}
ts.forEachChild(node, visit)
}
visit(scope)
if (found && ts.isIdentifier(found)) {
const aliased = bindingSource(found.text, scope, use, seen)
return aliased && { ...aliased, names: [name, ...aliased.names] }
}
return found && { value: found, names: [name] }
}
/** Whether a seam helper annotates one of `names` in place before `use` publishes it. */
function annotatedBefore(names, scope, use) {
let annotated = false
const visit = (node) => {
if (node.pos >= use.pos) {
return
}
if (
node.end <= use.pos &&
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
Object.hasOwn(ANNOTATED_ARGUMENT, node.expression.text)
) {
const argument = node.arguments[ANNOTATED_ARGUMENT[node.expression.text]]
annotated ||=
argument !== undefined && ts.isIdentifier(argument) && names.includes(argument.text)
}
ts.forEachChild(node, visit)
}
visit(scope)
return annotated
}
function rawValue(node) {
const initializer = ts.isShorthandPropertyAssignment(node) ? node.name : node.initializer
if (isRawRecord(initializer)) {
return true
}
if (!ts.isIdentifier(initializer)) {
return false
}
// Follow the local binding and its aliases; the record must be annotated before it is published.
for (let scope = enclosingScope(node); scope; scope = scope.parent && enclosingScope(scope)) {
const binding = bindingSource(initializer.text, scope, node)
if (binding) {
return isRawRecord(binding.value) && !annotatedBefore(binding.names, scope, node)
}
if (ts.isSourceFile(scope)) {
break
}
}
return false
}
export function writerViolations(file, text) {
const source = parse(file, text)
const violations = []
const visit = (node) => {
if (
(ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)) &&
(ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&
SEAM_FIELDS.has(node.name.text) &&
rawValue(node)
) {
violations.push(
`${file}:${line(source, node)} raw ${node.name.text} record; build it with a replace helper or draft, or mark it full resync`
)
}
ts.forEachChild(node, visit)
}
visit(source)
return violations
}
export function collect(root = process.cwd()) {
const violations = []
for (const [file, rules] of CONSUMERS) {
violations.push(
...consumerViolations(file, fs.readFileSync(path.join(root, file), 'utf8'), rules)
)
}
for (const file of WRITERS) {
violations.push(...writerViolations(file, fs.readFileSync(path.join(root, file), 'utf8')))
}
return violations
}
export function main(root = process.cwd()) {
const violations = collect(root)
if (violations.length) {
for (const violation of violations) {
console.error(violation)
}
console.error(
'Tab-map seam: ordinary handlers use addressed deltas (readTabMapChanges); whole-map walks belong only in the named cold paths.'
)
return 1
}
console.log(
`tab-map-seam ratchet OK — ${CONSUMERS.size} consumer and ${WRITERS.length} writer modules pinned.`
)
return 0
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exit(main(process.cwd()))
}
@@ -0,0 +1,87 @@
import { describe, expect, it } from 'vitest'
import { CONSUMERS, consumerViolations, writerViolations } from './check-tab-map-seam-ratchet.mjs'
const BRIDGE =
'src/renderer/src/components/native-chat/session/status/StructuredAgentSessionStatusBridge.tsx'
const rules = CONSUMERS.get(BRIDGE)
describe('tab-map seam guard', () => {
it('rejects an ordinary handler reaching the cold path or walking a map', () => {
const text = [
'const reconcileFromSnapshot = () => getStructuredAgentSessionTabs(source)',
'const reconcile = () => reconcileFromSnapshot()',
'const ordinary = () => { reconcileFromSnapshot(); Object.keys(source) }'
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:3 reconcileFromSnapshot() outside a named cold path',
'bridge.tsx:3 Object.keys outside a named cold path'
])
})
it('rejects an unreviewed value import, not type imports', () => {
const text = [
"import { wholeMap } from './new-walk'",
"import type { Walk } from './new-walk'"
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:1 imports wholeMap; repaired consumers import only reviewed helpers'
])
})
it('follows a raw record through a local binding unless it is marked full resync', () => {
const text = [
'function raw(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' set({ unifiedTabsByWorktree: next })',
'}',
'function annotated(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' markFullResync(next, "hydration")',
' set({ unifiedTabsByWorktree: next })',
'}'
].join('\n')
expect(writerViolations('writer.ts', text)).toEqual([
'writer.ts:3 raw unifiedTabsByWorktree record; build it with a replace helper or draft, or mark it full resync'
])
})
it('follows aliases of cold helpers and enumerators', () => {
const text = [
'const reconcileFromSnapshot = () => getStructuredAgentSessionTabs(source)',
'const reconcile = () => reconcileFromSnapshot()',
'const resync = reconcileFromSnapshot',
'const keys = Object.keys'
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:3 reconcileFromSnapshot() outside a named cold path',
'bridge.tsx:4 Object.keys outside a named cold path'
])
})
it('follows aliased raw records and requires annotation before publication', () => {
const raw =
'raw unifiedTabsByWorktree record; build it with a replace helper or draft, or mark it full resync'
const text = [
'function aliased(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' const unifiedTabsByWorktree = next',
' set({ unifiedTabsByWorktree })',
'}',
'function late(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' set({ unifiedTabsByWorktree: next })',
' markFullResync(next, "hydration")',
'}',
'function early(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' const alias = next',
' markFullResync(alias, "hydration")',
' set({ unifiedTabsByWorktree: alias })',
'}'
].join('\n')
expect(writerViolations('writer.ts', text)).toEqual([
`writer.ts:4 ${raw}`,
`writer.ts:8 ${raw}`
])
})
})
@@ -36,6 +36,7 @@ const NODE_RUNTIME_INCLUDE = [
// Asserts an observer survives a forced collection; conservative retention would hide a lost one.
'config/scripts/test-runtime/dom/happy-dom-mutation-observer-retention.test.ts',
// These reach Node SQLite through a shared helper or a dynamic import, which the scan skips.
'src/main/agent-hooks/server/testing/providers/codex/server-codex-session-start.test.ts',
'src/main/claude/claude-transcript-catch-up.test.ts',
'src/main/claude/claude-transcript-duration.test.ts',
'tests/e2e/native-chat-startup-option-pickers.unit.test.ts',
@@ -118,7 +119,6 @@ const NODE_RUNTIME_INCLUDE = [
'src/main/browser/cookies/sources/browser-cookie-import-undecryptable.test.ts',
'src/main/browser/cookies/sources/browser-cookie-source-partition.test.ts',
// Playwright's CDP transport needs Node's WebSocket implementation.
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-adapter-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts',
'src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
@@ -131,7 +131,6 @@ export const LINUX_PACKAGE_TESTS = [
'src/main/browser/client-pages/renderer/browser-client-page-renderer-lifecycle.electron.test.ts',
'src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-adapter-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts',
'src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts',
@@ -171,6 +170,7 @@ export const WINDOWS_BOUNDARY_TESTS = [
'src/shared/__tests__/process-host/windows-tree-kill-late-taskkill.win32.test.ts',
'src/shared/agent-launch/startup/omp-fresh-launch-cmd.win32.test.ts',
'src/shared/windows-batch-spawn.win32.test.ts',
'src/main/claude/structured/launch/claude-structured-child-env.test.ts',
'src/main/agent-hooks/installation/commands/windows-hook-payload-delivery.test.ts',
'src/main/agent-hooks/installation/scripts/managed-hook-stdin-lifecycle.test.ts',
'src/main/jcode/hook-gate-script.test.ts',
+1 -3
View File
@@ -6,7 +6,6 @@ import { nodeRuntimePool } from './scripts/test-runtime/node-worker/vitest-node-
const contracts = [
'src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts',
'src/renderer/src/components/terminal-pane/pty-connection/startup/saved-resume-cancellation.test.ts',
'src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-terminal-writer-acquire.test.ts',
'src/shared/agent-launch/permissions/tui-agent-permission-choice.test.ts',
'src/main/runtime/rpc/methods/projects/repo.test.ts',
@@ -22,8 +21,7 @@ const contracts = [
'config/scripts/static-analysis/oxlint-contracts/app-store-performance-plugin.test.mjs',
'config/scripts/static-analysis/oxlint-contracts/quadratic-buffer-concat-plugin.test.mjs',
'src/main/claude-usage/scanner-incremental-append.test.ts',
'src/main/claude-usage/store-checkpoint-persistence.test.ts',
'src/renderer/src/components/native-chat/surfaces/integration/NativeChatResolvedView.streaming-input.test.tsx'
'src/main/claude-usage/store-checkpoint-persistence.test.ts'
]
for (const contract of contracts) {
if (!existsSync(resolve(contract))) {
+1 -1
View File
@@ -44,7 +44,7 @@ Tokens pair a **surface** with a **foreground** that meets contrast on it; alway
- Body letter-spacing is set globally on `body`; don't override per component.
- Use Tailwind's scale. Common: 14px body/default buttons, 13px sidebar items and dense rows, 12px sub-text and paths, 11px uppercase meta/captions.
- Use `text-2xs` (11px) and `text-3xs` (10px), not `text-[11px]` / `text-[10px]`.
- Category labels: 11px, `font-semibold`, `uppercase`, `tracking-wider` (0.05em).
- Category labels: 11px, `font-semibold`, `uppercase`, `tracking-wider` (0.05em). Settings content uses sentence case without tracking: 14px row labels, `text-settings-detail` (12.5px) help, and `text-settings-heading` (15px) semibold section headings. Settings form rows use about 9px vertical padding (44px minimum), selects are 28px, and the centered content column is at most 720px with at least 32px side gutters. These density rules are scoped to `.settings-view-shell`; shared primitives retain their defaults elsewhere.
- Native chat has its own lint-enforced scale and colours: see [native-chat.md](./styleguide/native-chat.md).
## Radius and elevation
+4 -4
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="20" role="img" aria-label="downloads: 161m">
<title>downloads: 161m</title>
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="20" role="img" aria-label="downloads: 163m">
<title>downloads: 163m</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
@@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">downloads</text>
<text x="37" y="14">downloads</text>
<text x="93.5" y="15" fill="#010101" fill-opacity=".3">161m</text>
<text x="93.5" y="14">161m</text>
<text x="93.5" y="15" fill="#010101" fill-opacity=".3">163m</text>
<text x="93.5" y="14">163m</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 473 KiB

+57
View File
@@ -0,0 +1,57 @@
# Orca Development Environment
Setup details beyond [`CONTRIBUTING.md`](../.github/CONTRIBUTING.md).
## Prerequisites
Install Node 24, the pnpm version in `packageManager` in [`package.json`](../package.json), and the Bun version in [`config/.bun-version`](../config/.bun-version).
The server, its workers, and its packaged dependencies target Node 24. The compatibility launcher and CLI still target Node 18; the launcher hands off to bundled Node 24 before loading server code. WSL guest programs and host discovery retain their Node 18 minimum. Keep Node 24 APIs and server imports out of these host compatibility paths. Running an unpackaged server directly requires Node 24 or newer.
`corepack enable` needs Corepack 0.34.5 or newer (`npm install -g corepack@latest`). On Windows it fails with `EPERM` outside an elevated shell when Node is in `C:\Program Files\nodejs`; run `npm install -g pnpm@<that version>` instead.
`pnpm install` compiles native modules such as `node-pty`, so a fresh machine also needs:
- **macOS**: Xcode Command Line Tools (`xcode-select --install`).
- **Windows**: Python 3 and Visual Studio Build Tools with the "Desktop development with C++" workload.
- **Linux**: a C++ toolchain, Python 3, Electron's libraries, and `xvfb` (used by `*.electron.test.ts` when `DISPLAY` is unset). On Ubuntu 24.04+ and Debian 13+ (older releases name the last package `libasound2`):
```bash
sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb libgtk-3-0 libnss3 libgbm1 libasound2t64
```
Re-run `pnpm -C mobile install --frozen-lockfile` whenever `mobile/pnpm-lock.yaml` changes.
## Dev Profiles and Parallel Instances
`pnpm dev` in a linked Git worktree uses its own profile, renderer port (from 15200), and CDP port (from 9333), so worktrees run side by side. The main checkout uses the shared `orca-dev` profile. The chosen profile and ports are printed on an `ORCA_DEV_INSTANCE` line and written to `out/dev-instance.json`.
| Option | Effect |
| ----------------------------------------------------------------------- | ---------------------------------------------------------- |
| `pnpm dev --shared-profile` or `ORCA_DEV_PROFILE=shared` | Use the shared `orca-dev` profile |
| `pnpm dev --worktree-profile` or `ORCA_DEV_PROFILE=worktree` | Use a per-worktree profile, including in the main checkout |
| `ORCA_DEV_USER_DATA_PATH=<dir>` | Use an explicit profile directory |
| `REMOTE_DEBUGGING_PORT=<port>`, `ORCA_DEV_RENDERER_PORT=<port>` | Pin a port |
| `pnpm dev --seed-profile[=<dir>]` or `ORCA_DEV_SEED_PROFILE=<dir or 1>` | Seed an empty profile on first launch |
After a worktree is removed and no process still uses its profile, a later `pnpm dev` can automatically delete that profile and its saved state. Back up any state you need before removing the worktree; the `userData` field in `ORCA_DEV_INSTANCE` or `out/dev-instance.json` gives the profile directory.
Seeding copies projects, workspaces, settings, SSH targets, and the Orca account session from another profile (default `orca-dev`, else the installed app's). Seed from `orca-dev` if you need to stay signed in: dev builds cannot decrypt secrets the installed app encrypted.
```bash
pnpm dev:seed-profile [--from=<profile dir>] [--to=<profile dir>] [--reset]
pnpm dev:fresh [--keep] [--seed-from[=<profile dir>]] # temporary profile, removed on exit
```
## Installs and Builds
- Local installs use pnpm's global virtual store (`~/pnpm/store`) shared by all worktrees. Do not keep a `node_modules` in your home directory: it hides missing-dependency bugs. Set `ORCA_PNPM_GLOBAL_VIRTUAL_STORE=0` in your shell to opt out.
- Before a cross-architecture build (including `pnpm build:mac`, which builds x64 and arm64), run `pnpm install:release`.
- On Linux, `pnpm build`, `pnpm build:native`, and packaging need a running Docker daemon for the clipboard helper. For native Wayland development, run `pnpm build:native` before `pnpm dev`. See the [clipboard build guide](../native/wayland-clipboard/README.md#building-and-testing).
## Troubleshooting
- Slow main-process rebuilds: set `ORCA_DEV_MAIN_SOURCEMAPS=0` before `pnpm dev` when you don't need main-process source maps.
- Renderer misbehaving in dev: set `ORCA_DEV_BUNDLED=0` to turn off bundled dev mode, and report it.
- Test passes alone but fails after others: add `// @orca-isolated-worker` to the file; `ORCA_VITEST_ISOLATE_EVERY_FILE=1` isolates every file to confirm a leak.
- Slow typechecks: where supported, `pnpm tc` keeps incremental caches in `.cache/typecheck/`; delete it to force a fresh check.
+1 -1
View File
@@ -27,4 +27,4 @@ The chat root (`.native-chat-appearance`) redefines these steps as ratios of the
- **Focus rings:** full-width transcript rows use the inset ring (`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring`) so it isn't clipped by the scroll container; standalone controls use `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`. Inside the chat `--ring` is `--chat-focus-ring`, which clears 3:1 on the canvas; don't lower its opacity.
- **Theme overrides:** plugin themes can set `--chat-bold-foreground`, `--chat-muted-foreground`, `--chat-inline-code`, `--chat-inline-code-foreground`, `--chat-code-block`, `--chat-selection` and `--chat-selection-foreground`. They feed `text-chat-foreground-faint`, `bg-chat-inline-code-surface`, `bg-chat-code-surface` and the chat root's `::selection`, so use those rather than reading the theme tokens directly.
- **Selection** inside the chat is the link hue mixed into the canvas (close to the system highlight), with selected text in the full foreground so every role stays 4.5:1. Don't add per-component `selection:` colors in transcript text.
- **State colours:** warnings use `text-chat-warning`; diff lines use `text-chat-diff-added` / `text-chat-diff-removed`. They are mixed toward the foreground so they pass text contrast in light and dark. Don't use raw palette colours (`text-yellow-500`) or the git decoration tokens for text on a tinted row.
- **State colours:** warnings use `text-chat-warning`; diff lines use `text-chat-diff-added` / `text-chat-diff-removed`; what the reader has not seen yet uses `text-chat-unread`. They are mixed toward the foreground so they pass text contrast in light and dark. Don't use raw palette colours (`text-yellow-500`) or the git decoration tokens for text on a tinted row.
+1
View File
@@ -114,6 +114,7 @@
"./plugins/android-window-density.js",
"./plugins/android-release-signing.js",
"./plugins/android-navigation-bar-theme.js",
"./plugins/android-device-transfer-rules.js",
"./plugins/ios-pod-deployment-target-floor.js",
"./plugins/ios-scene-lifecycle.js",
"./plugins/ios-bundle-phase-quoted-script-path.js",
+31 -25
View File
@@ -20,6 +20,8 @@ import { HostSidebarRevealContext } from '../../src/layout/host-sidebar-reveal'
import { HostAreaServingContext } from '../../src/mobile-web-shell/host-area-serving'
import { usePageOwnsHostArea } from '../../src/mobile-web-shell/page-owns-host-area'
import { isOverlayPageRoute } from '../../src/new-workspace/overlay-page-route'
import { MobileDictationHostProvider } from '../../src/dictation/mobile-dictation-host'
import { MobileDictationPillLayer } from '../../src/dictation/MobileDictationPill'
// Keep at least this much room for the detail pane when resizing the sidebar.
const MIN_DETAIL_WIDTH = 320
@@ -121,33 +123,37 @@ export default function HostGroupLayout() {
// Keep detail Stack at a stable tree position so fold/rotation cannot remount navigation.
return (
<HostProtocolGate hostId={hostId} transparentWhilePending={overlayRoute}>
<View style={[styles.row, overlayRoute && styles.transparent]}>
{showSidebar && (sidebarOpen || foldSidebarWidth !== null) ? (
<View style={[styles.sidebar, { width: foldSidebarWidth ?? sidebarWidth }]}>
<HostScreen
embedded
hostId={hostId}
onHideSidebar={canCollapseSidebar ? hideSidebar : undefined}
/>
{/* Edge drag handle captures Android gestures above the list. */}
{foldSidebarWidth === null ? (
<View style={styles.resizeHandle} {...resizer.panHandlers} />
) : null}
{/* One dictation per host document, outliving the session screen that started it. */}
<MobileDictationHostProvider>
<View style={[styles.row, overlayRoute && styles.transparent]}>
{showSidebar && (sidebarOpen || foldSidebarWidth !== null) ? (
<View style={[styles.sidebar, { width: foldSidebarWidth ?? sidebarWidth }]}>
<HostScreen
embedded
hostId={hostId}
onHideSidebar={canCollapseSidebar ? hideSidebar : undefined}
/>
{/* Edge drag handle captures Android gestures above the list. */}
{foldSidebarWidth === null ? (
<View style={styles.resizeHandle} {...resizer.panHandlers} />
) : null}
</View>
) : null}
{foldGap > 0 ? <View style={{ width: foldGap }} /> : null}
<View style={styles.detail}>
<HostAreaServingContext.Provider value={setHostAreaServing}>
<HostSidebarRevealContext.Provider
value={
showSidebar && !sidebarOpen && foldSidebarWidth === null ? revealSidebar : null
}
>
<HostStack animation={showSidebar ? 'none' : 'default'} />
</HostSidebarRevealContext.Provider>
</HostAreaServingContext.Provider>
</View>
) : null}
{foldGap > 0 ? <View style={{ width: foldGap }} /> : null}
<View style={styles.detail}>
<HostAreaServingContext.Provider value={setHostAreaServing}>
<HostSidebarRevealContext.Provider
value={
showSidebar && !sidebarOpen && foldSidebarWidth === null ? revealSidebar : null
}
>
<HostStack animation={showSidebar ? 'none' : 'default'} />
</HostSidebarRevealContext.Provider>
</HostAreaServingContext.Provider>
</View>
</View>
<MobileDictationPillLayer />
</MobileDictationHostProvider>
</HostProtocolGate>
)
}
@@ -15,6 +15,8 @@ android {
}
dependencies {
// Android ships no brotli decoder; Google's pure-Java one, as okhttp-brotli uses.
implementation 'org.brotli:dec:0.1.2'
testImplementation 'junit:junit:4.13.2'
// The android.jar used by JVM unit tests stubs org.json; the shared vectors are JSON.
testImplementation 'org.json:json:20240303'
@@ -0,0 +1,31 @@
package expo.modules.orcagziputf8codec
import java.io.ByteArrayInputStream
import java.io.IOException
import org.brotli.dec.BrotliInputStream
class BoundedBrotliException(message: String) : Exception(message)
/** Inflate one brotli stream into at most outputLimit bytes; callers pass one byte beyond the length they
* accept and check the result's length and hash, so truncated or overlong input fails there. */
fun brotliDecompressBounded(input: ByteArray, outputLimit: Int): ByteArray {
if (outputLimit <= 0 || input.isEmpty()) {
throw BoundedBrotliException("nothing to inflate")
}
val output = ByteArray(outputLimit)
var produced = 0
try {
BrotliInputStream(ByteArrayInputStream(input)).use { stream ->
while (produced < outputLimit) {
val count = stream.read(output, produced, outputLimit - produced)
if (count < 0) {
break
}
produced += count
}
}
} catch (error: IOException) {
throw BoundedBrotliException(error.message ?: "invalid brotli data")
}
return if (produced == outputLimit) output else output.copyOf(produced)
}
@@ -15,5 +15,10 @@ class OrcaGzipUtf8CodecModule : Module() {
Function("decodeUtf8") { input: ByteArray ->
decodeStrictUtf8(input)
}
// Async: a whole bundle asset can take tens of ms, too long to hold the JS thread.
AsyncFunction("brotliDecompress") { input: ByteArray, outputLimit: Int ->
brotliDecompressBounded(input, outputLimit)
}
}
}
@@ -0,0 +1,28 @@
import Compression
import Foundation
struct BoundedBrotliError: Error, CustomStringConvertible {
let description: String
}
/// Inflate one brotli stream into at most outputLimit bytes; callers pass one byte beyond the length they
/// accept and check the result's length and hash. Compression returns nothing for a stream that is invalid,
/// truncated or longer than the buffer, so all three throw here.
func brotliDecompressBounded(_ input: Data, outputLimit: Int) throws -> Data {
guard outputLimit > 0, !input.isEmpty else {
throw BoundedBrotliError(description: "nothing to inflate")
}
var output = Data(count: outputLimit)
let produced = input.withUnsafeBytes { (inBytes: UnsafeRawBufferPointer) -> Int in
output.withUnsafeMutableBytes { (outBytes: UnsafeMutableRawBufferPointer) -> Int in
compression_decode_buffer(
outBytes.bindMemory(to: UInt8.self).baseAddress!, outputLimit,
inBytes.bindMemory(to: UInt8.self).baseAddress!, inBytes.count,
nil, COMPRESSION_BROTLI)
}
}
guard produced > 0 else {
throw BoundedBrotliError(description: "not a whole brotli stream within the output limit")
}
return produced == outputLimit ? output : output.prefix(produced)
}
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = 'OrcaGzipUtf8Codec'
s.version = '0.0.1'
s.summary = 'Bounded gunzip and strict UTF-8 decode'
s.summary = 'Bounded gunzip and brotli inflate, and strict UTF-8 decode'
s.description = s.summary
s.license = { :type => 'MIT' }
s.author = 'Orca'
@@ -11,6 +11,6 @@ Pod::Spec.new do |s|
s.swift_version = '5.9'
s.static_framework = true
s.dependency 'ExpoModulesCore'
s.libraries = 'z'
s.libraries = 'z', 'compression'
s.source_files = '**/*.swift'
end
@@ -12,5 +12,10 @@ public class OrcaGzipUtf8CodecModule: Module {
Function("decodeUtf8") { (input: Data) -> String? in
decodeStrictUtf8(input)
}
// Async: a whole bundle asset can take tens of ms, too long to hold the JS thread.
AsyncFunction("brotliDecompress") { (input: Data, outputLimit: Int) throws -> Data in
try brotliDecompressBounded(input, outputLimit: outputLimit)
}
}
}
@@ -5,6 +5,8 @@ export type OrcaGzipUtf8CodecModule = {
gunzip(input: Uint8Array, outputLimit: number): Uint8Array
/** Well-formed UTF-8 with no leading BOM, decoded; null otherwise. */
decodeUtf8(input: Uint8Array): string | null
/** Inflate one brotli stream into at most outputLimit bytes, off the JS thread; throws on bad input. */
brotliDecompress(input: Uint8Array, outputLimit: number): Promise<Uint8Array<ArrayBuffer>>
}
/** Null in a binary built before the module existed. */
@@ -13,6 +13,7 @@ import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.functions.Queues
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.io.File
class OrcaMobileWebShellModule : Module() {
private val main = Handler(Looper.getMainLooper())
@@ -57,6 +58,16 @@ class OrcaMobileWebShellModule : Module() {
discardSpare()
}
// Page builds live in filesDir: "Clear cache", cleaner apps and low storage empty cacheDir, which cost a full
// download on the next open. allowBackup false and the device-transfer rules keep them on this phone.
Function("generationStoreDirectory") { name: String ->
val filesDir = appContext.reactContext?.filesDir ?: throw Exceptions.ReactContextLost()
val directory = File(filesDir, name)
// A full disk can refuse the folder; the caller then keeps the old root.
if (!directory.mkdirs() && !directory.isDirectory) throw UnableToCreateGenerationStoreException()
Uri.fromFile(directory).toString()
}
AsyncFunction("prepareSpareDocument") { directory: String, sessionId: String ->
val activity = appContext.currentActivity ?: return@AsyncFunction false
MobileWebShellSparePool.prepare(activity, directory, sessionId)
@@ -124,3 +135,6 @@ class OrcaMobileWebShellModule : Module() {
}
}
}
private class UnableToCreateGenerationStoreException :
CodedException("The page build folder could not be created")
@@ -0,0 +1,15 @@
import Foundation
/// Application Support, which iOS never purges; the builds can be downloaded again, so backups skip them.
enum MobileWebShellGenerationStoreDirectory {
static func prepare(name: String) throws -> String {
var directory = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(name, isDirectory: true)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
var values = URLResourceValues()
values.isExcludedFromBackup = true
try directory.setResourceValues(values)
return directory.absoluteString
}
}
@@ -47,6 +47,11 @@ public class OrcaMobileWebShellModule: Module {
MobileWebShellSparePool.shared.discardAll()
}.runOnQueue(.main)
// The generation store's root as a file URL, created and excluded from backups.
Function("generationStoreDirectory") { (name: String) throws -> String in
try MobileWebShellGenerationStoreDirectory.prepare(name: name)
}
// Home calls this once it has a paired host, so a user with nothing to open pays nothing.
Function("warmUp") {
// A second's delay keeps the warm-up off the launch's first frames.
@@ -0,0 +1,14 @@
import { requireOptionalNativeModule } from 'expo-modules-core'
// <name> under the app's durable storage (iOS Application Support, outside backups; Android filesDir) as a file URL.
// Null from a binary built before the function existed, or when the directory can't be made.
export function generationStoreDirectory(name: string): string | null {
const shell = requireOptionalNativeModule<{
generationStoreDirectory?: (name: string) => string
}>('OrcaMobileWebShell')
try {
return shell?.generationStoreDirectory?.(name) ?? null
} catch {
return null
}
}
+1
View File
@@ -34,6 +34,7 @@
"@xterm/addon-webgl": "0.20.0-beta.329",
"@xterm/xterm": "6.1.0-beta.336",
"buffer": "^6.0.3",
"diff": "8.0.4",
"expo": "^55.0.31",
"expo-build-properties": "^55.0.18",
"expo-camera": "^55.0.23",
@@ -0,0 +1,33 @@
const fs = require('node:fs')
const path = require('node:path')
const { withAndroidManifest, withDangerousMod, AndroidConfig } = require('expo/config-plugins')
// allowBackup="false" stops cloud backup but not Android 12+ device-to-device transfer, which copies filesDir.
// Page builds are re-downloadable and belong to this phone's pairings, so a new phone starts without them.
module.exports = function withAndroidDeviceTransferRules(config) {
config = withDangerousMod(config, [
'android',
(cfg) => {
const folder = path.join(cfg.modRequest.platformProjectRoot, 'app/src/main/res/xml')
fs.mkdirSync(folder, { recursive: true })
fs.writeFileSync(
path.join(folder, 'data_extraction_rules.xml'),
[
'<?xml version="1.0" encoding="utf-8"?>',
'<data-extraction-rules>',
' <device-transfer>',
' <exclude domain="file" path="mobile-web/"/>',
' </device-transfer>',
'</data-extraction-rules>',
''
].join('\n')
)
return cfg
}
])
return withAndroidManifest(config, (cfg) => {
const application = AndroidConfig.Manifest.getMainApplicationOrThrow(cfg.modResults)
application.$['android:dataExtractionRules'] = '@xml/data_extraction_rules'
return cfg
})
}
+9
View File
@@ -224,6 +224,9 @@ importers:
buffer:
specifier: ^6.0.3
version: 6.0.3
diff:
specifier: 8.0.4
version: 8.0.4
expo:
specifier: ^55.0.31
version: 55.0.31(0def556739f9e1a9e497ee4bc3b81b41)
@@ -4096,6 +4099,10 @@ packages:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
diff@8.0.4:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
dnssd-advertise@1.1.6:
resolution: {integrity: sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==}
@@ -11737,6 +11744,8 @@ snapshots:
diff-sequences@29.6.3: {}
diff@8.0.4: {}
dnssd-advertise@1.1.6: {}
doctrine@2.1.0:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -4,6 +4,16 @@
"family": "session.pr-sidebar",
"namedDeltas": [],
"values": {
"0730389db3e9": {
"name": "hostedReview.forBranch#1",
"ordinal": 7,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"0d9f7988ff86": {
"name": "github.prChecks#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"1287167eaf56": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -39,21 +49,11 @@
}
}
},
"1723d8a5d8be": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"1f5765918736": {
"name": "github.repoSlug#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"2bb452e534eb": {
"name": "github.prChecks#1",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"2c045643bad3": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -88,6 +88,42 @@
}
}
},
"2dc2ca47b4e7": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"30a3a771295b": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -127,31 +163,39 @@
"kind": "loading"
}
},
"3b5c096da2a3": {
"name": "hostedReview.forBranch#1",
"357f9b7862e3": {
"name": "worktree.show#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
},
"8352e3c99668": {
"name": "status.get#1",
"ordinal": 11,
"3a8e6b13f380": {
"name": "github.prForBranch#1",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
},
"3ecc2d44a209": {
"name": "runtime.clientEvents.subscribe#1",
"ordinal": 3,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"runtime.clientEvents.subscribe\",\"params\":null}"
},
"4da7ea39401b": {
"name": "worktree.show#1",
"ordinal": 4,
"args": [
{
"name": "method",
"value": "status.get"
"value": "worktree.show"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
"worktree": "id:repo-9::/w"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
"failWhenDisconnected": true
}
}
],
@@ -160,10 +204,64 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"id": "frame-3",
"ok": true,
"result": {
"runtimeId": "recorded"
"worktree": {
"linkedPR": {
"$rpc": "null"
}
}
}
}
}
},
"5d309fcf43a7": {
"name": "github.workItemDetails#1",
"ordinal": 16,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"75885386f005": {
"name": "hostedReview.forBranch#1",
"ordinal": 5,
"args": [
{
"name": "method",
"value": "hostedReview.forBranch"
},
{
"name": "params",
"value": {
"active": true,
"branch": "feature",
"linkedGitHubPR": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
"number": 12,
"provider": "github",
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
@@ -205,6 +303,75 @@
}
}
},
"861c8796ae1f": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"870c2b9bacd0": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"87a169b1b8d4": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -240,26 +407,26 @@
}
}
},
"87ba39148f02": {
"name": "github.prChecks#1",
"ordinal": 10,
"8b7e690466d7": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "github.prChecks"
"value": "status.get"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
@@ -268,16 +435,11 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"id": "frame-7",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
"result": {
"runtimeId": "recorded"
}
}
}
},
@@ -348,46 +510,85 @@
}
}
},
"9c952632b0c0": {
"name": "status.get#1",
"ordinal": 11,
"9091d133c3b0": {
"name": "reply-salvage",
"ordinal": 8,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"a55c83eeaeea": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "status.get"
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"a0729c676b9a": {
"name": "worktree.show#1",
"ordinal": 3,
"a7e609fcb031": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "worktree.show"
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"worktree": "id:repo-9::/w"
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
@@ -398,20 +599,8 @@
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"worktree": {
"linkedPR": {
"$rpc": "null"
}
}
}
}
"status": "pending",
"startedAt": 0
}
},
"abae46078fe1": {
@@ -446,47 +635,6 @@
}
}
},
"abcf3bdc5cde": {
"name": "status.get#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"b3a7ec3ab1c7": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"b77761a8ce16": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -557,6 +705,69 @@
}
}
},
"bfedf583a747": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"args": [
{
"name": "method",
"value": "github.workItemDetails"
},
{
"name": "params",
"value": {
"number": 12,
"repo": "id:repo-9",
"type": "pr"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-8",
"ok": true,
"result": {
"body": "body",
"comments": [
{
"author": "rev",
"body": "please fix",
"createdAt": "2026-01-01T00:00:00Z",
"id": 55,
"isResolved": false,
"line": 3,
"path": "src/app.ts",
"threadId": "thread-1",
"url": "https://github.com/owner/repo/pull/12#discussion_r55"
}
],
"headSha": "head-sha-1",
"item": {
"assignees": [],
"id": "PR_1",
"labels": [],
"number": 12,
"state": "open",
"title": "Recorded",
"type": "pr",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c1bb3c2243f0": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -591,206 +802,16 @@
}
}
},
"c2f3182f1780": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
"c70e6fd19823": {
"name": "streams-registered-at-teardown",
"ordinal": 17,
"value": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
"cancelled": true,
"method": "runtime.clientEvents.subscribe",
"payload": "runtime.clientEvents.subscribe#1"
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c79bbd27f177": {
"name": "hostedReview.forBranch#1",
"ordinal": 4,
"args": [
{
"name": "method",
"value": "hostedReview.forBranch"
},
{
"name": "params",
"value": {
"active": true,
"branch": "feature",
"linkedGitHubPR": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
"number": 12,
"provider": "github",
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
},
"cb071b912510": {
"name": "github.prChecks#1",
"ordinal": 10,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"d3426861162d": {
"name": "github.workItemDetails#1",
"ordinal": 14,
"args": [
{
"name": "method",
"value": "github.workItemDetails"
},
{
"name": "params",
"value": {
"number": 12,
"repo": "id:repo-9",
"type": "pr"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"ok": true,
"result": {
"body": "body",
"comments": [
{
"author": "rev",
"body": "please fix",
"createdAt": "2026-01-01T00:00:00Z",
"id": 55,
"isResolved": false,
"line": 3,
"path": "src/app.ts",
"threadId": "thread-1",
"url": "https://github.com/owner/repo/pull/12#discussion_r55"
}
],
"headSha": "head-sha-1",
"item": {
"assignees": [],
"id": "PR_1",
"labels": [],
"number": 12,
"state": "open",
"title": "Recorded",
"type": "pr",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"d3d85b61b684": {
"name": "reply-salvage",
"ordinal": 7,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
]
},
"e0a11d8a1005": {
"commentError": {
@@ -817,10 +838,10 @@
"refreshFailed": false
}
},
"ea4c904bfb5f": {
"name": "worktree.show#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
"e10925fa98de": {
"name": "status.get#1",
"ordinal": 14,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"ead3ad184e51": {
"commentError": {
@@ -837,11 +858,6 @@
"value": {
"$rpc": "undefined"
}
},
"fcced642024c": {
"name": "github.prForBranch#1",
"ordinal": 9,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
}
},
"recording": {
@@ -850,13 +866,19 @@
{
"id": "pr-sidebar-load.normal:branch-hint",
"observation": {
"sender": ["1287167eaf56", "a0729c676b9a", "c79bbd27f177", "b3a7ec3ab1c7"],
"payloads": ["1f5765918736", "ea4c904bfb5f", "3b5c096da2a3", "fcced642024c"],
"sender": ["1287167eaf56", "4da7ea39401b", "75885386f005", "2dc2ca47b4e7"],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -864,25 +886,26 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"cb071b912510",
"9c952632b0c0"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"a7e609fcb031",
"870c2b9bacd0"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -890,27 +913,57 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
"id": "pr-sidebar-load.normal:cleanup",
"observation": {
"sender": [
"1287167eaf56",
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["9091d133c3b0", "c70e6fd19823"]
}
},
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -11,52 +11,6 @@
"$rpc": "null"
}
},
"00ef2dbe5026": {
"name": "github.project.resolveRef#1",
"ordinal": 21,
"args": [
{
"name": "method",
"value": "github.project.resolveRef"
},
{
"name": "params",
"value": {
"host": "github.com",
"input": "https://github.com/orgs/owner/projects/3"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"host": "github.com",
"number": 3,
"ok": true,
"owner": "owner",
"ownerType": "organization",
"title": "Board",
"viewNumber": 1
}
}
}
},
"0388fa155721": {
"name": "githubProjectError",
"ordinal": 11,
"value": ""
},
"060dcedb3fdb": {
"name": "githubProjectError",
"ordinal": 10,
@@ -331,13 +285,6 @@
"ordinal": 33,
"value": false
},
"34de2d655a0a": {
"name": "githubProjectTable",
"ordinal": 32,
"value": {
"$rpc": "null"
}
},
"376c9e8bd72a": {
"error": "",
"loading": false,
@@ -407,31 +354,6 @@
"ordinal": 14,
"value": "is:open"
},
"43352776b1e1": {
"name": "githubProjectTable",
"ordinal": 14,
"value": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
}
},
"47507819e28a": {
"name": "githubProjectPasteInput",
"ordinal": 23,
"value": ""
},
"4809ea7ad5b0": {
"name": "github.project.viewTable#1",
"ordinal": 12,
@@ -449,87 +371,6 @@
}
]
},
"4eb521bbefdd": {
"name": "githubProjectSearch",
"ordinal": 15,
"value": "is:open"
},
"528af16518e6": {
"name": "githubProjectViews",
"ordinal": 16,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"5a4fc5c86d0c": {
"name": "githubProjectPasteBusy",
"ordinal": 34,
"value": false
},
"5f5f56db9d64": {
"name": "github.project.viewTable#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "github.project.viewTable"
},
{
"name": "params",
"value": {
"host": "github.enterprise.test",
"owner": "owner",
"ownerType": "organization",
"projectNumber": 3,
"viewId": "view-1"
}
},
{
"name": "options",
"value": {
"timeoutMs": 60000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"data": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
},
"ok": true
}
}
}
},
"61ce1ab88e79": {
"name": "github.project.viewTable#1",
"ordinal": 13,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.viewTable\",\"params\":{\"owner\":\"owner\",\"host\":\"github.enterprise.test\",\"ownerType\":\"organization\",\"projectNumber\":3,\"viewId\":\"view-1\"}}"
},
"621b6b7b4159": {
"name": "github.project.listViews#1",
"ordinal": 7,
@@ -565,11 +406,6 @@
}
}
},
"654693493b51": {
"name": "githubProjectLoading",
"ordinal": 10,
"value": true
},
"685bc2bda05d": {
"name": "githubProjects",
"ordinal": 5,
@@ -623,16 +459,6 @@
"ordinal": 25,
"value": ""
},
"7a9df0d1a484": {
"name": "githubProjectLoading",
"ordinal": 25,
"value": true
},
"7b9d3b33c08b": {
"name": "githubProjectPasteError",
"ordinal": 19,
"value": ""
},
"7d488eda1d1c": {
"name": "githubProjectViews",
"ordinal": 15,
@@ -650,43 +476,16 @@
"ordinal": 16,
"value": false
},
"8792541c60ad": {
"name": "githubProjectError",
"ordinal": 26,
"value": ""
},
"8a0577280bad": {
"name": "showGitHubProjectPicker",
"ordinal": 23,
"value": false
},
"8e4dec1aad1f": {
"name": "github.project.listViews#2",
"ordinal": 28,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listViews\",\"params\":{\"owner\":\"owner\",\"host\":\"github.com\",\"ownerType\":\"organization\",\"projectNumber\":3}}"
},
"8f3ee6c510b1": {
"name": "github.project.resolveRef#1",
"ordinal": 22,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.resolveRef\",\"params\":{\"input\":\"https://github.com/orgs/owner/projects/3\",\"host\":\"github.com\"}}"
},
"97bc7dc12b92": {
"name": "githubProjectLoading",
"ordinal": 32,
"value": false
},
"9b9f8dd75514": {
"name": "githubProjectViews",
"ordinal": 9,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"9f9a21ac7b9e": {
"name": "github.project.listViews#2",
"ordinal": 27,
@@ -732,11 +531,6 @@
}
}
},
"a4a1ff5df287": {
"name": "githubProjectPasteBusy",
"ordinal": 18,
"value": true
},
"a5a3530ec11c": {
"name": "githubProjectPartialFailures",
"ordinal": 2,
@@ -807,68 +601,6 @@
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listAccessible\",\"params\":{\"host\":\"github.com\"}}"
},
"b49ff9f109a5": {
"name": "github.project.listViews#2",
"ordinal": 27,
"args": [
{
"name": "method",
"value": "github.project.listViews"
},
{
"name": "params",
"value": {
"host": "github.com",
"owner": "owner",
"ownerType": "organization",
"projectNumber": 3
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"ok": true,
"views": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
}
}
}
},
"b5e354ed6023": {
"name": "githubProjectLoading",
"ordinal": 17,
"value": false
},
"b66c4f75c9ae": {
"name": "githubProjectViews",
"ordinal": 29,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"b948e8307e81": {
"status": "rejected",
"startedAt": 0,
@@ -972,16 +704,6 @@
}
}
},
"c453f220a7e2": {
"name": "githubProjectLoading",
"ordinal": 33,
"value": false
},
"c4c4250b0567": {
"name": "showGitHubProjectPicker",
"ordinal": 24,
"value": false
},
"c5c018cf9a70": {
"name": "githubProjectTable",
"ordinal": 13,
@@ -1180,18 +902,6 @@
}
}
},
"fa1d1b339394": {
"name": "appliedGithubProjectSearch",
"ordinal": 30,
"value": {
"$rpc": "undefined"
}
},
"fb9764680a74": {
"name": "githubProjectError",
"ordinal": 20,
"value": ""
},
"fc799562716c": {
"name": "github.project.listViews#1",
"ordinal": 7,
@@ -1229,11 +939,6 @@
}
}
},
"fe075936e563": {
"name": "githubProjectSearch",
"ordinal": 31,
"value": ""
},
"ff43b5ec92a9": {
"error": "",
"loading": false,
@@ -1279,21 +984,15 @@
"projects-0": "eb79a9b3682a",
"views-1": "be0da5b53ffb"
},
"state": "376c9e8bd72a",
"effects": [
"2085118de92f",
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514"
]
"state": "ff43b5ec92a9",
"effects": ["2085118de92f", "a5a3530ec11c", "685bc2bda05d", "170fad8cb36b"]
}
},
{
"id": "tk-project-board-load.normal:table-settled",
"observation": {
"sender": ["dfe16215433b", "d254913957c1", "5f5f56db9d64"],
"payloads": ["b42c8edc0d53", "15cb97c93333", "61ce1ab88e79"],
"sender": ["dfe16215433b", "d254913957c1", "0f1aa264f581"],
"payloads": ["b42c8edc0d53", "15cb97c93333", "4809ea7ad5b0"],
"settlements": {
"mount": "eb79a9b3682a",
"projects-0": "eb79a9b3682a",
@@ -1306,13 +1005,12 @@
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514",
"654693493b51",
"0388fa155721",
"43352776b1e1",
"4eb521bbefdd",
"528af16518e6",
"b5e354ed6023"
"2f290fa2acf5",
"060dcedb3fdb",
"c5c018cf9a70",
"3a8617cc8fd8",
"7d488eda1d1c",
"842c950ee7cc"
]
}
},
@@ -1322,16 +1020,16 @@
"sender": [
"dfe16215433b",
"d254913957c1",
"5f5f56db9d64",
"00ef2dbe5026",
"b49ff9f109a5"
"0f1aa264f581",
"f6003aed2098",
"ae89e058b351"
],
"payloads": [
"b42c8edc0d53",
"15cb97c93333",
"61ce1ab88e79",
"8f3ee6c510b1",
"8e4dec1aad1f"
"4809ea7ad5b0",
"be93253869e6",
"9f9a21ac7b9e"
],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1346,26 +1044,25 @@
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514",
"654693493b51",
"0388fa155721",
"43352776b1e1",
"4eb521bbefdd",
"528af16518e6",
"b5e354ed6023",
"a4a1ff5df287",
"7b9d3b33c08b",
"fb9764680a74",
"47507819e28a",
"c4c4250b0567",
"7a9df0d1a484",
"8792541c60ad",
"b66c4f75c9ae",
"fa1d1b339394",
"fe075936e563",
"34de2d655a0a",
"c453f220a7e2",
"5a4fc5c86d0c"
"2f290fa2acf5",
"060dcedb3fdb",
"c5c018cf9a70",
"3a8617cc8fd8",
"7d488eda1d1c",
"842c950ee7cc",
"3985399034a2",
"26921f3a7c09",
"1b3350f71e56",
"dc8dd7739c49",
"8a0577280bad",
"ab1b10183665",
"6b03ace1146a",
"4c9b1e4ad1f5",
"0af48d475813",
"e87bea4bcd3c",
"0056704adc0b",
"97bc7dc12b92",
"3493818a7239"
]
}
},
@@ -4,6 +4,16 @@
"family": "session.pr-sidebar",
"namedDeltas": [],
"values": {
"0730389db3e9": {
"name": "hostedReview.forBranch#1",
"ordinal": 7,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"0d9f7988ff86": {
"name": "github.prChecks#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"1287167eaf56": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -39,82 +49,9 @@
}
}
},
"1723d8a5d8be": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"19ddadf49030": {
"name": "github.addIssueComment#1",
"ordinal": 16,
"args": [
{
"name": "method",
"value": "github.addIssueComment"
},
{
"name": "params",
"value": {
"body": "recorded comment",
"number": 12,
"repo": "id:repo-9",
"type": "pr"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-8",
"ok": true,
"result": {
"comment": {
"id": 57
},
"ok": true
}
}
}
},
"1f5765918736": {
"name": "github.repoSlug#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"2bb452e534eb": {
"name": "github.prChecks#1",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"35045fa5a752": {
"commentError": {
"$rpc": "null"
},
"pr": {
"kind": "loading"
}
},
"39b9c8ebcce5": {
"name": "github.addIssueComment#1",
"ordinal": 17,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.addIssueComment\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"body\":\"recorded comment\",\"type\":\"pr\"}}"
},
"3b5c096da2a3": {
"name": "hostedReview.forBranch#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"3eeb7e426600": {
"1e1163c70eba": {
"name": "github.prComments#1",
"ordinal": 18,
"ordinal": 19,
"args": [
{
"name": "method",
@@ -145,67 +82,80 @@
"startedAt": 0
}
},
"6f75be8d77df": {
"name": "github.prComments#1",
"ordinal": 19,
"json": "{\"id\":\"frame-9\",\"deviceToken\":\"recording-device\",\"method\":\"github.prComments\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"noCache\":true,\"prRepo\":{\"owner\":\"owner\",\"repo\":\"repo\",\"host\":\"github.com\"}}}"
"1f5765918736": {
"name": "github.repoSlug#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"8352e3c99668": {
"name": "status.get#1",
"ordinal": 11,
"2dc2ca47b4e7": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "status.get"
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
"status": "pending",
"startedAt": 0
}
},
"84e5ca07cb7a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": true
"35045fa5a752": {
"commentError": {
"$rpc": "null"
},
"pr": {
"kind": "loading"
}
},
"87ba39148f02": {
"name": "github.prChecks#1",
"357f9b7862e3": {
"name": "worktree.show#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
},
"3a8e6b13f380": {
"name": "github.prForBranch#1",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
},
"3b2df6b54829": {
"name": "github.addIssueComment#1",
"ordinal": 17,
"args": [
{
"name": "method",
"value": "github.prChecks"
"value": "github.addIssueComment"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
"body": "recorded comment",
"number": 12,
"repo": "id:repo-9",
"type": "pr"
}
},
{
@@ -220,54 +170,25 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"id": "frame-9",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
"result": {
"comment": {
"id": 57
},
"ok": true
}
}
}
},
"9270aeb7d9c6": {
"status": "pending",
"startedAt": 0
},
"9c952632b0c0": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a0729c676b9a": {
"name": "worktree.show#1",
"3ecc2d44a209": {
"name": "runtime.clientEvents.subscribe#1",
"ordinal": 3,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"runtime.clientEvents.subscribe\",\"params\":null}"
},
"4da7ea39401b": {
"name": "worktree.show#1",
"ordinal": 4,
"args": [
{
"name": "method",
@@ -291,7 +212,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"id": "frame-3",
"ok": true,
"result": {
"worktree": {
@@ -303,104 +224,14 @@
}
}
},
"abcf3bdc5cde": {
"name": "status.get#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
"5d309fcf43a7": {
"name": "github.workItemDetails#1",
"ordinal": 16,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"b3a7ec3ab1c7": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"c2f3182f1780": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c79bbd27f177": {
"75885386f005": {
"name": "hostedReview.forBranch#1",
"ordinal": 4,
"ordinal": 5,
"args": [
{
"name": "method",
@@ -429,7 +260,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"id": "frame-4",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
@@ -443,9 +274,210 @@
}
}
},
"cb071b912510": {
"7881ab1fb5d1": {
"name": "github.addIssueComment#1",
"ordinal": 18,
"json": "{\"id\":\"frame-9\",\"deviceToken\":\"recording-device\",\"method\":\"github.addIssueComment\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"body\":\"recorded comment\",\"type\":\"pr\"}}"
},
"84e5ca07cb7a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": true
},
"861c8796ae1f": {
"name": "github.prChecks#1",
"ordinal": 10,
"ordinal": 11,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"870c2b9bacd0": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8b7e690466d7": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"8f35f3a8538e": {
"name": "github.prComments#1",
"ordinal": 20,
"json": "{\"id\":\"frame-10\",\"deviceToken\":\"recording-device\",\"method\":\"github.prComments\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"noCache\":true,\"prRepo\":{\"owner\":\"owner\",\"repo\":\"repo\",\"host\":\"github.com\"}}}"
},
"9091d133c3b0": {
"name": "reply-salvage",
"ordinal": 8,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"9270aeb7d9c6": {
"status": "pending",
"startedAt": 0
},
"9be162e088f2": {
"name": "streams-registered-at-teardown",
"ordinal": 21,
"value": [
{
"cancelled": true,
"method": "runtime.clientEvents.subscribe",
"payload": "runtime.clientEvents.subscribe#1"
}
]
},
"a55c83eeaeea": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"a7e609fcb031": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
@@ -471,9 +503,9 @@
"startedAt": 0
}
},
"d3426861162d": {
"bfedf583a747": {
"name": "github.workItemDetails#1",
"ordinal": 14,
"ordinal": 15,
"args": [
{
"name": "method",
@@ -501,7 +533,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"id": "frame-8",
"ok": true,
"result": {
"body": "body",
@@ -564,17 +596,6 @@
"refreshFailed": false
}
},
"d3d85b61b684": {
"name": "reply-salvage",
"ordinal": 7,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"e0a11d8a1005": {
"commentError": {
"$rpc": "null"
@@ -600,22 +621,14 @@
"refreshFailed": false
}
},
"ea4c904bfb5f": {
"name": "worktree.show#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
"e10925fa98de": {
"name": "status.get#1",
"ordinal": 14,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"f742d75db77c": {
"e40828b3dc78": {
"name": "github.prComments#1",
"ordinal": 18,
"ordinal": 19,
"args": [
{
"name": "method",
@@ -646,7 +659,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-9",
"id": "frame-10",
"ok": true,
"result": [
{
@@ -671,10 +684,13 @@
}
}
},
"fcced642024c": {
"name": "github.prForBranch#1",
"ordinal": 9,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
}
},
"recording": {
@@ -683,13 +699,19 @@
{
"id": "branch-hint",
"observation": {
"sender": ["1287167eaf56", "a0729c676b9a", "c79bbd27f177", "b3a7ec3ab1c7"],
"payloads": ["1f5765918736", "ea4c904bfb5f", "3b5c096da2a3", "fcced642024c"],
"sender": ["1287167eaf56", "4da7ea39401b", "75885386f005", "2dc2ca47b4e7"],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -697,25 +719,26 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"cb071b912510",
"9c952632b0c0"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"a7e609fcb031",
"870c2b9bacd0"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -723,27 +746,28 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -751,32 +775,33 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d",
"19ddadf49030",
"3eeb7e426600"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"3b2df6b54829",
"1e1163c70eba"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be",
"39b9c8ebcce5",
"6f75be8d77df"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"7881ab1fb5d1",
"8f35f3a8538e"
],
"settlements": {
"mount": "eb79a9b3682a",
"post-comment": "9270aeb7d9c6"
},
"state": "e0a11d8a1005",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -784,32 +809,67 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d",
"19ddadf49030",
"f742d75db77c"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"3b2df6b54829",
"e40828b3dc78"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be",
"39b9c8ebcce5",
"6f75be8d77df"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"7881ab1fb5d1",
"8f35f3a8538e"
],
"settlements": {
"mount": "eb79a9b3682a",
"post-comment": "84e5ca07cb7a"
},
"state": "d39b1bf1038a",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
"id": "cleanup",
"observation": {
"sender": [
"1287167eaf56",
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"3b2df6b54829",
"e40828b3dc78"
],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"7881ab1fb5d1",
"8f35f3a8538e"
],
"settlements": {
"mount": "eb79a9b3682a",
"post-comment": "84e5ca07cb7a"
},
"state": "d39b1bf1038a",
"effects": ["9091d133c3b0", "9be162e088f2"]
}
}
]
@@ -4,6 +4,11 @@
"family": "session.pr-sidebar",
"namedDeltas": [],
"values": {
"0730389db3e9": {
"name": "hostedReview.forBranch#1",
"ordinal": 7,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"0ab0279ef3fe": {
"commentError": "Resource not accessible by integration",
"pr": {
@@ -27,6 +32,11 @@
"refreshFailed": false
}
},
"0d9f7988ff86": {
"name": "github.prChecks#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"1287167eaf56": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -62,11 +72,6 @@
}
}
},
"1723d8a5d8be": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"18a67e1b19d6": {
"commentError": {
"$rpc": "null"
@@ -97,14 +102,63 @@
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"2bb452e534eb": {
"name": "github.prChecks#1",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
"2dc2ca47b4e7": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"3362c89b344f": {
"35045fa5a752": {
"commentError": {
"$rpc": "null"
},
"pr": {
"kind": "loading"
}
},
"357f9b7862e3": {
"name": "worktree.show#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
},
"3a8e6b13f380": {
"name": "github.prForBranch#1",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
},
"3d6c69a8143e": {
"name": "github.resolveReviewThread#1",
"ordinal": 16,
"ordinal": 17,
"args": [
{
"name": "method",
@@ -134,175 +188,19 @@
"code": "upstream_error",
"message": "Resource not accessible by integration"
},
"id": "frame-8",
"id": "frame-9",
"ok": false
}
}
},
"35045fa5a752": {
"commentError": {
"$rpc": "null"
},
"pr": {
"kind": "loading"
}
},
"3b5c096da2a3": {
"name": "hostedReview.forBranch#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"61c7e519a6dc": {
"name": "github.resolveReviewThread#1",
"ordinal": 17,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.resolveReviewThread\",\"params\":{\"repo\":\"id:repo-9\",\"threadId\":\"thread-1\",\"resolve\":true}}"
},
"7ed3d39f0607": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": false
},
"8352e3c99668": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"87ba39148f02": {
"name": "github.prChecks#1",
"ordinal": 10,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"901052a1366a": {
"name": "github.resolveReviewThread#1",
"ordinal": 16,
"args": [
{
"name": "method",
"value": "github.resolveReviewThread"
},
{
"name": "params",
"value": {
"repo": "id:repo-9",
"resolve": true,
"threadId": "thread-1"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"9270aeb7d9c6": {
"status": "pending",
"startedAt": 0
},
"9c952632b0c0": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a0729c676b9a": {
"name": "worktree.show#1",
"3ecc2d44a209": {
"name": "runtime.clientEvents.subscribe#1",
"ordinal": 3,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"runtime.clientEvents.subscribe\",\"params\":null}"
},
"4da7ea39401b": {
"name": "worktree.show#1",
"ordinal": 4,
"args": [
{
"name": "method",
@@ -326,7 +224,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"id": "frame-3",
"ok": true,
"result": {
"worktree": {
@@ -338,104 +236,14 @@
}
}
},
"abcf3bdc5cde": {
"name": "status.get#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
"5d309fcf43a7": {
"name": "github.workItemDetails#1",
"ordinal": 16,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"b3a7ec3ab1c7": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"c2f3182f1780": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c79bbd27f177": {
"75885386f005": {
"name": "hostedReview.forBranch#1",
"ordinal": 4,
"ordinal": 5,
"args": [
{
"name": "method",
@@ -464,7 +272,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"id": "frame-4",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
@@ -478,9 +286,217 @@
}
}
},
"cb071b912510": {
"7ed3d39f0607": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": false
},
"84983e28731b": {
"name": "github.resolveReviewThread#1",
"ordinal": 17,
"args": [
{
"name": "method",
"value": "github.resolveReviewThread"
},
{
"name": "params",
"value": {
"repo": "id:repo-9",
"resolve": true,
"threadId": "thread-1"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"861c8796ae1f": {
"name": "github.prChecks#1",
"ordinal": 10,
"ordinal": 11,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"870c2b9bacd0": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8b7e690466d7": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"9091d133c3b0": {
"name": "reply-salvage",
"ordinal": 8,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"9270aeb7d9c6": {
"status": "pending",
"startedAt": 0
},
"a55c83eeaeea": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"a7e609fcb031": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
@@ -506,9 +522,9 @@
"startedAt": 0
}
},
"d3426861162d": {
"bfedf583a747": {
"name": "github.workItemDetails#1",
"ordinal": 14,
"ordinal": 15,
"args": [
{
"name": "method",
@@ -536,7 +552,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"id": "frame-8",
"ok": true,
"result": {
"body": "body",
@@ -569,16 +585,10 @@
}
}
},
"d3d85b61b684": {
"name": "reply-salvage",
"ordinal": 7,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
"c11667112704": {
"name": "github.resolveReviewThread#1",
"ordinal": 18,
"json": "{\"id\":\"frame-9\",\"deviceToken\":\"recording-device\",\"method\":\"github.resolveReviewThread\",\"params\":{\"repo\":\"id:repo-9\",\"threadId\":\"thread-1\",\"resolve\":true}}"
},
"e0a11d8a1005": {
"commentError": {
@@ -605,10 +615,10 @@
"refreshFailed": false
}
},
"ea4c904bfb5f": {
"name": "worktree.show#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
"e10925fa98de": {
"name": "status.get#1",
"ordinal": 14,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
@@ -618,10 +628,16 @@
"$rpc": "undefined"
}
},
"fcced642024c": {
"name": "github.prForBranch#1",
"ordinal": 9,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
"febdecd6dd97": {
"name": "streams-registered-at-teardown",
"ordinal": 19,
"value": [
{
"cancelled": true,
"method": "runtime.clientEvents.subscribe",
"payload": "runtime.clientEvents.subscribe#1"
}
]
}
},
"recording": {
@@ -630,13 +646,19 @@
{
"id": "branch-hint",
"observation": {
"sender": ["1287167eaf56", "a0729c676b9a", "c79bbd27f177", "b3a7ec3ab1c7"],
"payloads": ["1f5765918736", "ea4c904bfb5f", "3b5c096da2a3", "fcced642024c"],
"sender": ["1287167eaf56", "4da7ea39401b", "75885386f005", "2dc2ca47b4e7"],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -644,25 +666,26 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"cb071b912510",
"9c952632b0c0"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"a7e609fcb031",
"870c2b9bacd0"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -670,27 +693,28 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -698,30 +722,31 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d",
"901052a1366a"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"84983e28731b"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be",
"61c7e519a6dc"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"c11667112704"
],
"settlements": {
"mount": "eb79a9b3682a",
"resolve-thread": "9270aeb7d9c6"
},
"state": "18a67e1b19d6",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -729,30 +754,63 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d",
"3362c89b344f"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"3d6c69a8143e"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be",
"61c7e519a6dc"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"c11667112704"
],
"settlements": {
"mount": "eb79a9b3682a",
"resolve-thread": "7ed3d39f0607"
},
"state": "0ab0279ef3fe",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
"id": "cleanup",
"observation": {
"sender": [
"1287167eaf56",
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747",
"3d6c69a8143e"
],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7",
"c11667112704"
],
"settlements": {
"mount": "eb79a9b3682a",
"resolve-thread": "7ed3d39f0607"
},
"state": "0ab0279ef3fe",
"effects": ["9091d133c3b0", "febdecd6dd97"]
}
}
]
@@ -4,6 +4,16 @@
"family": "session.pr-sidebar",
"namedDeltas": [],
"values": {
"0730389db3e9": {
"name": "hostedReview.forBranch#1",
"ordinal": 7,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"0d9f7988ff86": {
"name": "github.prChecks#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"1287167eaf56": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -39,20 +49,46 @@
}
}
},
"1723d8a5d8be": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"1f5765918736": {
"name": "github.repoSlug#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"2bb452e534eb": {
"name": "github.prChecks#1",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
"2dc2ca47b4e7": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"341c71d5f61d": {
"commentError": {
@@ -85,78 +121,24 @@
"kind": "loading"
}
},
"3b5c096da2a3": {
"name": "hostedReview.forBranch#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"8352e3c99668": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"9c952632b0c0": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a0729c676b9a": {
"357f9b7862e3": {
"name": "worktree.show#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
},
"3a8e6b13f380": {
"name": "github.prForBranch#1",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
},
"3ecc2d44a209": {
"name": "runtime.clientEvents.subscribe#1",
"ordinal": 3,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"runtime.clientEvents.subscribe\",\"params\":null}"
},
"4da7ea39401b": {
"name": "worktree.show#1",
"ordinal": 4,
"args": [
{
"name": "method",
@@ -180,7 +162,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"id": "frame-3",
"ok": true,
"result": {
"worktree": {
@@ -192,104 +174,14 @@
}
}
},
"abcf3bdc5cde": {
"name": "status.get#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
"5d309fcf43a7": {
"name": "github.workItemDetails#1",
"ordinal": 16,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"b3a7ec3ab1c7": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"c2f3182f1780": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c79bbd27f177": {
"75885386f005": {
"name": "hostedReview.forBranch#1",
"ordinal": 4,
"ordinal": 5,
"args": [
{
"name": "method",
@@ -318,7 +210,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"id": "frame-4",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
@@ -332,9 +224,174 @@
}
}
},
"cb071b912510": {
"870c2b9bacd0": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8b7e690466d7": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"9091d133c3b0": {
"name": "reply-salvage",
"ordinal": 8,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"98eb9b836539": {
"name": "github.prChecks#1",
"ordinal": 10,
"ordinal": 11,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"checks": []
}
}
}
},
"a55c83eeaeea": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"a7e609fcb031": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
@@ -360,9 +417,9 @@
"startedAt": 0
}
},
"d3426861162d": {
"bfedf583a747": {
"name": "github.workItemDetails#1",
"ordinal": 14,
"ordinal": 15,
"args": [
{
"name": "method",
@@ -390,7 +447,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"id": "frame-8",
"ok": true,
"result": {
"body": "body",
@@ -423,57 +480,21 @@
}
}
},
"d3d85b61b684": {
"name": "reply-salvage",
"ordinal": 7,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"d43ea39cfd0a": {
"name": "github.prChecks#1",
"ordinal": 10,
"args": [
"c70e6fd19823": {
"name": "streams-registered-at-teardown",
"ordinal": 17,
"value": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
"cancelled": true,
"method": "runtime.clientEvents.subscribe",
"payload": "runtime.clientEvents.subscribe#1"
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"checks": []
}
}
}
]
},
"ea4c904bfb5f": {
"name": "worktree.show#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
"e10925fa98de": {
"name": "status.get#1",
"ordinal": 14,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
@@ -482,11 +503,6 @@
"value": {
"$rpc": "undefined"
}
},
"fcced642024c": {
"name": "github.prForBranch#1",
"ordinal": 9,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
}
},
"recording": {
@@ -495,13 +511,19 @@
{
"id": "branch-hint",
"observation": {
"sender": ["1287167eaf56", "a0729c676b9a", "c79bbd27f177", "b3a7ec3ab1c7"],
"payloads": ["1f5765918736", "ea4c904bfb5f", "3b5c096da2a3", "fcced642024c"],
"sender": ["1287167eaf56", "4da7ea39401b", "75885386f005", "2dc2ca47b4e7"],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -509,25 +531,26 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"cb071b912510",
"9c952632b0c0"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"a7e609fcb031",
"870c2b9bacd0"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -535,27 +558,57 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"d43ea39cfd0a",
"8352e3c99668",
"d3426861162d"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"98eb9b836539",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "341c71d5f61d",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
"id": "cleanup",
"observation": {
"sender": [
"1287167eaf56",
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"98eb9b836539",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "341c71d5f61d",
"effects": ["9091d133c3b0", "c70e6fd19823"]
}
}
]
+322 -269
View File
@@ -4,6 +4,16 @@
"family": "session.pr-sidebar",
"namedDeltas": [],
"values": {
"0730389db3e9": {
"name": "hostedReview.forBranch#1",
"ordinal": 7,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"0d9f7988ff86": {
"name": "github.prChecks#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
},
"1287167eaf56": {
"name": "github.repoSlug#1",
"ordinal": 1,
@@ -39,20 +49,46 @@
}
}
},
"1723d8a5d8be": {
"name": "github.workItemDetails#1",
"ordinal": 15,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"1f5765918736": {
"name": "github.repoSlug#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.repoSlug\",\"params\":{\"repo\":\"id:repo-9\"}}"
},
"2bb452e534eb": {
"name": "github.prChecks#1",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prChecks\",\"params\":{\"repo\":\"id:repo-9\",\"prNumber\":12,\"headSha\":\"head-sha-1\"}}"
"2dc2ca47b4e7": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"35045fa5a752": {
"commentError": {
@@ -62,119 +98,24 @@
"kind": "loading"
}
},
"3b5c096da2a3": {
"name": "hostedReview.forBranch#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"hostedReview.forBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedGitHubPR\":null,\"active\":true}}"
},
"8352e3c99668": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"87ba39148f02": {
"name": "github.prChecks#1",
"ordinal": 10,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"9c952632b0c0": {
"name": "status.get#1",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a0729c676b9a": {
"357f9b7862e3": {
"name": "worktree.show#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
},
"3a8e6b13f380": {
"name": "github.prForBranch#1",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
},
"3ecc2d44a209": {
"name": "runtime.clientEvents.subscribe#1",
"ordinal": 3,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"runtime.clientEvents.subscribe\",\"params\":null}"
},
"4da7ea39401b": {
"name": "worktree.show#1",
"ordinal": 4,
"args": [
{
"name": "method",
@@ -198,7 +139,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"id": "frame-3",
"ok": true,
"result": {
"worktree": {
@@ -210,104 +151,14 @@
}
}
},
"abcf3bdc5cde": {
"name": "status.get#1",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
"5d309fcf43a7": {
"name": "github.workItemDetails#1",
"ordinal": 16,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"github.workItemDetails\",\"params\":{\"repo\":\"id:repo-9\",\"number\":12,\"type\":\"pr\"}}"
},
"b3a7ec3ab1c7": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"c2f3182f1780": {
"name": "github.prForBranch#1",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"c79bbd27f177": {
"75885386f005": {
"name": "hostedReview.forBranch#1",
"ordinal": 4,
"ordinal": 5,
"args": [
{
"name": "method",
@@ -336,7 +187,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"id": "frame-4",
"ok": true,
"result": {
"mergeable": "MERGEABLE",
@@ -350,9 +201,179 @@
}
}
},
"cb071b912510": {
"861c8796ae1f": {
"name": "github.prChecks#1",
"ordinal": 10,
"ordinal": 11,
"args": [
{
"name": "method",
"value": "github.prChecks"
},
{
"name": "params",
"value": {
"headSha": "head-sha-1",
"prNumber": 12,
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": [
{
"checkRunId": 7,
"conclusion": "success",
"name": "build",
"status": "completed"
}
]
}
}
},
"870c2b9bacd0": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8b7e690466d7": {
"name": "status.get#1",
"ordinal": 12,
"args": [
{
"name": "method",
"value": "status.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true,
"timeoutMs": 30000
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"ok": true,
"result": {
"runtimeId": "recorded"
}
}
}
},
"9091d133c3b0": {
"name": "reply-salvage",
"ordinal": 8,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
},
"a55c83eeaeea": {
"name": "github.prForBranch#1",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "github.prForBranch"
},
{
"name": "params",
"value": {
"acceptMergedFallbackPR": true,
"branch": "feature",
"currentHeadOid": {
"$rpc": "null"
},
"fallbackPRNumber": 12,
"linkedPRNumber": {
"$rpc": "null"
},
"repo": "id:repo-9"
}
},
{
"name": "options",
"value": {
"beforeSend": "[local beforeSend]",
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"fetchedAt": 0,
"kind": "found",
"pr": {
"headSha": "head-sha-1",
"mergeable": "MERGEABLE",
"number": 12,
"state": "open",
"title": "Recorded",
"updatedAt": "2026-01-01T00:00:00Z",
"url": "https://github.com/owner/repo/pull/12"
}
}
}
}
},
"a7e609fcb031": {
"name": "github.prChecks#1",
"ordinal": 11,
"args": [
{
"name": "method",
@@ -378,9 +399,9 @@
"startedAt": 0
}
},
"d3426861162d": {
"bfedf583a747": {
"name": "github.workItemDetails#1",
"ordinal": 14,
"ordinal": 15,
"args": [
{
"name": "method",
@@ -408,7 +429,7 @@
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-7",
"id": "frame-8",
"ok": true,
"result": {
"body": "body",
@@ -441,16 +462,16 @@
}
}
},
"d3d85b61b684": {
"name": "reply-salvage",
"ordinal": 7,
"value": {
"droppedCount": 1,
"droppedPaths": ["linkedPR"],
"method": "worktree.show",
"operation": "worktree.summary-or-skip",
"variant": "worktree-summary"
}
"c70e6fd19823": {
"name": "streams-registered-at-teardown",
"ordinal": 17,
"value": [
{
"cancelled": true,
"method": "runtime.clientEvents.subscribe",
"payload": "runtime.clientEvents.subscribe#1"
}
]
},
"e0a11d8a1005": {
"commentError": {
@@ -477,10 +498,10 @@
"refreshFailed": false
}
},
"ea4c904bfb5f": {
"name": "worktree.show#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo-9::/w\"}}"
"e10925fa98de": {
"name": "status.get#1",
"ordinal": 14,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"status.get\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
@@ -489,11 +510,6 @@
"value": {
"$rpc": "undefined"
}
},
"fcced642024c": {
"name": "github.prForBranch#1",
"ordinal": 9,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.prForBranch\",\"params\":{\"repo\":\"id:repo-9\",\"branch\":\"feature\",\"linkedPRNumber\":null,\"fallbackPRNumber\":12,\"acceptMergedFallbackPR\":true,\"currentHeadOid\":null}}"
}
},
"recording": {
@@ -502,13 +518,19 @@
{
"id": "branch-hint",
"observation": {
"sender": ["1287167eaf56", "a0729c676b9a", "c79bbd27f177", "b3a7ec3ab1c7"],
"payloads": ["1f5765918736", "ea4c904bfb5f", "3b5c096da2a3", "fcced642024c"],
"sender": ["1287167eaf56", "4da7ea39401b", "75885386f005", "2dc2ca47b4e7"],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -516,25 +538,26 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"cb071b912510",
"9c952632b0c0"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"a7e609fcb031",
"870c2b9bacd0"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "35045fa5a752",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
@@ -542,27 +565,57 @@
"observation": {
"sender": [
"1287167eaf56",
"a0729c676b9a",
"c79bbd27f177",
"c2f3182f1780",
"87ba39148f02",
"8352e3c99668",
"d3426861162d"
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"ea4c904bfb5f",
"3b5c096da2a3",
"fcced642024c",
"2bb452e534eb",
"abcf3bdc5cde",
"1723d8a5d8be"
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["d3d85b61b684"]
"effects": ["9091d133c3b0"]
}
},
{
"id": "cleanup",
"observation": {
"sender": [
"1287167eaf56",
"4da7ea39401b",
"75885386f005",
"a55c83eeaeea",
"861c8796ae1f",
"8b7e690466d7",
"bfedf583a747"
],
"payloads": [
"1f5765918736",
"3ecc2d44a209",
"357f9b7862e3",
"0730389db3e9",
"3a8e6b13f380",
"0d9f7988ff86",
"e10925fa98de",
"5d309fcf43a7"
],
"settlements": {
"mount": "eb79a9b3682a"
},
"state": "e0a11d8a1005",
"effects": ["9091d133c3b0", "c70e6fd19823"]
}
}
]
File diff suppressed because it is too large Load Diff
@@ -4,187 +4,28 @@
"family": "tasks.project-board-load",
"namedDeltas": [],
"values": {
"00ef2dbe5026": {
"name": "github.project.resolveRef#1",
"ordinal": 21,
"args": [
{
"name": "method",
"value": "github.project.resolveRef"
},
{
"name": "params",
"value": {
"host": "github.com",
"input": "https://github.com/orgs/owner/projects/3"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"host": "github.com",
"number": 3,
"ok": true,
"owner": "owner",
"ownerType": "organization",
"title": "Board",
"viewNumber": 1
}
}
}
},
"0388fa155721": {
"name": "githubProjectError",
"ordinal": 11,
"value": ""
},
"15cb97c93333": {
"name": "github.project.listViews#1",
"ordinal": 8,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listViews\",\"params\":{\"owner\":\"owner\",\"host\":\"github.enterprise.test\",\"ownerType\":\"organization\",\"projectNumber\":3}}"
},
"170fad8cb36b": {
"name": "githubProjectPartialFailures",
"ordinal": 6,
"value": []
},
"2085118de92f": {
"name": "githubProjectError",
"ordinal": 1,
"value": ""
},
"2ab1b35ff194": {
"error": "",
"loading": false,
"pasteError": "",
"projects": [
{
"host": "github.com",
"number": 3,
"owner": "owner",
"ownerType": "organization",
"title": "Board"
}
],
"table": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
},
"views": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"34de2d655a0a": {
"0056704adc0b": {
"name": "githubProjectTable",
"ordinal": 32,
"ordinal": 31,
"value": {
"$rpc": "null"
}
},
"376c9e8bd72a": {
"error": "",
"loading": false,
"pasteError": "",
"projects": [
{
"host": "github.com",
"number": 3,
"owner": "owner",
"ownerType": "organization",
"title": "Board"
}
],
"table": {
"$rpc": "null"
},
"views": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"43352776b1e1": {
"name": "githubProjectTable",
"ordinal": 14,
"value": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
}
},
"47507819e28a": {
"name": "githubProjectPasteInput",
"ordinal": 23,
"060dcedb3fdb": {
"name": "githubProjectError",
"ordinal": 10,
"value": ""
},
"4eb521bbefdd": {
"name": "githubProjectSearch",
"ordinal": 15,
"value": "is:open"
"0af48d475813": {
"name": "appliedGithubProjectSearch",
"ordinal": 29,
"value": {
"$rpc": "undefined"
}
},
"528af16518e6": {
"name": "githubProjectViews",
"ordinal": 16,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"5a4fc5c86d0c": {
"name": "githubProjectPasteBusy",
"ordinal": 34,
"value": false
},
"5f5f56db9d64": {
"0f1aa264f581": {
"name": "github.project.viewTable#1",
"ordinal": 12,
"ordinal": 11,
"args": [
{
"name": "method",
@@ -236,15 +77,130 @@
}
}
},
"61ce1ab88e79": {
"15cb97c93333": {
"name": "github.project.listViews#1",
"ordinal": 8,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listViews\",\"params\":{\"owner\":\"owner\",\"host\":\"github.enterprise.test\",\"ownerType\":\"organization\",\"projectNumber\":3}}"
},
"170fad8cb36b": {
"name": "githubProjectPartialFailures",
"ordinal": 6,
"value": []
},
"1b3350f71e56": {
"name": "githubProjectError",
"ordinal": 19,
"value": ""
},
"2085118de92f": {
"name": "githubProjectError",
"ordinal": 1,
"value": ""
},
"26921f3a7c09": {
"name": "githubProjectPasteError",
"ordinal": 18,
"value": ""
},
"2ab1b35ff194": {
"error": "",
"loading": false,
"pasteError": "",
"projects": [
{
"host": "github.com",
"number": 3,
"owner": "owner",
"ownerType": "organization",
"title": "Board"
}
],
"table": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
},
"views": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"2f290fa2acf5": {
"name": "githubProjectLoading",
"ordinal": 9,
"value": true
},
"3493818a7239": {
"name": "githubProjectPasteBusy",
"ordinal": 33,
"value": false
},
"376c9e8bd72a": {
"error": "",
"loading": false,
"pasteError": "",
"projects": [
{
"host": "github.com",
"number": 3,
"owner": "owner",
"ownerType": "organization",
"title": "Board"
}
],
"table": {
"$rpc": "null"
},
"views": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"3985399034a2": {
"name": "githubProjectPasteBusy",
"ordinal": 17,
"value": true
},
"3a8617cc8fd8": {
"name": "githubProjectSearch",
"ordinal": 14,
"value": "is:open"
},
"4809ea7ad5b0": {
"name": "github.project.viewTable#1",
"ordinal": 13,
"ordinal": 12,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.viewTable\",\"params\":{\"owner\":\"owner\",\"host\":\"github.enterprise.test\",\"ownerType\":\"organization\",\"projectNumber\":3,\"viewId\":\"view-1\"}}"
},
"654693493b51": {
"name": "githubProjectLoading",
"ordinal": 10,
"value": true
"4c9b1e4ad1f5": {
"name": "githubProjectViews",
"ordinal": 28,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
},
"685bc2bda05d": {
"name": "githubProjects",
@@ -259,34 +215,14 @@
}
]
},
"7a9df0d1a484": {
"name": "githubProjectLoading",
"ordinal": 25,
"value": true
},
"7b9d3b33c08b": {
"name": "githubProjectPasteError",
"ordinal": 19,
"value": ""
},
"8792541c60ad": {
"6b03ace1146a": {
"name": "githubProjectError",
"ordinal": 26,
"ordinal": 25,
"value": ""
},
"8e4dec1aad1f": {
"name": "github.project.listViews#2",
"ordinal": 28,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listViews\",\"params\":{\"owner\":\"owner\",\"host\":\"github.com\",\"ownerType\":\"organization\",\"projectNumber\":3}}"
},
"8f3ee6c510b1": {
"name": "github.project.resolveRef#1",
"ordinal": 22,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.resolveRef\",\"params\":{\"input\":\"https://github.com/orgs/owner/projects/3\",\"host\":\"github.com\"}}"
},
"9b9f8dd75514": {
"7d488eda1d1c": {
"name": "githubProjectViews",
"ordinal": 9,
"ordinal": 15,
"value": [
{
"id": "view-1",
@@ -296,24 +232,39 @@
}
]
},
"a4a1ff5df287": {
"name": "githubProjectPasteBusy",
"ordinal": 18,
"value": true
"842c950ee7cc": {
"name": "githubProjectLoading",
"ordinal": 16,
"value": false
},
"8a0577280bad": {
"name": "showGitHubProjectPicker",
"ordinal": 23,
"value": false
},
"97bc7dc12b92": {
"name": "githubProjectLoading",
"ordinal": 32,
"value": false
},
"9f9a21ac7b9e": {
"name": "github.project.listViews#2",
"ordinal": 27,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listViews\",\"params\":{\"owner\":\"owner\",\"host\":\"github.com\",\"ownerType\":\"organization\",\"projectNumber\":3}}"
},
"a5a3530ec11c": {
"name": "githubProjectPartialFailures",
"ordinal": 2,
"value": []
},
"b42c8edc0d53": {
"name": "github.project.listAccessible#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listAccessible\",\"params\":{\"host\":\"github.com\"}}"
"ab1b10183665": {
"name": "githubProjectLoading",
"ordinal": 24,
"value": true
},
"b49ff9f109a5": {
"ae89e058b351": {
"name": "github.project.listViews#2",
"ordinal": 27,
"ordinal": 26,
"args": [
{
"name": "method",
@@ -356,22 +307,10 @@
}
}
},
"b5e354ed6023": {
"name": "githubProjectLoading",
"ordinal": 17,
"value": false
},
"b66c4f75c9ae": {
"name": "githubProjectViews",
"ordinal": 29,
"value": [
{
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
]
"b42c8edc0d53": {
"name": "github.project.listAccessible#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.listAccessible\",\"params\":{\"host\":\"github.com\"}}"
},
"be0da5b53ffb": {
"status": "fulfilled",
@@ -386,15 +325,30 @@
}
]
},
"c453f220a7e2": {
"name": "githubProjectLoading",
"ordinal": 33,
"value": false
"be93253869e6": {
"name": "github.project.resolveRef#1",
"ordinal": 21,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"github.project.resolveRef\",\"params\":{\"input\":\"https://github.com/orgs/owner/projects/3\",\"host\":\"github.com\"}}"
},
"c4c4250b0567": {
"name": "showGitHubProjectPicker",
"ordinal": 24,
"value": false
"c5c018cf9a70": {
"name": "githubProjectTable",
"ordinal": 13,
"value": {
"fields": [],
"project": {
"id": "project-1",
"number": 3,
"title": "Board"
},
"rows": [],
"selectedView": {
"filter": "is:open",
"id": "view-1",
"layout": "TABLE_LAYOUT",
"name": "Table",
"number": 1
}
}
},
"d254913957c1": {
"name": "github.project.listViews#1",
@@ -441,6 +395,11 @@
}
}
},
"dc8dd7739c49": {
"name": "githubProjectPasteInput",
"ordinal": 22,
"value": ""
},
"dfe16215433b": {
"name": "github.project.listAccessible#1",
"ordinal": 3,
@@ -485,6 +444,11 @@
}
}
},
"e87bea4bcd3c": {
"name": "githubProjectSearch",
"ordinal": 30,
"value": ""
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
@@ -493,22 +457,46 @@
"$rpc": "undefined"
}
},
"fa1d1b339394": {
"name": "appliedGithubProjectSearch",
"ordinal": 30,
"value": {
"$rpc": "undefined"
}
},
"fb9764680a74": {
"name": "githubProjectError",
"f6003aed2098": {
"name": "github.project.resolveRef#1",
"ordinal": 20,
"value": ""
},
"fe075936e563": {
"name": "githubProjectSearch",
"ordinal": 31,
"value": ""
"args": [
{
"name": "method",
"value": "github.project.resolveRef"
},
{
"name": "params",
"value": {
"host": "github.com",
"input": "https://github.com/orgs/owner/projects/3"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"host": "github.com",
"number": 3,
"ok": true,
"owner": "owner",
"ownerType": "organization",
"title": "Board",
"viewNumber": 1
}
}
}
},
"ff43b5ec92a9": {
"error": "",
@@ -555,21 +543,15 @@
"projects-0": "eb79a9b3682a",
"views-1": "be0da5b53ffb"
},
"state": "376c9e8bd72a",
"effects": [
"2085118de92f",
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514"
]
"state": "ff43b5ec92a9",
"effects": ["2085118de92f", "a5a3530ec11c", "685bc2bda05d", "170fad8cb36b"]
}
},
{
"id": "table-settled",
"observation": {
"sender": ["dfe16215433b", "d254913957c1", "5f5f56db9d64"],
"payloads": ["b42c8edc0d53", "15cb97c93333", "61ce1ab88e79"],
"sender": ["dfe16215433b", "d254913957c1", "0f1aa264f581"],
"payloads": ["b42c8edc0d53", "15cb97c93333", "4809ea7ad5b0"],
"settlements": {
"mount": "eb79a9b3682a",
"projects-0": "eb79a9b3682a",
@@ -582,13 +564,12 @@
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514",
"654693493b51",
"0388fa155721",
"43352776b1e1",
"4eb521bbefdd",
"528af16518e6",
"b5e354ed6023"
"2f290fa2acf5",
"060dcedb3fdb",
"c5c018cf9a70",
"3a8617cc8fd8",
"7d488eda1d1c",
"842c950ee7cc"
]
}
},
@@ -598,16 +579,16 @@
"sender": [
"dfe16215433b",
"d254913957c1",
"5f5f56db9d64",
"00ef2dbe5026",
"b49ff9f109a5"
"0f1aa264f581",
"f6003aed2098",
"ae89e058b351"
],
"payloads": [
"b42c8edc0d53",
"15cb97c93333",
"61ce1ab88e79",
"8f3ee6c510b1",
"8e4dec1aad1f"
"4809ea7ad5b0",
"be93253869e6",
"9f9a21ac7b9e"
],
"settlements": {
"mount": "eb79a9b3682a",
@@ -622,26 +603,25 @@
"a5a3530ec11c",
"685bc2bda05d",
"170fad8cb36b",
"9b9f8dd75514",
"654693493b51",
"0388fa155721",
"43352776b1e1",
"4eb521bbefdd",
"528af16518e6",
"b5e354ed6023",
"a4a1ff5df287",
"7b9d3b33c08b",
"fb9764680a74",
"47507819e28a",
"c4c4250b0567",
"7a9df0d1a484",
"8792541c60ad",
"b66c4f75c9ae",
"fa1d1b339394",
"fe075936e563",
"34de2d655a0a",
"c453f220a7e2",
"5a4fc5c86d0c"
"2f290fa2acf5",
"060dcedb3fdb",
"c5c018cf9a70",
"3a8617cc8fd8",
"7d488eda1d1c",
"842c950ee7cc",
"3985399034a2",
"26921f3a7c09",
"1b3350f71e56",
"dc8dd7739c49",
"8a0577280bad",
"ab1b10183665",
"6b03ace1146a",
"4c9b1e4ad1f5",
"0af48d475813",
"e87bea4bcd3c",
"0056704adc0b",
"97bc7dc12b92",
"3493818a7239"
]
}
}
+46 -54
View File
@@ -5,6 +5,10 @@ import { useFocusEffect, useLocalSearchParams } from 'expo-router'
import { ChevronLeft, RefreshCw, User } from 'lucide-react-native'
import { loadHosts, peekHosts } from '../transport/hosts/host-store'
import { useForceReconnect, useHostClient } from '../transport/connection/host-client-hooks'
import { useHostConnectionInputs } from '../transport/connection/client-context-connection-metrics'
import { classifyConnection, isConnectionOutage } from '../transport/connection/connection-health'
import { HostOfflineBanner, useHostOfflineBanner } from '../components/HostOfflineBanner'
import { useRefreshHint } from '../query/refresh-hint'
import { MobileAccountsLoadState } from './MobileAccountsLoadState'
import { colors, spacing } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -12,9 +16,7 @@ import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { styles } from './mobile-accounts-screen-styles'
import { useNow } from '../hooks/use-now'
import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons'
import { AccountsSnapshotError, decodeAccountsSnapshot } from '../components/accounts-snapshot'
import {
type AccountsSnapshot,
type ProviderKey,
getActiveProviderRateLimits,
getInactiveProviderUsage,
@@ -35,7 +37,7 @@ import { NavBar } from '../components/nav-bar/NavBar'
import { NavBarButton } from '../components/nav-bar/NavBarButton'
import { ListGroup, SectionHeader, ListRow } from '../components/list/ListGroup'
import { useRouteHandoff } from '../navigation/route-handoff'
import { subscribeAccountsSnapshots } from './accounts-snapshot-stream'
import { useMobileAccountsSnapshot } from './use-mobile-accounts-snapshot'
type AccountsNotice = { title: string; message: string }
@@ -50,26 +52,39 @@ export function MobileAccountsScreen() {
// Why: shared client per host. See docs/mobile-shared-client-per-host.md.
const { client, state: connState } = useHostClient(hostId)
const forceReconnect = useForceReconnect()
const connectionInputs = useHostConnectionInputs(hostId)
// Seed from the last host read so the subtitle is there on the first frame.
const [hostName, setHostName] = useState(
() => peekHosts()?.find((host) => host.id === hostId)?.name ?? ''
)
const [snapshot, setSnapshot] = useState<AccountsSnapshot | null>(null)
const [error, setError] = useState<string | null>(null)
const [refreshing, setRefreshing] = useState(false)
const [hostError, setHostError] = useState<string | null>(null)
const [busyAccountId, setBusyAccountId] = useState<string | null>(null)
const [clockEnabled, setClockEnabled] = useState(false)
const [notice, setNotice] = useState<AccountsNotice | null>(null)
const acceptSnapshot = useCallback((nextSnapshot: AccountsSnapshot) => {
setSnapshot(nextSnapshot)
setError(null)
}, [])
const rejectInvalidSnapshot = useCallback(() => {
// Fail closed on unrecognized snapshot: stale data can expose finite reset for wrong account.
setSnapshot(null)
setError(t('accounts.screen.invalidSnapshot', 'Invalid accounts snapshot from host'))
}, [t])
const accounts = useMobileAccountsSnapshot({
hostId,
client,
connState,
lastConnectedAt: connectionInputs.lastConnectedAt
})
const { snapshot, live, refresh, refreshing, acceptSnapshot } = accounts
const error = accounts.error ?? hostError
// Stored accounts stay up while connecting or offline; re-pair and a failed read on a live connection say so instead.
const showsAccounts =
snapshot !== null &&
(live || (connState !== 'auth-failed' && !(connState === 'connected' && error)))
const offlineBanner = useHostOfflineBanner({
client,
connState,
lastConnectedAt: connectionInputs.lastConnectedAt,
outage: isConnectionOutage(classifyConnection({ state: connState, ...connectionInputs })),
forceReconnect: hostId && forceReconnect ? () => void forceReconnect(hostId) : null,
hasData: showsAccounts,
asOf: accounts.readAt
})
// Stored accounts wait on the stream's first answer for their usage.
const showRefreshLine = useRefreshHint(showsAccounts && !live && offlineBanner === null)
const {
supported: codexResetSupported,
resetting: resettingCodex,
@@ -108,7 +123,7 @@ export function MobileAccountsScreen() {
}
const host = hosts.find((h) => h.id === hostId)
if (!host) {
setError(t('accounts.screen.hostNotFound', 'Host not found'))
setHostError(t('accounts.screen.hostNotFound', 'Host not found'))
return
}
setHostName(host.name)
@@ -118,41 +133,6 @@ export function MobileAccountsScreen() {
}
}, [hostId, t])
const refresh = useCallback(async () => {
if (!client) {
return
}
setRefreshing(true)
try {
const res = await client.sendRequest('accounts.list')
if (res.ok) {
acceptSnapshot(decodeAccountsSnapshot(res.result))
} else {
setError(res.error.message)
}
} catch (e) {
if (e instanceof AccountsSnapshotError) {
rejectInvalidSnapshot()
} else {
setError(e instanceof Error ? e.message : String(e))
}
} finally {
setRefreshing(false)
}
}, [acceptSnapshot, client, rejectInvalidSnapshot])
// Stream desktop poll/account-switch updates, falling back to one-shot accounts.list on subscription error.
useEffect(() => {
if (!client || connState !== 'connected') {
return
}
return subscribeAccountsSnapshots(client, {
onSnapshot: acceptSnapshot,
onInvalidSnapshot: rejectInvalidSnapshot,
onStreamError: () => void refresh()
})
}, [acceptSnapshot, client, connState, refresh, rejectInvalidSnapshot])
const selectAccount = useCallback(
async (provider: ProviderKey, accountId: string | null) => {
if (!client) {
@@ -202,7 +182,8 @@ export function MobileAccountsScreen() {
const activeUsage = getActiveProviderRateLimits(snapshot, provider)
const resetCredit = provider === 'codex' ? getCodexResetCreditSummary(activeUsage, now) : null
const Icon = provider === 'claude' ? ClaudeIcon : OpenAIIcon
const switchBlocked = busyAccountId !== null || resettingCodex || connState !== 'connected'
// Stored rows may name a selection or Codex target the desktop has since changed.
const switchBlocked = busyAccountId !== null || resettingCodex || !live
const spinner = <ActivityIndicator size="small" color={colors.textSecondary} />
return (
<View>
@@ -324,6 +305,7 @@ export function MobileAccountsScreen() {
}
title={t('accounts.screen.title', 'Accounts')}
subtitle={hostName || undefined}
refreshing={showRefreshLine}
trailing={
<NavBarButton
icon={refreshing ? RefreshingIcon : RefreshCw}
@@ -337,11 +319,11 @@ export function MobileAccountsScreen() {
<ScrollView
contentContainerStyle={[
styles.scroll,
!snapshot && { flexGrow: 1 },
!showsAccounts && { flexGrow: 1 },
{ paddingBottom: insets.bottom + spacing.xl }
]}
>
{!snapshot ? (
{!showsAccounts ? (
<MobileAccountsLoadState
connState={connState}
hostName={hostName}
@@ -352,6 +334,16 @@ export function MobileAccountsScreen() {
/>
) : (
<>
{offlineBanner ? (
<View style={styles.offlineBanner}>
<HostOfflineBanner
hostName={hostName}
offline={offlineBanner.offline}
asOf={offlineBanner.asOf}
onRetry={offlineBanner.retry}
/>
</View>
) : null}
{renderProviderSection('claude')}
{renderProviderSection('codex')}
{renderOtherProviders()}
@@ -1,11 +1,12 @@
import { queryOptions, skipToken } from '@tanstack/react-query'
import { AccountsSnapshotSchema, decodeAccountsSnapshot } from '../components/accounts-snapshot'
import type { AccountsSnapshot } from '../components/AccountUsage'
import { hostQueryKey } from '../query/host-query-key'
import { hostQueryKey, type HostQueryKey } from '../query/host-query-key'
import type { PersistedHostFamily } from '../query/host-query-persistence'
import type { RpcClient } from '../transport/rpc/rpc-client-contract'
/** Usage and rate limits are live facts: a stored snapshot keeps the accounts and drops every number. */
function withoutAccountUsage(snapshot: AccountsSnapshot | null): AccountsSnapshot | null {
export function withoutAccountUsage(snapshot: AccountsSnapshot | null): AccountsSnapshot | null {
if (!snapshot) {
return null
}
@@ -37,6 +38,15 @@ export const accountsSnapshotFamily: PersistedHostFamily<AccountsSnapshot | null
export const accountsSnapshotKey = (hostId: string) =>
hostQueryKey(hostId, accountsSnapshotFamily.family, { exec: 'local' }, {})
/** Kept by the accounts stream or an `accounts.list` read, never fetched. */
export const accountsSnapshotOptions = (hostId: string) =>
queryOptions<AccountsSnapshot | null, Error, AccountsSnapshot | null, HostQueryKey>({
queryKey: accountsSnapshotKey(hostId),
queryFn: skipToken,
gcTime: Infinity,
meta: { streamBacked: true, persist: accountsSnapshotFamily.persist }
})
/** The desktop's `accounts.subscribe` stream, decoded: rate-limit polls and account switches as they happen. */
export function subscribeAccountsSnapshots(
client: RpcClient,
@@ -11,6 +11,9 @@ export const styles = themedStyleSheet(() => ({
paddingHorizontal: spacing.lg,
paddingTop: spacing.sm
},
offlineBanner: {
paddingBottom: spacing.sm
},
// On a screen, not a sheet: one step above the page, as the other screen groups are.
group: {
backgroundColor: colors.bgPanel
@@ -0,0 +1,107 @@
import { createElement } from 'react'
import { QueryClientProvider, type QueryClient } from '@tanstack/react-query'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
// Also tells TanStack observers on a microtask, so act() sees replies.
import '../test-support/query-client-test-renderer'
import { settleQueryNotifications } from '../test-support/rpc-recording/hook-mount'
import { createOrcaQueryClient } from '../query/orca-query-client'
import type { RpcClient } from '../transport/rpc/rpc-client-contract'
import { accountsSnapshotKey } from './accounts-snapshot-stream'
import { useMobileAccountsSnapshot } from './use-mobile-accounts-snapshot'
const HOST = 'host-1'
const CONNECTED_AT = 1_794_000_000_000
const SNAPSHOT = {
claude: {
accounts: [{ id: 'claude-1', email: 'claude@example.test', updatedAt: 100 }],
activeAccountId: 'claude-1'
},
codex: { accounts: [], activeAccountId: null },
rateLimits: {
claude: {
provider: 'claude',
session: { usedPercent: 42, windowMinutes: 300, resetsAt: null, resetDescription: null },
weekly: null,
updatedAt: 100,
error: null,
status: 'ok'
},
codex: null,
inactiveClaudeAccounts: [],
inactiveCodexAccounts: []
}
}
function accountsClient(listReply: unknown) {
let stream: (payload: unknown) => void = () => {}
const sendRequest = vi.fn(async () => ({ id: 'reply', ok: true as const, result: listReply }))
const client: RpcClient = {
sendRequest,
subscribe: (_method, _params, listener) => {
stream = listener
return () => {}
},
updateTerminalSubscriptionViewport: () => {},
getState: () => 'connected',
getReconnectAttempt: () => 0,
getLastConnectedAt: () => CONNECTED_AT,
getGeneration: () => 1,
onStateChange: () => () => {},
notifyForeground: () => {},
close: () => {}
}
return { client, sendRequest, frame: (payload: unknown) => stream(payload) }
}
let tree: ReactTestRenderer | null = null
afterEach(() => {
act(() => tree?.unmount())
tree = null
})
async function mountAccounts(queryClient: QueryClient, client: RpcClient) {
let state: ReturnType<typeof useMobileAccountsSnapshot> | null = null
function Probe() {
state = useMobileAccountsSnapshot({
hostId: HOST,
client,
connState: 'connected',
lastConnectedAt: CONNECTED_AT
})
return null
}
await act(async () => {
tree = create(createElement(QueryClientProvider, { client: queryClient }, createElement(Probe)))
})
return () => {
if (!state) {
throw new Error('Not mounted')
}
return state
}
}
describe('accounts snapshot', () => {
it('still shows usage when the stream fails and accounts.list answers', async () => {
const port = accountsClient(SNAPSHOT)
const read = await mountAccounts(createOrcaQueryClient(), port.client)
await act(async () => port.frame({ type: 'error' }))
await settleQueryNotifications()
expect(port.sendRequest).toHaveBeenCalledWith('accounts.list')
expect(read().live).toBe(true)
expect(read().snapshot?.rateLimits.claude?.session?.usedPercent).toBe(42)
})
it('hides usage this screen did not read on the current connection, keeping the accounts', async () => {
const queryClient = createOrcaQueryClient()
queryClient.setQueryData(accountsSnapshotKey(HOST), SNAPSHOT, { updatedAt: CONNECTED_AT - 1 })
const read = await mountAccounts(queryClient, accountsClient(SNAPSHOT).client)
expect(read().live).toBe(false)
expect(read().snapshot?.rateLimits.claude).toBeNull()
expect(read().snapshot?.claude.accounts.map((account) => account.email)).toEqual([
'claude@example.test'
])
})
})
@@ -0,0 +1,128 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { hashKey, useQuery, useQueryClient } from '@tanstack/react-query'
import { AccountsSnapshotError, decodeAccountsSnapshot } from '../components/accounts-snapshot'
import type { AccountsSnapshot } from '../components/AccountUsage'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { holdsRestoredHostQueryData } from '../query/host-query-persistence'
import type { ConnectionState } from '../transport/connection/connection-state'
import type { RpcClient } from '../transport/rpc/rpc-client-contract'
import {
accountsSnapshotKey,
accountsSnapshotOptions,
subscribeAccountsSnapshots,
withoutAccountUsage
} from './accounts-snapshot-stream'
/**
* One host's accounts: the last stored snapshot at once, then the desktop's stream, or `accounts.list` when the
* stream fails. Usage shows only from a read on the current connection; anything older shows accounts alone.
*/
export function useMobileAccountsSnapshot({
hostId,
client,
connState,
lastConnectedAt
}: {
hostId: string
client: RpcClient | null
connState: ConnectionState
lastConnectedAt: number | null
}) {
const t = useMobileTranslation()
const queryClient = useQueryClient()
const { data, dataUpdatedAt } = useQuery(accountsSnapshotOptions(hostId))
// Dated by when its read was sent, so a failure from an earlier connection doesn't outlive it.
const [failure, setFailure] = useState<{ message: string; at: number } | null>(null)
const [refreshing, setRefreshing] = useState(false)
// Data this screen didn't read itself, such as a previous visit's in the same document, can't vouch for usage.
const [mountedAt] = useState(Date.now)
const acceptSnapshot = useCallback(
(nextSnapshot: AccountsSnapshot) => {
queryClient.setQueryData(accountsSnapshotKey(hostId), nextSnapshot)
setFailure(null)
},
[hostId, queryClient]
)
const rejectInvalidSnapshot = useCallback(
(readAt = Date.now()) => {
// Fail closed on unrecognized snapshot: stale data can expose finite reset for wrong account.
queryClient.setQueryData(accountsSnapshotKey(hostId), null)
setFailure({
message: t('accounts.screen.invalidSnapshot', 'Invalid accounts snapshot from host'),
at: readAt
})
},
[hostId, queryClient, t]
)
const refresh = useCallback(async () => {
if (!client) {
return
}
setRefreshing(true)
const sentAt = Date.now()
// A reply sent before a reconnect speaks for the old connection; the new one's stream answers instead.
const reconnectedSince = () => (client.getLastConnectedAt() ?? 0) > sentAt
try {
const res = await client.sendRequest('accounts.list')
if (reconnectedSince()) {
return
}
if (res.ok) {
acceptSnapshot(decodeAccountsSnapshot(res.result))
} else {
setFailure({ message: res.error.message, at: sentAt })
}
} catch (e) {
if (reconnectedSince()) {
return
}
if (e instanceof AccountsSnapshotError) {
rejectInvalidSnapshot(sentAt)
} else {
setFailure({ message: e instanceof Error ? e.message : String(e), at: sentAt })
}
} finally {
setRefreshing(false)
}
}, [acceptSnapshot, client, rejectInvalidSnapshot])
// Stream desktop poll/account-switch updates, falling back to one-shot accounts.list on subscription error.
useEffect(() => {
if (!client || connState !== 'connected') {
return
}
return subscribeAccountsSnapshots(client, {
onSnapshot: acceptSnapshot,
onInvalidSnapshot: () => rejectInvalidSnapshot(),
onStreamError: () => void refresh()
})
}, [acceptSnapshot, client, connState, refresh, rejectInvalidSnapshot])
// Only this screen's own read on the current connection shows usage. A stored snapshot never does, whatever its
// date: native Home writes it from its own stream, possibly after this connect or on a clock since set back.
const live =
data != null &&
!holdsRestoredHostQueryData(queryClient, hashKey(accountsSnapshotKey(hostId))) &&
connState === 'connected' &&
lastConnectedAt !== null &&
dataUpdatedAt >= Math.max(lastConnectedAt, mountedAt)
const snapshot = useMemo(
() => (live ? (data ?? null) : withoutAccountUsage(data ?? null)),
[live, data]
)
const error =
failure && (lastConnectedAt === null || failure.at >= lastConnectedAt) ? failure.message : null
return {
snapshot,
live,
readAt: data != null ? dataUpdatedAt : null,
error,
refreshing,
refresh,
acceptSnapshot
}
}
@@ -41,6 +41,9 @@ export const customKeyModalStyles = themedStyleSheet(() => ({
borderRadius: radii.surface,
overflow: 'hidden'
},
smartRailGroup: {
marginTop: spacing.md
},
shortcutForm: {
paddingTop: spacing.sm
},
+9 -1
View File
@@ -19,6 +19,7 @@ import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { MobileSwitch } from './MobileSwitch'
import { ListGroup, ListRow } from './list/ListGroup'
import { FieldTextInput } from './composer-input/FieldTextInput'
import { SmartRailRow } from './SmartRailRow'
type Step = 'choose-type' | 'shortcut-combo' | 'special-keys' | 'text-macro'
@@ -62,9 +63,11 @@ type Props = {
onClose: () => void
onKeysChanged: (keys: CustomKey[]) => void
onManageShortcuts?: () => void
smartRail?: { enabled: boolean; onChange: (enabled: boolean) => void }
}
export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortcuts }: Props) {
export function CustomKeyModal(props: Props) {
const { visible, onClose, onKeysChanged, onManageShortcuts, smartRail } = props
useMobileColorScheme()
const t = useMobileTranslation()
const [step, setStep] = useState<Step>('choose-type')
@@ -235,6 +238,11 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
) : null}
</ListGroup>
)}
{step === 'choose-type' && smartRail ? (
<ListGroup style={styles.smartRailGroup}>
<SmartRailRow {...smartRail} />
</ListGroup>
) : null}
{step === 'shortcut-combo' && (
<View style={styles.shortcutForm}>
+9 -1
View File
@@ -22,6 +22,8 @@ type MobileStateViewProps = {
loading?: boolean
/** Keeps the content centred above a floating bottom bar. */
bottomInset?: number
/** Full-width content under the message, such as a list of choices. */
children?: ReactNode
}
/** Every screen's loading, empty, no-match, error and offline state: icon, title, one sentence, one action. */
@@ -33,7 +35,8 @@ export function MobileStateView({
details,
action,
loading = false,
bottomInset = 0
bottomInset = 0,
children
}: MobileStateViewProps) {
useMobileColorScheme()
return (
@@ -49,6 +52,7 @@ export function MobileStateView({
{message ? <Text style={styles.message}>{message}</Text> : null}
{details ? <MobileStateDetails details={details} /> : null}
{action ? <MobileStateButton label={action.label} onPress={action.onPress} /> : null}
{children ? <View style={styles.children}>{children}</View> : null}
</View>
)
}
@@ -187,6 +191,10 @@ export function MobileNoMatchState({
}
const styles = themedStyleSheet(() => ({
children: {
alignSelf: 'stretch',
marginTop: spacing.md
},
state: {
flex: 1,
alignItems: 'center',
@@ -64,6 +64,7 @@ vi.mock('../host-screen/host-list-queries', () => ({
import '../transport/hosts/host-descriptor-store'
import { FakeSession } from '../transport/connection/mobile-endpoint-supervisor-test-fakes'
import { markRpcDeliveryUnknown } from '../transport/rpc/rpc-delivery-ambiguity'
import '../../../src/shared/execution-host'
import type { ExecutionHostId } from '../../../src/shared/execution-host'
@@ -194,6 +195,108 @@ describe('NewWorktreeModal project targets', () => {
).toBe('/srv/orca')
})
it("offers only the folder host's copy of a repo when opened from a folder", async () => {
const serverRepo = { ...repos[0]!, path: '/srv/orca' }
const listing = (listed: unknown[]) =>
vi.fn((method: string) =>
method === 'repo.list' ? replied({ repos: listed }) : new Promise<never>(() => {})
)
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(listing(repos)),
serverClients: new Map<ExecutionHostId, RpcClient>([
['runtime:env-1', requestsOnly(listing([serverRepo]))]
]),
hostId: 'host-1',
onCreated: () => {},
onClose: () => {},
openExternalUrl: () => {},
folderLaunch: {
repoId: 'repo-1',
executionHostId: 'runtime:env-1',
name: 'payments',
parentWorkspace: 'folder:f-1',
submit: { begin: () => null, presenting: () => true }
}
})
)
})
await flushUpdates()
// The desktop's same-id repo is not offered: its store doesn't hold this folder.
expect(pickerItems(renderer, 'Run on').map((item) => item.detail)).toEqual(['/srv/orca'])
expect(runTargetPicker(renderer).props.selectedId).toBe('runtime:env-1')
})
async function submitFromFolder(createReply: () => Promise<RpcResponse>) {
const serverRepo = { ...repos[0]!, path: '/srv/orca' }
const server = requestsOnly(
vi.fn((method: string) =>
method === 'repo.list'
? replied({ repos: [serverRepo] })
: method === 'worktree.create'
? createReply()
: Promise.reject(new Error(`unexpected ${method}`))
)
)
const settle = vi.fn()
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(() => new Promise<never>(() => {})),
serverClients: new Map<ExecutionHostId, RpcClient>([['runtime:env-1', server]]),
hostId: 'host-1',
onCreated: () => {},
onClose: () => {},
openExternalUrl: () => {},
folderLaunch: {
repoId: 'repo-1',
executionHostId: 'runtime:env-1',
name: 'payments',
parentWorkspace: 'folder:f-1',
submit: { begin: () => ({ settle }), presenting: () => true }
}
})
)
})
await flushUpdates()
const form = renderer.root.find((node) => typeof node.props.onCreate === 'function')
await act(async () => form.props.onCreate())
await flushUpdates()
const creates = vi
.mocked(server.sendRequest)
.mock.calls.filter(([method]) => method === 'worktree.create')
return { creates: creates.map(([, params]) => params), settle }
}
it('creates from a folder attached to that folder, on its host', async () => {
const { creates, settle } = await submitFromFolder(() =>
replied({ worktree: { id: 'repo-1::/srv/w/payments', displayName: 'payments' } })
)
expect(creates).toEqual([
expect.objectContaining({
repo: 'id:repo-1',
parentWorkspace: 'folder:f-1',
parentWorkspaceOrigin: 'manual'
})
])
expect(settle).toHaveBeenCalledWith('created')
})
it("doesn't try another name when a folder create's reply is lost", async () => {
const { creates, settle } = await submitFromFolder(() =>
Promise.reject(markRpcDeliveryUnknown(new Error('connection lost')))
)
expect(creates).toHaveLength(1)
expect(settle).toHaveBeenCalledWith('uncertain')
})
it('ignores a stale repo list after the client changes', async () => {
let resolveOldList: ((value: unknown) => void) | undefined
const oldList = new Promise((resolve) => {
+30 -4
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Keyboard } from 'react-native'
import { getComposerRepoWorktreeBranches } from '../../../src/shared/composer-branch-selection'
import { getRepoExecutionHostId, parseExecutionHostId } from '../../../src/shared/execution-host'
@@ -71,14 +71,20 @@ function NewWorktreeModalContent(props: NewWorktreeModalProps) {
existingWorktrees,
openExternalUrl,
onCreated,
onClose
onClose,
folderLaunch
} = props
const { repos, allRepos, selectedRepo, setSelectedRepo, loading, loadError, retryLoad } =
useNewWorkspaceRepositories({
client: desktopClient,
serverClients,
hostId,
visible
visible,
...(folderLaunch
? {
initialRepo: { id: folderLaunch.repoId, executionHostId: folderLaunch.executionHostId }
}
: {})
})
// Route picked-repo operations to its host; desktop settings/accounts stay on desktop.
const repoServer = selectedRepo ? serverOf(selectedRepo) : undefined
@@ -126,6 +132,14 @@ function NewWorktreeModalContent(props: NewWorktreeModalProps) {
worktreeBranches: selectedRepoWorktreeBranches,
onError: setError
})
// The folder's name, seeded once per opening; the user's edits stand.
const seededFolderName = useRef(false)
useEffect(() => {
if (folderLaunch && !seededFolderName.current) {
seededFolderName.current = true
composer.setName(folderLaunch.name)
}
}, [composer, folderLaunch])
const selectedRepoIsGit = selectedRepo ? selectedRepo.kind !== 'folder' : true
useWorktreeCreateBasePrefetch({
client: visible ? client : null,
@@ -179,7 +193,19 @@ function NewWorktreeModalContent(props: NewWorktreeModalProps) {
setNote((current) => (current === note ? '' : current))
onCreated(worktreeId, name, warning, repoServer)
},
onClose
onClose,
...(folderLaunch
? {
folderLaunch: {
...folderLaunch.submit,
executionHostId: folderLaunch.executionHostId,
parent: {
parentWorkspace: folderLaunch.parentWorkspace,
parentWorkspaceOrigin: 'manual' as const
}
}
}
: {})
})
const sourceAvailability: SmartModeAvailabilityInput = {
+24
View File
@@ -0,0 +1,24 @@
import { ListRow } from './list/ListGroup'
import { MobileSwitch } from './MobileSwitch'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
/** The Smart rail setting: keys for what the terminal's agent is doing, at the front of the key row. */
export function SmartRailRow({
enabled,
onChange
}: {
enabled: boolean
onChange: (enabled: boolean) => void
}): React.JSX.Element {
const t = useMobileTranslation()
const title = t('components.smartRailRow.title', 'Smart rail')
return (
<ListRow
title={title}
subtitle={t('components.smartRailRow.subtitle', 'Shows keys for what your agent is doing')}
accessory={
<MobileSwitch accessibilityLabel={title} value={enabled} onValueChange={onChange} />
}
/>
)
}
@@ -27,6 +27,7 @@ import {
setTerminalAccessoryBuiltInVisible
} from '../terminal/terminal-accessory-layout'
import { MobileSwitch } from './MobileSwitch'
import { SmartRailRow } from './SmartRailRow'
// Absolutely positioned DragReorderList rows require one fixed height per section.
const REORDER_ROW_HEIGHT = 56
@@ -148,10 +149,13 @@ export function TerminalShortcutSettings({
[persistLayout]
)
// Resetting the keys leaves the Smart rail setting alone.
const resetBuiltInKeys = useCallback(() => {
const next = defaultLayout()
setShortcutLayout(next)
persistLayout(next)
setShortcutLayout((current) => {
const next = { ...defaultLayout(), smartRail: current.smartRail }
persistLayout(next)
return next
})
}, [defaultLayout, persistLayout])
const reorderCustomKeys = useCallback(
@@ -182,6 +186,18 @@ export function TerminalShortcutSettings({
return (
<>
<SettingsSection>
<SmartRailRow
enabled={shortcutLayout.smartRail === true}
onChange={(enabled) =>
setShortcutLayout((current) => {
const next = { ...current, smartRail: enabled }
persistLayout(next)
return next
})
}
/>
</SettingsSection>
{hasPad ? (
<SettingsSection
title={t('components.terminalShortcutSettings.directionPad', 'DIRECTION PAD')}

Some files were not shown because too many files have changed in this diff Show More