diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 491d0cac794..456fa8f2831 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,6 +21,11 @@ pnpm install pnpm dev ``` +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. +See [the install policy](../docs/reference/pnpm-install-policy.md). + ## Branch Naming Use a clear, descriptive branch name that reflects the change. diff --git a/.github/actions/install-node-dependencies/action.yml b/.github/actions/install-node-dependencies/action.yml index 7695d2bec9b..30c98070f31 100644 --- a/.github/actions/install-node-dependencies/action.yml +++ b/.github/actions/install-node-dependencies/action.yml @@ -10,6 +10,10 @@ inputs: description: Node.js version override; defaults to the version declared in package.json. required: false default: '' + cache-dependency-path: + description: Lockfiles for the pnpm download store; include mobile/pnpm-lock.yaml only when the job installs mobile dependencies. + required: false + default: pnpm-lock.yaml persist-native-cache: description: Save restored native modules at job end. Set false when a later step overwrites the same path with a different ABI. required: false @@ -39,9 +43,7 @@ runs: with: install: false - # Why both lockfiles: setup-node keys the pnpm store on the root lockfile alone, so - # jobs that also install mobile restored a store with none of the React Native tree - # in it and re-downloaded the lot on every run. + # Desktop-only jobs should not miss their download cache when mobile dependencies change. - name: Setup Node.js id: default-node if: inputs.node-version == '' @@ -49,9 +51,7 @@ runs: with: node-version-file: package.json cache: pnpm - cache-dependency-path: | - pnpm-lock.yaml - mobile/pnpm-lock.yaml + cache-dependency-path: ${{ inputs.cache-dependency-path }} - name: Setup requested Node.js id: requested-node @@ -60,9 +60,7 @@ runs: with: node-version: ${{ inputs.node-version }} cache: pnpm - cache-dependency-path: | - pnpm-lock.yaml - mobile/pnpm-lock.yaml + cache-dependency-path: ${{ inputs.cache-dependency-path }} - name: Validate native runtime shell: bash @@ -152,9 +150,9 @@ runs: with: path: | node_modules/.pnpm/node-pty@*/node_modules/node-pty/build - node_modules/.pnpm/windows-native-registry@*/node_modules/windows-native-registry/build - node_modules/.pnpm/@vscode+windows-process-tree@*/node_modules/@vscode/windows-process-tree/build - key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch') }} + native/windows-registry/build + node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build + key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }} - name: Restore compiled native modules without saving id: native-cache-restore-only @@ -163,9 +161,9 @@ runs: with: path: | node_modules/.pnpm/node-pty@*/node_modules/node-pty/build - node_modules/.pnpm/windows-native-registry@*/node_modules/windows-native-registry/build - node_modules/.pnpm/@vscode+windows-process-tree@*/node_modules/@vscode/windows-process-tree/build - key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch') }} + native/windows-registry/build + node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build + key: native-modules-${{ runner.os }}-${{ steps.native-cache-scope.outputs.scope }}-${{ runner.arch }}-${{ inputs.native-runtime }}-node${{ steps.requested-node.outputs.node-version || steps.default-node.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }} # pnpm's bundled gyp_main.py is not executable on fresh Linux runners. - name: Use external node-gyp diff --git a/.github/workflows/adhoc-mac-build.yml b/.github/workflows/adhoc-mac-build.yml index 3e17eee9b68..53501add289 100644 --- a/.github/workflows/adhoc-mac-build.yml +++ b/.github/workflows/adhoc-mac-build.yml @@ -160,16 +160,14 @@ jobs: - name: Checkout the requested ref uses: actions/checkout@v6 - env: - # Full-history checkout must also preserve case-twin branch and tag names. - GIT_DEFAULT_REF_FORMAT: reftable with: # Why an input at all rather than just github.ref: the whole point is to # build code that has not landed, and the workflow definition itself # always comes from the dispatch ref — naming the branch here instead # applies main's current copy of this file to an arbitrary branch. ref: ${{ steps.vetted.outputs.sha }} - fetch-depth: 0 + # Version helpers only read HEAD; published versions come from the release API. + fetch-depth: 1 # This job only reads stablyai/orca and never pushes; every write goes # to the adhoc repo through a minted App token passed by env. Not # persisting the checkout credential shrinks the blast radius if a build @@ -197,13 +195,15 @@ jobs: restore-keys: | electron-builder-mac- + # Why both CPUs: the mac config packages x64 and arm64 from this arm64 + # runner, so the install must carry both variants of the native optional deps. - name: Install dependencies uses: nick-fields/retry@v4 with: timeout_minutes: 10 max_attempts: 3 retry_wait_seconds: 30 - command: pnpm install --frozen-lockfile + command: pnpm install --frozen-lockfile --cpu=current,x64,arm64 # Why: signing is what makes an adhoc build installable over an existing # Orca, so a missing cert must fail here rather than after a 20-minute build. diff --git a/.github/workflows/computer-e2e.yml b/.github/workflows/computer-e2e.yml index 7c617550082..325ce6cb61c 100644 --- a/.github/workflows/computer-e2e.yml +++ b/.github/workflows/computer-e2e.yml @@ -6,6 +6,9 @@ on: - '.github/workflows/computer-e2e.yml' - 'config/electron-builder.config.cjs' - 'config/scripts/build-computer-macos.mjs' + - 'config/scripts/build-native-for-platform.mjs' + - 'config/scripts/build-native-for-platform.test.mjs' + - 'config/scripts/pnpm-cli-invocation.mjs' - 'config/scripts/build-windows-cli-launcher.mjs' - 'config/scripts/build-windows-cli-launcher.test.mjs' - 'config/scripts/computer-e2e-workflow.test.mjs' @@ -157,6 +160,12 @@ jobs: pnpm vitest run config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs config/scripts/macos-computer-helper-owner-loss-processes.test.mjs + # Why: the parallel launcher's cancellation tests are darwin-only and no + # other PR job runs on macOS. + - name: Parallel native build launcher cancellation + run: >- + pnpm vitest run --config config/vitest.config.ts + config/scripts/build-native-for-platform.test.mjs - name: Authenticated helper owner-loss smoke run: pnpm bench:macos-computer-helper-owner-loss --expect reaped --trials 1 - name: Swift tests and signed universal helper verification diff --git a/.github/workflows/daily-mac-build.yml b/.github/workflows/daily-mac-build.yml index 45130e932d8..a758d8db3a1 100644 --- a/.github/workflows/daily-mac-build.yml +++ b/.github/workflows/daily-mac-build.yml @@ -90,7 +90,8 @@ jobs: uses: actions/checkout@v6 with: ref: main - fetch-depth: 0 + # Version helpers only read HEAD; published versions come from the release API. + fetch-depth: 1 # Why: this job only reads stablyai/orca and never pushes; every write # goes to the daily repo through a minted App token passed by env. # Not persisting the checkout credential shrinks the blast radius if a @@ -167,6 +168,8 @@ jobs: restore-keys: | electron-builder-mac- + # Why both CPUs: the mac config packages x64 and arm64 from this arm64 + # runner, so the install must carry both variants of the native optional deps. - name: Install dependencies if: steps.freshness.outputs.should_build == 'true' uses: nick-fields/retry@v4 @@ -174,7 +177,7 @@ jobs: timeout_minutes: 10 max_attempts: 3 retry_wait_seconds: 30 - command: pnpm install --frozen-lockfile + command: pnpm install --frozen-lockfile --cpu=current,x64,arm64 # Why: signing is what makes a daily installable over an existing Orca, so # a missing cert must fail here rather than after a 20-minute build. diff --git a/.github/workflows/dev-channel-win-build.yml b/.github/workflows/dev-channel-win-build.yml index 89fda2ebef9..7913e7e6e80 100644 --- a/.github/workflows/dev-channel-win-build.yml +++ b/.github/workflows/dev-channel-win-build.yml @@ -219,6 +219,8 @@ jobs: # Why retried: pnpm install triggers electron's postinstall, which pulls the # Electron binary from GitHub release assets, and that CDN returns transient # 504s often enough to lose a build to it. + # Why host-only: this job packages only for its own runner OS and + # architecture, so the default host-scoped install is deliberate. - name: Install dependencies uses: nick-fields/retry@v4 with: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index a07bf0991ec..f42d1184ce9 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -170,8 +170,34 @@ jobs: # artifact instead of starting five concurrent electron-vite builds. # ORCA_E2E_FORWARD_APP_LOGS keeps startup failures visible when Electron # launches but never creates a BrowserWindow. + - name: Balance E2E shard from timing evidence + env: + ORCA_BACKGROUND_LAUNCH: '1' + SKIP_BUILD: '1' + ORCA_E2E_FORWARD_APP_LOGS: '1' + ORCA_E2E_WEB_CLIENT: '1' + ORCA_RELAY_PATH: ${{ github.workspace }}/out/relay + run: | + mkdir -p ci-shards + pnpm exec playwright test --config tests/playwright.config.ts --project=electron-headless --list --reporter=json > ci-shards/discovery.json + export ORCA_SHARD_SOURCE_SHA="$(git rev-parse HEAD)" + node config/scripts/ci-e2e-shard-plan.mjs ci-shards/discovery.json '${{ matrix.shard }}' ci-shards + pnpm exec playwright test --config tests/playwright.config.ts --project=electron-headless --test-list=ci-shards/selected.txt --list --reporter=json > ci-shards/selected-discovery.json + node config/scripts/ci-e2e-shard-plan.mjs --verify ci-shards/assignment.json ci-shards/selected-discovery.json + - name: Run E2E tests (${{ matrix.shard_name }}) - run: xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 ORCA_E2E_WEB_CLIENT=1 ORCA_RELAY_PATH="$GITHUB_WORKSPACE/out/relay" pnpm run test:e2e --shard=${{ matrix.shard }} + run: xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 ORCA_E2E_WEB_CLIENT=1 ORCA_RELAY_PATH="$GITHUB_WORKSPACE/out/relay" pnpm run test:e2e --test-list=ci-shards/selected.txt + + - name: Upload E2E shard assignment + if: always() + # Diagnostic upload outages must not change the test verdict. + continue-on-error: true + uses: actions/upload-artifact@v7 + with: + name: e2e-shard-${{ matrix.shard_name }}-attempt-${{ github.run_attempt }} + path: ci-shards/ + retention-days: 14 + if-no-files-found: warn # The frame benchmark needs a mapped window, which the headless shards exclude. - name: Run worktree first-paint benchmark diff --git a/.github/workflows/hourly-mac-build.yml b/.github/workflows/hourly-mac-build.yml index c300b2543b8..b4bdd803a6a 100644 --- a/.github/workflows/hourly-mac-build.yml +++ b/.github/workflows/hourly-mac-build.yml @@ -137,7 +137,8 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ needs.preflight.outputs.head_sha }} - fetch-depth: 0 + # Version helpers only read HEAD; published versions come from the release API. + fetch-depth: 1 # Why: this job only reads stablyai/orca and never pushes; every write # goes to the hourly repo through a minted App token passed by env. # Not persisting the checkout credential shrinks the blast radius if a @@ -174,13 +175,15 @@ jobs: restore-keys: | electron-builder-mac- + # Why both CPUs: the mac config packages x64 and arm64 from this arm64 + # runner, so the install must carry both variants of the native optional deps. - name: Install dependencies uses: nick-fields/retry@v4 with: timeout_minutes: 10 max_attempts: 3 retry_wait_seconds: 30 - command: pnpm install --frozen-lockfile + command: pnpm install --frozen-lockfile --cpu=current,x64,arm64 # Why: signing is what makes an hourly installable over an existing Orca, so # a missing cert must fail here rather than after a 20-minute build. diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 6dbfc02aa3c..9afc865f87e 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -41,6 +41,10 @@ jobs: uses: actions/checkout@v6 - uses: ./.github/actions/install-node-dependencies + with: + cache-dependency-path: | + pnpm-lock.yaml + mobile/pnpm-lock.yaml # bundler-cache installs mobile/Gemfile.lock, so this job is also what # proves the pinned fastlane the release workflow depends on still diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c49091ab148..398bd61bd04 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -126,6 +126,9 @@ jobs: - uses: ./.github/actions/install-node-dependencies with: native-runtime: node + cache-dependency-path: | + pnpm-lock.yaml + mobile/pnpm-lock.yaml - name: Lint run: pnpm exec oxlint --format github @@ -273,6 +276,12 @@ jobs: HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: node .github/scripts/check-root-directory-entries.mjs "$BASE_SHA" "$HEAD_SHA" + # Why here: the READMEs embed media owned by docs/site and resources/onboarding, + # and the classifier skips static_analysis for docs-only diffs. This job runs + # on every PR and needs no install. + - name: Check README local links + run: node config/scripts/check-readme-local-links.mjs + typecheck: needs: [code_paths] if: needs.code_paths.outputs.typecheck == 'true' @@ -775,18 +784,9 @@ jobs: [[ "$rpm_marker" == rpm ]] || { echo "Expected rpm marker, got: $rpm_marker"; exit 1; } - name: Verify headless serve signal shutdown - run: node config/scripts/run-headless-serve-shutdown-docker.mjs --appimage dist/orca-linux.AppImage - - - name: Verify extracted launcher serve signal shutdown run: >- node config/scripts/run-headless-serve-shutdown-docker.mjs - --appimage dist/orca-linux.AppImage --entrypoint launcher - - - name: Verify AppImage CLI registration and serve signal shutdown - run: >- - node config/scripts/run-headless-serve-shutdown-docker.mjs - --appimage dist/orca-linux.AppImage --entrypoint appimage - --signal-target serving-electron --int-delivery pid + --appimage dist/orca-linux.AppImage --all-entrypoints # A default container reproduces the hostile AppImage launch environment. - name: Verify Linux CLI launch contract @@ -835,9 +835,9 @@ jobs: with: path: | node_modules/.pnpm/node-pty@*/node_modules/node-pty/build - node_modules/.pnpm/windows-native-registry@*/node_modules/windows-native-registry/build - node_modules/.pnpm/@vscode+windows-process-tree@*/node_modules/@vscode/windows-process-tree/build - key: native-modules-${{ runner.os }}-${{ steps.deps.outputs.native-cache-scope }}-${{ runner.arch }}-node-node${{ steps.deps.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch') }} + native/windows-registry/build + node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build + key: native-modules-${{ runner.os }}-${{ steps.deps.outputs.native-cache-scope }}-${{ runner.arch }}-node-node${{ steps.deps.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }} # vitest runs here directly rather than through `pnpm test`, so the addon # assertions only hold once install-node-dependencies has rebuilt natives. @@ -846,6 +846,11 @@ jobs: pnpm exec vitest run --config config/vitest.config.ts config/scripts/rebuild-native-deps.test.mjs config/scripts/rebuild-native-deps-windows-process-tree.test.mjs + src/main/windows-registry-addon.test.ts + config/scripts/windows-process-tree-gyp-path.test.mjs + config/scripts/windows-process-tree-gyp-rebuild.test.mjs + config/scripts/package-electron-runtime-contract.test.mjs + config/scripts/electron-builder-runtime-resources.test.mjs src/main/browser/browser-client-page-renderer-lifecycle.electron.test.ts src/main/browser/browser-route-tcp-egress.electron.test.ts src/main/browser/browser-route-webrtc-egress.electron.test.ts @@ -903,9 +908,9 @@ jobs: with: path: | node_modules/.pnpm/node-pty@*/node_modules/node-pty/build - node_modules/.pnpm/windows-native-registry@*/node_modules/windows-native-registry/build - node_modules/.pnpm/@vscode+windows-process-tree@*/node_modules/@vscode/windows-process-tree/build - key: native-modules-${{ runner.os }}-${{ steps.deps.outputs.native-cache-scope }}-${{ runner.arch }}-electron-node${{ steps.deps.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch') }} + native/windows-registry/build + node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build + key: native-modules-${{ runner.os }}-${{ steps.deps.outputs.native-cache-scope }}-${{ runner.arch }}-electron-node${{ steps.deps.outputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', '.github/actions/install-node-dependencies/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json') }} - name: Prepare Electron native runtime run: node config/scripts/ensure-native-runtime.mjs --runtime=electron diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index c2124d12990..a644aef53c1 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -1262,6 +1262,8 @@ jobs: # Electron binary from GitHub release assets. GitHub's download CDN # occasionally returns 504s that fail the whole release. Retry on # failure so transient network errors don't require a manual re-run. + # Why host-only: this job packages only for its own runner OS and + # architecture, so the default host-scoped install is deliberate. - name: Install dependencies uses: nick-fields/retry@v4 with: diff --git a/.github/workflows/release-mac-build.yml b/.github/workflows/release-mac-build.yml index f97ac0e5537..3d7e4dd05bf 100644 --- a/.github/workflows/release-mac-build.yml +++ b/.github/workflows/release-mac-build.yml @@ -64,13 +64,15 @@ jobs: # Electron binary from GitHub release assets. GitHub's download CDN # occasionally returns 504s that fail the whole release. Retry on # failure so transient network errors don't require a manual re-run. + # Why both CPUs: the mac config packages x64 and arm64 from this arm64 + # runner, so the install must carry both variants of the native optional deps. - name: Install dependencies uses: nick-fields/retry@v4 with: timeout_minutes: 10 max_attempts: 3 retry_wait_seconds: 30 - command: pnpm install --frozen-lockfile + command: pnpm install --frozen-lockfile --cpu=current,x64,arm64 - name: Verify macOS signing environment run: node config/scripts/verify-macos-release-env.mjs diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 0c215db94d3..b21feae3230 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -45,7 +45,11 @@ jobs: npx --yes pnpm@10.24.0 --filter '@orca-cloud/relay^...' build - name: Test shard + env: + ORCA_BALANCE_UNIT_SHARDS: '1' + ORCA_BACKGROUND_LAUNCH: '1' run: | + export ORCA_SHARD_SOURCE_SHA="$(git rev-parse HEAD)" pnpm exec vitest run --config config/vitest.config.ts \ --exclude=src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts \ --exclude=src/main/daemon/shell-ready.test.ts \ @@ -65,3 +69,14 @@ jobs: --exclude=src/shared/posix-command-path-lookup.test.ts \ --exclude=tests/e2e/cross-version-wire/** \ --shard=${{ matrix.shard }}/${{ matrix.shard_total }} + + - name: Upload unit shard assignment + if: always() + # Diagnostic upload outages must not change the test verdict. + continue-on-error: true + uses: actions/upload-artifact@v7 + with: + name: unit-shard-node-${{ matrix.node }}-${{ matrix.shard }}-attempt-${{ github.run_attempt }} + path: ci-shards/ + retention-days: 14 + if-no-files-found: warn diff --git a/.github/workflows/windows-signing-rehearsal.yml b/.github/workflows/windows-signing-rehearsal.yml index 90ab8db137c..244ee4d3e08 100644 --- a/.github/workflows/windows-signing-rehearsal.yml +++ b/.github/workflows/windows-signing-rehearsal.yml @@ -68,6 +68,8 @@ jobs: restore-keys: | electron-builder-win- + # Why host-only: this job packages only for its own runner OS and + # architecture, so the default host-scoped install is deliberate. - name: Install dependencies uses: nick-fields/retry@v4 with: diff --git a/.gitignore b/.gitignore index cf2f7244eb3..b033ebe1cca 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ out/ /build/ release/ native/**/.build/ +# node-gyp output for the vendored Windows registry addon; generated per host and ABI. +native/windows-registry/build/ +native/windows-registry/bin/ # pnpm .pnpm-store/ @@ -113,6 +116,7 @@ docs/** !docs/reference/linux-glibc-compatibility.md !docs/reference/macos-press-and-hold.md !docs/reference/orcad-operations.md +!docs/reference/pnpm-install-policy.md !docs/reference/relay-grace-time-reconfiguration.md !docs/reference/windows-cmd-shim-resolution.md !docs/reference/windows-daemon-host-relocation.md diff --git a/AGENTS.md b/AGENTS.md index f1ce31e404b..74c049a49fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,14 @@ Never use vague names like `helpers`, `utils`, `common`, `misc`, or `shared-stuf ## Type Declarations: Prefer `.ts` Over `.d.ts` +## Type Assertions: Prefer Checked Types + +Avoid type assertions except `as const`. Unavoidable casts need a line-specific `SAFETY:` explanation: + +```ts +// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Explain the verified invariant here. +``` + # Verifying Changes - **Typecheck**: `pnpm tc` (or `tc:node` / `tc:cli` / `tc:web`) @@ -60,6 +68,10 @@ Orca targets macOS, Linux, and Windows. Keep all platform-dependent behavior beh - **WSL commands**: build argv with `buildWslExecArgs` (always `--exec` — under `--`, `wsl.exe` expands `$name` in every argument and silently rewrites the script), and fence anything whose stdout you parse with `buildWslCapturedLoginShellCommand`, because the interactive login shell prints the distro banner to stdout. See [`docs/reference/wsl-command-execution.md`](./docs/reference/wsl-command-execution.md). - **Linux native modules**: keep the glibc floor at Ubuntu 20.04 / glibc 2.31. A module compiled from source on a newer runner can reference symbol versions absent on the floor and crash the app on startup. See [`docs/reference/linux-glibc-compatibility.md`](./docs/reference/linux-glibc-compatibility.md); packaging fails if a bundled native binary needs newer glibc. +## Native Dependency Installs + +Ordinary `pnpm install` covers the host OS and CPU only. Before packaging for another architecture — including `pnpm build:mac`, which builds x64 and arm64 by default — run `pnpm install:release`. electron-builder only warns on a missing `extraResources` source, so the `beforePack` guard is what turns a thin install into a build failure instead of a silently broken artifact; see [`docs/reference/pnpm-install-policy.md`](./docs/reference/pnpm-install-policy.md). + ## SSH Use Case All changes must consider the SSH use case. Don't assume local-only execution. Before changing anything that reports on, stops, or lists remote work, follow [`docs/reference/ssh-execution-boundary.md`](./docs/reference/ssh-execution-boundary.md): the execution host owns everything that touches execution, and loss of contact is never evidence of process death — the verdict vocabulary is `live` / `unverifiable` / `exited`, with no synonyms. diff --git a/README.md b/README.md index 7e3540c80f1..aeb8f355450 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Fan one prompt across five agents, each in its own isolated git worktree — com - Parallel worktree orchestration + Parallel worktree orchestration @@ -68,7 +68,7 @@ Ghostty-class terminals with WebGL rendering, infinite splits, and scrollback th - Terminal splits + Terminal splits @@ -82,7 +82,7 @@ Click any UI element in a real Chromium window to send its HTML, CSS, and a crop - Embedded browser and Design Mode + Embedded browser and Design Mode @@ -96,7 +96,7 @@ Browse PRs, issues, and project boards in-app — open a worktree from any task - GitHub and Linear task workflows in Orca + GitHub and Linear task workflows in Orca @@ -110,7 +110,7 @@ Run agents on a beefy remote box with full file editing, git, and terminals — - Remote worktrees over SSH + Remote worktrees over SSH @@ -124,7 +124,7 @@ Drop comments on any diff line and ship them back to the agent — review, edit, - Annotate AI-generated diffs + Annotate AI-generated diffs @@ -138,7 +138,7 @@ VS Code's editor with autosave everywhere — drag files or images straight into - Drag files and images into an agent prompt + Drag files and images into an agent prompt @@ -152,7 +152,7 @@ Agents drive Orca too — script every workflow with `orca worktree create`, `sn - Script Orca from the CLI + Script Orca from the CLI diff --git a/cloud/apps/relay/src/assignment-store.ts b/cloud/apps/relay/src/assignment-store.ts index 7b877e3af7d..bb7812dda86 100644 --- a/cloud/apps/relay/src/assignment-store.ts +++ b/cloud/apps/relay/src/assignment-store.ts @@ -1,3 +1,4 @@ +import { createDrainMigrationRowLookup } from './drain-migration-row-lookup.js' import { IDLE_REHOME_PAGE_SIZE, selectIdleRegionalRehomes } from './idle-regional-rehome-selection.js' import { readRegionCorrectionOutcomes } from './region-correction-outcomes.js' import { @@ -2187,22 +2188,16 @@ export class RelayAssignmentStore { [input.cellId] ) const cells = await this.lockCellInventory(transaction, 'request') + const assignmentRows = createDrainMigrationRowLookup(assignments, text) + const leaseRows = createDrainMigrationRowLookup(activityLeases, text) for (const migrationRow of migrations) { const identity = { userId: text(migrationRow, 'user_id'), relayHostId: text(migrationRow, 'relay_host_id') } - const assignment = assignments.find( - (candidate) => - text(candidate, 'user_id') === identity.userId && - text(candidate, 'relay_host_id') === identity.relayHostId - ) + const assignment = assignmentRows.first(identity) assertCurrentMigrationAssignment(assignment, migrationRow) - const leases = activityLeases.filter( - (lease) => - text(lease, 'user_id') === identity.userId && - text(lease, 'relay_host_id') === identity.relayHostId - ) + const leases = leaseRows.all(identity) const migrationLeases = leases.filter( (lease) => text(lease, 'activity_kind') === 'migration' ) @@ -2377,22 +2372,16 @@ export class RelayAssignmentStore { ) { throw new Error('drain_migration_source_incarnation_mismatch') } + const assignmentRows = createDrainMigrationRowLookup(assignments, text) + const leaseRows = createDrainMigrationRowLookup(activityLeases, text) for (const migrationRow of migrationIncarnations) { - const assignment = assignments.find( - (candidate) => - text(candidate, 'user_id') === text(migrationRow, 'user_id') && - text(candidate, 'relay_host_id') === text(migrationRow, 'relay_host_id') - ) + const identity = { + userId: text(migrationRow, 'user_id'), + relayHostId: text(migrationRow, 'relay_host_id') + } + const assignment = assignmentRows.first(identity) assertCurrentMigrationAssignment(assignment, migrationRow) - assertAssignmentActivityAccounting( - assignment, - activityLeases.filter( - (lease) => - text(lease, 'user_id') === text(migrationRow, 'user_id') && - text(lease, 'relay_host_id') === text(migrationRow, 'relay_host_id') - ), - migrationRow - ) + assertAssignmentActivityAccounting(assignment, leaseRows.all(identity), migrationRow) } const sendPermitExpiresAt = now + CELL_DRAIN_SEND_PERMIT_MS await transaction.query( diff --git a/cloud/apps/relay/src/drain-migration-row-lookup.test.ts b/cloud/apps/relay/src/drain-migration-row-lookup.test.ts new file mode 100644 index 00000000000..72df5224ee2 --- /dev/null +++ b/cloud/apps/relay/src/drain-migration-row-lookup.test.ts @@ -0,0 +1,62 @@ +import { expect, it } from 'vitest' +import { createDrainMigrationRowLookup } from './drain-migration-row-lookup.js' +import type { SqlRow } from './database.js' + +function text(row: SqlRow, field: string): string { + const value = row[field] + if (typeof value !== 'string') { + throw new Error(`invalid_${field}`) + } + return value +} + +it('keeps first assignment matches, lease order, row identity, and separate identity components', () => { + const rows = [ + { user_id: 'a:b', relay_host_id: 'c', value: 1 }, + { user_id: 'a', relay_host_id: 'b:c', value: 2 }, + { user_id: 'a:b', relay_host_id: 'c', value: 3 }, + { user_id: '', relay_host_id: '', value: 4 } + ] + const lookup = createDrainMigrationRowLookup(rows, text) + for (const identity of [ + { userId: 'a:b', relayHostId: 'c' }, + { userId: 'a', relayHostId: 'b:c' }, + { userId: '', relayHostId: '' }, + { userId: 'missing', relayHostId: 'c' } + ]) { + const expected = rows.filter( + (row) => + row.user_id === identity.userId && row.relay_host_id === identity.relayHostId + ) + expect(lookup.first(identity)).toBe(expected[0]) + expect(lookup.all(identity)).toEqual(expected) + lookup.all(identity).forEach((row, index) => expect(row).toBe(expected[index])) + } +}) + +it('retains lazy validation and short circuiting when an inventory is malformed', () => { + const first = { user_id: 'user', relay_host_id: 'host' } + const identity = { userId: 'user', relayHostId: 'host' } + const lookup = createDrainMigrationRowLookup( + [first, { user_id: null, relay_host_id: 'bad' }], + text + ) + expect(lookup.first(identity)).toBe(first) + expect(() => lookup.all(identity)).toThrow('invalid_user_id') + const unrelated = createDrainMigrationRowLookup( + [first, { user_id: 'other', relay_host_id: null }], + text + ) + expect(unrelated.all(identity)).toEqual([first]) + expect(() => unrelated.first({ userId: 'other', relayHostId: 'host' })).toThrow( + 'invalid_relay_host_id' + ) +}) + +it('does not share an index between refreshed inventories', () => { + const identity = { userId: 'user', relayHostId: 'host' } + const oldRow = { user_id: 'user', relay_host_id: 'host', version: 1 } + const newRow = { ...oldRow, version: 2 } + expect(createDrainMigrationRowLookup([oldRow], text).first(identity)).toBe(oldRow) + expect(createDrainMigrationRowLookup([newRow], text).first(identity)).toBe(newRow) +}) diff --git a/cloud/apps/relay/src/drain-migration-row-lookup.ts b/cloud/apps/relay/src/drain-migration-row-lookup.ts new file mode 100644 index 00000000000..b9dd5776f7d --- /dev/null +++ b/cloud/apps/relay/src/drain-migration-row-lookup.ts @@ -0,0 +1,58 @@ +import type { SqlRow } from './database.js' + +type Identity = { userId: string; relayHostId: string } +type RowIndex = Map> + +/** A single locked inventory, never retained across transactions or refreshed queries. */ +export function createDrainMigrationRowLookup( + rows: SqlRow[], + readText: (row: SqlRow, field: string) => string +): { + first: (identity: Identity) => SqlRow | undefined + all: (identity: Identity) => SqlRow[] +} { + let index: RowIndex | null | undefined + const indexed = (identity: Identity): SqlRow[] | undefined => { + if (index === undefined) { + index = indexRows(rows) + } + return index?.get(identity.userId)?.get(identity.relayHostId) + } + const matches = (row: SqlRow, identity: Identity): boolean => + readText(row, 'user_id') === identity.userId && + readText(row, 'relay_host_id') === identity.relayHostId + return { + first(identity) { + const group = indexed(identity) + return index === null ? rows.find((row) => matches(row, identity)) : group?.[0] + }, + all(identity) { + const group = indexed(identity) + return index === null ? rows.filter((row) => matches(row, identity)) : (group ?? []) + } + } +} + +function indexRows(rows: SqlRow[]): RowIndex | null { + const index: RowIndex = new Map() + for (const row of rows) { + const userId = row.user_id + const hostId = row.relay_host_id + // Preserve the original lazy validation and refusal order for malformed database rows. + if (typeof userId !== 'string' || typeof hostId !== 'string') { + return null + } + let hosts = index.get(userId) + if (!hosts) { + hosts = new Map() + index.set(userId, hosts) + } + const group = hosts.get(hostId) + if (group) { + group.push(row) + } else { + hosts.set(hostId, [row]) + } + } + return index +} diff --git a/cloud/apps/relay/src/drain-migration-row-scaling.test.ts b/cloud/apps/relay/src/drain-migration-row-scaling.test.ts new file mode 100644 index 00000000000..3ffd6490bd1 --- /dev/null +++ b/cloud/apps/relay/src/drain-migration-row-scaling.test.ts @@ -0,0 +1,121 @@ +import { ASSIGNMENT_LIMITS } from '@orca-cloud/relay-contract' +import { afterEach, expect, it } from 'vitest' +import { RelayAssignmentStore } from './assignment-store.js' +import { openInMemoryRelayDatabase, type RelayDatabase, type SqlRow } from './database.js' + +let database: RelayDatabase | undefined +afterEach(async () => await database?.close()) + +it.each([false, true])( + 'looks up a whole drain inventory with linear identity reads (expired: %s)', + async (expired) => { + database = await openInMemoryRelayDatabase() + let measuring = false + let identityReads = 0 + let indexedRows = 0 + const instrument = (delegate: RelayDatabase): RelayDatabase => ({ + query: (sql, params) => delegate.query(sql, params), + queryLocked: async (sql, params, options) => { + const rows = await delegate.queryLocked(sql, params, options) + if ( + !measuring || + !sql.includes('WHERE EXISTS') || + !(sql.includes('SELECT assignment.*') || sql.includes('SELECT lease.*')) + ) { + return rows + } + indexedRows += rows.length + return rows.map( + (row): SqlRow => + new Proxy(row, { + get(target, key) { + if (key === 'user_id' || key === 'relay_host_id') { + identityReads++ + } + return Reflect.get(target, key) + } + }) + ) + }, + transaction: (operation, options) => + delegate.transaction((tx) => operation(instrument(tx)), options), + close: () => delegate.close() + }) + let now = 100 + const store = new RelayAssignmentStore(instrument(database), () => now, { + requireLiveCells: true + }) + const cells = ['a', 'b'].map((id) => ({ + id: `cell-${id}`, + url: `https://relay-${id}.example.com`, + capacityRequests: 500 + })) + await store.reconcileCells(cells) + const incarnation = '11111111-1111-4111-8111-111111111111' + for (const cell of cells) { + await store.recordCellHeartbeat({ + cellId: cell.id, + cellUrl: cell.url, + cellIncarnation: incarnation, + startedAt: 50, + ready: true, + observedRequests: 0 + }) + } + await store.setCellEnabled('cell-b', false) + const identities = Array.from({ length: 50 }, (_, index) => ({ + userId: `user-${index % 5}`, + relayHostId: `host${String(index).padStart(12, '0')}` + })) + for (const identity of identities) { + await store.assign(identity) + } + await store.setCellEnabled('cell-b', true) + await store.setCellEnabled('cell-a', false) + for (const identity of identities) { + const migration = await store.startEvacuation(identity, 'cell-b') + await store.markMigrationTargetRegistered(identity, { + cellId: 'cell-b', + assignmentEpoch: migration.assignmentEpoch + }) + } + const attempt = { + attemptId: '55555555-5555-4555-8555-555555555555', + cellId: 'cell-a', + cellIncarnation: incarnation, + traceValue: '66666666-6666-4666-8666-666666666666', + plannedGraceMs: 120_000 + } + await store.prepareCellDrainAttempt(attempt) + if (expired) { + now += ASSIGNMENT_LIMITS.migrationLeaseMs + 1 + await store.releaseExpiredActivityLeases() + for (const cell of cells) { + await store.recordCellHeartbeat({ + cellId: cell.id, + cellUrl: cell.url, + cellIncarnation: incarnation, + startedAt: 50, + ready: true, + observedRequests: 0 + }) + } + } + measuring = true + await expect(store.beginCellDrainSend(attempt)).resolves.toMatchObject({ + state: 'send-may-have-started', + shouldSend: true + }) + expect(indexedRows).toBeGreaterThanOrEqual(100) + expect(identityReads).toBeLessThanOrEqual(indexedRows * 2) + const migrations = await database.query( + 'SELECT expires_at FROM relay_assignment_migrations' + ) + expect(migrations).toHaveLength(50) + expect( + migrations.every( + (row) => row.expires_at === now + ASSIGNMENT_LIMITS.migrationLeaseMs + ) + ).toBe(true) + } +) diff --git a/cloud/apps/relay/src/host-session-client-accept.test.ts b/cloud/apps/relay/src/host-session-client-accept.test.ts index 04f86c533e4..0b7febaa7b4 100644 --- a/cloud/apps/relay/src/host-session-client-accept.test.ts +++ b/cloud/apps/relay/src/host-session-client-accept.test.ts @@ -424,6 +424,8 @@ describe('successful client accept timing', () => { ) as { connId: string; connTicket: string } // The desktop's data leg is the attach window this is meant to expose. now += 23 + const session = h.registry.get({ userId: identity.sub, relayHostId: identity.relayHostId })! + const ownerProbe = vi.spyOn(session.pendingConns, 'has') const accepted = await h.registry.acceptHostData( hostData as unknown as WebSocket, connOpen.connId, @@ -432,6 +434,7 @@ describe('successful client accept timing', () => { ) expect(accepted).toBe(true) + expect(ownerProbe).toHaveBeenCalledOnce() expect(h.observer.recordClientAcceptCompleted).toHaveBeenCalledWith({ totalMs: 49, stageMs: { assignment: 5, credential: 7, activity: 11, attach: 23, basis: 3 } @@ -485,6 +488,80 @@ async function advanceToPing(control: FakeSocket, clock: { now: number }): Promi return (JSON.parse(String(ping[0])) as { t: number }).t } +// The attach resolves its owning session once and hands it to the unfenced leg; +// these hold the session it must be and the order the client hears about it. +describe('host data attach ownership', () => { + beforeEach(() => vi.useFakeTimers()) + afterEach(() => { + vi.clearAllTimers() + vi.useRealTimers() + }) + + const bystander = { ...identity, sub: 'user-2', relayHostId: 'qponmlkjihgfedcb' } + + async function pendingAttach(h: ReturnType) { + const client = new FakeSocket() + await h.registry.acceptClient( + client as unknown as WebSocket, + identity.relayHostId, + 'credential' + ) + const session = h.registry.get({ userId: identity.sub, relayHostId: identity.relayHostId })! + return { client, session, pending: [...session.pendingConns.values()][0]! } + } + + it('attaches the session that owns the connection, not the first one registered', async () => { + const h = harness() + const idle = new FakeSocket() + await h.activate(idle as unknown as WebSocket, bystander, null, 1, false, 1, '1.4.197') + await activeHost(h) + const { client, session, pending } = await pendingAttach(h) + const idleSession = h.registry.get({ + userId: bystander.sub, + relayHostId: bystander.relayHostId + })! + const host = new FakeSocket() + expect( + await h.registry.acceptHostData( + host as unknown as WebSocket, + pending.connId, + pending.connTicket, + 1 + ) + ).toBe(true) + expect(client.send).toHaveBeenCalledWith(expect.stringContaining('"type":"relay-hello"')) + expect(session.activeSplices.has(pending.connId)).toBe(true) + expect(idleSession.activeSplices.size).toBe(0) + expect(idleSession.activeConnIds.size).toBe(0) + h.registry.drain(0) + vi.advanceTimersByTime(0) + }) + + it('acknowledges the client only after the connection basis is persisted', async () => { + const h = harness() + await activeHost(h) + const { client, session, pending } = await pendingAttach(h) + const basis = deferred() + h.store.recordConnectionBasis.mockReturnValueOnce(basis.promise) + const host = new FakeSocket() + const attaching = h.registry.acceptHostData( + host as unknown as WebSocket, + pending.connId, + pending.connTicket, + 1 + ) + await vi.advanceTimersByTimeAsync(0) + expect(h.store.recordConnectionBasis).toHaveBeenCalledOnce() + expect(client.send).not.toHaveBeenCalledWith(expect.stringContaining('relay-hello')) + basis.resolve() + expect(await attaching).toBe(true) + expect(client.send).toHaveBeenCalledWith(expect.stringContaining('"type":"relay-hello"')) + expect(session.activeSplices.has(pending.connId)).toBe(true) + h.registry.drain(0) + vi.advanceTimersByTime(0) + }) +}) + describe('control round-trip sampling', () => { beforeEach(() => vi.useFakeTimers()) afterEach(() => { diff --git a/cloud/apps/relay/src/host-session-registry.test.ts b/cloud/apps/relay/src/host-session-registry.test.ts index dcfcd3f36c5..7c50285454c 100644 --- a/cloud/apps/relay/src/host-session-registry.test.ts +++ b/cloud/apps/relay/src/host-session-registry.test.ts @@ -111,6 +111,7 @@ function createRegistry( renewControlActivity: ReturnType releaseActivity: ReturnType observer: { + recordAuth: ReturnType recordControlClose: ReturnType recordSpliceClose: ReturnType } @@ -1396,6 +1397,57 @@ describe('source-owned idle cutover', () => { cleanup.resolve() await attached }) + it('rejects an attach mid-cutover before its ticket is ever examined', async () => { + const h = await source({ failReservation: vi.fn().mockResolvedValue(undefined) }) + const result = deferred<{ outcome: 'deferred' }>() + // The cutover must already be in flight: an idle host is what it claims. + const moving = h.registry.idleRehome(request, () => result.promise, vi.fn()) + const client = new FakeSocket() + h.session.pendingConns.set('conn', { + connId: 'conn', + connTicket: 'ticket', + client: client as unknown as WebSocket, + reservation: { + userId: identity.sub, + relayHostId: identity.relayHostId, + credentialKind: 'invite', + leaseExpiresAt: Date.now() + 1000 + }, + attachTimer: setTimeout(() => {}, 1000), + credentialActivityId: null + } as never) + const host = new FakeSocket() + // The ticket below is the live one: only the cutover fence may reject it. + expect( + await h.registry.acceptHostData(host as unknown as WebSocket, 'conn', 'ticket', 1) + ).toBe(false) + expect(host.close).toHaveBeenCalledWith(RELAY_CLOSE_CODE.WRONG_CELL, expect.any(String)) + expect(h.observer.recordAuth).not.toHaveBeenCalled() + expect(h.session.pendingConns.has('conn')).toBe(true) + expect(h.session.activeConnIds.size).toBe(0) + result.resolve({ outcome: 'deferred' }) + await moving + }) + it('holds no attach ownership when no session owns the connection', async () => { + const h = await source() + const host = new FakeSocket() + expect( + await h.registry.acceptHostData(host as unknown as WebSocket, 'stranger', 'ticket', 1) + ).toBe(false) + expect(h.observer.recordAuth).toHaveBeenCalledWith(false) + expect(host.close).toHaveBeenCalledWith( + RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL, + expect.any(String) + ) + // A leaked idle-work hold from the unowned attach would report `busy` here. + expect( + await h.registry.idleRehome( + request, + vi.fn().mockResolvedValue({ outcome: 'committed' }), + vi.fn() + ) + ).toEqual({ outcome: 'committed' }) + }) it('returns the durable operation outcome after source retirement', async () => { const h = await source() const commit = vi.fn().mockResolvedValue({ outcome: 'committed' }) @@ -1421,3 +1473,226 @@ describe('source-owned idle cutover', () => { expect(h.registry.get(request)).toBeNull() }) }) + +// The host data leg's owner lookup is the registry's only whole-inventory scan on +// an attach. These count what that scan touches, not how long it takes. +describe('host data attach owner lookup', () => { + beforeEach(() => vi.useFakeTimers()) + afterEach(() => { + vi.clearAllTimers() + vi.useRealTimers() + }) + + const SESSION_COUNT = 1000 + const CONN_ID = 'conn-owned' + const OWNER_INDEX = { first: 0, middle: 499, last: 999 } as const + type Placement = keyof typeof OWNER_INDEX | 'absent' + type LookupCounts = { visits: number; membership: number } + + function bindOwn(map: Map, property: string | symbol): unknown { + const value: unknown = Reflect.get(map, property, map) + return typeof value === 'function' ? value.bind(map) : value + } + + // One visit per session the scan pulls off the map iterator; answers unchanged. + function countingValues(map: Map, counts: LookupCounts): Map { + return new Proxy(map, { + get(target, property) { + if (property !== 'values') return bindOwn(target, property) + return function* (): Generator { + for (const value of target.values()) { + counts.visits += 1 + yield value + } + } + } + }) + } + + // One membership check per `pendingConns.has`; answers unchanged. + function countingHas(map: Map, counts: LookupCounts): Map { + return new Proxy(map, { + get(target, property) { + if (property !== 'has') return bindOwn(target, property) + return (key: K) => { + counts.membership += 1 + return target.has(key) + } + } + }) + } + + // The pre-change implementation, kept inline as the oracle the new counts are + // differenced against: two inventory arrays, two independent finds. + function legacyOwnerLookup( + sessions: Map, + connId: string + ): { owner: HostSession | undefined; session: HostSession | undefined } { + const owner = [...sessions.values()].find((candidate) => candidate.pendingConns.has(connId)) + const session = [...sessions.values()].find((candidate) => candidate.pendingConns.has(connId)) + return { owner, session } + } + + function pendingConn(client: FakeSocket, connTicket: string) { + return { + connId: CONN_ID, + connTicket, + client: client as unknown as WebSocket, + reservation: { + userId: identity.sub, + relayHostId: identity.relayHostId, + credentialKind: 'invite', + leaseExpiresAt: Date.now() + 1000 + }, + attachTimer: setTimeout(() => {}, 1000), + credentialActivityId: null + } as never + } + + // Every decoy holds a pending conn of its own, so each membership check the + // scan makes is real work rather than a lookup in an empty map. + function decoySession(index: number, counts: LookupCounts): HostSession { + const pendingConns = new Map([[`conn-decoy-${index}`, { connId: 'decoy' }]]) + return { + relayHostId: `decoy-host-${index}`, + generation: 1, + state: 'active', + activeConnIds: new Set(), + pendingConns: countingHas(pendingConns, counts) + } as unknown as HostSession + } + + async function attachRegistry(placement: Placement, store: Partial = {}) { + const h = createRegistry(vi.fn().mockResolvedValue('control:1'), { + failReservation: vi.fn().mockResolvedValue(undefined), + recordConnectionBasis: vi.fn().mockResolvedValue(undefined), + deactivateBasis: vi.fn().mockResolvedValue(undefined), + ...store + }) + const control = new FakeSocket() + await h.activate(control as unknown as WebSocket, identity, null, 1, false, 1) + const internals = h.registry as unknown as { sessions: Map } + const [ownerKey, owner] = [...internals.sessions.entries()][0]! + const counts: LookupCounts = { visits: 0, membership: 0 } + const client = new FakeSocket() + if (placement !== 'absent') owner.pendingConns.set(CONN_ID, pendingConn(client, 'ticket')) + owner.pendingConns = countingHas(owner.pendingConns, counts) + const ordered: HostSession[] = [] + const sessions = new Map() + const ownerIndex = placement === 'absent' ? 0 : OWNER_INDEX[placement] + for (let index = 0; index < SESSION_COUNT; index += 1) { + const session = index === ownerIndex ? owner : decoySession(index, counts) + ordered.push(session) + sessions.set(index === ownerIndex ? ownerKey : `decoy-${index}`, session) + } + internals.sessions = countingValues(sessions, counts) + return { ...h, owner, ordered, counts, client, control, sessions: internals.sessions } + } + + it.each([ + { + placement: 'first', + before: { visits: 2000, membership: 2 }, + after: { visits: 1, membership: 1 } + }, + { + placement: 'middle', + before: { visits: 2000, membership: 1000 }, + after: { visits: 500, membership: 500 } + }, + { + placement: 'last', + before: { visits: 2000, membership: 2000 }, + after: { visits: 1000, membership: 1000 } + }, + { + placement: 'absent', + before: { visits: 2000, membership: 2000 }, + after: { visits: 1000, membership: 1000 } + } + ] as const)( + 'visits the inventory once, not twice, for a $placement owner', + async ({ placement, before, after }) => { + const h = await attachRegistry(placement) + expect(h.sessions.size).toBe(SESSION_COUNT) + const oracle = legacyOwnerLookup(h.sessions, CONN_ID) + const legacy = { ...h.counts } + h.counts.visits = 0 + h.counts.membership = 0 + const host = new FakeSocket() + // An unusable ticket stops the attach immediately after the lookup, so the + // counts below belong to the lookup alone. + expect( + await h.registry.acceptHostData(host as unknown as WebSocket, CONN_ID, 'wrong', 1) + ).toBe(false) + expect(h.observer.recordAuth).toHaveBeenCalledExactlyOnceWith(false) + expect(host.close).toHaveBeenCalledWith( + RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL, + 'invalid host data ticket' + ) + expect(legacy).toEqual(before) + expect({ ...h.counts }).toEqual(after) + expect(oracle.owner).toBe(placement === 'absent' ? undefined : h.owner) + expect(oracle.owner).toBe(oracle.session) + } + ) + + it.each([ + { reason: 'ticket', ticket: 'wrong', generation: 1, state: 'active' }, + { reason: 'generation', ticket: 'ticket', generation: 2, state: 'active' }, + { reason: 'state', ticket: 'ticket', generation: 1, state: 'orphaned' } + ] as const)('fails an attach whose $reason does not match the owner', async (input) => { + const h = await attachRegistry('middle') + h.owner.state = input.state + const host = new FakeSocket() + expect( + await h.registry.acceptHostData( + host as unknown as WebSocket, + CONN_ID, + input.ticket, + input.generation + ) + ).toBe(false) + expect(h.observer.recordAuth).toHaveBeenCalledExactlyOnceWith(false) + expect(host.close).toHaveBeenCalledWith( + RELAY_CLOSE_CODE.BAD_OUTER_CREDENTIAL, + 'invalid host data ticket' + ) + expect(h.owner.pendingConns.has(CONN_ID)).toBe(true) + expect(h.owner.activeConnIds.size).toBe(0) + }) + + it('rejects on the earlier duplicate owner rather than the later live one', async () => { + const h = await attachRegistry('middle') + h.ordered[0]!.pendingConns.set(CONN_ID, pendingConn(new FakeSocket(), 'stale-ticket') as never) + expect(legacyOwnerLookup(h.sessions, CONN_ID).owner).toBe(h.ordered[0]) + h.counts.visits = 0 + h.counts.membership = 0 + const host = new FakeSocket() + expect( + await h.registry.acceptHostData(host as unknown as WebSocket, CONN_ID, 'ticket', 1) + ).toBe(false) + expect({ ...h.counts }).toEqual({ visits: 1, membership: 1 }) + expect(h.owner.pendingConns.has(CONN_ID)).toBe(true) + }) + + it('splices the earlier duplicate owner and leaves the later one untouched', async () => { + const basis = vi.fn().mockRejectedValue(new Error('basis failed')) + const h = await attachRegistry('first', { recordConnectionBasis: basis }) + const duplicate = h.ordered[3]! + duplicate.pendingConns.set(CONN_ID, pendingConn(new FakeSocket(), 'ticket') as never) + h.counts.visits = 0 + h.counts.membership = 0 + const host = new FakeSocket() + expect( + await h.registry.acceptHostData(host as unknown as WebSocket, CONN_ID, 'ticket', 1) + ).toBe(false) + expect({ ...h.counts }).toEqual({ visits: 1, membership: 1 }) + expect(h.observer.recordAuth).toHaveBeenCalledWith(true) + expect(basis).toHaveBeenCalledOnce() + // The first owner's entry was consumed; the later duplicate never was. + expect(h.owner.pendingConns.has(CONN_ID)).toBe(false) + expect(duplicate.pendingConns.has(CONN_ID)).toBe(true) + expect(h.owner.activeConnIds.size).toBe(0) + }) +}) diff --git a/cloud/apps/relay/src/host-session-registry.ts b/cloud/apps/relay/src/host-session-registry.ts index 61a1b706fa9..0d0cf4a7940 100644 --- a/cloud/apps/relay/src/host-session-registry.ts +++ b/cloud/apps/relay/src/host-session-registry.ts @@ -511,16 +511,22 @@ export class HostSessionRegistry { connTicket: string, generation: number ): Promise { - const owner = [...this.sessions.values()].find((candidate) => - candidate.pendingConns.has(connId) - ) + // First insertion-order owner, and the only scan the attach makes: the + // unfenced leg reuses this result instead of repeating the search. + let owner: HostSession | undefined + for (const candidate of this.sessions.values()) { + if (candidate.pendingConns.has(connId)) { + owner = candidate + break + } + } const release = owner ? this.beginIdleWork(owner.relayHostId) : () => {} if (!release) { socket.close(RELAY_CLOSE_CODE.WRONG_CELL, 'idle cutover in progress') return false } try { - return await this.acceptHostDataUnfenced(socket, connId, connTicket, generation) + return await this.acceptHostDataUnfenced(socket, connId, connTicket, generation, owner) } finally { release() } @@ -530,11 +536,9 @@ export class HostSessionRegistry { socket: WebSocket, connId: string, connTicket: string, - generation: number + generation: number, + session: HostSession | undefined ): Promise { - const session = [...this.sessions.values()].find((candidate) => - candidate.pendingConns.has(connId) - ) const pending = session?.pendingConns.get(connId) if ( !session || diff --git a/cloud/apps/relay/src/relay-observability.test.ts b/cloud/apps/relay/src/relay-observability.test.ts index ea6734412be..605aacf6422 100644 --- a/cloud/apps/relay/src/relay-observability.test.ts +++ b/cloud/apps/relay/src/relay-observability.test.ts @@ -5,6 +5,7 @@ import { observeRelayDatabase } from './observed-relay-database.js' import { CONTROL_RTT_RESERVOIR_LIMIT, observedRelayRequests, + percentile, RelayObservability, type RelayProcessCounts } from './relay-observability.js' @@ -412,3 +413,181 @@ describe('relay observability', () => { expect(recordSql.mock.calls.map((call) => call[1])).toEqual([true, false, true, false]) }) }) + +// The pre-change implementation, kept verbatim as the differential oracle. Both +// ranks sorted their own copy and the maximum was a zero-seeded fold. +function legacyPercentile(values: number[], percentileRank: number): number { + if (values.length === 0) return 0 + const sorted = [...values].sort((left, right) => left - right) + return sorted[Math.ceil(percentileRank * sorted.length) - 1] ?? 0 +} + +function legacyLatencySummary(samples: number[]): { p50: number; p95: number; max: number } { + const round = (value: number): number => Number(value.toFixed(3)) + return { + p50: round(legacyPercentile(samples, 0.5)), + p95: round(legacyPercentile(samples, 0.95)), + max: round(samples.reduce((highest, sample) => Math.max(highest, sample), 0)) + } +} + +// `-0` and `NaN` both survive a string round trip, unlike a bare equality check. +function describeNumber(value: number): string { + return Object.is(value, -0) ? '-0' : String(value) +} + +function expectSameNumber(actual: number, expected: number, label: string): void { + expect(`${label} = ${describeNumber(actual)}`).toBe(`${label} = ${describeNumber(expected)}`) +} + +function sparseWindow(size: number, filled: Record): number[] { + const values: number[] = new Array(size) + for (const [index, value] of Object.entries(filled)) values[Number(index)] = value + return values +} + +// Lehmer generator: stays inside the safe-integer range so the window is +// byte-identical on every engine the relay runs on. +function deterministicWindow(size: number): number[] { + let seed = 20_260_912 + return Array.from({ length: size }, () => { + seed = (seed * 48_271) % 2_147_483_647 + return (seed % 4_000_000) / 1_000 + }) +} + +const DENSE_WINDOWS: Array<{ name: string; values: number[] }> = [ + { name: 'empty', values: [] }, + { name: 'single', values: [7.5] }, + { name: 'single negative', values: [-7.5] }, + { name: 'ascending', values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { name: 'descending', values: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { name: 'duplicates', values: [4, 4, 4, 4, 4] }, + // The trap: a sorted last element reads -1 here, the zero-seeded fold reads 0. + { name: 'all negative', values: [-5, -1, -9, -3, -2] }, + { name: 'mixed signs', values: [-2, 3, -7, 0, 11, -0.5] }, + { name: 'signed zero', values: [0, -0, -0, 0] }, + { name: 'negative then signed zero', values: [-3, -0, -1] }, + { name: 'nan leading', values: [NaN, 5, 1, 9] }, + { name: 'nan trailing', values: [5, 1, 9, NaN] }, + { name: 'nan interleaved', values: [5, NaN, 1, NaN, 9] }, + { name: 'all nan', values: [NaN, NaN, NaN] }, + { name: 'positive infinity', values: [Infinity, 3, 1] }, + { name: 'negative infinity', values: [-Infinity, -3, -1] }, + { name: 'both infinities', values: [Infinity, -Infinity, 3, -Infinity] }, + { name: 'infinities and nan', values: [Infinity, NaN, -Infinity, 0] }, + { name: 'sub-millisecond rounding', values: [0.00049, 0.0005, 0.00051, 0.9995] }, + { name: 'reservoir sized', values: deterministicWindow(CONTROL_RTT_RESERVOIR_LIMIT) } +] + +// Holes cannot reach the recorders, so they are exercised through `percentile` +// alone — the surface `host-session-registry` also calls. +const SPARSE_WINDOWS: Array<{ name: string; values: number[] }> = [ + { name: 'all holes', values: sparseWindow(4, {}) }, + { name: 'leading hole', values: sparseWindow(5, { 3: 8, 4: 2 }) }, + { name: 'trailing hole', values: sparseWindow(5, { 0: 8, 1: 2 }) }, + { name: 'interleaved holes', values: sparseWindow(6, { 0: 3, 2: -4, 5: 1 }) }, + { name: 'holes with nan', values: sparseWindow(5, { 1: NaN, 3: 6 }) } +] + +const PERCENTILE_RANKS = [0, 0.05, 0.5, 0.9, 0.95, 0.99, 1] + +type SortWork = { sorts: number; comparisons: number; copiedElements: number } + +// Every sorted array here is a fresh spread copy, so its length is the number of +// elements copied to produce it. +function countSortWork(run: () => void): SortWork { + const work: SortWork = { sorts: 0, comparisons: 0, copiedElements: 0 } + const original = Array.prototype.sort + const patched = Array.prototype as { sort: unknown } + patched.sort = function (this: T[], compare?: (left: T, right: T) => number): T[] { + work.sorts++ + work.copiedElements += this.length + return original.call(this, (left: T, right: T) => { + work.comparisons++ + return compare ? compare(left, right) : String(left) < String(right) ? -1 : 1 + }) + } + try { + run() + } finally { + patched.sort = original + } + return work +} + +function summaryThroughFlush(samples: number[]): { p50: number; p95: number; max: number } { + const entries: Array> = [] + const observability = new RelayObservability( + { role: 'cell', cellId: 'staging-c1', region: 'us-central1' }, + (entry) => entries.push(entry) + ) + for (const sample of samples) observability.recordControlRenewal(sample, 'renewed') + observability.flush(counts) + const entry = entries[0]! + return { + p50: entry.controlRenewalLatencyMsP50 as number, + p95: entry.controlRenewalLatencyMsP95 as number, + max: entry.controlRenewalLatencyMsMax as number + } +} + +describe('latency window summarisation', () => { + it('matches the pre-change percentile on every edge-case window', () => { + let compared = 0 + for (const { name, values } of [...DENSE_WINDOWS, ...SPARSE_WINDOWS]) { + for (const rank of PERCENTILE_RANKS) { + expectSameNumber( + percentile(values, rank), + legacyPercentile(values, rank), + `${name} @ p${rank}` + ) + compared++ + } + } + expect(compared).toBe((DENSE_WINDOWS.length + SPARSE_WINDOWS.length) * PERCENTILE_RANKS.length) + }) + + it('matches the pre-change p50, p95 and maximum through a flush', () => { + let compared = 0 + for (const { name, values } of DENSE_WINDOWS) { + const actual = summaryThroughFlush(values) + const expected = legacyLatencySummary(values) + expectSameNumber(actual.p50, expected.p50, `${name} p50`) + expectSameNumber(actual.p95, expected.p95, `${name} p95`) + // The zero-seeded fold, not the sorted last element: all-negative and NaN + // windows disagree between the two. + expectSameNumber(actual.max, expected.max, `${name} max`) + compared += 3 + } + expect(compared).toBe(DENSE_WINDOWS.length * 3) + // The trap, spelled out: the sorted window ends at -1 but the fold reports 0. + expect(summaryThroughFlush([-5, -1, -9, -3, -2]).max).toBe(0) + expect(Number.isNaN(summaryThroughFlush([5, NaN, 1]).max)).toBe(true) + }) + + it('sorts each latency window once instead of once per rank', () => { + const samples = deterministicWindow(CONTROL_RTT_RESERVOIR_LIMIT) + const before = countSortWork(() => legacyLatencySummary(samples)) + const after = countSortWork(() => summaryThroughFlush(samples)) + + expect(before.sorts).toBe(2) + expect(after.sorts).toBe(1) + expect(before.copiedElements).toBe(2 * CONTROL_RTT_RESERVOIR_LIMIT) + expect(after.copiedElements).toBe(CONTROL_RTT_RESERVOIR_LIMIT) + // Identical input and comparator, so the dropped sort is exactly half the + // comparator calls rather than an engine-specific constant. + expect(before.comparisons).toBeGreaterThan(CONTROL_RTT_RESERVOIR_LIMIT) + expect(after.comparisons).toBe(before.comparisons / 2) + }) + + it('never sorts an empty window and leaves the caller window untouched', () => { + const samples = [5, -1, NaN, 3, -0] + const before = samples.map(describeNumber) + expect(countSortWork(() => summaryThroughFlush([])).sorts).toBe(0) + expect(countSortWork(() => percentile([], 0.95)).sorts).toBe(0) + countSortWork(() => summaryThroughFlush(samples)) + percentile(samples, 0.5) + expect(samples.map(describeNumber)).toEqual(before) + }) +}) diff --git a/cloud/apps/relay/src/relay-observability.ts b/cloud/apps/relay/src/relay-observability.ts index 5e85758ca56..f5eabb60a4c 100644 --- a/cloud/apps/relay/src/relay-observability.ts +++ b/cloud/apps/relay/src/relay-observability.ts @@ -168,10 +168,18 @@ const emptyDeltas = (): RelayMetricDeltas => ({ controlActivityRecoveryFailures: 0 }) +function ascending(values: number[]): number[] { + return [...values].sort((left, right) => left - right) +} + +// Holes and NaN land past the requested rank, so the fallback still applies. +function nearestRank(sorted: number[], percentileRank: number): number { + return sorted[Math.ceil(percentileRank * sorted.length) - 1] ?? 0 +} + export function percentile(values: number[], percentileRank: number): number { if (values.length === 0) return 0 - const sorted = [...values].sort((left, right) => left - right) - return sorted[Math.ceil(percentileRank * sorted.length) - 1] ?? 0 + return nearestRank(ascending(values), percentileRank) } function roundMs(value: number): number { @@ -179,11 +187,15 @@ function roundMs(value: number): number { } // Spreading a window into Math.max blows the stack once a busy cell samples -// enough of it, so the maximum is folded instead. +// enough of it, so the maximum is folded instead. The fold is also not +// interchangeable with the sorted last element: it is seeded with zero, so an +// all-negative or NaN window reads differently. function latencySummary(samples: number[]): { p50: number; p95: number; max: number } { + // One sorted copy serves both ranks. + const sorted = samples.length === 0 ? samples : ascending(samples) return { - p50: roundMs(percentile(samples, 0.5)), - p95: roundMs(percentile(samples, 0.95)), + p50: roundMs(nearestRank(sorted, 0.5)), + p95: roundMs(nearestRank(sorted, 0.95)), max: roundMs(samples.reduce((highest, sample) => Math.max(highest, sample), 0)) } } diff --git a/cloud/dev/scripts/probe-relay-rehome-trust.mjs b/cloud/dev/scripts/probe-relay-rehome-trust.mjs index 2208131e58a..73ea44a40f7 100644 --- a/cloud/dev/scripts/probe-relay-rehome-trust.mjs +++ b/cloud/dev/scripts/probe-relay-rehome-trust.mjs @@ -38,7 +38,7 @@ export function parseRehomeTrustProbeArguments(argv, environment = process.env) export async function probeRehomeTrust(config, dependencies = {}) { const fetchImpl = dependencies.fetch ?? fetch - const response = await fetchAdminOnceMore( + const request = () => fetchAdminOnceMore( fetchImpl, `${config.directorOrigin}/v1/admin/regional-rehome-trust-probe`, { @@ -55,9 +55,26 @@ export async function probeRehomeTrust(config, dependencies = {}) { }, { wait: dependencies.wait } ) - const body = await response.json().catch(() => ({})) + let response = await request() + let body = await response.json().catch(() => ({})) + // The director wraps source HTTP failures in 409; retry only explicit transient statuses. + if (response.status === 409 && /^regional_rehome_trust_probe_source_(500|502|503|504)$/.test(body?.error ?? '')) { + await (dependencies.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(2_000) + response = await request() + body = await response.json().catch(() => ({})) + } if (!response.ok) { - throw new Error(`application-mediated rehome trust probe returned ${response.status}`) + const safeReasons = new Set([ + 'invalid_token', 'director_only', 'invalid_request', + 'regional_rehome_trust_not_configured', + 'regional_rehome_trust_probe_source_unavailable', + 'regional_rehome_trust_probe_source_invalid_response', + 'regional_rehome_trust_probe_not_proven', + ...[400, 401, 403, 404, 409, 429, 500, 502, 503, 504] + .map((status) => `regional_rehome_trust_probe_source_${status}`) + ]) + const reason = safeReasons.has(body?.error) ? body.error : 'unrecognized_error' + throw new Error(`application-mediated rehome trust probe returned ${response.status}: ${reason}`) } if ( body.v !== 1 || diff --git a/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs b/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs index 789d33c40b6..034fcd06c4f 100644 --- a/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs +++ b/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs @@ -131,3 +131,40 @@ test('approves the asia-east2 rehome sources and still rejects unlisted cells', ) } }) + +test('retries one director-wrapped source 503 without relaxing the proof', async () => { + let calls = 0 + const result = await probeRehomeTrust(parseRehomeTrustProbeArguments(argv, environment), { + wait: async () => {}, + fetch: async () => ++calls === 1 + ? Response.json({ error: 'regional_rehome_trust_probe_source_503' }, { status: 409 }) + : Response.json(provenProbe) + }) + assert.equal(calls, 2) + assert.equal(result.proven, true) +}) + +test('reports safe trust reasons, keeps rejection final, and redacts arbitrary error text', async () => { + for (const reason of ['regional_rehome_trust_probe_source_403', 'secret-token-example']) { + let calls = 0 + await assert.rejects(probeRehomeTrust(parseRehomeTrustProbeArguments(argv, environment), { + wait: async () => { throw new Error('must not retry') }, + fetch: async () => { calls++; return Response.json({ error: reason }, { status: 409 }) } + }), error => { + assert.match(error.message, /returned 409/) + assert.ok(!error.message.includes('secret-token-example')) + if (reason.endsWith('_403')) assert.match(error.message, /source_403/) + return true + }) + assert.equal(calls, 1) + } +}) + +test('stops after the second wrapped transient failure', async () => { + let calls = 0 + await assert.rejects(probeRehomeTrust(parseRehomeTrustProbeArguments(argv, environment), { + wait: async () => {}, + fetch: async () => { calls++; return Response.json({ error: 'regional_rehome_trust_probe_source_503' }, { status: 409 }) } + }), /returned 409.*source_503/) + assert.equal(calls, 2) +}) diff --git a/config/electron-builder.config.cjs b/config/electron-builder.config.cjs index 6b36938a3d1..8a5f8c27475 100644 --- a/config/electron-builder.config.cjs +++ b/config/electron-builder.config.cjs @@ -7,6 +7,7 @@ const { verifyPackagedDaemonEntryBoots } = require('./scripts/verify-packaged-daemon-entry.cjs') const { + assertPackagedNativeVariantsInstalled, createPackagedRuntimeNodeModuleResources, prunePackagedRuntimeNodeModules, verifyPackagedMainRuntimeDeps @@ -147,6 +148,16 @@ const rpmElectronRuntimeDependencies = [ // config/nsis/orca-installer-hooks.nsh, which registers the same set on Windows. const MARKDOWN_FILE_EXTENSIONS = ['md', 'markdown', 'mdx'] +// Why: the config must load on a host-only install without resolving unused Windows addons. +// This is load-time tolerance only; beforePack enforces that the target's natives are installed. +// Why one package: @vscode/windows-process-tree is the only os: win32 npm addon; +// @orca/windows-registry is a workspace link present on every host, so its presence proves nothing. +const windowsRuntimeResources = existsSync( + join(__dirname, '..', 'node_modules', '@vscode', 'windows-process-tree', 'package.json') +) + ? createPackagedRuntimeNodeModuleResources('win32') + : [] + /** @type {import('electron-builder').Configuration} */ module.exports = { appId, @@ -278,6 +289,9 @@ module.exports = { verifyStaticAppImagePackage(file, arch) } }, + beforePack: (context) => { + assertPackagedNativeVariantsInstalled(context.electronPlatformName, context.arch) + }, afterPack: async (context) => { const resourcesDir = context.electronPlatformName === 'darwin' @@ -319,9 +333,9 @@ module.exports = { // Why: a Linux runner-image glibc bump silently shipped a node-pty pty.node // requiring GLIBC_2.34, crashing the app on startup on Ubuntu 20.04 (#9902). // Fail packaging if any bundled native binary exceeds the supported floor. - // Why after the prune: cross-builds intentionally install every optional - // native variant, so an arm64 slice still carries the x64 @parcel/watcher - // until prunePackagedRuntimeNodeModules drops it. + // Why after the prune: `pnpm install:release` widens the CPU set for cross-builds, + // so an arm64 slice can still carry the x64 @parcel/watcher until + // prunePackagedRuntimeNodeModules drops it. if (context.electronPlatformName === 'linux') { // Why the arch is passed: symbol-version checks pass happily on a wrong-architecture binary, // so a cross-built slice could ship the host's pty.node and only fail at runtime. @@ -418,7 +432,7 @@ module.exports = { ...(isWinDevChannel ? { verifyUpdateCodeSignature: false } : {}), extraResources: [ ...commonExtraResources, - ...createPackagedRuntimeNodeModuleResources('win32'), + ...windowsRuntimeResources, winSpeechNativeResource, { from: 'resources/win32/bin/orca.cmd', diff --git a/config/oxlint-code-quality-casting.json b/config/oxlint-code-quality-casting.json new file mode 100644 index 00000000000..3db01c3a04d --- /dev/null +++ b/config/oxlint-code-quality-casting.json @@ -0,0 +1,17 @@ +{ + "$schema": "../node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript"], + "categories": { + "correctness": "off", + "suspicious": "off", + "pedantic": "off", + "perf": "off", + "style": "off", + "restriction": "off", + "nursery": "off" + }, + "rules": { + "typescript/consistent-type-assertions": ["error", { "assertionStyle": "never" }] + }, + "ignorePatterns": ["**/node_modules", "**/dist", "**/out"] +} diff --git a/config/packaged-runtime-node-modules.cjs b/config/packaged-runtime-node-modules.cjs index 1ee443f8288..30784e79f5c 100644 --- a/config/packaged-runtime-node-modules.cjs +++ b/config/packaged-runtime-node-modules.cjs @@ -35,7 +35,7 @@ const PACKAGED_RUNTIME_PACKAGE_ROOTS = [ ] const WINDOWS_PACKAGED_RUNTIME_PACKAGE_ROOTS = [ '@vscode/windows-process-tree', - 'windows-native-registry' + '@orca/windows-registry' ] const NODE_PTY_PREBUILD_PREFIX_BY_PLATFORM = { @@ -436,7 +436,7 @@ function prunePackagedParcelWatcher(resourcesDir, electronPlatformName, electron } // Why: we package every installed @parcel/watcher- optional - // subpackage (supportedArchitectures fetches all), but each build only needs + // subpackage (pnpm install:release fetches every CPU), but each build only needs // its own platform/architecture binaries. Keep the core package and matching // native variants; drop the rest. const keepPrefix = PARCEL_WATCHER_PLATFORM_PREFIX_BY_PLATFORM[electronPlatformName] @@ -511,6 +511,75 @@ function prunePackagedZodSources(resourcesDir) { rmSync(join(resourcesDir, 'node_modules', 'zod', 'src'), { recursive: true, force: true }) } +// Why: electron-builder only warns on a missing extraResources source, so a host-only +// install would silently ship a foreign-arch slice without its native addons. +function assertPackagedNativeVariantsInstalled(electronPlatformName, electronArch) { + const architecture = normalizeElectronArchitecture(electronArch) + const nodeModulesDir = join(projectDir, 'node_modules') + 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) + } + + // Why prefix, not equality: linux variants carry a libc suffix (watcher-linux-x64-glibc), + // mirroring what prunePackagedParcelWatcher keeps. + const watcherPrefix = `watcher-${electronPlatformName}-${architecture}` + const parcelDir = join(nodeModulesDir, '@parcel') + if (isInstalled('@parcel/watcher')) { + const watcherOptionalDependencies = Object.keys( + JSON.parse(readFileSync(join(parcelDir, 'watcher', 'package.json'), 'utf8')) + .optionalDependencies ?? {} + ) + const expectedVariants = watcherOptionalDependencies.filter((name) => + name.startsWith(`@parcel/${watcherPrefix}`) + ) + // Why not withFileTypes: pnpm links the variants, so isDirectory() is false for them. + const hasVariant = readdirSync(parcelDir).some( + (name) => name.startsWith(watcherPrefix) && isInstalled(`@parcel/${name}`) + ) + if (expectedVariants.length > 0 && !hasVariant) { + missing.push(...expectedVariants) + } + } + + // Why one package: @vscode/windows-process-tree is the only os: win32 npm addon; + // @orca/windows-registry is a workspace link present on every host, so its presence proves nothing. + const missingWindowsAddons = [] + if (electronPlatformName === 'win32' && !isInstalled('@vscode/windows-process-tree')) { + missingWindowsAddons.push('@vscode/windows-process-tree') + } + + if (missing.length === 0 && missingWindowsAddons.length === 0) { + return + } + // Why separate remedies: install:release widens only the CPU set, so the os: win32 addon + // never arrives on a non-Windows host and is compiled only by the Windows-only rebuild. + const remedies = [] + if (missing.length > 0) { + remedies.push('Run pnpm install:release to install another architecture.') + } + if (missingWindowsAddons.length > 0) { + remedies.push( + 'Windows packaging requires a Windows host: the Windows addons are installed only where ' + + 'os: win32 matches and compiled only by the Windows-only rebuild.' + ) + } + throw new Error( + `Packaging ${electronPlatformName}/${architecture} requires native variants that are not installed: ` + + `${[...new Set([...missing, ...missingWindowsAddons])].sort().join(', ')}. ${remedies.join(' ')}` + ) +} + function prunePackagedRuntimeNodeModules(resourcesDir, electronPlatformName, electronArch) { const architecture = normalizeElectronArchitecture(electronArch) prunePackagedNodePty(resourcesDir, electronPlatformName, architecture) @@ -534,6 +603,7 @@ function pruneMatchingFiles(directory, shouldPrune) { module.exports = { PACKAGED_RUNTIME_PACKAGE_ROOTS, + assertPackagedNativeVariantsInstalled, createPackagedRuntimeNodeModuleResources, findAsarEntry, isPackagedExternalSpecifier, diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 5b21fa9b0ad..2bb4cc13b08 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "updatedAt": "2026-08-31", + "updatedAt": "2026-09-11", "policy": { "maturityLevels": ["experimental", "soak", "blocking", "accepted-gap", "deprecated"], "blockingPromotion": { @@ -10,6 +10,78 @@ } }, "gates": [ + { + "id": "agent-session.journal-streaming-replay", + "title": "Journal replay bounds obsolete revision memory without changing recovery", + "maturity": "experimental", + "protection": "partial", + "owner": "agent-session-runtime", + "layer": "runtime-unit", + "surfaces": ["structured chat journal replay", "structured chat recovery"], + "platforms": ["macos", "linux", "windows"], + "providers": ["local", "remote-runtime"], + "coveredPlatforms": ["macos"], + "coveredProviders": ["local"], + "coverageNotes": "Production SQLite and reducer tests on macOS. Execution-host-local storage behavior is shared by remote runtimes; no live SSH or Windows/Linux run. PTY, daemon, WSL process launch, transport framing and mobile rendering are unaffected.", + "motivatingLinks": [ + "https://github.com/stablyai/orca/blob/main/src/main/native-chat/agent-session-journal/journal-open.ts" + ], + "invariant": "Replay preserves latest revisions, original item order, fences, aliases, submissions, repair precedence, read-only schema latching and cursor cleanup while retaining live items rather than all historical bodies.", + "oracle": "Replay 2,048 16 KiB revisions into one latest item with less than 8 MiB sampled live heap growth; preserve prefix and future-schema latching after a gap, malformed suffix repair precedence, and hold no SQLite read snapshot across reduction (a mid-replay checkpoint is not busy). Existing journal and subscriber tests cover replayed content and recovery.", + "commands": [ + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts", + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal/journal-streaming-replay.test.ts src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts" + ], + "testFiles": [ + "src/main/native-chat/agent-session-journal/journal-streaming-replay.test.ts", + "src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts" + ], + "assertionRefs": [ + { + "file": "src/main/native-chat/agent-session-journal/journal-streaming-replay.test.ts", + "assertions": [ + "releases superseded revision bodies while reducing a long journal", + "holds no read snapshot while reducing, so a checkpoint can pass mid-replay", + "keeps the prefix but latches read-only for a future row beyond a gap", + "keeps gap repair precedence when a later row is malformed", + "rejects an unanchored prefix before a later gap" + ] + } + ], + "evidenceRuns": [ + { + "date": "2026-09-11", + "runner": "local", + "platform": "macos", + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/native-chat/agent-session-journal src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts", + "result": "passed", + "durationSeconds": 7.71, + "summary": "245 tests passed across 22 files. Retained-heap oracle fails on baseline at 68.6 MB and passes under 8 MiB with streaming; gap/schema and cursor-cleanup assertions passed." + } + ], + "runtimeBudget": { + "p95Seconds": 30, + "scope": "Unit fixtures; p95 not established." + }, + "flakeHistory": { + "status": "not-started", + "evidence": "Local candidate validation only; no CI soak." + }, + "redGreenEvidence": { + "status": "complete", + "evidence": "Production-function AB/BA benchmark samples 132.6 MB live heap in baseline vs 88-92 KB with streaming on a 66.7 MB revision-heavy journal. The retained-heap unit test fails against the original code and passes with streaming; value and cursor assertions pass in both implementations." + }, + "performanceBudget": { + "required": true, + "evidence": "Paged SQLite reads (one completed statement per page) and immediate reduction retain reduced state plus one page of rows. No cursor or read snapshot outlives its statement, so a WAL checkpoint can pass mid-replay; no new polling, subprocess, provider call or wire change." + }, + "knownGaps": [ + "No Windows/Linux runtime execution or real remote-host validation.", + "Latest live message bodies still require memory proportional to their total size; this removes superseded-history retention, not live-history storage." + ], + "promotionCriteria": ["Retain red/green heap and value assertions and complete CI soak."], + "demotionRule": "Keep experimental until cross-platform and soak evidence; investigate failures without weakening content or memory assertions." + }, { "id": "runtime.connection-owned-host-status", "title": "Host status recovers with its owning connection", @@ -162,6 +234,160 @@ ], "demotionRule": "Keep experimental if lifecycle or policy assertions fail; do not weaken them to bypass platform delivery gaps." }, + { + "id": "agent-session.structured-send-at-most-once", + "title": "Ambiguous structured sends never become a second provider delivery", + "maturity": "experimental", + "protection": "partial", + "owner": "agent-session-runtime", + "layer": "shared-host-renderer-and-mobile-unit", + "surfaces": ["desktop native chat", "mobile native chat", "structured agent-session RPC"], + "platforms": ["macos", "linux", "windows"], + "providers": ["local", "ssh", "remote-runtime", "mobile"], + "coveredPlatforms": ["macos"], + "coveredProviders": ["local", "mobile"], + "coverageNotes": "Deterministic host, renderer, direct/Relay transport, and mobile hook tests cover durable identity, caller changes, journal loss, acknowledgement loss, expiry, and remount. The host code is execution-location neutral, but live SSH/remote runtimes and physical iOS/Android lifecycle are not exercised.", + "motivatingLinks": ["https://github.com/stablyai/orca/pull/20133"], + "invariant": "One structured-send operation id causes at most one provider dispatch. A recorded or transport-ambiguous send reuses that id across retry, caller reconnect, client remount, and journal recovery; only a terminal rejection may rotate to a first delivery.", + "oracle": "Inject adapter acknowledgement loss, RPC response loss, caller replacement, logical-client close after response, auth recovery with a written request, missing journal submissions, legacy pending rows, stale fences, operation expiry, mobile remount, and durable-journal capacity. Assert one provider dispatch or one operation id for every ambiguous retry, fresh identity only after rejection, and no eviction of ambiguous mobile ids.", + "commands": [ + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts", + "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/mobile-structured-session-operation-retention.test.ts ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts" + ], + "testFiles": [ + "src/shared/agent-session-operation-ledger.test.ts", + "src/shared/structured-agent-session-send-disposition.test.ts", + "src/main/runtime/agent-session-operation-admission.test.ts", + "src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts", + "src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts", + "src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts", + "src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts", + "src/main/runtime/orchestration/structured-pointer-operation-id.test.ts", + "src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx", + "src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx", + "src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx", + "src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx", + "src/renderer/src/lib/launch-structured-agent-session.test.ts", + "mobile/src/session/mobile-native-chat-image-attachment.test.ts", + "mobile/src/session/use-mobile-native-chat-image-attachments.test.ts", + "mobile/src/session/mobile-structured-send-operation-journal.test.ts", + "mobile/src/session/mobile-structured-session-operation-retention.test.ts", + "mobile/src/session/mobile-structured-send-delivery.test.ts", + "mobile/src/session/use-mobile-structured-agent-session-send.test.tsx", + "mobile/src/session/use-mobile-structured-agent-session.test.tsx", + "mobile/src/transport/mobile-relay-rpc-session.test.ts", + "mobile/src/transport/rpc-client-delivery-ambiguity.test.ts", + "mobile/src/transport/stable-logical-rpc-client.test.ts" + ], + "assertionRefs": [ + { + "file": "src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts", + "assertions": [ + "never redelivers after admission survives without its journal submission", + "fails closed when a legacy pending row survives without its submission", + "never reruns an admission-only send after the caller changes", + "reuses a pending send admission after the client refreshes its fence", + "settles a submission write failure as rejected before provider dispatch" + ] + }, + { + "file": "src/main/runtime/agent-session-operation-admission.test.ts", + "assertions": ["replays the original row after the caller identity changes"] + }, + { + "file": "mobile/src/session/use-mobile-structured-agent-session-send.test.tsx", + "assertions": [ + "keeps one id across acknowledgement loss and host unknown replays", + "reuses an ambiguous id after the session hook remounts", + "keeps an ambiguous id after the host replay window expires", + "reuses the original uploaded attachment identity after acknowledgement loss", + "keeps the send id after a pending-admission refusal", + "rotates after a %s pre-handler RPC refusal that proves the send did not run" + ] + }, + { + "file": "src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx", + "assertions": ["reuses an option operation after a pending admission refusal"] + }, + { + "file": "src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts", + "assertions": ["retries a rejected nudge on the next journal edge"] + }, + { + "file": "src/main/runtime/orchestration/structured-pointer-operation-id.test.ts", + "assertions": ["never re-mints an ambiguous batch after the host replay window expires"] + }, + { + "file": "mobile/src/transport/rpc-client-delivery-ambiguity.test.ts", + "assertions": [ + "marks a written request unknown when another request triggers auth recovery" + ] + }, + { + "file": "mobile/src/transport/stable-logical-rpc-client.test.ts", + "assertions": ["preserves a committed response when logical close wins the callback race"] + }, + { + "file": "mobile/src/transport/mobile-relay-rpc-session.test.ts", + "assertions": ["keeps a synchronous pre-write relay failure definite"] + }, + { + "file": "src/shared/structured-agent-session-send-disposition.test.ts", + "assertions": ["never rotates $state operation after its host tombstone expires"] + } + ], + "evidenceRuns": [ + { + "date": "2026-09-12", + "runner": "local", + "platform": "macos", + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts", + "result": "passed", + "durationSeconds": 22.1, + "summary": "Thirteen focused host, shared, renderer, and orchestration files passed 148 tests." + }, + { + "date": "2026-09-12", + "runner": "local", + "platform": "macos", + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/mobile-structured-session-operation-retention.test.ts ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts", + "result": "passed", + "durationSeconds": 2.37, + "summary": "Ten focused mobile session, attachment, and transport files passed 113 tests." + } + ], + "runtimeBudget": { + "p95Seconds": 45, + "scope": "Two deterministic unit commands; initial target pending CI soak." + }, + "flakeHistory": { + "status": "not-started", + "evidence": "Focused local runs pass; repeated CI history is not established." + }, + "redGreenEvidence": { + "status": "partial", + "evidence": "Four targeted failures were observed: spending the mobile id after acknowledgement loss made three retries use two ids; settling cross-caller recovery under the new caller left the original ledger row pending; returning no synthetic result for a legacy or crash-left pending row dispatched it as a new accepted send; and a8d3de8d20's blanket settlement rotated a stale-fence option id and failed the renderer invariant. The focused tests pass with the production fixes restored; a byte-identical old-production run is not recorded." + }, + "performanceBudget": { + "required": true, + "evidence": "No polling, timers, subprocesses, or provider fanout were added. Each mobile journal mutation parses the bounded journal and performs at most one full AsyncStorage rewrite; entries retain hashes, ids, and original attachment paths, cap at 4,096, and fail closed at capacity. A first host send performs three serialized whole-store durable transactions: admission, the pre-effect unknown tombstone, and final settlement. Lease and fence admission now share one transaction with the operation row, so a transient pre-effect refusal leaves no row and, absent pruning, performs no store write. No capacity-scale host fsync or mobile AsyncStorage latency benchmark is recorded." + }, + "promotionCriteria": [ + "Soak both commands in CI with zero unexplained flakes.", + "Add live physical-mobile and remote-runtime interruption evidence before claiming full provider coverage." + ], + "knownGaps": [ + "A payload-keyed mobile ambiguity cannot distinguish retrying the original send from an intentional later send with identical content. It fails closed until authoritative settlement, so the claim that retention costs no liveness is false without a durable composer-action identity.", + "Clearing or externally corrupting desktop localStorage or mobile AsyncStorage can erase a client-owned ambiguous identity.", + "An ambiguous id retained past host tombstone expiry remains safely blocked rather than becoming live again.", + "A host crash after premarking the operation unknown but before journal append or provider dispatch can conservatively suppress a message that never reached the provider.", + "A replay synthesized from an operation row whose journal submission is missing is not republished into the journal; desktop stops automatic polling but remains safely blocked on that FIFO head.", + "The changed send guarantee is not capability-negotiated. New mobile clients fail closed across caller-identity change, but old clients can rotate an ambiguous id against a new host, and an old mobile client can interpret an ok response carrying unknown as accepted.", + "An ambiguous attachment retry depends on the original host temp path remaining usable when the first request never reached the host; if it is gone, the retry rejects rather than rotating.", + "No live SSH, remote-runtime, physical iOS/Android, Linux, or Windows interruption run is recorded." + ], + "demotionRule": "Keep experimental or demote if any ambiguous retry changes operation id, provider dispatch count exceeds one, durable identity is evicted by age or capacity, or the focused commands flake without a diagnosed harness defect." + }, { "id": "agent-session.completed-turn-duration", "title": "Completed turn duration survives client recovery and history pagination", @@ -15544,6 +15770,93 @@ ], "demotionRule": "Cannot promote without metric artifacts and stable p95 runtime history." }, + { + "id": "terminal-performance.daemon-ndjson-wire-parity-and-serialization", + "title": "Daemon NDJSON preserves wire bytes within a serialization count budget", + "maturity": "experimental", + "protection": "partial", + "owner": "terminal-performance", + "layer": "daemon-provider-contract", + "surfaces": ["daemon stream", "NDJSON framing", "stream data batching"], + "platforms": ["macos", "linux", "windows"], + "providers": ["daemon", "ssh", "remote-runtime"], + "coveredPlatforms": ["macos"], + "coveredProviders": ["daemon"], + "coverageNotes": "Deterministic writer, batcher, droppability and NDJSON suites run locally on macOS with mocked socket/process boundaries. An SSH-shaped session ID is a string fixture, not SSH transport evidence. No live daemon, platform integration or mixed-version client/host pair is exercised; folder and git workspaces are not distinguished by these stream contracts.", + "motivatingLinks": ["src/main/daemon/daemon-stream-data-split.ts"], + "invariant": "Reusing an encoded unsplit metadata-free frame must preserve the previous writer's exact wire bytes and chunk boundaries while reducing that path to one encode; oversized and metadata-bearing writes retain existing semantics and transformed writes remain uncapped single frames.", + "oracle": "Compare emitted lines to the previous writer algorithm across byte caps, escaped and Unicode payloads, session IDs, raw lengths, sequence numbers and transformed spans; assert exact newline framing, reconstruct split payloads, check surrogate boundaries and sequence spans, and count encodeNdjson calls.", + "commands": [ + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/daemon-stream-data-split.test.ts src/main/daemon/daemon-stream-data-batcher.test.ts src/main/daemon/daemon-stream-droppable-membership.test.ts src/main/daemon/daemon-stream-droppability-lifecycle.test.ts src/main/daemon/ndjson.test.ts" + ], + "testFiles": [ + "src/main/daemon/daemon-stream-data-split.test.ts", + "src/main/daemon/daemon-stream-data-batcher.test.ts", + "src/main/daemon/daemon-stream-droppable-membership.test.ts", + "src/main/daemon/daemon-stream-droppability-lifecycle.test.ts", + "src/main/daemon/ndjson.test.ts" + ], + "assertionRefs": [ + { + "file": "src/main/daemon/daemon-stream-data-split.test.ts", + "assertions": [ + "encodes an unsplit metadata-free frame once: %j", + "reuses the encoded frame exactly at the inclusive byte cap", + "does not add a duplicate full-data sizing probe to oversized writes", + "keeps transformed writes at one encode without applying the ordinary byte cap", + "preserves exact frames, chunk boundaries and metadata across payloads and caps", + "keeps JSON escaping, Unicode and newline framing byte-for-byte", + "keeps split frames within the byte cap and preserves code points and sequence spans" + ] + }, + { + "file": "src/main/daemon/daemon-stream-data-batcher.test.ts", + "assertions": ["writes large stream data as parser-sized NDJSON events"] + }, + { + "file": "src/main/daemon/ndjson.test.ts", + "assertions": ["measures multibyte payloads in UTF-8 bytes, not characters"] + } + ], + "evidenceRuns": [ + { + "date": "2026-09-11", + "runner": "local", + "platform": "macos", + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/daemon-stream-data-split.test.ts src/main/daemon/daemon-stream-data-batcher.test.ts src/main/daemon/daemon-stream-droppable-membership.test.ts src/main/daemon/daemon-stream-droppability-lifecycle.test.ts src/main/daemon/ndjson.test.ts", + "result": "passed", + "summary": "Five daemon suites passed: 70 tests total, including the new 12-test splitter suite. Vitest reported 14.76 seconds; no app or live transport validation was performed.", + "durationSeconds": 14.76 + } + ], + "runtimeBudget": { + "p95Seconds": 60, + "scope": "Target budget for the five deterministic suites; one local Vitest run took 14.76 seconds, not an established p95. Native-runtime setup is excluded from the reported Vitest duration." + }, + "flakeHistory": { + "status": "not-started", + "evidence": "Fresh local five-suite run passed; no sustained CI soak history is established." + }, + "redGreenEvidence": { + "status": "partial", + "evidence": "Historical report states serialization-count tests failed against the old writer. That baseline run was not repeated for this registration; the fresh candidate run passed all 70 tests. The previous-writer oracle checks byte parity, not live cross-version compatibility." + }, + "performanceBudget": { + "required": true, + "evidence": "Unsplit metadata-free frames, including the inclusive byte-cap boundary, require exactly one encodeNdjson call; oversized writes must not exceed the previous writer's encode count; transformed writes require one encode. These are deterministic call-count budgets, not measured throughput, CPU, heap or input-latency improvements." + }, + "promotionCriteria": [ + "Capture reproducible old-writer red and candidate green artifacts for the serialization-count assertions.", + "Collect CI soak evidence before promotion and validate Linux/Windows runtimes and live SSH/remote and mixed-version pairs before claiming those integrations." + ], + "knownGaps": [ + "No live daemon/Electron, Linux, Windows, WSL, SSH, remote-runtime or mixed-version client/host evidence.", + "The historical red run has not been independently reproduced for this registration; the parity oracle reuses current splitter/encoder helpers.", + "No sustained soak, measured p95, wall-clock performance benchmark or native socket backpressure guarantee.", + "Byte-cap assertions cover ordinary split frames at a viable cap; tiny caps and transformed frames retain legacy behavior rather than gaining a universal cap guarantee." + ], + "demotionRule": "Keep experimental until reproducible red/green and soak evidence exist; do not relax exact wire-byte, chunk-boundary or encode-count assertions to hide regressions." + }, { "id": "terminal-performance.daemon-stream-backpressure", "title": "Daemon terminal streams respect socket backpressure under output floods", diff --git a/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs b/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs new file mode 100644 index 00000000000..47407764e0f --- /dev/null +++ b/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs @@ -0,0 +1,90 @@ +// Run: node --expose-gc config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs [baseline-ref] +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFile } from 'node:fs/promises' +import { performance } from 'node:perf_hooks' +import { transform } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const sourcePath = 'src/renderer/src/components/dashboard/agent-row-lineage-model.ts' +const baseline = process.argv[2] ?? '20ab9950654' +const beforeSource = execFileSync('git', ['show', `${baseline}:${sourcePath}`], { + encoding: 'utf8', + windowsHide: true +}) +async function load(source) { + const { code } = await transform(source, { loader: 'ts', format: 'esm' }) + return (await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`)) + .buildAgentRowLineageTree +} +const before = await load(beforeSource) +const after = await load(await readFile(sourcePath, 'utf8')) + +function row(index, parent) { + return { + paneKey: `pane-${index}`, + entry: { + terminalHandle: `term-${index}`, + orchestration: parent === undefined ? undefined : { parentPaneKey: `pane-${parent}` } + } + } +} + +// Exercise duplicate keys, disconnected cycles, missing parents, and handle fallback. +let seed = 7391 +function random(max) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return seed % max +} +for (let sample = 0; sample < 500; sample++) { + const rows = Array.from({ length: 40 }, () => { + const value = row(random(30), random(40)) + value.entry.orchestration.parentTerminalHandle = `term-${random(40)}` + value.entry.orchestration.coordinatorHandle = `term-${random(40)}` + return value + }) + if (sample % 2 === 0) { + rows.unshift(row('root', undefined)) + } + assert.deepEqual(after(rows), before(rows)) +} + +const results = [] +for (const [shape, count] of [ + ['flat', 1000], + ['all-cycles', 1000], + ['mixed-cycles', 100], + ['mixed-cycles', 500], + ['mixed-cycles', 1000] +]) { + const rows = Array.from({ length: count }, (_, index) => + row(index, shape === 'flat' ? undefined : index ^ 1) + ) + if (shape === 'mixed-cycles') { + rows.unshift(row('root', undefined)) + } + assert.deepEqual(after(rows), before(rows)) + for (let warmup = 0; warmup < 30; warmup++) { + before(rows) + after(rows) + } + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) { + for (const arm of pair) { + global.gc?.() + const run = arm === 'before' ? before : after + const cpu = process.cpuUsage() + const start = performance.now() + for (let iteration = 0; iteration < 30; iteration++) { + run(rows) + } + const wallMs = (performance.now() - start) / 30 + const used = process.cpuUsage(cpu) + samples[arm].push({ wallMs, cpuMs: (used.user + used.system) / 30_000 }) + } + } + results.push({ shape, count, samples }) +} +console.log( + JSON.stringify({ baseline, node: process.version, parityGraphs: 500, results }, null, 2) +) diff --git a/config/scripts/agent-lineage-reachability-benchmark.mjs b/config/scripts/agent-lineage-reachability-benchmark.mjs new file mode 100644 index 00000000000..4122ac18dab --- /dev/null +++ b/config/scripts/agent-lineage-reachability-benchmark.mjs @@ -0,0 +1,114 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { performance } from 'node:perf_hooks' +import { transform } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' +import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs' + +// git show :src/renderer/src/components/dashboard/agent-row-lineage-model.ts | node config/scripts/agent-lineage-reachability-benchmark.mjs +async function load(source) { + const { code } = await transform(source, { loader: 'ts', format: 'esm' }) + return (await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`)) + .buildAgentRowLineageTree +} +const implementations = { + before: await load(readFileSync(0, 'utf8')), + after: await load( + readFileSync('src/renderer/src/components/dashboard/agent-row-lineage-model.ts', 'utf8') + ) +} +function orderedTree(tree) { + return { + roots: tree.rootRows, + children: [...tree.childrenByParentPaneKey], + childKeys: [...tree.childPaneKeys] + } +} + +let seed = 42 +const random = (max) => { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return Math.floor((seed / 2 ** 32) * max) +} +let differentialCases = 0 +for (let trial = 0; trial < 5000; trial += 1) { + const count = random(100) + const rows = Object.freeze( + Array.from({ length: count }, (_, index) => + Object.freeze({ + paneKey: `pane-${random(count + 4)}`, + index, + entry: Object.freeze({ + terminalHandle: random(2) ? `term-${random(count)}` : undefined, + orchestration: Object.freeze({ + parentPaneKey: random(3) ? `pane-${random(count + 4)}` : undefined, + parentTerminalHandle: random(2) ? `term-${random(count)}` : undefined, + coordinatorHandle: random(2) ? `term-${random(count)}` : undefined + }) + }) + }) + ) + ) + assert.deepEqual( + orderedTree(implementations.after(rows)), + orderedTree(implementations.before(rows)) + ) + differentialCases += 1 +} + +const results = [] +for (const count of [8, 32, 128, 512, 1024]) { + for (const shape of ['flat', 'fanout', 'balanced', 'chain']) { + const rows = Array.from({ length: count }, (_, index) => { + const parent = + shape === 'fanout' ? 0 : shape === 'balanced' ? Math.floor((index - 1) / 4) : index - 1 + return { + paneKey: `pane-${index}`, + entry: { + orchestration: + index > 0 && shape !== 'flat' ? { parentPaneKey: `pane-${parent}` } : undefined + } + } + }) + const expected = orderedTree(implementations.before(rows)) + assert.deepEqual(orderedTree(implementations.after(rows)), expected) + const iterations = Math.max(5, Math.floor(10_000 / count)) + for (let warmup = 0; warmup < 20; warmup += 1) { + implementations.before(rows) + implementations.after(rows) + } + /** @type {{ before: number[], after: number[] }} */ + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) { + for (const arm of pair) { + let result + const started = performance.now() + for (let repeat = 0; repeat < iterations; repeat += 1) { + result = implementations[arm](rows) + } + samples[arm].push(performance.now() - started) + assert.deepEqual(orderedTree(result), expected) + } + } + results.push({ + count, + shape, + iterations, + meanMicrosecondsPerTree: Object.fromEntries( + Object.entries(samples).map(([arm, values]) => [ + arm, + (values.reduce((sum, ms) => sum + ms, 0) * 1000) / values.length / iterations + ]) + ), + before: summarizeBenchmarkSamples(samples.before), + after: summarizeBenchmarkSamples(samples.after) + }) + } +} +console.log( + JSON.stringify( + { node: process.version, platform: process.platform, differentialCases, results }, + null, + 2 + ) +) diff --git a/config/scripts/build-native-for-platform.mjs b/config/scripts/build-native-for-platform.mjs index d6294f4b5c7..c8817ab864b 100755 --- a/config/scripts/build-native-for-platform.mjs +++ b/config/scripts/build-native-for-platform.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { spawnSync } from 'node:child_process' +import { spawn, spawnSync } from 'node:child_process' import { resolvePnpmCliInvocation } from './pnpm-cli-invocation.mjs' if (process.platform === 'win32') { @@ -13,21 +13,184 @@ if (process.platform !== 'darwin') { process.exit(0) } -runPnpmScript('build:computer-macos') -runPnpmScript('build:keyboard-layout-macos') -runPnpmScript('build:notification-status-macos') -process.exit(0) +// Each compiler tree needs its own group so cancellation reaches Swift descendants. +const children = new Map() +let externalSignal = null +let stopping = false +let outputFailed = false +// Status of the child whose failure started cancellation; siblings we stop are not failures. +let firstFailure = null +let forceTimer +const signalHandlers = new Map() + +process.on('SIGINT', handlerFor('SIGINT')) +process.on('SIGTERM', handlerFor('SIGTERM')) +// Own sessions do not see a terminal hangup; forward it so compilers do not outlive the shell. +process.on('SIGHUP', handlerFor('SIGHUP')) +for (const target of [process.stdout, process.stderr]) { + target.on('error', () => { + outputFailed = true + process.exitCode = 1 + stopBuilds() + }) +} + +const exitCodes = await Promise.all( + ['build:computer-macos', 'build:keyboard-layout-macos', 'build:notification-status-macos'].map( + (scriptName) => runPnpmScript(scriptName) + ) +) +clearTimeout(forceTimer) +for (const [signal, handler] of signalHandlers) { + process.removeListener(signal, handler) +} +if (externalSignal) { + process.kill(process.pid, externalSignal) +} else if (firstFailure?.signal) { + // Node ignores some signals (SIGPIPE); the build still failed if the re-raise is a no-op. + process.exitCode = 1 + process.kill(process.pid, firstFailure.signal) +} else { + process.exitCode = firstFailure?.code ?? Math.max(outputFailed ? 1 : 0, ...exitCodes) +} + +function handlerFor(signal) { + if (!signalHandlers.has(signal)) { + signalHandlers.set(signal, () => { + externalSignal ??= signal + stopBuilds(signal) + }) + } + return signalHandlers.get(signal) +} + +function stopBuilds(signal = 'SIGTERM') { + if (stopping) { + return + } + stopping = true + terminateAll(signal) + if (children.size > 0) { + forceTimer ??= setTimeout(() => terminateAll('SIGKILL'), 2_000) + } +} + +function terminateAll(signal) { + for (const [child, label] of children) { + if (!child.pid) { + continue + } + console.log(`[native-build] stopping ${label} (${signal})`) + try { + process.kill(-child.pid, signal) + } catch { + // group already gone + try { + child.kill(signal) + } catch {} + } + } +} function runPnpmScript(scriptName) { + if (stopping) { + return Promise.resolve(1) + } + const label = scriptName.replace(/^build:|-macos$/g, '') const { command, prefixArgs, shell } = resolvePnpmCliInvocation() - const result = spawnSync(command, [...prefixArgs, 'run', scriptName], { stdio: 'inherit', shell }) + const child = spawn(command, [...prefixArgs, 'run', scriptName], { + detached: true, + shell, + stdio: ['ignore', 'pipe', 'pipe'] + }) + children.set(child, scriptName) + pipePrefixed(child.stdout, label, process.stdout) + pipePrefixed(child.stderr, label, process.stderr) - if (result.signal) { - process.kill(process.pid, result.signal) - } - if (result.status !== 0 || result.error) { - process.exit(result.status ?? 1) - } + return new Promise((resolve) => { + let failed = false + child.on('error', (error) => { + failed = true + console.error(`[${label}] ${error.message}`) + if (!stopping) { + firstFailure = { code: 1, signal: null } + } + stopBuilds() + }) + let exited = false + let closeTimer + // A descendant that inherited the pipes must not hold the launcher open forever. + const armReap = () => { + clearTimeout(closeTimer) + // A backpressure pause also delays 'close'; only count time spent actually draining. + if (child.stdout.isPaused() || child.stderr.isPaused()) { + return + } + closeTimer = setTimeout(() => { + console.error(`[native-build] ${label} left descendants holding its output; reaping them`) + try { + process.kill(-child.pid, 'SIGKILL') + } catch {} + child.stdout.destroy() + child.stderr.destroy() + }, 2_000) + } + for (const stream of [child.stdout, child.stderr]) { + stream.on('pause', () => clearTimeout(closeTimer)) + stream.on('resume', () => { + if (exited) { + armReap() + } + }) + } + child.on('exit', (code, signal) => { + if (code !== 0 || signal) { + if (!stopping) { + firstFailure = { code: code ?? 1, signal } + } + stopBuilds() + } + exited = true + armReap() + }) + // Re-raise the parent's signal only after every child and its output pipes close. + child.on('close', (code, signal) => { + clearTimeout(closeTimer) + children.delete(child) + resolve(failed || signal ? 1 : (code ?? 1)) + }) + }) +} + +function pipePrefixed(stream, label, target) { + stream.setEncoding('utf8') + let buffer = '' + stream.on('data', (chunk) => { + if (target.destroyed) { + return + } + buffer += chunk + const lines = buffer.split('\n') + buffer = lines.pop() ?? '' + for (const line of lines) { + target.write(`[${label}] ${line}\n`) + } + if (target.writableNeedDrain) { + stream.pause() + const resume = () => { + target.off('drain', resume) + target.off('close', resume) + stream.resume() + } + target.once('drain', resume) + target.once('close', resume) + } + }) + stream.on('end', () => { + if (buffer.length > 0 && !target.destroyed) { + target.write(`[${label}] ${buffer}\n`) + } + }) } function runNodeScript(scriptPath) { diff --git a/config/scripts/build-native-for-platform.test.mjs b/config/scripts/build-native-for-platform.test.mjs new file mode 100644 index 00000000000..ce15aa54dc6 --- /dev/null +++ b/config/scripts/build-native-for-platform.test.mjs @@ -0,0 +1,361 @@ +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { afterEach, describe, expect, it } from 'vitest' +import { spawnProcess } from '../../src/shared/child-process/run-process' + +const buildScript = fileURLToPath(new URL('./build-native-for-platform.mjs', import.meta.url)) +const directories = [] +const children = [] +const buildPids = new Set() + +afterEach(() => { + for (const child of children.splice(0)) { + child.kill('SIGKILL') + } + for (const pid of buildPids) { + try { + process.kill(-pid, 'SIGKILL') + } catch {} + } + buildPids.clear() + for (const directory of directories.splice(0)) { + rmSync(directory, { recursive: true, force: true }) + } +}) + +function startBuild(mode, options = {}) { + const directory = mkdtempSync(join(tmpdir(), 'orca-native-build-test-')) + directories.push(directory) + const cli = join(directory, 'fake-pnpm.mjs') + const journal = join(directory, 'events.jsonl') + writeFileSync(journal, '') + mkdirSync(join(directory, 'config', 'scripts'), { recursive: true }) + writeFileSync( + join(directory, 'config', 'scripts', 'build-windows-cli-launcher.mjs'), + "console.log('windows launcher only')" + ) + writeFileSync( + cli, + ` + import { appendFileSync, existsSync } from 'node:fs' + import { spawn } from 'node:child_process' + const name = process.argv.at(-1) + const delay = name.includes('computer') ? 0 : name.includes('keyboard') ? 200 : 400 + const record = (event, extra = {}) => appendFileSync(process.env.NATIVE_BUILD_JOURNAL, JSON.stringify({ name, event, pid: process.pid, ...extra }) + '\\n') + const finish = (signal) => { record(signal); process.exit(process.env.NATIVE_BUILD_MODE === 'failure-status' ? 9 : 0) } + if (process.env.NATIVE_BUILD_MODE !== 'signal-default') process.on('SIGTERM', () => { if (process.env.NATIVE_BUILD_MODE !== 'ignore') setTimeout(() => finish('SIGTERM'), delay) }) + if (process.env.NATIVE_BUILD_MODE !== 'signal-default' || !name.includes('computer')) process.on('SIGINT', () => setTimeout(() => finish('SIGINT'), delay)) + process.on('SIGHUP', () => setTimeout(() => finish('SIGHUP'), delay)) + record('started') + process.stdout.write('ready ' + process.pid + '\\n') + if (process.env.NATIVE_BUILD_MODE.startsWith('descendant')) { + spawn(process.execPath, ['-e', ${JSON.stringify("process.on('SIGTERM', () => {}); console.log('descendant ' + process.pid); setInterval(() => {}, 1000)")}], { stdio: 'inherit' }) + } + let flooding = false + setInterval(() => { + if (!existsSync(process.env.NATIVE_BUILD_GATE)) return + if (process.env.NATIVE_BUILD_MODE.startsWith('output-closed-')) { + const target = process.env.NATIVE_BUILD_MODE.endsWith('stderr') ? process.stderr : process.stdout + if (name.includes('computer')) target.write('compiler progress\\n') + return + } + if (process.env.NATIVE_BUILD_MODE === 'flood') { + if (flooding) return + flooding = true + const chunk = 'f'.repeat(65535) + '\\n' + const pump = () => { while (process.stdout.write(chunk)) {} ; process.stdout.once('drain', pump) } + pump() + return + } + if (['success', 'descendant-success'].includes(process.env.NATIVE_BUILD_MODE)) { record('completed'); process.exit(0) } + if (process.env.NATIVE_BUILD_MODE === 'stalled-consumer') { + if (!name.includes('computer') || existsSync(process.env.NATIVE_BUILD_GATE + '-exit')) { record('completed'); process.exit(0) } + if (flooding) return + flooding = true + // Each callback means the kernel pipe accepted the line, so it survives our exit. + const pump = (line) => process.stdout.write('line ' + line + ' ' + 'x'.repeat(190) + '\\n', () => { record('accepted', { line }); pump(line + 1) }) + pump(1) + return + } + if (!name.includes('computer')) return + record('failed') + if (process.env.NATIVE_BUILD_MODE === 'failure-signal') process.kill(process.pid, 'SIGALRM') + else if (process.env.NATIVE_BUILD_MODE === 'failure-sigpipe') { process.on('SIGPIPE', () => {}); process.removeAllListeners('SIGPIPE'); process.kill(process.pid, 'SIGPIPE') } + else process.exit(7) + }, 10) + ` + ) + const child = spawnProcess({ + program: process.execPath, + args: [ + ...(options.platform + ? [ + '--import', + `data:text/javascript,${encodeURIComponent(`Object.defineProperty(process, 'platform', { value: '${options.platform}' })`)}` + ] + : []), + ...(options.reportBuffered + ? [ + '--import', + `data:text/javascript,${encodeURIComponent(`import { appendFileSync } from 'node:fs'; setInterval(() => appendFileSync(process.env.NATIVE_BUILD_JOURNAL, JSON.stringify({ name: 'launcher', event: 'buffered', bytes: process.stdout.writableLength }) + '\\n'), 50).unref()`)}` + ] + : []), + ...(options.lateOutputError + ? [ + '--import', + `data:text/javascript,${encodeURIComponent(`process.once('beforeExit', () => process.${options.lateOutputError}.emit('error', new Error('late output failure')))`)}` + ] + : []), + buildScript + ], + cwd: directory, + env: { + ...process.env, + ORCA_BACKGROUND_LAUNCH: '1', + npm_execpath: options.missingCli ? join(directory, 'missing-pnpm') : cli, + NATIVE_BUILD_JOURNAL: journal, + NATIVE_BUILD_GATE: join(directory, 'release'), + NATIVE_BUILD_MODE: mode + }, + stdio: ['ignore', 'pipe', 'pipe'] + }) + children.push(child) + let output = '' + let stderr = '' + let descendantPids = [] + let readyResolve + const ready = new Promise((resolve) => { + readyResolve = resolve + }) + child.stdout.on('data', (chunk) => { + output += chunk.toString() + const pids = [...output.matchAll(/ready (\d+)/g)].map((match) => Number(match[1])) + descendantPids = [...output.matchAll(/descendant (\d+)/g)].map((match) => Number(match[1])) + for (const pid of pids) { + buildPids.add(pid) + } + if (pids.length === 3 && (!mode.startsWith('descendant') || descendantPids.length === 3)) { + readyResolve() + } + }) + child.stderr.on('data', (chunk) => { + stderr += chunk.toString() + }) + const closed = new Promise((resolve, reject) => { + child.on('error', reject) + child.on('close', (code, signal) => resolve({ code, signal, output, stderr })) + }) + return { + child, + ready, + closed, + descendants: () => descendantPids, + release: () => writeFileSync(join(directory, 'release'), ''), + releaseExit: () => writeFileSync(join(directory, 'release-exit'), ''), + events: () => + readFileSync(journal, 'utf8') + .trim() + .split('\n') + .filter(Boolean) + .map((line) => JSON.parse(line)) + } +} + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + +async function waitFor(condition, timeoutMs = 5_000) { + const deadline = Date.now() + timeoutMs + while (!condition()) { + if (Date.now() > deadline) { + throw new Error('timed out waiting for condition') + } + await sleep(50) + } +} + +describe.skipIf(process.platform !== 'darwin')('parallel native builds', () => { + it('starts every independent build before any completes', async () => { + const build = startBuild('success') + await build.ready + expect(build.events().map(({ event }) => event)).toEqual(['started', 'started', 'started']) + build.release() + expect(await build.closed).toMatchObject({ code: 0, signal: null }) + expect(build.events().filter(({ event }) => event === 'completed')).toHaveLength(3) + }) + + it.each(['SIGINT', 'SIGTERM', 'SIGHUP'])( + 'waits for every sibling before re-raising %s', + async (signal) => { + const build = startBuild('signal') + await build.ready + build.child.kill(signal) + expect(await build.closed).toMatchObject({ code: null, signal }) + expect(build.events().filter(({ event }) => event === signal)).toHaveLength(3) + } + ) + + it('waits for sibling cancellation when a build fails', async () => { + const build = startBuild('failure') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: 7, signal: null }) + expect(build.events().filter(({ event }) => event === 'SIGTERM')).toHaveLength(2) + }) + + it('reports the first failure, not the status of siblings it cancelled', async () => { + const build = startBuild('failure-status') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: 7, signal: null }) + expect(build.events().filter(({ event }) => event === 'SIGTERM')).toHaveLength(2) + }) + + it('re-raises the signal that killed a build', async () => { + const build = startBuild('failure-signal') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: null, signal: 'SIGALRM' }) + expect(build.events().filter(({ event }) => event === 'SIGTERM')).toHaveLength(2) + }) + + it('fails when the signal that killed a build is one the launcher ignores', async () => { + const build = startBuild('failure-sigpipe') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: 1, signal: null }) + expect(build.events().filter(({ event }) => event === 'SIGTERM')).toHaveLength(2) + }) + + it('lets siblings finish SIGINT cleanup when one child uses the default handler', async () => { + const build = startBuild('signal-default') + await build.ready + build.child.kill('SIGINT') + expect(await build.closed).toMatchObject({ code: null, signal: 'SIGINT' }) + expect(build.events().filter(({ event }) => event === 'SIGINT')).toHaveLength(2) + }) + + it('forces a sibling that ignores graceful cancellation to exit', async () => { + const build = startBuild('ignore') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: 7, signal: null }) + for (const { pid } of build.events().filter(({ event }) => event === 'started')) { + expect(() => process.kill(pid, 0)).toThrow() + } + }) + + it.each(['stdout', 'stderr'])( + 'stops quiet siblings when the %s consumer closes', + async (target) => { + const build = startBuild(`output-closed-${target}`) + await build.ready + build.child[target].destroy() + build.release() + + const result = await build.closed + expect(result).toMatchObject({ code: 1, signal: null }) + expect(result.stderr).not.toContain('Unhandled') + for (const { pid } of build.events().filter(({ event }) => event === 'started')) { + expect(() => process.kill(pid, 0)).toThrow() + } + } + ) + + it.each(['stdout', 'stderr'])( + 'fails on a late %s error after successful child exits', + async (target) => { + const build = startBuild('success', { lateOutputError: target }) + await build.ready + build.release() + + const result = await build.closed + expect(result).toMatchObject({ code: 1, signal: null }) + expect(result.stderr).not.toContain('Unhandled') + expect(build.events().filter(({ event }) => event === 'completed')).toHaveLength(3) + } + ) + + it('reports a missing build command without waiting forever', async () => { + const build = startBuild('success', { missingCli: true }) + expect(await build.closed).toMatchObject({ code: 1, signal: null }) + }) + + it('reaps compiler descendants that retain pipes after their launcher exits', async () => { + const build = startBuild('descendant') + await build.ready + build.child.kill('SIGTERM') + expect(await build.closed).toMatchObject({ code: null, signal: 'SIGTERM' }) + for (const pid of build.descendants()) { + expect(() => process.kill(pid, 0)).toThrow() + } + }) + + it("reaps descendants that keep a finished build's pipes open instead of hanging", async () => { + const build = startBuild('descendant-success') + await build.ready + build.release() + expect(await build.closed).toMatchObject({ code: 0, signal: null }) + expect(build.events().filter(({ event }) => event === 'completed')).toHaveLength(3) + for (const pid of build.descendants()) { + expect(() => process.kill(pid, 0)).toThrow() + } + }) + + it('stops reading compiler output while its own stdout is blocked', async () => { + const build = startBuild('flood', { reportBuffered: true }) + await build.ready + build.child.stdout.pause() + build.release() + await new Promise((resolve) => setTimeout(resolve, 1_500)) + const buffered = build + .events() + .filter(({ event }) => event === 'buffered') + .map(({ bytes }) => bytes) + expect(buffered.length).toBeGreaterThan(0) + expect(Math.max(...buffered)).toBeLessThan(1_000_000) + }) + + it('delivers every compiler line when its own stdout consumer stalls past the reap timeout', async () => { + const build = startBuild('stalled-consumer', { reportBuffered: true }) + await build.ready + build.child.stdout.pause() + build.release() + // Launcher stops reading once its stdout hits the high-water mark; then let the compiler fill its pipe. + await waitFor(() => + build.events().some(({ event, bytes }) => event === 'buffered' && bytes >= 16_384) + ) + await sleep(300) + build.releaseExit() + await waitFor(() => build.events().some(({ event }) => event === 'completed')) + const accepted = Math.max( + ...build + .events() + .filter(({ event }) => event === 'accepted') + .map(({ line }) => line) + ) + expect(accepted).toBeGreaterThan(0) + await sleep(3_000) + build.child.stdout.resume() + + const result = await build.closed + expect(result).toMatchObject({ code: 0, signal: null }) + const delivered = [...result.output.matchAll(/^\[computer\] line (\d+) /gm)].map((match) => + Number(match[1]) + ) + expect(delivered).toEqual(Array.from({ length: accepted }, (_, index) => index + 1)) + }) + + it.each(['linux', 'win32'])('keeps the %s entry point out of macOS builds', async (platform) => { + const build = startBuild('success', { platform }) + const result = await build.closed + expect(result).toMatchObject({ code: 0, signal: null }) + expect(build.events()).toEqual([]) + expect(result.output).toContain( + platform === 'win32' + ? 'windows launcher only' + : 'no macOS native computer build required on linux' + ) + }) +}) diff --git a/config/scripts/casting-code-quality.test.mjs b/config/scripts/casting-code-quality.test.mjs new file mode 100644 index 00000000000..a9be7bf7c80 --- /dev/null +++ b/config/scripts/casting-code-quality.test.mjs @@ -0,0 +1,153 @@ +import { spawnSync } from 'node:child_process' +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import path from 'node:path' +import { expect, it } from 'vitest' +import { + OXLINT_SCANS, + diagnosticTouchesAddedLines, + findCastingDirectivesMissingSafety, + isCastingDirectiveUnusedWarning +} from './check-changed-code-quality.mjs' +import { resolveOxlintInvocation } from './oxlint-cli-invocation.mjs' + +const root = path.resolve(import.meta.dirname, '..', '..') +const oxlint = resolveOxlintInvocation(root) +const rule = 'typescript(consistent-type-assertions)' +const ruleName = 'typescript/consistent-type-assertions' +// Built rather than written out so no line here is itself a casting directive the gate would scan. +const directive = (reason) => `// oxlint-disable-next-line ${ruleName} -- ${reason}` +const trailingDirective = (reason) => `// oxlint-disable-line ${ruleName} -- ${reason}` + +function lint(file, args = []) { + const result = spawnSync( + oxlint.command, + [...oxlint.prefixArgs, ...args, '--format', 'json', file], + { cwd: root, encoding: 'utf8', windowsHide: true } + ) + expect(result.error).toBeUndefined() + return { status: result.status, diagnostics: JSON.parse(result.stdout).diagnostics } +} + +it.each(['config', 'mobile'])('enforces new casts without changing full lint in %s', (parent) => { + const directory = mkdtempSync(path.join(root, parent, 'casting-lint-test-')) + const file = path.join(directory, 'fixture.test.ts') + try { + writeFileSync( + file, + [ + "export const oldCast = { current: '⌘N' as string | null }", + 'export const doubleCast = undefined as unknown as string', + "export const annotated: { current: string | null } = { current: '⌘N' }", + "export const constant = { current: '⌘N' } as const", + "export const checked = { current: '⌘N' } satisfies { current: string | null }", + directive('SAFETY: Exercise the explicit exception.'), + 'export const justified = undefined as unknown' + ].join('\n') + ) + + const full = lint(file) + expect(full.status).toBe(0) + expect(full.diagnostics.filter((diagnostic) => diagnostic.code === rule)).toEqual([]) + + const scan = OXLINT_SCANS.find((candidate) => candidate.label === 'casting code quality') + expect(scan).toBeDefined() + const casting = lint(file, scan.args) + expect(casting.status).toBe(1) + expect(casting.diagnostics).toHaveLength(3) + expect(casting.diagnostics.every((diagnostic) => diagnostic.code === rule)).toBe(true) + + const relative = path.relative(root, file).split(path.sep).join('/') + const changed = new Map([[relative, [{ start: 2, end: 2 }]]]) + const findings = casting.diagnostics.filter((diagnostic) => + diagnosticTouchesAddedLines(diagnostic, changed, root) + ) + expect(findings).toHaveLength(2) + expect(findings.every((diagnostic) => diagnostic.severity === 'error')).toBe(true) + + writeFileSync(file, 'export const angle = undefined\n') + expect(lint(file).status).toBe(1) + expect(lint(file, scan.args).diagnostics.map((diagnostic) => diagnostic.code)).toEqual([rule]) + } finally { + rmSync(directory, { recursive: true, force: true }) + } +}) + +it("exempts the SAFETY: directive from the untyped scan's unused-directive warning", () => { + const directory = mkdtempSync(path.join(root, 'config', 'casting-lint-test-')) + const file = path.join(directory, 'fixture.test.ts') + try { + writeFileSync( + file, + [ + directive('SAFETY: Verified invariant.'), + 'export const justified = undefined as unknown', + '' + ].join('\n') + ) + + const scan = OXLINT_SCANS.find((candidate) => candidate.label === 'code quality') + const untyped = lint(file, scan.args) + const unused = untyped.diagnostics.filter((diagnostic) => + diagnostic.message.startsWith('Unused oxlint-disable directive') + ) + + expect(unused).toHaveLength(1) + expect(unused.every((diagnostic) => isCastingDirectiveUnusedWarning(diagnostic, root))).toBe( + true + ) + } finally { + rmSync(directory, { recursive: true, force: true }) + } +}) + +it('rejects a casting suppression on an added line that omits the SAFETY: rationale', () => { + const directory = mkdtempSync(path.join(root, 'config', 'casting-lint-test-')) + const file = path.join(directory, 'fixture.test.ts') + try { + writeFileSync( + file, + [ + directive('no required prefix'), + 'export const unchecked = undefined as unknown', + directive('SAFETY: Verified invariant.'), + 'export const justified = undefined as unknown', + '' + ].join('\n') + ) + + const relative = path.relative(root, file).split(path.sep).join('/') + const findings = findCastingDirectivesMissingSafety( + root, + new Map([[relative, [{ start: 1, end: 4 }]]]) + ) + + expect(findings.map((finding) => finding.labels[0].span.line)).toEqual([1]) + + // Unchanged lines stay out of the gate. + expect( + findCastingDirectivesMissingSafety(root, new Map([[relative, [{ start: 3, end: 4 }]]])) + ).toEqual([]) + } finally { + rmSync(directory, { recursive: true, force: true }) + } +}) + +// Why: an earlier pattern skipped a directive whose `//` sat right after a quote, which let an +// unjustified cast through the gate -- the wrong failure direction for a gate. +it('catches a trailing casting suppression that abuts a string literal', () => { + const directory = mkdtempSync(path.join(root, 'config', 'casting-lint-test-')) + const file = path.join(directory, 'fixture.test.ts') + try { + writeFileSync(file, `export const abutted = 'a'${trailingDirective('no required prefix')}\n`) + + const relative = path.relative(root, file).split(path.sep).join('/') + const findings = findCastingDirectivesMissingSafety( + root, + new Map([[relative, [{ start: 1, end: 1 }]]]) + ) + + expect(findings.map((finding) => finding.labels[0].span.line)).toEqual([1]) + } finally { + rmSync(directory, { recursive: true, force: true }) + } +}) diff --git a/config/scripts/check-changed-code-quality.mjs b/config/scripts/check-changed-code-quality.mjs index af4e9e82776..a1b5b2fc88a 100644 --- a/config/scripts/check-changed-code-quality.mjs +++ b/config/scripts/check-changed-code-quality.mjs @@ -8,6 +8,9 @@ import { resolveOxlintInvocation } from './oxlint-cli-invocation.mjs' const SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/ const ROOT_CODE_QUALITY_IGNORED_PREFIXES = ['cloud/'] +const CASTING_RULE = 'typescript/consistent-type-assertions' +const CASTING_DISABLE_PATTERN = + /\/[/*]\s*(?:oxlint|eslint)-disable(?:-next-line|-line)?\s[^\n]*typescript\/consistent-type-assertions/ export const OXLINT_SCANS = [ { // Why: no --config, so Oxlint keeps discovering nested configs. Pinning the root @@ -15,6 +18,10 @@ export const OXLINT_SCANS = [ label: 'code quality', args: ['--report-unused-disable-directives-severity', 'warn'] }, + { + label: 'casting code quality', + args: ['--config', 'config/oxlint-code-quality-casting.json'] + }, { label: 'type-aware code quality', args: ['--type-aware', '--config', 'config/oxlint-code-quality-type-aware.json'] @@ -303,6 +310,50 @@ function printDiagnostic(diagnostic, root) { console.error(`${file}:${line} ${code}: ${diagnostic.message}`) } +// Why: only the casting scan enforces `assertionStyle: never`, so under the root config an +// `as` cast is legal and the SAFETY: directive AGENTS.md mandates reads as unused. The untyped +// scan reports that as a warning, which the gate counts, so exempt exactly those directives. +export function isCastingDirectiveUnusedWarning(diagnostic, root) { + if (!/^Unused (?:oxlint|eslint)-disable/.test(diagnostic.message ?? '')) { + return false + } + return (diagnostic.labels ?? []).some((label) => + diagnosticHighlightedLines(root, diagnostic.filename, label.span).some((line) => + CASTING_DISABLE_PATTERN.test(line) + ) + ) +} + +// Why: oxlint cannot see the AGENTS.md requirement that every casting suppression carry a +// line-specific SAFETY: rationale, so the directive text itself is checked over added lines. +export function findCastingDirectivesMissingSafety(root, rangesByFile) { + const findings = [] + for (const [file, ranges] of rangesByFile) { + const absolutePath = path.join(root, file) + if (!existsSync(absolutePath)) { + continue + } + readFileSync(absolutePath, 'utf8') + .split(/\r?\n/) + .forEach((text, index) => { + const line = index + 1 + if ( + CASTING_DISABLE_PATTERN.test(text) && + !text.includes('SAFETY:') && + overlapsAddedLines(line, line, ranges) + ) { + findings.push({ + filename: file, + code: `${CASTING_RULE} (missing SAFETY:)`, + message: `Suppressing ${CASTING_RULE} requires a line-specific "SAFETY:" explanation.`, + labels: [{ span: { line } }] + }) + } + }) + } + return findings +} + function isSuppressedDiagnostic(diagnostic, root) { const files = SUPPRESSED_REACT_DOCTOR_DIAGNOSTICS.get(diagnostic.code) return files?.has(normalizedDiagnosticPath(root, diagnostic.filename)) ?? false @@ -344,6 +395,7 @@ export function main( const diagnostics = runOxlintScan(root, scan, files).filter( (diagnostic) => !isSuppressedDiagnostic(diagnostic, root) && + !isCastingDirectiveUnusedWarning(diagnostic, root) && diagnosticTouchesAddedLines(diagnostic, rangesByFile, root, baseBlocks) ) for (const diagnostic of diagnostics) { @@ -355,6 +407,15 @@ export function main( ) } + const missingSafety = findCastingDirectivesMissingSafety(root, rangesByFile) + for (const diagnostic of missingSafety) { + printDiagnostic(diagnostic, root) + } + failures += missingSafety.length + console.log( + `casting SAFETY: rationale: ${missingSafety.length} new finding(s) across ${files.length} changed file(s).` + ) + if (failures > 0) { console.error( `Changed-code quality gate failed with ${failures} finding(s) since ${comparisonBase.slice(0, 12)}.` diff --git a/config/scripts/check-job-log-byte-cap-benchmark.mjs b/config/scripts/check-job-log-byte-cap-benchmark.mjs new file mode 100644 index 00000000000..3a8fd530919 --- /dev/null +++ b/config/scripts/check-job-log-byte-cap-benchmark.mjs @@ -0,0 +1,162 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' + +// Pipe the baseline check-job-log-tail-slice.ts on stdin; both arms use the actual UTF-8 implementation. +const entry = path.resolve('src/shared/check-job-log-tail-slice.ts') +const sources = [readFileSync(0, 'utf8'), readFileSync(entry, 'utf8')] +assert(sources.every((source) => source.includes('export function sliceCheckLogTail'))) + +async function load(source) { + const result = await build({ + entryPoints: [entry], + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: [ + { + name: 'log-excerpt-source', + setup(builder) { + builder.onLoad({ filter: /check-job-log-tail-slice\.ts$/ }, () => ({ + contents: source, + loader: 'ts', + resolveDir: path.dirname(entry) + })) + } + } + ] + }) + const bundled = `${result.outputFiles[0].text}\n//# sourceURL=check-log-byte-cap-benchmark-bundle.js` + return import(`data:text/javascript;base64,${Buffer.from(bundled).toString('base64')}`) +} + +const modules = await Promise.all(sources.map(load)) +const arms = modules.map((module) => module.sliceCheckLogTail) +const limit = modules[0].PR_CHECK_LOG_TAIL_BYTES +assert.equal(modules[1].PR_CHECK_LOG_TAIL_BYTES, limit) +let seed = 0xc0ffee16 +function random(max) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return (seed >>> 8) % max +} + +let comparisons = 0 +function compare(text) { + const expected = arms[0](text) + assert.equal(arms[1](text), expected) + assert(Buffer.byteLength(expected, 'utf8') <= limit) + comparisons++ + return expected +} + +const units = ['x', 'é', '界', '😀', '\ud83d', '\udc00', 'x\ud83d界\udc00'] +for (const unit of units) { + for (let delta = -4; delta <= 4; delta++) { + const text = unit.repeat(Math.floor(limit / Buffer.byteLength(unit)) + delta) + compare(text) + compare(`error: ${text}\n${'recent\n'.repeat(103)}`) + } +} +const endings = ['\n', '\r\n', '\r', ''] +const tokens = [ + 'plain text', + '##[error]', + '::error::', + 'error:', + 'FAILED', + 'exit code', + 'ENOENT', + 'EACCES', + 'panic:', + 'AssertionError', + 'emoji 😀', + '\ud83d', + '\udc00', + '\0', + '界', + 'é', + '\r' +] +for (let iteration = 0; iteration < 5000; iteration++) { + const rows = Array.from({ length: random(250) }, (_, index) => { + const token = tokens[random(tokens.length)] + if (index === 0 && iteration % 20 === 0) { + return `${token}${units[random(units.length)].repeat(limit + random(4))}` + } + return `${token} ${index} ${units[random(units.length)].repeat(random(30))}` + }) + compare(rows.join(endings[random(endings.length)]) + endings[random(endings.length)]) +} +console.log(`${comparisons} full-output differential cases passed`) + +const workloads = [ + ['short ASCII', 'log '.repeat(16)], + ['short Unicode', '🦀界'.repeat(20)], + ['8KiB ASCII', 'x'.repeat(8192)], + ['16KiB ASCII exact cap', 'x'.repeat(limit)], + ['8Ki code units / 24KiB Unicode', '界'.repeat(8192)], + ['2MiB ASCII line', 'x'.repeat(2 * 1024 * 1024)], + ['8MiB ASCII line', 'x'.repeat(8 * 1024 * 1024)], + ['2MiB Unicode line', '界'.repeat(Math.floor((2 * 1024 * 1024) / 3))], + [ + '2MiB earlier error context', + `error: ${'x'.repeat(2 * 1024 * 1024)}\n${'recent\n'.repeat(103)}` + ], + [ + '220 ordinary lines', + Array.from({ length: 220 }, (_, i) => `line ${i} ${'text'.repeat(8)}`).join('\n') + ], + [ + '220 lines / small earlier error', + Array.from( + { length: 220 }, + (_, i) => `${i === 30 ? 'error:' : 'line'} ${i} ${'text'.repeat(8)}` + ).join('\n') + ] +] + +function sample(arm, input, expected, repeats) { + const started = performance.now() + let output + for (let i = 0; i < repeats; i++) { + output = arm(input) + } + const elapsed = (performance.now() - started) / repeats + assert.equal(output, expected) + return elapsed +} + +console.log( + JSON.stringify({ + node: process.version, + platform: process.platform, + arch: process.arch, + pairs: 8, + unit: 'ms' + }) +) +for (const [name, input] of workloads) { + const expected = compare(input) + for (const arm of arms) { + const until = performance.now() + 80 + while (performance.now() < until) { + sample(arm, input, expected, 1) + } + } + const repeats = Math.max(1, Math.min(100000, Math.ceil(40 / sample(arms[0], input, expected, 1)))) + /** @type {number[][]} */ + const samples = [[], []] + for (let pair = 0; pair < 8; pair++) { + for (const index of pair % 2 ? [1, 0] : [0, 1]) { + samples[index].push(sample(arms[index], input, expected, repeats)) + } + } + const median = samples.map((values) => { + values.sort((a, b) => a - b) + return (values[3] + values[4]) / 2 + }) + console.log(JSON.stringify({ name, repeats, median, samples })) +} diff --git a/config/scripts/check-readme-local-links.mjs b/config/scripts/check-readme-local-links.mjs new file mode 100644 index 00000000000..723d7e3ccce --- /dev/null +++ b/config/scripts/check-readme-local-links.mjs @@ -0,0 +1,102 @@ +import { execFileSync } from 'node:child_process' +import { readdirSync, readFileSync } from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { pathToFileURL } from 'node:url' + +// Why: the READMEs embed media from trees other jobs own (docs-site public media, +// generated feature-wall tiles), and the docs-only classifier skips the whole CI +// matrix for one of them. GitHub renders only committed files, so this checks the +// git index rather than the working tree. +const TRANSLATED_README_DIR = path.join('docs', 'readme') +const EXTERNAL_TARGET = /^(?:[a-z][a-z0-9+.-]*:|#|\/\/)/i +const HTML_ATTRIBUTE = /\b(?:src|srcset|href)\s*=\s*(?:"([^"]*)"|'([^']*)')/g +const MARKDOWN_LINK = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g + +function readmeFiles(root) { + const translated = readdirSync(path.join(root, TRANSLATED_README_DIR)) + .filter((name) => name.endsWith('.md')) + .sort() + .map((name) => path.posix.join('docs', 'readme', name)) + return ['README.md', ...translated] +} + +// Why only the referenced paths: a full `git ls-files` of this repo overflows the +// default child buffer; asking about a few dozen pathspecs stays bounded. +function trackedFiles(root, candidates) { + if (candidates.length === 0) { + return new Set() + } + const stdout = execFileSync( + 'git', + ['--literal-pathspecs', 'ls-files', '-z', '--', ...candidates], + { cwd: root, encoding: 'utf8' } + ) + return new Set(stdout.split('\0').filter(Boolean)) +} + +function* localTargets(markdown) { + for (const match of markdown.matchAll(HTML_ATTRIBUTE)) { + // Why: srcset is a candidate list ("a.gif 1x, b.gif 2x"); each entry starts with a URL. + for (const candidate of (match[1] ?? match[2]).split(',')) { + const target = candidate.trim().split(/\s+/)[0] + if (target) { + yield target + } + } + } + for (const match of markdown.matchAll(MARKDOWN_LINK)) { + yield match[1].replace(/^<|>$/g, '') + } +} + +function resolveTarget(readme, target) { + const bare = target.split(/[?#]/)[0] + if (!bare) { + return null + } + const resolved = path.posix.normalize( + path.posix.join(path.posix.dirname(readme), decodeURIComponent(bare)) + ) + return resolved.startsWith('../') ? null : resolved +} + +function collectLinks(root) { + const links = [] + for (const readme of readmeFiles(root)) { + const markdown = readFileSync(path.join(root, readme), 'utf8') + for (const target of new Set(localTargets(markdown))) { + if (EXTERNAL_TARGET.test(target)) { + continue + } + links.push({ readme, target, resolved: resolveTarget(readme, target) }) + } + } + return links +} + +export function findBrokenReadmeLinks(root) { + const links = collectLinks(root) + const candidates = [...new Set(links.map((link) => link.resolved).filter(Boolean))] + const tracked = trackedFiles(root, candidates) + return links.filter(({ resolved }) => resolved === null || !tracked.has(resolved)) +} + +export function main(root = process.cwd()) { + const broken = findBrokenReadmeLinks(root) + if (broken.length > 0) { + console.error(`README local link check failed with ${broken.length} broken link(s):`) + for (const { readme, target, resolved } of broken) { + console.error( + `- ${readme}: ${target} -> ${resolved ?? 'outside the repository'} is not tracked` + ) + } + return 1 + } + console.log('README local link check passed.') + return 0 +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + process.exit(main()) +} diff --git a/config/scripts/check-readme-local-links.test.mjs b/config/scripts/check-readme-local-links.test.mjs new file mode 100644 index 00000000000..2f7cfc76c92 --- /dev/null +++ b/config/scripts/check-readme-local-links.test.mjs @@ -0,0 +1,156 @@ +import { execFileSync } from 'node:child_process' +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { parse } from 'yaml' +import { findBrokenReadmeLinks, main } from './check-readme-local-links.mjs' + +const projectDir = path.resolve(import.meta.dirname, '../..') +const tempDirs = [] + +function git(cwd, args) { + return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim() +} + +function writeFiles(root, files) { + for (const [relativePath, contents] of Object.entries(files)) { + const target = path.join(root, relativePath) + mkdirSync(path.dirname(target), { recursive: true }) + writeFileSync(target, contents) + } +} + +function makeFixture(files, { untracked = {} } = {}) { + const root = mkdtempSync(path.join(tmpdir(), 'orca-readme-links-')) + tempDirs.push(root) + git(root, ['init', '--quiet']) + git(root, ['config', 'user.email', 'readme-links-test@example.com']) + git(root, ['config', 'user.name', 'README Links Test']) + writeFiles(root, files) + git(root, ['add', '-A']) + git(root, ['commit', '--quiet', '-m', 'fixture']) + writeFiles(root, untracked) + return root +} + +const validReadmes = { + 'README.md': [ + '', + '', + '日本語', + "", + '', + '[Contributing](.github/CONTRIBUTING.md) [Docs](https://example.com/docs) [Top](#top)', + '![hero](docs/assets/hero%20image.jpg "Hero")' + ].join('\n'), + 'docs/readme/README.ja.md': [ + '', + '', + 'English self', + '[LICENSE](../../LICENSE)' + ].join('\n'), + 'resources/build/icon.png': 'png', + 'resources/onboarding/feature-wall/tile-01.poster.jpg': 'jpg', + 'docs/site/public/docs/tab-split.gif': 'gif', + 'docs/assets/hero image.jpg': 'jpg', + '.github/CONTRIBUTING.md': 'contributing', + LICENSE: 'mit' +} + +afterEach(() => { + vi.restoreAllMocks() + while (tempDirs.length > 0) { + rmSync(tempDirs.pop(), { force: true, recursive: true }) + } +}) + +describe('README local link check', () => { + it('accepts the checked-in READMEs', () => { + vi.spyOn(console, 'log').mockImplementation(() => {}) + expect(main(projectDir)).toBe(0) + }) + + it('accepts local links in every supported shape', () => { + expect(findBrokenReadmeLinks(makeFixture(validReadmes))).toEqual([]) + }) + + it('reports a deleted media file for the root and translated READMEs', () => { + const { 'docs/site/public/docs/tab-split.gif': _gif, ...files } = validReadmes + vi.spyOn(console, 'error').mockImplementation(() => {}) + const root = makeFixture(files) + + expect(findBrokenReadmeLinks(root)).toEqual([ + { + readme: 'README.md', + target: 'docs/site/public/docs/tab-split.gif', + resolved: 'docs/site/public/docs/tab-split.gif' + }, + { + readme: 'docs/readme/README.ja.md', + target: '../site/public/docs/tab-split.gif', + resolved: 'docs/site/public/docs/tab-split.gif' + } + ]) + expect(main(root)).toBe(1) + expect(console.error).toHaveBeenCalledWith( + expect.stringContaining('README.md: docs/site/public/docs/tab-split.gif') + ) + }) + + // Why: GitHub renders the commit, so a file that only exists on disk is broken. + it('reports a referenced file that exists on disk but is not tracked', () => { + const { 'resources/build/icon.png': icon, ...files } = validReadmes + const root = makeFixture(files, { untracked: { 'resources/build/icon.png': icon } }) + + expect(findBrokenReadmeLinks(root).map((link) => link.resolved)).toEqual([ + 'resources/build/icon.png', + 'resources/build/icon.png' + ]) + }) + + it('reports a link that escapes the repository', () => { + const root = makeFixture({ + ...validReadmes, + 'docs/readme/README.ja.md': '' + }) + + expect(findBrokenReadmeLinks(root)).toEqual([ + { readme: 'docs/readme/README.ja.md', target: '../../../outside.png', resolved: null } + ]) + }) + + // Why: a single-quoted attribute is valid HTML and GitHub renders it, so a parser + // that only reads double quotes would pass a README with a broken image. + it('reports a missing target in a single-quoted attribute', () => { + const files = { + ...validReadmes, + 'README.md': `${validReadmes['README.md']}\n` + } + + expect(findBrokenReadmeLinks(makeFixture(files))).toEqual([ + { + readme: 'README.md', + target: 'docs/assets/missing.gif', + resolved: 'docs/assets/missing.gif' + } + ]) + }) + + // Why the ungated job: static_analysis is skipped for docs-only diffs, which is + // exactly the kind of PR that deletes a docs-site GIF the README embeds. + it('runs on every PR through the ungated guard job and in the lint script', () => { + const { scripts } = JSON.parse(readFileSync(path.join(projectDir, 'package.json'), 'utf8')) + const workflow = parse(readFileSync(path.join(projectDir, '.github/workflows/pr.yml'), 'utf8')) + const guardJob = workflow.jobs.root_directory_guard + const step = guardJob.steps.find((candidate) => candidate.name === 'Check README local links') + + expect(guardJob.if).toBeUndefined() + expect(guardJob.needs).toBeUndefined() + expect(step.run).toBe('node config/scripts/check-readme-local-links.mjs') + expect(scripts['check:readme-local-links']).toBe( + 'node config/scripts/check-readme-local-links.mjs' + ) + expect(scripts.lint).toContain('pnpm run check:readme-local-links') + }) +}) diff --git a/config/scripts/ci-dependency-download-cache.test.mjs b/config/scripts/ci-dependency-download-cache.test.mjs new file mode 100644 index 00000000000..d2111a230bb --- /dev/null +++ b/config/scripts/ci-dependency-download-cache.test.mjs @@ -0,0 +1,76 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' +import { parse } from 'yaml' + +const read = (path) => parse(readFileSync(path, 'utf8')) +const workflow = (name) => read(`.github/workflows/${name}.yml`) +const action = read('.github/actions/install-node-dependencies/action.yml') + +describe('CI dependency download caches', () => { + it('scopes desktop stores to the root lockfile and lets mixed installs opt in', () => { + expect(action.inputs['cache-dependency-path'].default).toBe('pnpm-lock.yaml') + for (const step of action.runs.steps.filter((step) => step.uses === 'actions/setup-node@v6')) { + expect(step.with.cache).toBe('pnpm') + expect(step.with['cache-dependency-path']).toBe('${{ inputs.cache-dependency-path }}') + } + const install = action.runs.steps.find((step) => step.name === 'Install dependencies') + expect(install.if).toBeUndefined() + expect(install.run).toContain('pnpm install --frozen-lockfile --ignore-scripts') + expect(install.run).toContain( + 'diff --exit-code -- package.json pnpm-lock.yaml pnpm-workspace.yaml' + ) + const mobile = workflow('mobile').jobs.verify.steps.find((step) => + step.uses?.includes('install-node-dependencies') + ) + expect(mobile.with['cache-dependency-path'].trim().split('\n')).toEqual([ + 'pnpm-lock.yaml', + 'mobile/pnpm-lock.yaml' + ]) + }) +}) + +describe('release install targets', () => { + const macCpuFlag = '--cpu=current,x64,arm64' + // Both shapes: `run:` steps and steps wrapped in nick-fields/retry (`with.command`). + const installSteps = (name) => + Object.values(workflow(name).jobs) + .flatMap((job) => job.steps ?? []) + .map((step) => step.with?.command ?? step.run) + .filter((command) => typeof command === 'string' && command.includes('pnpm install ')) + + it.each(['adhoc-mac-build', 'daily-mac-build', 'hourly-mac-build', 'release-mac-build'])( + '%s installs both mac CPU variants for the x64+arm64 package config', + (name) => { + const installs = installSteps(name) + expect(installs.length).toBeGreaterThan(0) + expect(installs.some((command) => command.includes(macCpuFlag))).toBe(true) + } + ) + + it.each(['release-cut', 'dev-channel-win-build', 'windows-signing-rehearsal'])( + '%s keeps installs scoped to the runner host', + (name) => { + const installs = installSteps(name) + expect(installs.length).toBeGreaterThan(0) + for (const command of installs) { + expect(command).not.toContain('--os=') + expect(command).not.toContain('--cpu=') + } + } + ) + + it('offers the mac CPU targets for local packaging without touching the lockfile', () => { + const script = JSON.parse(readFileSync('package.json', 'utf8')).scripts['install:release'] + expect(script).toContain('--frozen-lockfile') + expect(script).toContain(macCpuFlag) + }) + + it('keeps installed Windows addon checks in the Windows CI lane', () => { + const steps = Object.values(workflow('pr').jobs).flatMap((job) => job.steps ?? []) + const test = steps.find((step) => step.name === 'Test Windows-specific boundaries') + expect(test.run).toContain('config/scripts/windows-process-tree-gyp-path.test.mjs') + expect(test.run).toContain('config/scripts/windows-process-tree-gyp-rebuild.test.mjs') + expect(test.run).toContain('config/scripts/package-electron-runtime-contract.test.mjs') + expect(test.run).toContain('config/scripts/electron-builder-runtime-resources.test.mjs') + }) +}) diff --git a/config/scripts/ci-e2e-shard-plan.mjs b/config/scripts/ci-e2e-shard-plan.mjs new file mode 100644 index 00000000000..ea6002451a3 --- /dev/null +++ b/config/scripts/ci-e2e-shard-plan.mjs @@ -0,0 +1,97 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { + balanceFiles, + compareIds, + readTimingBaseline, + writeAssignment +} from './ci-shard-assignment.mjs' + +export function discoverE2eFiles(report) { + if (report.errors?.length) { + throw new Error('Playwright discovery reported errors') + } + const files = new Map() + function visit(suite) { + for (const spec of suite.specs ?? []) { + const file = spec.file.replaceAll('\\', '/') + if (file.startsWith('/') || file.split('/').includes('..') || /[\n\r>›]/.test(file)) { + throw new Error(`Unsafe test-list path: ${file}`) + } + for (const test of spec.tests) { + const id = `${test.projectName}:${spec.id}` + const ids = files.get(file) ?? [] + ids.push(id) + files.set(file, ids) + } + } + for (const child of suite.suites ?? []) { + visit(child) + } + } + for (const suite of report.suites) { + visit(suite) + } + if (!files.size) { + throw new Error('Playwright discovered no tests') + } + const ids = [...files.values()].flat() + if (new Set(ids).size !== ids.length) { + throw new Error('Duplicate discovered test identity') + } + return Object.fromEntries([...files.entries()].sort(([a], [b]) => compareIds(a, b))) +} + +export function planE2e(report, count, baseline) { + const testsByFile = discoverE2eFiles(report) + const timings = Object.fromEntries( + Object.entries(baseline.timings).map(([file, duration]) => [ + file.replace(/^tests\/e2e\//, ''), + duration + ]) + ) + const assignment = balanceFiles(Object.keys(testsByFile), count, timings) + return { ...assignment, testsByFile, baselineSha256: baseline.baselineSha256 } +} + +export function verifyE2eSelection(assignment, report) { + const actual = Object.values(discoverE2eFiles(report)).flat().sort(compareIds) + const expected = assignment.shards[assignment.selectedShard - 1].files + .flatMap((file) => assignment.testsByFile[file]) + .sort(compareIds) + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + throw new Error('Native Playwright selection differs from shard assignment') + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (process.argv[2] === '--verify') { + verifyE2eSelection( + JSON.parse(readFileSync(process.argv[3], 'utf8')), + JSON.parse(readFileSync(process.argv[4], 'utf8')) + ) + } else { + const [input, shard, directory] = process.argv.slice(2) + const match = shard?.match(/^(\d+)\/(\d+)$/) + if (!input || !directory || !match) { + throw new Error('Usage: ci-e2e-shard-plan.mjs DISCOVERY INDEX/COUNT OUTPUT_DIRECTORY') + } + const index = Number(match[1]) + const count = Number(match[2]) + if (index < 1 || index > count) { + throw new Error('Invalid shard index') + } + const assignment = planE2e( + JSON.parse(readFileSync(input, 'utf8')), + count, + readTimingBaseline('e2e') + ) + const selected = assignment.shards[index - 1].files + if (!selected.length) { + throw new Error('Empty E2E shard') + } + writeAssignment(join(directory, 'assignment.json'), { ...assignment, selectedShard: index }) + writeFileSync(join(directory, 'selected.txt'), `${selected.join('\n')}\n`) + } +} diff --git a/config/scripts/ci-e2e-shard-selection.test.mjs b/config/scripts/ci-e2e-shard-selection.test.mjs new file mode 100644 index 00000000000..15a031179e7 --- /dev/null +++ b/config/scripts/ci-e2e-shard-selection.test.mjs @@ -0,0 +1,73 @@ +import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { expect, it } from 'vitest' +import { runProcess } from '../../src/shared/child-process/run-process' +import { planE2e, verifyE2eSelection } from './ci-e2e-shard-plan.mjs' + +const require = createRequire(import.meta.url) + +it('native Playwright test-list preserves full discovery, serial suites, skips and headful filtering', async () => { + const directory = realpathSync(mkdtempSync(join(tmpdir(), 'orca-playwright-shards-'))) + const testPackage = JSON.stringify(require.resolve('@stablyai/playwright-test')) + const config = join(directory, 'playwright.config.cjs') + writeFileSync( + config, + `module.exports = { testDir: '.', fullyParallel: true, projects: [{ name: 'electron-headless', grepInvert: /@headful/ }] }` + ) + for (let index = 0; index < 17; index++) { + writeFileSync( + join(directory, `file-${index}.spec.cjs`), + ` + const { test } = require(${testPackage}); + test('normal', () => {}); + test.skip('skipped', () => {}); + test('visible @headful', () => {}); + test.describe.serial('serial', () => { + test('first', () => {}); + test('second', () => {}); + }); + ` + ) + } + async function discover(extra = []) { + const result = await runProcess({ + program: process.execPath, + cwd: directory, + args: [ + join(dirname(require.resolve('playwright/package.json')), 'cli.js'), + 'test', + '--config', + config, + '--list', + '--reporter=json', + ...extra + ], + env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1' }, + timeoutMs: 20000 + }) + expect(result.code, result.stderr).toBe(0) + return JSON.parse(result.stdout) + } + try { + const full = await discover() + const assignment = planE2e(full, 14, { timings: {} }) + const ids = [] + for (let index = 0; index < 14; index++) { + const path = join(directory, 'selected.txt') + writeFileSync(path, `${assignment.shards[index].files.join('\n')}\n`) + const selected = await discover(['--test-list', path]) + verifyE2eSelection({ ...assignment, selectedShard: index + 1 }, selected) + for (const suite of selected.suites) { + expect(suite.specs.some((spec) => spec.title.includes('@headful'))).toBe(false) + } + ids.push(...assignment.shards[index].files.flatMap((file) => assignment.testsByFile[file])) + } + expect(ids).toHaveLength(17 * 4) + expect(new Set(ids).size).toBe(ids.length) + expect(() => verifyE2eSelection({ ...assignment, selectedShard: 1 }, full)).toThrow('differs') + } finally { + rmSync(directory, { recursive: true, force: true }) + } +}, 60000) diff --git a/config/scripts/ci-shard-assignment.mjs b/config/scripts/ci-shard-assignment.mjs new file mode 100644 index 00000000000..1cb44969a63 --- /dev/null +++ b/config/scripts/ci-shard-assignment.mjs @@ -0,0 +1,66 @@ +import { createHash } from 'node:crypto' +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname } from 'node:path' + +export const compareIds = (a, b) => (a < b ? -1 : a > b ? 1 : 0) + +export function balanceFiles(files, count, timings, overheadMs = 0) { + if (!Number.isInteger(count) || count < 1) { + throw new Error('Invalid shard count') + } + if (new Set(files).size !== files.length) { + throw new Error('Duplicate discovered file') + } + const known = Object.values(timings).filter((value) => Number.isFinite(value) && value > 0) + known.sort((a, b) => a - b) + const fallbackMs = known[Math.floor(known.length / 2)] ?? 1000 + const weighted = files.map((file) => ({ + file, + durationMs: + (Number.isFinite(timings[file]) && timings[file] > 0 ? timings[file] : fallbackMs) + + overheadMs + })) + weighted.sort((a, b) => b.durationMs - a.durationMs || compareIds(a.file, b.file)) + const shards = Array.from({ length: count }, () => ({ files: [], durationMs: 0 })) + for (const entry of weighted) { + const target = shards.reduce((best, shard) => + shard.durationMs < best.durationMs || + (shard.durationMs === best.durationMs && shard.files.length < best.files.length) + ? shard + : best + ) + target.files.push(entry.file) + target.durationMs += entry.durationMs + } + for (const shard of shards) { + shard.files.sort(compareIds) + } + const assigned = shards.flatMap((shard) => shard.files).sort(compareIds) + if (JSON.stringify(assigned) !== JSON.stringify([...files].sort(compareIds))) { + throw new Error('Shard coverage differs from discovery') + } + return { algorithm: 'file-lpt-v1', fallbackMs, overheadMs, shards } +} + +export function readTimingBaseline(suite) { + const bytes = readFileSync(new URL('./ci-shard-timings.json', import.meta.url), 'utf8') + const baseline = JSON.parse(bytes) + return { ...baseline[suite], baselineSha256: createHash('sha256').update(bytes).digest('hex') } +} + +export function writeAssignment(path, assignment) { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync( + path, + `${JSON.stringify( + { + sourceSha: process.env.ORCA_SHARD_SOURCE_SHA ?? process.env.GITHUB_SHA ?? null, + runId: process.env.GITHUB_RUN_ID ?? null, + runAttempt: process.env.GITHUB_RUN_ATTEMPT ?? null, + ...assignment + }, + null, + 2 + )}\n` + ) +} diff --git a/config/scripts/ci-shard-assignment.test.mjs b/config/scripts/ci-shard-assignment.test.mjs new file mode 100644 index 00000000000..607a73d392c --- /dev/null +++ b/config/scripts/ci-shard-assignment.test.mjs @@ -0,0 +1,124 @@ +import { mkdtempSync, readFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { BaseSequencer } from 'vitest/node' +import { balanceFiles } from './ci-shard-assignment.mjs' +import { discoverE2eFiles, planE2e } from './ci-e2e-shard-plan.mjs' +import { parseTimingLog } from './ci-shard-timing-import.mjs' +import TimingSequencer from './ci-unit-sequencer.mjs' + +const directories = [] +afterEach(() => { + vi.unstubAllEnvs() + for (const directory of directories.splice(0)) { + rmSync(directory, { recursive: true, force: true }) + } +}) + +describe('timing-weighted shard selection', () => { + it('distributes long files, includes unknowns exactly once, and ignores discovery order', () => { + const files = ['long', 'medium', 'short', 'unknown', 'new', 'zero', 'invalid'] + const timings = { long: 100, medium: 80, short: 20, zero: 0, invalid: -1, deleted: 20 } + const plan = balanceFiles(files, 3, timings, 10) + expect(plan).toEqual(balanceFiles(files.toReversed(), 3, timings, 10)) + expect(plan.fallbackMs).toBe(80) + expect(plan.shards.flatMap((shard) => shard.files).sort()).toEqual([...files].sort()) + expect(Math.max(...plan.shards.map((shard) => shard.durationMs))).toBeLessThan(250) + }) + + it('has a deterministic cold fallback and permits fewer files than shards', () => { + expect(balanceFiles(['b', 'a'], 3, {}).shards).toEqual([ + { files: ['a'], durationMs: 1000 }, + { files: ['b'], durationMs: 1000 }, + { files: [], durationMs: 0 } + ]) + expect(() => balanceFiles(['a', 'a'], 8, {})).toThrow('Duplicate') + expect(() => balanceFiles(['a'], 0, {})).toThrow('count') + }) + + it('uses the post-filter Vitest discovery unchanged across eight shards and retains default sort', async () => { + const directory = mkdtempSync(join(tmpdir(), 'orca-unit-shards-')) + directories.push(directory) + vi.stubEnv('ORCA_SHARD_MANIFEST', join(directory, 'assignment.json')) + const specs = Array.from({ length: 37 }, (_, i) => ({ + moduleId: resolve(`src/fixture-${i}.test.ts`) + })) + const selected = [] + for (let index = 1; index <= 8; index++) { + const sequencer = new TimingSequencer({ + config: { root: process.cwd(), shard: { index, count: 8 } } + }) + expect(sequencer.sort).toBe(BaseSequencer.prototype.sort) + selected.push(...(await sequencer.shard(specs))) + const manifest = JSON.parse(readFileSync(join(directory, 'assignment.json'), 'utf8')) + expect(manifest.selectedShard).toBe(index) + expect(manifest.baselineSha256).toMatch(/^[a-f0-9]{64}$/) + } + expect(new Set(selected).size).toBe(specs.length) + expect(selected).toHaveLength(specs.length) + expect(new Set(selected)).toEqual(new Set(specs)) + }) + + it('wires a constructor into the opt-in Vitest config', async () => { + vi.stubEnv('ORCA_BALANCE_UNIT_SHARDS', '1') + const { default: config } = await import('../vitest.config') + expect(config.test.sequence.sequencer).toBe(TimingSequencer) + }) + + it('keeps nested/serial E2E files atomic and fails closed on discovery errors', () => { + const spec = (id, file) => ({ id, file, tests: [{ projectName: 'electron-headless' }] }) + const report = { + suites: [ + { + specs: [spec('a', 'one.spec.ts')], + suites: [{ specs: [spec('b', 'one.spec.ts'), spec('c', 'two.spec.ts')] }] + } + ] + } + const plan = planE2e(report, 14, { timings: { 'tests/e2e/one.spec.ts': 4000 } }) + expect(plan.shards.flatMap((shard) => shard.files).sort()).toEqual([ + 'one.spec.ts', + 'two.spec.ts' + ]) + expect(plan.testsByFile['one.spec.ts']).toHaveLength(2) + expect(() => discoverE2eFiles({ ...report, errors: [{}] })).toThrow('errors') + expect(() => discoverE2eFiles({ suites: [] })).toThrow('no tests') + expect(() => + discoverE2eFiles({ suites: [{ specs: [spec('a', '../escape.spec.ts')] }] }) + ).toThrow('Unsafe') + expect(() => + discoverE2eFiles({ + suites: [{ specs: [spec('a', 'one.spec.ts'), spec('a', 'one.spec.ts')] }] + }) + ).toThrow('Duplicate') + }) + + it('imports ANSI unit timings and E2E failures without counting headful reruns', () => { + const parsed = parseTimingLog( + [ + '\u001b[32m✓\u001b[39m src/a.test.ts (2 tests) 35ms', + 'Duration 1s (transform 0.1s, setup 0.2s, import 0.3s, tests 0.04s, environment 0.4s)', + '✓ 1 [electron-headless] › tests/e2e/a.spec.ts:1:1 › works (2s)', + '✘ 2 [electron-headless] › tests/e2e/a.spec.ts:2:1 › fails (1.2m)', + '✓ 3 [electron-headful] › tests/e2e/a.spec.ts:3:1 › benchmark (9s)' + ].join('\n') + ) + expect(parsed).toEqual({ + unit: { 'src/a.test.ts': 35 }, + e2e: { 'tests/e2e/a.spec.ts': 74000 }, + overheadMs: 1000 + }) + }) + + it('reads mixed units from captured Vitest output', () => { + const parsed = parseTimingLog( + 'Duration 5.14s (transform 952ms, setup 449ms, import 1.18s, tests 9.41s, environment 1ms)' + ) + expect(parsed.overheadMs).toBe(2582) + }) + + it('rejects incomplete unit evidence instead of silently dropping overhead', () => { + expect(() => parseTimingLog('✓ src/a.test.ts (2 tests) 35ms')).toThrow('Duration summary') + }) +}) diff --git a/config/scripts/ci-shard-timing-import.mjs b/config/scripts/ci-shard-timing-import.mjs new file mode 100644 index 00000000000..5b62c0f9f7f --- /dev/null +++ b/config/scripts/ci-shard-timing-import.mjs @@ -0,0 +1,86 @@ +import { readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { stripVTControlCharacters } from 'node:util' +import { pathToFileURL } from 'node:url' + +export function parseTimingLog(text) { + const clean = stripVTControlCharacters(text) + const unit = {} + const e2e = {} + for (const match of clean.matchAll( + /[✓×❯] ([\w./-]+\.test\.(?:ts|tsx|mjs)) \([^\n]*?\)\s+([\d.]+)ms/g + )) { + unit[match[1]] = Number(match[2]) + } + for (const match of clean.matchAll( + /[✓✘]\s+\d+ \[electron-headless\] › (tests\/e2e\/[^:]+):\d+:\d+ › .*? \(([\d.]+)(ms|s|m)\)/g + )) { + e2e[match[1]] = (e2e[match[1]] ?? 0) + Number(match[2]) * { ms: 1, s: 1000, m: 60000 }[match[3]] + } + const summary = clean.match( + /Duration\s+[\d.]+(?:ms|s) \(transform ([\d.]+(?:ms|s)), setup ([\d.]+(?:ms|s)), import ([\d.]+(?:ms|s)), tests [\d.]+(?:ms|s), environment ([\d.]+(?:ms|s))\)/ + ) + if (Object.keys(unit).length && !summary) { + throw new Error('Unit timing log has no supported Duration summary') + } + return { + unit, + e2e, + overheadMs: summary + ? summary + .slice(1) + .reduce( + (sum, value) => sum + Number.parseFloat(value) * (value.endsWith('ms') ? 1 : 1000), + 0 + ) + : 0 + } +} + +export function importTimingLogs(directory, unitRun, e2eRun) { + const baseline = { + unit: { runId: unitRun, jobIds: [], overheadMs: 0, timings: {} }, + e2e: { runId: e2eRun, jobIds: [], overheadMs: 0, timings: {} } + } + for (const file of readdirSync(directory) + .filter((file) => /^log-\d+\.txt$/.test(file)) + .sort()) { + const parsed = parseTimingLog(readFileSync(join(directory, file), 'utf8')) + for (const suite of ['unit', 'e2e']) { + if (!Object.keys(parsed[suite]).length) { + continue + } + baseline[suite].jobIds.push(file.match(/\d+/)[0]) + for (const [name, duration] of Object.entries(parsed[suite])) { + if (suite === 'unit' && name in baseline.unit.timings) { + throw new Error(`Duplicate unit timing: ${name}`) + } + baseline[suite].timings[name] = (baseline[suite].timings[name] ?? 0) + duration + } + } + baseline.unit.overheadMs += parsed.overheadMs + } + for (const suite of ['unit', 'e2e']) { + if (!baseline[suite].jobIds.length) { + throw new Error(`No ${suite} timing evidence`) + } + baseline[suite].timings = Object.fromEntries( + Object.entries(baseline[suite].timings).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) + ) + } + baseline.unit.overheadMs = Math.ceil( + baseline.unit.overheadMs / Object.keys(baseline.unit.timings).length + ) + return baseline +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + const [directory, unitRun, e2eRun, output] = process.argv.slice(2) + if (!directory || !unitRun || !e2eRun || !output) { + throw new Error('Usage: ci-shard-timing-import.mjs LOG_DIRECTORY UNIT_RUN E2E_RUN OUTPUT') + } + writeFileSync( + output, + `${JSON.stringify(importTimingLogs(directory, unitRun, e2eRun), null, 2)}\n` + ) +} diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json new file mode 100644 index 00000000000..46ea33ce679 --- /dev/null +++ b/config/scripts/ci-shard-timings.json @@ -0,0 +1,8815 @@ +{ + "unit": { + "runId": "34675583768", + "jobIds": [ + "103505449197", + "103505449205", + "103505449211", + "103505449212", + "103505449228", + "103505449235", + "103505449248", + "103505449256" + ], + "overheadMs": 526, + "timings": { + "config/scripts/adhoc-build-version.test.mjs": 31, + "config/scripts/agent-status-hot-path-benchmark.test.ts": 2277, + "config/scripts/app-store-performance-plugin.test.mjs": 912, + "config/scripts/audit-localization-coverage.test.mjs": 23, + "config/scripts/benchmark-artifact-comparison.test.mjs": 132, + "config/scripts/benchmark-sample-summary.test.mjs": 8, + "config/scripts/build-linux-local.test.mjs": 15, + "config/scripts/build-mac-local.test.mjs": 5, + "config/scripts/build-orcad-prebuilds.test.mjs": 27, + "config/scripts/build-windows-cli-launcher.test.mjs": 39, + "config/scripts/check-changed-code-quality.test.mjs": 7, + "config/scripts/check-max-lines-ratchet.test.mjs": 10, + "config/scripts/check-react-doctor-changed.test.mjs": 189, + "config/scripts/check-reliability-gates.test.mjs": 171, + "config/scripts/check-root-directory-entries.test.mjs": 880, + "config/scripts/check-runtime-electron-ratchet.test.mjs": 1931, + "config/scripts/check-terminal-perf-report-budgets.test.mjs": 398, + "config/scripts/check-ts-nocheck-ratchet.test.mjs": 5, + "config/scripts/ci-native-toolchain.test.mjs": 38, + "config/scripts/client-hosted-browser-package-coverage.test.mjs": 81, + "config/scripts/codex-index-heal-contract-workflow.test.mjs": 10, + "config/scripts/codex-primary-home-tripwire.test.ts": 421, + "config/scripts/computer-e2e-workflow.test.mjs": 132, + "config/scripts/computer-use-modifier-safety.test.mjs": 5, + "config/scripts/computer-use-mouse-button-routing.test.mjs": 9, + "config/scripts/computer-use-skill-guidance.test.mjs": 15, + "config/scripts/computer-use-smoke.test.mjs": 471, + "config/scripts/computer-use-windows-horizontal-scroll.test.mjs": 3, + "config/scripts/counterbalanced-benchmark-schedule.test.mjs": 10, + "config/scripts/create-draft-release.test.mjs": 18, + "config/scripts/daily-build-version.test.mjs": 36, + "config/scripts/daily-e2e-dispatch-contract.test.mjs": 6, + "config/scripts/dev-channel-base-version.test.mjs": 8, + "config/scripts/dev-channel-windows-workflow-contract.test.mjs": 218, + "config/scripts/dev-cli-terminal-wrapper.test.mjs": 9, + "config/scripts/dev-electron-bundle-cache.test.ts": 10, + "config/scripts/dev-electron-bundle-identity.test.ts": 7, + "config/scripts/electron-builder-config.test.mjs": 155, + "config/scripts/electron-builder-mac-channel-config.test.mjs": 577, + "config/scripts/electron-builder-markdown-associations.test.mjs": 14, + "config/scripts/electron-builder-native-rebuild.test.mjs": 11, + "config/scripts/electron-builder-runtime-resources.test.mjs": 393, + "config/scripts/electron-builder-speech-config.test.mjs": 16, + "config/scripts/electron-runtime-floor.test.ts": 5, + "config/scripts/electron-vite-output-contract.test.ts": 13, + "config/scripts/ensure-native-runtime-job-ownership.test.mjs": 10, + "config/scripts/ensure-native-runtime.test.mjs": 422, + "config/scripts/generate-bundled-skill-guides.test.mjs": 293, + "config/scripts/generate-runtime-required-english-catalog.test.mjs": 8, + "config/scripts/generate-skill-bundle-manifest.test.mjs": 448, + "config/scripts/generate-terminal-perf-html-report.test.mjs": 19, + "config/scripts/git-binary-compatibility-workflow.test.mjs": 98, + "config/scripts/git-pull-request-diff-base.test.mjs": 4, + "config/scripts/hang-watchdog-process-metrics.test.mjs": 6, + "config/scripts/happy-dom-mutation-observer-retention.test.ts": 100, + "config/scripts/happy-dom-offscreen-canvas.test.ts": 42, + "config/scripts/headless-serve-shutdown-workflow.test.mjs": 15, + "config/scripts/hourly-build-version.test.mjs": 27, + "config/scripts/hourly-preflight-workflow.test.mjs": 33, + "config/scripts/idle-cpu-process-sampling.test.mjs": 5, + "config/scripts/install-electron-package-binary.test.mjs": 2145, + "config/scripts/install-node-dependencies-action.test.mjs": 140, + "config/scripts/latest-stable-release.test.mjs": 10, + "config/scripts/lint-staged-worktree-backup.test.mjs": 276, + "config/scripts/linux-package-maintainer-scripts.test.mjs": 6, + "config/scripts/live-freeze-bounded-history.test.mjs": 6, + "config/scripts/live-remote-bulk-open-freeze-metrics.test.mjs": 17, + "config/scripts/live-remote-freeze-rpc.test.mjs": 5, + "config/scripts/live-remote-status-watchdog.test.mjs": 248, + "config/scripts/locale-count-fragment-separator.test.mjs": 17, + "config/scripts/locale-generic-ui-terms.test.mjs": 20, + "config/scripts/locale-ko-frozen-terminal-search-keywords.test.mjs": 35, + "config/scripts/locale-ko-key-overrides.test.mjs": 38, + "config/scripts/locale-repair-catalog-missing-leaves.test.mjs": 14, + "config/scripts/locale-translation-policy-ko-round5.test.mjs": 12, + "config/scripts/locale-translation-policy.es-pr.test.mjs": 13, + "config/scripts/locale-translation-policy.es-round5.test.mjs": 9, + "config/scripts/locale-translation-policy.ja-relocalization.test.mjs": 17, + "config/scripts/locale-translation-policy.ja-round5.test.mjs": 13, + "config/scripts/locale-translation-policy.test.mjs": 38, + "config/scripts/locale-translation-policy.zh-round5.test.mjs": 14, + "config/scripts/locale-translation-policy.zh-status-bar-usage.test.mjs": 24, + "config/scripts/localization-package-contract.test.mjs": 8, + "config/scripts/mac-build-compatibility.test.mjs": 8, + "config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs": 10, + "config/scripts/macos-tcc-prompt-localization.test.mjs": 75, + "config/scripts/mobile-pairing-qrcode-import-plugin.test.mjs": 450, + "config/scripts/node-old-space-limit.test.mjs": 6, + "config/scripts/node-pty-console-list-agent-patch.test.mjs": 7, + "config/scripts/node-pty-master-cloexec-patch.test.mjs": 22, + "config/scripts/node-pty-windows-pty-teardown-patch.test.mjs": 59, + "config/scripts/orca-cli-skill-guidance.test.mjs": 14, + "config/scripts/orca-dev-bin.test.mjs": 68, + "config/scripts/orca-linear-skill-guidance.test.mjs": 15, + "config/scripts/orcad-operations-restart-safety.test.mjs": 5, + "config/scripts/orchestration-guide-command-contract.test.mjs": 11, + "config/scripts/orchestration-skill-guidance.test.mjs": 23, + "config/scripts/oxc-cli-invocation.test.mjs": 128, + "config/scripts/oxlint-cli-invocation.test.mjs": 46, + "config/scripts/package-electron-runtime-contract.test.mjs": 360, + "config/scripts/packaged-browser-lane-contract.test.mjs": 7, + "config/scripts/packaged-hang-watchdog-worker-contract.test.mjs": 55, + "config/scripts/packaged-node-pty-prebuild-prune.test.mjs": 25, + "config/scripts/packaged-source-map-prune.test.mjs": 53, + "config/scripts/patched-dependencies-frozen-install.test.mjs": 451, + "config/scripts/plain-node-entry-guard.test.ts": 610, + "config/scripts/pnpm-cli-invocation.test.mjs": 6, + "config/scripts/pr-code-change-scope.test.mjs": 346, + "config/scripts/pr-e2e-gate-contract.test.mjs": 108, + "config/scripts/pr-e2e-native-only-routing.test.mjs": 19, + "config/scripts/pr-test-loc-summary.test.mjs": 89, + "config/scripts/pr-workflow-lint-parity.test.mjs": 53, + "config/scripts/pr-workflow-parallelism.test.mjs": 898, + "config/scripts/project-renderer-web-client.test.mjs": 213, + "config/scripts/pty-transcript-secret-scan.test.mjs": 14, + "config/scripts/publish-complete-draft-releases.test.mjs": 168, + "config/scripts/quadratic-buffer-concat-plugin.test.mjs": 2010, + "config/scripts/rebuild-native-deps-node-pty.test.mjs": 2338, + "config/scripts/rebuild-native-deps.test.mjs": 831, + "config/scripts/reclaim-dev-electron-bundles.test.ts": 35, + "config/scripts/regenerate-xterm-patches.test.mjs": 300, + "config/scripts/relay-artifact-manifest.test.mjs": 3554, + "config/scripts/relay-asset-line-ending-pin.test.mjs": 74, + "config/scripts/release-blocker-fixes.test.mjs": 39, + "config/scripts/release-cut-signpath-slack.test.mjs": 72, + "config/scripts/release-cut-sourcemap-publish.test.mjs": 11, + "config/scripts/release-cut-token-permissions.test.mjs": 19, + "config/scripts/release-e2e-dispatch-contract.test.mjs": 8, + "config/scripts/release-mac-build-workflow-dispatch.test.mjs": 10, + "config/scripts/release-rc-history.test.mjs": 166, + "config/scripts/renderer-boot-graph.test.mjs": 54, + "config/scripts/renderer-scrollbar-style-plugin.test.mjs": 2888, + "config/scripts/replace-cached-nsis-elevate.test.mjs": 1880, + "config/scripts/resolve-7za-path.test.mjs": 1412, + "config/scripts/run-codex-real-account-validation.test.ts": 3652, + "config/scripts/run-internal-dev-setup.test.mjs": 10, + "config/scripts/run-linux-packaged-node-pty-floor-smoke.test.mjs": 10, + "config/scripts/run-terminal-scale-perf-report-gate.test.mjs": 17, + "config/scripts/shared-electron-dist-cache.test.ts": 44, + "config/scripts/shebang-script-line-ending-pin.test.mjs": 77, + "config/scripts/skill-critical-guidance.test.mjs": 6, + "config/scripts/skill-description-length.test.mjs": 26, + "config/scripts/skill-recipe-shell.test.mjs": 43, + "config/scripts/skill-sharing-release-workflow.test.mjs": 12, + "config/scripts/skill-update-roundtrip-workflow.test.mjs": 5, + "config/scripts/skills-cli-package-workflow.test.mjs": 8, + "config/scripts/sort-comparator-performance-plugin.test.mjs": 269, + "config/scripts/space-sharing-copy.test.ts": 40, + "config/scripts/ssh-browser-e2e-routing.test.mjs": 23, + "config/scripts/ssh-localhost-e2e-routing.test.mjs": 28, + "config/scripts/static-appimage-package-contract.test.mjs": 42, + "config/scripts/summarize-terminal-perf-report.test.mjs": 52, + "config/scripts/telemetry-bundle-constant-patterns.test.mjs": 4, + "config/scripts/terminal-ime-e2e-workflow.test.mjs": 9, + "config/scripts/terminal-ime-engagement-receipt.test.mjs": 11, + "config/scripts/terminal-perf-report-annotations.test.mjs": 4, + "config/scripts/trim-windows-icon-source.test.mjs": 121, + "config/scripts/verify-cli-bin.test.mjs": 64, + "config/scripts/verify-dev-channel-packaging.test.mjs": 666, + "config/scripts/verify-linux-glibc-floor.test.mjs": 118, + "config/scripts/verify-localization-catalog.test.mjs": 46, + "config/scripts/verify-localization-extraction.test.mjs": 5, + "config/scripts/verify-packaged-browser-participation.test.mjs": 10, + "config/scripts/verify-packaged-daemon-entry.test.mjs": 111, + "config/scripts/verify-packaged-node-pty-job-ownership.test.mjs": 10, + "config/scripts/verify-packaged-plugin-resources.test.mjs": 67, + "config/scripts/verify-release-required-assets.test.mjs": 8, + "config/scripts/verify-skills-cli-runtime.test.mjs": 141, + "config/scripts/verify-windows-inner-signature.test.mjs": 20, + "config/scripts/verify-wsl-e2e-participation.test.mjs": 9, + "config/scripts/websocket-server-loopback-bind.test.ts": 5, + "config/scripts/win-crash-survival-e2e.test.mjs": 12, + "config/scripts/win32-test-lane-registration.test.mjs": 22, + "config/scripts/windows-cmd-shim-spawn-boundary.test.mjs": 6, + "config/scripts/windows-process-tree-gyp-path.test.mjs": 42, + "config/scripts/windows-process-tree-gyp-rebuild.test.mjs": 49, + "config/scripts/windows-process-tree-patch-contract.test.mjs": 530, + "config/scripts/windows-pty-native-capability-workflow.test.mjs": 7, + "config/scripts/windows-signing-gate-toolset.test.mjs": 53, + "config/scripts/windows-signing-workflow-contract.test.mjs": 531, + "config/scripts/windows-uninstaller-signing.test.mjs": 17, + "config/scripts/workflow-ref-mirror-case-safety.test.mjs": 74, + "config/scripts/workflow-ref-reachability.test.mjs": 592, + "config/scripts/wsl-e2e-lane-contract.test.mjs": 76, + "config/scripts/xterm-webgl-runtime-contract.test.mjs": 19, + "src/cli/agent-context.test.ts": 16, + "src/cli/args.test.ts": 36, + "src/cli/automation-format.test.ts": 16, + "src/cli/automation-owner-conflict-recovery.test.ts": 12, + "src/cli/base64-payload-byte-count.test.ts": 3, + "src/cli/browser-cookie-credentials-empty-value.test.ts": 58, + "src/cli/browser-storage-empty-value.test.ts": 48, + "src/cli/browser.test.ts": 109, + "src/cli/cli-command-name-parity.test.ts": 7, + "src/cli/cli-version.test.ts": 13, + "src/cli/codex-command-classification.test.ts": 11, + "src/cli/command-suggestion-budget.test.ts": 25, + "src/cli/command-suggestion.test.ts": 20, + "src/cli/computer-format.test.ts": 3, + "src/cli/emulator-logcat-format.test.ts": 4, + "src/cli/execution-host-flag.test.ts": 21, + "src/cli/flags.test.ts": 4, + "src/cli/format-recovery.test.ts": 10, + "src/cli/format.test.ts": 29, + "src/cli/handler-group-manifest.test.ts": 638, + "src/cli/handlers/account.test.ts": 558, + "src/cli/handlers/agent-hooks.test.ts": 83, + "src/cli/handlers/artifacts.test.ts": 36, + "src/cli/handlers/automation-destination-fencing.test.ts": 17, + "src/cli/handlers/automation-owner-fencing.test.ts": 16, + "src/cli/handlers/computer-action-routing.test.ts": 69, + "src/cli/handlers/computer-action-validation.test.ts": 86, + "src/cli/handlers/computer-state-formatting.test.ts": 65, + "src/cli/handlers/computer.test.ts": 103, + "src/cli/handlers/core.test.ts": 7, + "src/cli/handlers/emulator.test.ts": 37, + "src/cli/handlers/file-absolute-paths.test.ts": 60, + "src/cli/handlers/file.test.ts": 46, + "src/cli/handlers/interactive-login-interruption.test.ts": 12, + "src/cli/handlers/linear.test.ts": 131, + "src/cli/handlers/orchestration-caller-identity-cli.test.ts": 18, + "src/cli/handlers/orchestration-check-identity.test.ts": 12, + "src/cli/handlers/orchestration-federated-legacy-settlement.test.ts": 8, + "src/cli/handlers/orchestration-gate-cli.test.ts": 99, + "src/cli/handlers/orchestration-legacy-read-only.test.ts": 9, + "src/cli/handlers/orchestration-lifecycle-json-rejection.test.ts": 15, + "src/cli/handlers/orchestration-lifecycle-rejection.test.ts": 12, + "src/cli/handlers/orchestration-migration.test.ts": 12, + "src/cli/handlers/orchestration-module-boundaries.test.ts": 11, + "src/cli/handlers/orchestration-request-show-cli.test.ts": 7, + "src/cli/handlers/orchestration-run-cli.test.ts": 22, + "src/cli/handlers/orchestration-send-receipt-warnings.test.ts": 7, + "src/cli/handlers/orchestration-task-create-cli.test.ts": 8, + "src/cli/handlers/orchestration-task-list-brief.test.ts": 12, + "src/cli/handlers/orchestration-timeout-cli.test.ts": 29, + "src/cli/handlers/orchestration-timeout.test.ts": 26, + "src/cli/handlers/orchestration-windows-ask-cli.test.ts": 11, + "src/cli/handlers/orchestration-worker-cli.test.ts": 32, + "src/cli/handlers/orchestration-worker-show-wait-cli.test.ts": 9, + "src/cli/handlers/orchestration.test.ts": 31, + "src/cli/handlers/orchestration/worker-list-run-scope.test.ts": 13, + "src/cli/handlers/orchestration/worker-output.test.ts": 13, + "src/cli/handlers/skill-sharing.test.ts": 16, + "src/cli/handlers/terminal.test.ts": 29, + "src/cli/host-selector-alternatives.test.ts": 14, + "src/cli/index-automation-identifiers.test.ts": 42, + "src/cli/index-automation-schedule.test.ts": 64, + "src/cli/index-automation-session-reuse.test.ts": 54, + "src/cli/index-automation-source-context.test.ts": 75, + "src/cli/index-automation-target.test.ts": 89, + "src/cli/index-device-commands.test.ts": 57, + "src/cli/index-environment-commands.test.ts": 103, + "src/cli/index-local-command-routing-flags.test.ts": 141, + "src/cli/index-memory-diagnostics.test.ts": 54, + "src/cli/index-omitted-host-scope-selectors.test.ts": 54, + "src/cli/index-orchestration.test.ts": 72, + "src/cli/index-project-setup.test.ts": 95, + "src/cli/index-serve-command.test.ts": 121, + "src/cli/index-terminal-commands.test.ts": 78, + "src/cli/index-terminal-list-host-scope.test.ts": 92, + "src/cli/index-vm-recipe-doctor.test.ts": 125, + "src/cli/index-worktree-create-agent.test.ts": 64, + "src/cli/index-worktree-create-linear.test.ts": 44, + "src/cli/index-worktree-create-parent.test.ts": 118, + "src/cli/index-worktree-create-target.test.ts": 65, + "src/cli/index-worktree-selector-resolution.test.ts": 185, + "src/cli/index-worktree-set.test.ts": 83, + "src/cli/index.test.ts": 263, + "src/cli/linear-format.test.ts": 9, + "src/cli/main-module-bundle-parity.test.ts": 14, + "src/cli/orchestration-dispatch-refusal-format.test.ts": 8, + "src/cli/orchestration-mutation-recovery.test.ts": 52, + "src/cli/orchestration-structured-sender-identity.test.ts": 93, + "src/cli/orchestration-structured-session-no-identity.test.ts": 11, + "src/cli/quote-stripped-json-flag.test.ts": 8, + "src/cli/registry-parity.test.ts": 7, + "src/cli/retry-request-flag.test.ts": 9, + "src/cli/runtime-client-deferral.test.ts": 486, + "src/cli/runtime-client.test.ts": 110, + "src/cli/runtime/client-recovery.test.ts": 250, + "src/cli/runtime/client-timeout-policy.test.ts": 13, + "src/cli/runtime/envelope-schema.test.ts": 33, + "src/cli/runtime/environments.test.ts": 27, + "src/cli/runtime/launch.test.ts": 53, + "src/cli/runtime/orchestration-recovery-command.test.ts": 5, + "src/cli/runtime/serve-signal-exit-diagnostic.test.ts": 28, + "src/cli/runtime/status.test.ts": 19, + "src/cli/runtime/transport-framing.test.ts": 78, + "src/cli/runtime/transport.test.ts": 533, + "src/cli/runtime/types.test.ts": 5, + "src/cli/runtime/websocket-transport-error.test.ts": 8, + "src/cli/runtime/websocket-transport.test.ts": 176, + "src/cli/serve-electron-flag-parity.test.ts": 8, + "src/cli/shell-command-quote.test.ts": 8, + "src/cli/skill-guide-cli-parity.test.ts": 14, + "src/cli/skills-reference-selector.test.ts": 28, + "src/cli/skills.test.ts": 963, + "src/cli/specs/account.test.ts": 6, + "src/cli/specs/bundled-guide-flags.test.ts": 40, + "src/cli/specs/computer.test.ts": 9, + "src/cli/specs/orchestration.test.ts": 16, + "src/cli/specs/skills.test.ts": 6, + "src/cli/terminal-format-draft.test.ts": 6, + "src/cli/terminal-format.test.ts": 12, + "src/cli/terminal-list-host-scope-format.test.ts": 8, + "src/cli/terminal-read-screen.test.ts": 68, + "src/cli/vocabulary-policy.test.ts": 8, + "src/cli/worktree-selector-wsl-posix-path.test.ts": 11, + "src/main/active-view-persistence-boundary.test.ts": 725, + "src/main/active-view-preference-sync-flush-veto.test.ts": 777, + "src/main/active-view-preference.test.ts": 20, + "src/main/agent-auth-restart-preservation.test.ts": 16, + "src/main/agent-awake-service-platform-assertions.test.ts": 10, + "src/main/agent-awake-service.test.ts": 29, + "src/main/agent-hooks/branch-rename-failure-output.test.ts": 7, + "src/main/agent-hooks/ended-process-reconciliation.test.ts": 34, + "src/main/agent-hooks/first-work-branch-rename.test.ts": 140, + "src/main/agent-hooks/first-work-folder-rename.test.ts": 13, + "src/main/agent-hooks/hook-script-outside-orca.test.ts": 32, + "src/main/agent-hooks/hook-status-session-tabs-republish.test.ts": 22, + "src/main/agent-hooks/install-telemetry.test.ts": 10, + "src/main/agent-hooks/installer-utils-remote.test.ts": 14, + "src/main/agent-hooks/installer-utils.test.ts": 66, + "src/main/agent-hooks/local-agent-cli-presence.test.ts": 11, + "src/main/agent-hooks/managed-agent-hook-controls.test.ts": 76, + "src/main/agent-hooks/managed-hook-detection-commands.test.ts": 5, + "src/main/agent-hooks/managed-hook-install-lock.test.ts": 263, + "src/main/agent-hooks/managed-hook-local-filesystem.test.ts": 147, + "src/main/agent-hooks/managed-hook-owner-identity.test.ts": 33, + "src/main/agent-hooks/managed-hook-runtime.test.ts": 41, + "src/main/agent-hooks/managed-hook-script-refresh-main-thread.test.ts": 27, + "src/main/agent-hooks/managed-hook-script-refresh.test.ts": 108, + "src/main/agent-hooks/managed-hook-stdin-lifecycle.test.ts": 1979, + "src/main/agent-hooks/managed-hook-timeout.test.ts": 1585, + "src/main/agent-hooks/managed-toml-ownership.test.ts": 12, + "src/main/agent-hooks/manual-compact-hook-stream.test.ts": 119, + "src/main/agent-hooks/manual-compact-status-cleanup.test.ts": 6, + "src/main/agent-hooks/migration-unsupported-pty-state.test.ts": 8, + "src/main/agent-hooks/opencode-message-part-flood-bench.test.ts": 1234, + "src/main/agent-hooks/remote-hook-service-installers.test.ts": 89, + "src/main/agent-hooks/restored-subagent-liveness-sweep.test.ts": 75, + "src/main/agent-hooks/server-ai-vault-liveness.test.ts": 34, + "src/main/agent-hooks/server-amp-normalization.test.ts": 13, + "src/main/agent-hooks/server-authority-evidence.test.ts": 21, + "src/main/agent-hooks/server-claude-child-permission-lifecycle.test.ts": 213, + "src/main/agent-hooks/server-claude-normalization.test.ts": 36, + "src/main/agent-hooks/server-claude-permission-visibility.test.ts": 114, + "src/main/agent-hooks/server-claude-statusline.test.ts": 41, + "src/main/agent-hooks/server-closed-tab-suppression.test.ts": 167, + "src/main/agent-hooks/server-codex-normalization.test.ts": 14, + "src/main/agent-hooks/server-codex-subagent-transcript.test.ts": 4092, + "src/main/agent-hooks/server-copilot-normalization.test.ts": 416, + "src/main/agent-hooks/server-cursor-normalization.test.ts": 13, + "src/main/agent-hooks/server-droid-normalization.test.ts": 10, + "src/main/agent-hooks/server-endpoint-file-lifecycle.test.ts": 35, + "src/main/agent-hooks/server-gemini-normalization.test.ts": 13, + "src/main/agent-hooks/server-grok-discovery.test.ts": 513, + "src/main/agent-hooks/server-hook-http-ingest.test.ts": 94, + "src/main/agent-hooks/server-ingest-remote.test.ts": 45, + "src/main/agent-hooks/server-ingest-structured-status.test.ts": 34, + "src/main/agent-hooks/server-ingest-terminal-status.test.ts": 18, + "src/main/agent-hooks/server-interrupt-inference-guards.test.ts": 76, + "src/main/agent-hooks/server-interrupt-inference-resurrection.test.ts": 35, + "src/main/agent-hooks/server-interrupt-inference-validation.test.ts": 24, + "src/main/agent-hooks/server-last-status-hydrate-confirmation.test.ts": 75, + "src/main/agent-hooks/server-last-status-hydrate-validation.test.ts": 83, + "src/main/agent-hooks/server-last-status-lead-boundary.test.ts": 172, + "src/main/agent-hooks/server-last-status-restored-children.test.ts": 596, + "src/main/agent-hooks/server-last-status-write.test.ts": 125, + "src/main/agent-hooks/server-observation-provenance.test.ts": 27, + "src/main/agent-hooks/server-opencode-lifecycle.test.ts": 228, + "src/main/agent-hooks/server-opencode-normalization.test.ts": 15, + "src/main/agent-hooks/server-pane-authority.test.ts": 254, + "src/main/agent-hooks/server-pi-normalization.test.ts": 14, + "src/main/agent-hooks/server-prompt-sent-telemetry.test.ts": 59, + "src/main/agent-hooks/server-relay-listener-replay.test.ts": 25, + "src/main/agent-hooks/server-reminted-pane-key.test.ts": 112, + "src/main/agent-hooks/server-replay-evidence-clock.test.ts": 28, + "src/main/agent-hooks/server-retired-pane-new-turn.test.ts": 21, + "src/main/agent-hooks/server-start-failure-lifecycle.test.ts": 32, + "src/main/agent-hooks/server-status-listener-fanout.test.ts": 84, + "src/main/agent-hooks/server-transport-interference.test.ts": 5826, + "src/main/agent-hooks/server.claude-interactive-question.test.ts": 21, + "src/main/agent-hooks/spool.test.ts": 134, + "src/main/agent-hooks/terminal-handle-row-identity.test.ts": 40, + "src/main/agent-hooks/windows-direct-cmd-hook-command.test.ts": 8, + "src/main/agent-hooks/windows-hook-payload-delivery.test.ts": 8, + "src/main/agent-hooks/windows-hook-post-interpreter.test.ts": 83, + "src/main/agent-hooks/windows-powershell-hook-launcher.test.ts": 5, + "src/main/agent-hooks/wsl-guest-plugin-install.test.ts": 9, + "src/main/agent-hooks/wsl-hook-relay-launch.test.ts": 7, + "src/main/agent-hooks/wsl-hook-relay-live.integration.test.ts": 2716, + "src/main/agent-hooks/wsl-hook-relay-manager.test.ts": 1110, + "src/main/agent-hooks/wsl-hook-relay-reattach.test.ts": 6, + "src/main/agent-hooks/wsl-hook-relay-recovery.test.ts": 2323, + "src/main/agent-hooks/wsl-hook-relay-sentinel.test.ts": 17, + "src/main/agent-state-file-reader.test.ts": 39, + "src/main/agent-trust-presets.test.ts": 33, + "src/main/ai-vault-search/session-search-content-hash.test.ts": 5, + "src/main/ai-vault-search/session-search-cwd-key.test.ts": 9, + "src/main/ai-vault-search/session-search-deleted-sources.test.ts": 176, + "src/main/ai-vault-search/session-search-directory-listings.test.ts": 10, + "src/main/ai-vault-search/session-search-engine.test.ts": 1166, + "src/main/ai-vault-search/session-search-file-write.test.ts": 423, + "src/main/ai-vault-search/session-search-fts5-contract.test.ts": 62, + "src/main/ai-vault-search/session-search-hit-ranking.test.ts": 13, + "src/main/ai-vault-search/session-search-identifier-split.test.ts": 8, + "src/main/ai-vault-search/session-search-index-consumer.test.ts": 172, + "src/main/ai-vault-search/session-search-index-generation.test.ts": 111, + "src/main/ai-vault-search/session-search-index-pass.test.ts": 134, + "src/main/ai-vault-search/session-search-index-writer.test.ts": 114, + "src/main/ai-vault-search/session-search-indexer.test.ts": 2283, + "src/main/ai-vault-search/session-search-lifecycle-matrix.test.ts": 4538, + "src/main/ai-vault-search/session-search-live-transcript.test.ts": 83, + "src/main/ai-vault-search/session-search-merged-roots.test.ts": 111, + "src/main/ai-vault-search/session-search-message-rows.test.ts": 787, + "src/main/ai-vault-search/session-search-native-chat-indexing.test.ts": 211, + "src/main/ai-vault-search/session-search-opencode-decline.test.ts": 218, + "src/main/ai-vault-search/session-search-orphan-rows.test.ts": 179, + "src/main/ai-vault-search/session-search-paging.test.ts": 497, + "src/main/ai-vault-search/session-search-query-planner.test.ts": 14, + "src/main/ai-vault-search/session-search-read-decision.test.ts": 4, + "src/main/ai-vault-search/session-search-retention-delete.test.ts": 108, + "src/main/ai-vault-search/session-search-retention-policy.test.ts": 4, + "src/main/ai-vault-search/session-search-row-filter.test.ts": 194, + "src/main/ai-vault-search/session-search-row-identity.test.ts": 52, + "src/main/ai-vault-search/session-search-scan-roots.test.ts": 5, + "src/main/ai-vault-search/session-search-schema.test.ts": 322, + "src/main/ai-vault-search/session-search-sidebar-parity.test.ts": 213, + "src/main/ai-vault-search/session-search-snippet-marks.test.ts": 118, + "src/main/ai-vault-search/session-search-store-is-memory.test.ts": 305, + "src/main/ai-vault-search/session-search-synthetic-corpus.test.ts": 188, + "src/main/ai-vault-search/session-search-typo-policy.test.ts": 556, + "src/main/ai-vault-search/session-search-typo-scope.test.ts": 47, + "src/main/ai-vault/ai-vault-scan-cancellation.test.ts": 5, + "src/main/ai-vault/ai-vault-scan-coordinator.test.ts": 16, + "src/main/ai-vault/cached-session-list.test.ts": 10, + "src/main/ai-vault/claude-project-dir-encoding.test.ts": 10, + "src/main/ai-vault/codex-session-collection.test.ts": 32, + "src/main/ai-vault/codex-session-root-dedup.test.ts": 13, + "src/main/ai-vault/local-log-tail-reader.test.ts": 25, + "src/main/ai-vault/remote-session-parse-cache.test.ts": 26, + "src/main/ai-vault/remote-session-scan-batching.test.ts": 6, + "src/main/ai-vault/remote-session-scanner-omp-subagents.test.ts": 29, + "src/main/ai-vault/remote-session-scanner.test.ts": 37, + "src/main/ai-vault/remote-session-sidecar-observation.test.ts": 31, + "src/main/ai-vault/runtime-session-scanner.test.ts": 16, + "src/main/ai-vault/session-delete-target.test.ts": 20, + "src/main/ai-vault/session-delete.test.ts": 22, + "src/main/ai-vault/session-first-user-prompt-read.test.ts": 28, + "src/main/ai-vault/session-list-result-validation.test.ts": 27, + "src/main/ai-vault/session-list-results.test.ts": 12, + "src/main/ai-vault/session-newest-files.test.ts": 121, + "src/main/ai-vault/session-parse-cache-persistence.test.ts": 73, + "src/main/ai-vault/session-scanner-antigravity-parser.test.ts": 9, + "src/main/ai-vault/session-scanner-antigravity-source.test.ts": 57, + "src/main/ai-vault/session-scanner-background.test.ts": 8, + "src/main/ai-vault/session-scanner-claude-cwd-drift.test.ts": 23, + "src/main/ai-vault/session-scanner-claude-subagent-prune.test.ts": 21, + "src/main/ai-vault/session-scanner-claude-subagents.test.ts": 71, + "src/main/ai-vault/session-scanner-claude-title.test.ts": 32, + "src/main/ai-vault/session-scanner-claude-unicode-scope.test.ts": 43, + "src/main/ai-vault/session-scanner-cline-parser.test.ts": 32, + "src/main/ai-vault/session-scanner-codex-dual-root.test.ts": 51, + "src/main/ai-vault/session-scanner-codex-fast-path.test.ts": 180, + "src/main/ai-vault/session-scanner-codex-parser.test.ts": 47, + "src/main/ai-vault/session-scanner-codex-title-index.test.ts": 175, + "src/main/ai-vault/session-scanner-codex-tool-records.test.ts": 16, + "src/main/ai-vault/session-scanner-codex-workers.test.ts": 36, + "src/main/ai-vault/session-scanner-core-parser-wsl-stall.test.ts": 45, + "src/main/ai-vault/session-scanner-cursor-chat-meta.test.ts": 154, + "src/main/ai-vault/session-scanner-dedup-batches.test.ts": 228, + "src/main/ai-vault/session-scanner-devin-parser.test.ts": 13, + "src/main/ai-vault/session-scanner-directory-reader.test.ts": 15, + "src/main/ai-vault/session-scanner-discovery-wsl-gate.test.ts": 16, + "src/main/ai-vault/session-scanner-first-user-prompt.test.ts": 9, + "src/main/ai-vault/session-scanner-fs-import-guard.test.ts": 28, + "src/main/ai-vault/session-scanner-graph-parsers.test.ts": 6, + "src/main/ai-vault/session-scanner-grok-parser.test.ts": 26, + "src/main/ai-vault/session-scanner-grok-user-text.test.ts": 6, + "src/main/ai-vault/session-scanner-index-cache-wsl-stall.test.ts": 25, + "src/main/ai-vault/session-scanner-injected-title.test.ts": 29, + "src/main/ai-vault/session-scanner-jsonl-reader.test.ts": 84, + "src/main/ai-vault/session-scanner-kimi-index-cache.test.ts": 31, + "src/main/ai-vault/session-scanner-kimi-parser.test.ts": 153, + "src/main/ai-vault/session-scanner-omp-subagent-listing.test.ts": 24, + "src/main/ai-vault/session-scanner-omp-subagent-prune.test.ts": 38, + "src/main/ai-vault/session-scanner-omp-subagent-transcripts.test.ts": 15, + "src/main/ai-vault/session-scanner-opencode-parser.test.ts": 25, + "src/main/ai-vault/session-scanner-opencode-sources-wsl-stall.test.ts": 23, + "src/main/ai-vault/session-scanner-opencode-sources.test.ts": 9, + "src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts": 539, + "src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts": 58, + "src/main/ai-vault/session-scanner-opencode-sqlite-open.test.ts": 4131, + "src/main/ai-vault/session-scanner-opencode-sqlite-paths.test.ts": 9, + "src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.test.ts": 23, + "src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.test.ts": 6, + "src/main/ai-vault/session-scanner-opencode-sqlite.test.ts": 225, + "src/main/ai-vault/session-scanner-parse-cache-agents.test.ts": 198, + "src/main/ai-vault/session-scanner-parse-cache.test.ts": 57, + "src/main/ai-vault/session-scanner-parse-wsl-stall.test.ts": 31, + "src/main/ai-vault/session-scanner-parser-stream-cleanup.test.ts": 21, + "src/main/ai-vault/session-scanner-preview-window-truncation.test.ts": 6, + "src/main/ai-vault/session-scanner-recoverable-empty.test.ts": 57, + "src/main/ai-vault/session-scanner-roots.test.ts": 7, + "src/main/ai-vault/session-scanner-scope.test.ts": 82, + "src/main/ai-vault/session-scanner-service-client.test.ts": 178, + "src/main/ai-vault/session-scanner-service-entry-path.test.ts": 5, + "src/main/ai-vault/session-scanner-service-entry.test.ts": 486, + "src/main/ai-vault/session-scanner-service-env.test.ts": 8, + "src/main/ai-vault/session-scanner-service-restart-policy.test.ts": 6, + "src/main/ai-vault/session-scanner-service-spawn.test.ts": 6, + "src/main/ai-vault/session-scanner-values.test.ts": 21, + "src/main/ai-vault/session-scanner-worker-client.test.ts": 17, + "src/main/ai-vault/session-scanner.test.ts": 109, + "src/main/ai-vault/session-sidecar-stat.test.ts": 9, + "src/main/ai-vault/session-title-file-reader-wsl-stall.test.ts": 22, + "src/main/ai-vault/session-title-file-reader.test.ts": 15, + "src/main/ai-vault/session-transcript-consumers.test.ts": 107, + "src/main/ai-vault/session-transcript-message-content.test.ts": 10, + "src/main/ai-vault/ssh-session-list.test.ts": 87, + "src/main/ai-vault/structured-session-ownership.test.ts": 18, + "src/main/amp/hook-service.test.ts": 13, + "src/main/antigravity/hook-service.test.ts": 61, + "src/main/antigravity/windows-hook-payload-delivery.test.ts": 10, + "src/main/app-icon.test.ts": 24, + "src/main/app-relaunch.test.ts": 7, + "src/main/appimage-runtime-identity.test.ts": 36, + "src/main/appkit-scene-mutation.test.ts": 9, + "src/main/artifacts/artifact-cloud-config.test.ts": 6, + "src/main/artifacts/artifact-cloud-recovery.test.ts": 132, + "src/main/artifacts/artifact-cloud-service-races.test.ts": 219, + "src/main/artifacts/artifact-cloud-service.test.ts": 725, + "src/main/artifacts/artifact-create-intent-store.test.ts": 339, + "src/main/artifacts/artifact-recovery-directory-fsync.test.ts": 14, + "src/main/artifacts/artifact-share-record-store.test.ts": 310, + "src/main/asar-transparent-fs.test.ts": 9, + "src/main/automations/automation-dispatch-host-fence.test.ts": 1300, + "src/main/automations/automation-owner-fencing.test.ts": 1874, + "src/main/automations/automation-owner-migration.test.ts": 17, + "src/main/automations/automation-run-terminal-surface.test.ts": 34, + "src/main/automations/automation-run-writer.test.ts": 7, + "src/main/automations/automation-skip-coalescing.test.ts": 1783, + "src/main/automations/automation-ssh-readoption-migration.test.ts": 9, + "src/main/automations/automation-update-host-retarget.test.ts": 1187, + "src/main/automations/automation-workspace-host-attribution.test.ts": 757, + "src/main/automations/external-automation-manager-cache.test.ts": 12, + "src/main/automations/external-automation-owner-guard.test.ts": 8, + "src/main/automations/external-automation-probe-scheduler.test.ts": 123, + "src/main/automations/external-job-run-sorting.test.ts": 170, + "src/main/automations/external-manager-scoped.test.ts": 23, + "src/main/automations/external-manager.test.ts": 15, + "src/main/automations/headless-workspace-create.test.ts": 9, + "src/main/automations/hermes-cron-output.test.ts": 73, + "src/main/automations/precheck-runner.test.ts": 1066, + "src/main/automations/refused-manual-run.test.ts": 2290, + "src/main/automations/retained-run-reconciliation.test.ts": 491, + "src/main/automations/run-completion-watcher.test.ts": 1046, + "src/main/automations/run-target-resolution.test.ts": 6, + "src/main/automations/runtime-terminal-run-observer.test.ts": 54, + "src/main/automations/service-precheck.test.ts": 931, + "src/main/automations/service.test.ts": 1218, + "src/main/azure-devops/azure-devops-api-request.test.ts": 61, + "src/main/azure-devops/client.test.ts": 84, + "src/main/azure-devops/pull-request-creation.test.ts": 74, + "src/main/azure-devops/pull-request-mappers.test.ts": 5, + "src/main/azure-devops/repository-ref.test.ts": 33, + "src/main/bitbucket/client.test.ts": 102, + "src/main/bitbucket/credential-connection.test.ts": 151, + "src/main/bitbucket/credential-store.test.ts": 146, + "src/main/bitbucket/pull-request-creation.test.ts": 70, + "src/main/bitbucket/pull-request-mappers.test.ts": 7, + "src/main/bitbucket/repository-ref.test.ts": 24, + "src/main/bitbucket/status-no-decrypt.test.ts": 171, + "src/main/browser/agent-browser-bridge-automation-visibility.test.ts": 91, + "src/main/browser/agent-browser-bridge-command-transport.test.ts": 102, + "src/main/browser/agent-browser-bridge-mouse-input.test.ts": 24, + "src/main/browser/agent-browser-bridge-navigation.test.ts": 156, + "src/main/browser/agent-browser-bridge-session-lifecycle.test.ts": 244, + "src/main/browser/agent-browser-bridge-tab-routing.test.ts": 89, + "src/main/browser/agent-browser-bridge-text-input.test.ts": 104, + "src/main/browser/agent-browser-orphan-sweep.test.ts": 14, + "src/main/browser/agent-browser-process-environment.test.ts": 9, + "src/main/browser/agent-browser-session-reset.test.ts": 9, + "src/main/browser/browser-certificate-trust-controller.test.ts": 186, + "src/main/browser/browser-clicked-link-routing.test.ts": 111, + "src/main/browser/browser-client-download-relay.test.ts": 39, + "src/main/browser/browser-client-download-routing.test.ts": 12, + "src/main/browser/browser-client-file-channel-negotiation.test.ts": 13, + "src/main/browser/browser-client-file-channel-reconnect.test.ts": 317, + "src/main/browser/browser-client-host-attach-request.test.ts": 13, + "src/main/browser/browser-client-host-authority-replacement.test.ts": 20, + "src/main/browser/browser-client-host-command-dispatcher.test.ts": 26, + "src/main/browser/browser-client-host-id.test.ts": 37, + "src/main/browser/browser-client-host-placement-preparation.test.ts": 17, + "src/main/browser/browser-client-host-published-url-wiring.test.ts": 451, + "src/main/browser/browser-client-host-reconciliation-command-order.test.ts": 19, + "src/main/browser/browser-client-network-route-registry.test.ts": 45, + "src/main/browser/browser-client-page-automation-runtime.test.ts": 15, + "src/main/browser/browser-client-page-command-executor-fencing.test.ts": 25, + "src/main/browser/browser-client-page-command-executor.test.ts": 33, + "src/main/browser/browser-client-page-command-integration.test.ts": 21, + "src/main/browser/browser-client-page-execution-host-supersession.test.ts": 12, + "src/main/browser/browser-client-page-inventory.test.ts": 68, + "src/main/browser/browser-client-page-metadata-transport.test.ts": 21, + "src/main/browser/browser-client-page-published-url.test.ts": 12, + "src/main/browser/browser-client-page-reconciliation-adapters.test.ts": 28, + "src/main/browser/browser-client-page-renderer-bridge.test.ts": 35, + "src/main/browser/browser-client-page-renderer-lifecycle.electron.test.ts": 1571, + "src/main/browser/browser-client-page-renderer-runtime.test.ts": 12, + "src/main/browser/browser-client-page-unavailability.test.ts": 13, + "src/main/browser/browser-client-page-upload-routing.test.ts": 60, + "src/main/browser/browser-client-route-cookie-import-partition-parity.test.ts": 20, + "src/main/browser/browser-client-route-cookie-import.test.ts": 14, + "src/main/browser/browser-client-upload-command.test.ts": 34, + "src/main/browser/browser-client-upload-staging.test.ts": 57, + "src/main/browser/browser-client-upload-transfer.test.ts": 3444, + "src/main/browser/browser-cookie-clear-preserve.test.ts": 71, + "src/main/browser/browser-cookie-clear-store-lifecycle.test.ts": 11, + "src/main/browser/browser-cookie-clear-store.test.ts": 25, + "src/main/browser/browser-cookie-import-app-bound-prefix.test.ts": 7, + "src/main/browser/browser-cookie-import-clear-atomicity.test.ts": 15, + "src/main/browser/browser-cookie-import-concurrency.test.ts": 113, + "src/main/browser/browser-cookie-import-google-exclusion.test.ts": 135, + "src/main/browser/browser-cookie-import-partition-fidelity.test.ts": 502, + "src/main/browser/browser-cookie-import-partition-reland.electron.test.ts": 2913, + "src/main/browser/browser-cookie-import-partition-rollback.electron.test.ts": 4236, + "src/main/browser/browser-cookie-import-partition-success.electron.test.ts": 513, + "src/main/browser/browser-cookie-import-partition.electron.test.ts": 606, + "src/main/browser/browser-cookie-import-plan-writes.test.ts": 19, + "src/main/browser/browser-cookie-import-policy.test.ts": 127, + "src/main/browser/browser-cookie-import-replace-partition-rollback.electron.test.ts": 1412, + "src/main/browser/browser-cookie-import-replacement.test.ts": 72, + "src/main/browser/browser-cookie-import-route-partition-staging.test.ts": 51, + "src/main/browser/browser-cookie-import-scope.test.ts": 72, + "src/main/browser/browser-cookie-import-undecryptable.test.ts": 285, + "src/main/browser/browser-cookie-import-validated-partition.electron.test.ts": 594, + "src/main/browser/browser-cookie-import-write.test.ts": 11, + "src/main/browser/browser-cookie-import.comet.test.ts": 427, + "src/main/browser/browser-cookie-import.helium.test.ts": 358, + "src/main/browser/browser-cookie-import.test.ts": 1644, + "src/main/browser/browser-cookie-registrable-family.test.ts": 19, + "src/main/browser/browser-cookie-samesite.electron.test.ts": 714, + "src/main/browser/browser-cookie-source-partition.test.ts": 8, + "src/main/browser/browser-cookie-validation.test.ts": 7, + "src/main/browser/browser-download-destination.test.ts": 15, + "src/main/browser/browser-execution-host-storage-identity.test.ts": 10, + "src/main/browser/browser-google-auth-ua.test.ts": 7, + "src/main/browser/browser-grab-payload.test.ts": 13, + "src/main/browser/browser-guest-shortcut-forwarding.test.ts": 31, + "src/main/browser/browser-guest-wheel-zoom-scroll.test.ts": 6, + "src/main/browser/browser-host-client-identity.test.ts": 30, + "src/main/browser/browser-host-lease-reconnect-delay.test.ts": 6, + "src/main/browser/browser-manager-annotation-bridge.test.ts": 16, + "src/main/browser/browser-manager-auth-user-agent.test.ts": 19, + "src/main/browser/browser-manager-client-hosted-downloads.test.ts": 135, + "src/main/browser/browser-manager-downloads.test.ts": 40, + "src/main/browser/browser-manager-grab-capture.test.ts": 13, + "src/main/browser/browser-manager-grab-mode.test.ts": 16, + "src/main/browser/browser-manager-grab-selection.test.ts": 37, + "src/main/browser/browser-manager-grab-shortcuts.test.ts": 14, + "src/main/browser/browser-manager-guest-lifecycle.test.ts": 33, + "src/main/browser/browser-manager-guest-policy-profile.test.ts": 11, + "src/main/browser/browser-manager-guest-shortcuts.test.ts": 26, + "src/main/browser/browser-manager-guest-visibility.test.ts": 16, + "src/main/browser/browser-manager-load-failure-replay.test.ts": 29, + "src/main/browser/browser-manager-popup-child.test.ts": 19, + "src/main/browser/browser-manager-popup-routing.test.ts": 77, + "src/main/browser/browser-manager-viewport-override.test.ts": 45, + "src/main/browser/browser-manager-viewport-partial-failure.test.ts": 13, + "src/main/browser/browser-network-deferred-socket.test.ts": 6, + "src/main/browser/browser-network-execution-route.test.ts": 14, + "src/main/browser/browser-network-tunnel-client-memory-budget.test.ts": 13, + "src/main/browser/browser-network-tunnel-client.test.ts": 36, + "src/main/browser/browser-network-tunnel-conformance.test.ts": 56, + "src/main/browser/browser-network-tunnel-outbound-memory-budget.test.ts": 7, + "src/main/browser/browser-network-tunnel-session-aggregate-memory.test.ts": 6, + "src/main/browser/browser-network-tunnel-session-stream-scoped-failure.test.ts": 26, + "src/main/browser/browser-network-tunnel-session.test.ts": 57, + "src/main/browser/browser-page-initiated-tab-budget.test.ts": 5, + "src/main/browser/browser-route-dns-prefetch.electron.test.ts": 4337, + "src/main/browser/browser-route-guest-popups.test.ts": 14, + "src/main/browser/browser-route-h3-egress.electron.test.ts": 13849, + "src/main/browser/browser-route-identity.test.ts": 8, + "src/main/browser/browser-route-partition-binding-capacity.test.ts": 27, + "src/main/browser/browser-route-partition-binding-store.test.ts": 42, + "src/main/browser/browser-route-partition-migration.test.ts": 261, + "src/main/browser/browser-route-partition-stability.test.ts": 30, + "src/main/browser/browser-route-partition-storage-lifecycle.test.ts": 33, + "src/main/browser/browser-route-partition-storage-retirement.test.ts": 14, + "src/main/browser/browser-route-partition-storage-runtime.test.ts": 10, + "src/main/browser/browser-route-persisted-worker-egress.electron.test.ts": 1471, + "src/main/browser/browser-route-prepared-page-rekey.test.ts": 9, + "src/main/browser/browser-route-session-registry.test.ts": 30, + "src/main/browser/browser-route-tcp-egress.electron.test.ts": 1082, + "src/main/browser/browser-route-webcontents-registry.test.ts": 43, + "src/main/browser/browser-route-webrtc-egress.electron.test.ts": 8203, + "src/main/browser/browser-screencast-lifecycle.test.ts": 16, + "src/main/browser/browser-screencast-snapshot-scaling.test.ts": 116, + "src/main/browser/browser-screencast-stream.test.ts": 549, + "src/main/browser/browser-session-cookie-staging.scoped.test.ts": 105, + "src/main/browser/browser-session-cookie-staging.test.ts": 8, + "src/main/browser/browser-session-partition-policies.test.ts": 36, + "src/main/browser/browser-session-partition-proxy-install.test.ts": 222, + "src/main/browser/browser-session-proxy.test.ts": 171, + "src/main/browser/browser-session-registry.persistence.test.ts": 550, + "src/main/browser/browser-session-registry.test.ts": 497, + "src/main/browser/browser-session-startup.test.ts": 33, + "src/main/browser/browser-session-ua-wire-identity.electron.test.ts": 2126, + "src/main/browser/browser-text-insertion.test.ts": 19, + "src/main/browser/browser-viewport-user-agent.test.ts": 9, + "src/main/browser/browser-webauthn-access.test.ts": 7, + "src/main/browser/browser-webauthn-account-picker.test.ts": 15, + "src/main/browser/browser-webauthn-profile-delete.test.ts": 64, + "src/main/browser/cdp-bridge-integration.test.ts": 3216, + "src/main/browser/cdp-bridge-state.test.ts": 26, + "src/main/browser/cdp-print-to-pdf.test.ts": 22, + "src/main/browser/cdp-screenshot.test.ts": 16, + "src/main/browser/cdp-ws-proxy-focus-replay.test.ts": 199, + "src/main/browser/cdp-ws-proxy.test.ts": 2405, + "src/main/browser/chromium-cookie-snapshot.test.ts": 279, + "src/main/browser/client-route-cookie-import-source-store.test.ts": 51, + "src/main/browser/doc-preview-download-block-notice.test.ts": 15, + "src/main/browser/doc-preview-failure-notice.test.ts": 7, + "src/main/browser/doc-preview-file-reader.test.ts": 31, + "src/main/browser/doc-preview-grant-registry.test.ts": 12, + "src/main/browser/doc-preview-guest-policy.test.ts": 46, + "src/main/browser/doc-preview-protocol.test.ts": 48, + "src/main/browser/electron-debugger-lease.test.ts": 4, + "src/main/browser/electron-probe-display-launch.test.ts": 8, + "src/main/browser/execution-route-socket-duplex.test.ts": 42, + "src/main/browser/grab-guest-script.test.ts": 29, + "src/main/browser/local-ssh-browser-partition-identity.test.ts": 11, + "src/main/browser/local-ssh-browser-partitions.probe-cache.test.ts": 7, + "src/main/browser/local-ssh-browser-route.test.ts": 40, + "src/main/browser/offscreen-browser-backend-lifecycle.test.ts": 67, + "src/main/browser/offscreen-browser-backend.web-preferences.test.ts": 7, + "src/main/browser/paired-runtime-browser-client-host-composition.test.ts": 51, + "src/main/browser/paired-runtime-browser-client-host-identity-wiring.test.ts": 732, + "src/main/browser/paired-runtime-browser-client-host-reconnect.test.ts": 274, + "src/main/browser/paired-runtime-browser-client-host-registry.test.ts": 13, + "src/main/browser/paired-runtime-browser-client-host-route-identity.test.ts": 60, + "src/main/browser/paired-runtime-browser-client-host-runtime.test.ts": 7, + "src/main/browser/paired-runtime-browser-client-host.test.ts": 95, + "src/main/browser/paired-runtime-browser-host-command-admission.test.ts": 24, + "src/main/browser/paired-runtime-browser-host-lease-reconnect.test.ts": 708, + "src/main/browser/paired-runtime-browser-host-lease.test.ts": 692, + "src/main/browser/paired-runtime-browser-host-reconciliation-negotiation.test.ts": 66, + "src/main/browser/paired-runtime-browser-network-route.test.ts": 1007, + "src/main/browser/popup-origin-bar-window.test.ts": 19, + "src/main/browser/remote-browser-socks-buffering.test.ts": 1148, + "src/main/browser/remote-browser-socks-server.test.ts": 296, + "src/main/browser/snapshot-engine.test.ts": 13, + "src/main/browser/ssh-browser-network-execution-route.test.ts": 175, + "src/main/browser/system-ssh-socks-client-socket.test.ts": 16, + "src/main/browser/wsl-browser-network-execution-route.test.ts": 128, + "src/main/browser/wsl-browser-network-relay-launch.test.ts": 23, + "src/main/claude-accounts/claude-account-service-account-selection.test.ts": 134, + "src/main/claude-accounts/claude-account-service-add-account.test.ts": 91, + "src/main/claude-accounts/claude-account-service-api-parity.test.ts": 65, + "src/main/claude-accounts/claude-account-service-config-dir-capture.test.ts": 157, + "src/main/claude-accounts/claude-account-service-credential-capture.test.ts": 94, + "src/main/claude-accounts/claude-account-service-login-process.test.ts": 480, + "src/main/claude-accounts/claude-account-service-reauth-rollback.test.ts": 193, + "src/main/claude-accounts/claude-duplicate-account.test.ts": 4, + "src/main/claude-accounts/claude-login-completion.oracle.test.ts": 105, + "src/main/claude-accounts/claude-structured-auth-policy.test.ts": 8, + "src/main/claude-accounts/claude-windows-interactive-login.test.ts": 75, + "src/main/claude-accounts/keychain.test.ts": 13, + "src/main/claude-accounts/live-pty-gate.test.ts": 13, + "src/main/claude-accounts/oauth-refresh.test.ts": 20, + "src/main/claude-accounts/runtime-auth-service-account-switching.test.ts": 147, + "src/main/claude-accounts/runtime-auth-service-deselect-restore.test.ts": 284, + "src/main/claude-accounts/runtime-auth-service-keychain-snapshots.test.ts": 431, + "src/main/claude-accounts/runtime-auth-service-launch-refresh.test.ts": 159, + "src/main/claude-accounts/runtime-auth-service-materialization.test.ts": 226, + "src/main/claude-accounts/runtime-auth-service-readback-identity.test.ts": 298, + "src/main/claude-accounts/runtime-auth-service-snapshot-validation.test.ts": 319, + "src/main/claude-accounts/runtime-auth-service-wsl-runtime.test.ts": 197, + "src/main/claude-accounts/runtime-selection.test.ts": 4, + "src/main/claude-accounts/windows-command-invocation.test.ts": 8, + "src/main/claude-usage/claude-model-pricing.test.ts": 12, + "src/main/claude-usage/claude-usage-report-aggregation.test.ts": 6, + "src/main/claude-usage/scanner-large-directory.test.ts": 530, + "src/main/claude-usage/scanner-scan.test.ts": 114, + "src/main/claude-usage/scanner.test.ts": 31, + "src/main/claude-usage/store.test.ts": 35, + "src/main/claude-usage/transcript-record-parser-prefilter.test.ts": 5, + "src/main/claude-usage/worktree-attribution-scaling.test.ts": 13, + "src/main/claude/claude-agent-sdk-contract-pins.test.ts": 2683, + "src/main/claude/claude-agent-sdk-control-requests.test.ts": 10, + "src/main/claude/claude-agent-sdk-exit-proof-identity.test.ts": 7, + "src/main/claude/claude-agent-sdk-exit-proof.test.ts": 12842, + "src/main/claude/claude-agent-sdk-import-boundary.test.ts": 951, + "src/main/claude/claude-agent-sdk-process-spawn.test.ts": 13, + "src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts": 15, + "src/main/claude/claude-agent-sdk-user-message-queue.test.ts": 109, + "src/main/claude/claude-background-task-resume.test.ts": 14, + "src/main/claude/claude-background-task-tracker.test.ts": 93, + "src/main/claude/claude-child-process-environment.test.ts": 4, + "src/main/claude/claude-command-lifecycle-frames.test.ts": 7, + "src/main/claude/claude-config-dir-pin.test.ts": 6, + "src/main/claude/claude-descendant-escalation-boundary.test.ts": 462, + "src/main/claude/claude-slash-command-catalog.test.ts": 10, + "src/main/claude/claude-stream-json-connection-close.test.ts": 140, + "src/main/claude/claude-stream-json-connection.test.ts": 22607, + "src/main/claude/claude-streamed-text-checkpoints.test.ts": 9, + "src/main/claude/claude-structured-auth-parity.test.ts": 100, + "src/main/claude/claude-structured-compaction.test.ts": 10, + "src/main/claude/claude-structured-content-parts.test.ts": 18, + "src/main/claude/claude-structured-control-actions.test.ts": 12, + "src/main/claude/claude-structured-dispatch-admission.test.ts": 15, + "src/main/claude/claude-structured-dispatch-content.test.ts": 10, + "src/main/claude/claude-structured-dispatch.test.ts": 1411, + "src/main/claude/claude-structured-effort-reporting.test.ts": 27, + "src/main/claude/claude-structured-inbound-control.test.ts": 15, + "src/main/claude/claude-structured-journal-translation-subagents.test.ts": 23, + "src/main/claude/claude-structured-journal-translation-turn-timing.test.ts": 22, + "src/main/claude/claude-structured-journal-translation.test.ts": 63, + "src/main/claude/claude-structured-launch-resolution.test.ts": 28, + "src/main/claude/claude-structured-location-support.test.ts": 6, + "src/main/claude/claude-structured-model-confirmation.test.ts": 18, + "src/main/claude/claude-structured-option-confirmation.test.ts": 16, + "src/main/claude/claude-structured-options.test.ts": 8, + "src/main/claude/claude-structured-owner-identity.test.ts": 10, + "src/main/claude/claude-structured-prompt-items.test.ts": 11, + "src/main/claude/claude-structured-provider-fallback.test.ts": 25, + "src/main/claude/claude-structured-rewind.test.ts": 25, + "src/main/claude/claude-structured-session-acquisition-processless.test.ts": 8, + "src/main/claude/claude-structured-session-adapter-turns.test.ts": 25, + "src/main/claude/claude-structured-session-adapter.test.ts": 63, + "src/main/claude/claude-structured-session-close.test.ts": 26, + "src/main/claude/claude-structured-session-commands.test.ts": 10, + "src/main/claude/claude-structured-session-recovery.test.ts": 47, + "src/main/claude/claude-subagent-group-row.test.ts": 6, + "src/main/claude/claude-subagent-id-aliases.test.ts": 7, + "src/main/claude/claude-subagent-roster.test.ts": 22, + "src/main/claude/claude-subagent-task-frames.test.ts": 12, + "src/main/claude/claude-transcript-rewind-proof.test.ts": 10, + "src/main/claude/claude-tui-exit.test.ts": 18, + "src/main/claude/claude-tui-resume-launch.test.ts": 10, + "src/main/claude/claude-tui-resume-proof.test.ts": 9, + "src/main/claude/compact-status-registration.test.ts": 27, + "src/main/claude/hook-service.test.ts": 58, + "src/main/claude/statusline-script.test.ts": 186, + "src/main/cli/appimage-extracted-root.test.ts": 148, + "src/main/cli/appimage-extraction-pruning.test.ts": 37, + "src/main/cli/appimage-payload-removal.reentrancy.test.ts": 12, + "src/main/cli/appimage-payload-removal.test.ts": 13, + "src/main/cli/appimage-registration-lock.test.ts": 31, + "src/main/cli/appimage-stable-launcher.test.ts": 158, + "src/main/cli/cli-command-installation-races.test.ts": 78, + "src/main/cli/cli-installer-appimage-ownership.test.ts": 104, + "src/main/cli/cli-installer-appimage-removal.test.ts": 93, + "src/main/cli/cli-installer-command-conflicts.test.ts": 65, + "src/main/cli/cli-installer-macos-command-path.test.ts": 84, + "src/main/cli/cli-installer-windows-path.test.ts": 59, + "src/main/cli/cli-installer.test.ts": 153, + "src/main/cli/cli-privileged-processes.test.ts": 10, + "src/main/cli/keyed-promise-queue.test.ts": 8, + "src/main/cli/legacy-appimage-cli-wrapper.test.ts": 5, + "src/main/cli/linux-bare-orca-dispatcher.test.ts": 51, + "src/main/cli/linux-terminal-orca-cli-shim.test.ts": 75, + "src/main/cli/orca-cli-child-path.test.ts": 14, + "src/main/cli/packaged-cli-assets.test.ts": 492, + "src/main/cli/windows-launcher-asset.test.ts": 7, + "src/main/cli/windows-user-path-registry.test.ts": 10, + "src/main/cli/wsl-cli-installer.test.ts": 52, + "src/main/cli/wsl-cli-powershell-boundary.test.ts": 6, + "src/main/cli/wsl-cli-registration-operation.test.ts": 63, + "src/main/cli/wsl-cli-registration-reconciliation.test.ts": 13, + "src/main/cli/wsl-cli-registration-registry.test.ts": 84, + "src/main/codex-accounts/codex-account-identity-api-key-guard.test.ts": 7, + "src/main/codex-accounts/codex-auth-workspace-identity.test.ts": 15, + "src/main/codex-accounts/codex-credential-absence-grace.test.ts": 9, + "src/main/codex-accounts/codex-windows-interactive-login.test.ts": 234, + "src/main/codex-accounts/fs-utils.test.ts": 69, + "src/main/codex-accounts/host-codex-managed-home-ownership.test.ts": 12, + "src/main/codex-accounts/legacy-shared-auth-migration.test.ts": 62, + "src/main/codex-accounts/legacy-shared-config-compatibility.test.ts": 20, + "src/main/codex-accounts/legacy-wsl-runtime-auth-drain-apply-script.test.ts": 11759, + "src/main/codex-accounts/legacy-wsl-runtime-auth-drain-rollback-script.test.ts": 400, + "src/main/codex-accounts/legacy-wsl-runtime-auth-drain.test.ts": 23, + "src/main/codex-accounts/managed-codex-auth-readiness.test.ts": 36, + "src/main/codex-accounts/runtime-home-legacy-migration.test.ts": 381, + "src/main/codex-accounts/runtime-home-managed-auth-recovery.test.ts": 217, + "src/main/codex-accounts/runtime-home-mirrored-status-home.test.ts": 163, + "src/main/codex-accounts/runtime-home-per-account-homes.test.ts": 440, + "src/main/codex-accounts/runtime-home-real-home-lane-routing.test.ts": 278, + "src/main/codex-accounts/runtime-home-resume-selection-gate.test.ts": 119, + "src/main/codex-accounts/runtime-home-retained-auth-provenance.test.ts": 657, + "src/main/codex-accounts/runtime-home-service-per-account-migration.test.ts": 278, + "src/main/codex-accounts/runtime-home-session-migration-pass.test.ts": 282, + "src/main/codex-accounts/runtime-home-system-default-mirror-readback.test.ts": 709, + "src/main/codex-accounts/runtime-home-system-default-snapshot.test.ts": 618, + "src/main/codex-accounts/runtime-home-system-resource-materialization.test.ts": 216, + "src/main/codex-accounts/runtime-home-windows-profile-ownership.test.ts": 3, + "src/main/codex-accounts/runtime-home-wsl-managed-accounts.test.ts": 742, + "src/main/codex-accounts/runtime-home-wsl-session-bridge.test.ts": 393, + "src/main/codex-accounts/runtime-home-wsl-system-default.test.ts": 408, + "src/main/codex-accounts/runtime-selection.test.ts": 6, + "src/main/codex-accounts/service-account-add-login.test.ts": 289, + "src/main/codex-accounts/service-account-selection-and-removal.test.ts": 614, + "src/main/codex-accounts/service-add-account-from-home.test.ts": 177, + "src/main/codex-accounts/service-login-process-teardown.test.ts": 166, + "src/main/codex-accounts/service-managed-home-removal-retries.test.ts": 165, + "src/main/codex-accounts/service-quota-refresh-decoupling.test.ts": 174, + "src/main/codex-accounts/service-reauthenticate-activation.test.ts": 129, + "src/main/codex-accounts/service-reset-credit-durability.test.ts": 449, + "src/main/codex-accounts/service-reset-credit-home-ownership.test.ts": 175, + "src/main/codex-accounts/service-reset-credit-target-routing.test.ts": 339, + "src/main/codex-accounts/service-system-default-identity.test.ts": 207, + "src/main/codex-accounts/service-wsl-accounts.test.ts": 298, + "src/main/codex-accounts/service.test.ts": 181, + "src/main/codex-accounts/sta-4422-transient-ownership-error.test.ts": 209, + "src/main/codex-accounts/sta-4734-login-rollback-deletes-authenticated-home.test.ts": 376, + "src/main/codex-accounts/sta-4735-unreadable-host-lane-writes.test.ts": 145, + "src/main/codex-accounts/wsl-codex-command.test.ts": 8, + "src/main/codex-cli/codex-home-process-lock.test.ts": 15, + "src/main/codex-cli/command.test.ts": 39, + "src/main/codex-usage/scanner-large-directory.test.ts": 1093, + "src/main/codex-usage/scanner-paths.test.ts": 64, + "src/main/codex-usage/scanner.test.ts": 10, + "src/main/codex-usage/store-automation-usage.test.ts": 12, + "src/main/codex-usage/store-model-pricing.test.ts": 17, + "src/main/codex-usage/store-orca-scope.test.ts": 17, + "src/main/codex-usage/store-persistence.test.ts": 20, + "src/main/codex-usage/store-snapshot.benchmark.test.ts": 152, + "src/main/codex/codex-account-session-bridge.test.ts": 47, + "src/main/codex/codex-ai-vault-session-resume.test.ts": 14, + "src/main/codex/codex-app-server-capability-cache.test.ts": 11, + "src/main/codex/codex-app-server-client.test.ts": 1418, + "src/main/codex/codex-app-server-connection.test.ts": 5017, + "src/main/codex/codex-app-server-posix-supervisor.test.ts": 9, + "src/main/codex/codex-app-server-process-teardown.test.ts": 26, + "src/main/codex/codex-app-server-session.test.ts": 116, + "src/main/codex/codex-app-server-teardown.integration.test.ts": 17565, + "src/main/codex/codex-background-command-tracker.test.ts": 19, + "src/main/codex/codex-background-task-tracker.test.ts": 38, + "src/main/codex/codex-config-mirror.test.ts": 96, + "src/main/codex/codex-config-settings-removal.test.ts": 5, + "src/main/codex/codex-goal-journal-rows.test.ts": 14, + "src/main/codex/codex-home-paths.test.ts": 31, + "src/main/codex/codex-hook-legacy-profile-block.test.ts": 8, + "src/main/codex/codex-hook-trust-grant.test.ts": 241, + "src/main/codex/codex-hooks-read-denial-followup.test.ts": 116, + "src/main/codex/codex-legacy-session-resume.test.ts": 69, + "src/main/codex/codex-model-provider-config.test.ts": 9, + "src/main/codex/codex-notice-item-translation.test.ts": 13, + "src/main/codex/codex-pane-account-registry-mutations.test.ts": 14, + "src/main/codex/codex-pane-launch-account.test.ts": 8, + "src/main/codex/codex-persistent-command-retention.test.ts": 226, + "src/main/codex/codex-real-home-hook-install.test.ts": 70, + "src/main/codex/codex-real-home-path.test.ts": 7, + "src/main/codex/codex-requested-close-turn-timing.test.ts": 15, + "src/main/codex/codex-resume-process-proof.test.ts": 20, + "src/main/codex/codex-rollout-session-meta.test.ts": 21, + "src/main/codex/codex-server-request-disposition.test.ts": 21, + "src/main/codex/codex-session-backfill-marker.test.ts": 29, + "src/main/codex/codex-session-backfill-scan-dates.test.ts": 27, + "src/main/codex/codex-session-backfill.test.ts": 408, + "src/main/codex/codex-session-bridge.test.ts": 66, + "src/main/codex/codex-session-index-heal-state.test.ts": 369, + "src/main/codex/codex-session-index-heal.test.ts": 1379, + "src/main/codex/codex-session-migration-recent-exits.test.ts": 13, + "src/main/codex/codex-session-migration-scheduler.test.ts": 369, + "src/main/codex/codex-session-resume-home.test.ts": 22, + "src/main/codex/codex-session-resume-preparation.test.ts": 13, + "src/main/codex/codex-session-resume-wrong-account.test.ts": 24, + "src/main/codex/codex-session-source-home.test.ts": 5, + "src/main/codex/codex-stale-pane-accounts.test.ts": 95, + "src/main/codex/codex-state-db-backfill-recovery.test.ts": 147, + "src/main/codex/codex-state-db.test.ts": 27, + "src/main/codex/codex-structured-acquisition-exit-proof.test.ts": 11, + "src/main/codex/codex-structured-app-server-args.test.ts": 7, + "src/main/codex/codex-structured-child-environment.test.ts": 8, + "src/main/codex/codex-structured-item-translation.test.ts": 50, + "src/main/codex/codex-structured-journal-compactions.test.ts": 16, + "src/main/codex/codex-structured-journal-goal-admission.test.ts": 33, + "src/main/codex/codex-structured-journal-goal-resume.test.ts": 125, + "src/main/codex/codex-structured-journal-goal-rows.test.ts": 16, + "src/main/codex/codex-structured-journal-translation-settlement.test.ts": 843, + "src/main/codex/codex-structured-journal-translation-streams.test.ts": 85, + "src/main/codex/codex-structured-journal-translation-subagents.test.ts": 26, + "src/main/codex/codex-structured-journal-translation-turn-lifecycle.test.ts": 60, + "src/main/codex/codex-structured-journal-translation-turn-state.test.ts": 12, + "src/main/codex/codex-structured-journal-translation.test.ts": 96, + "src/main/codex/codex-structured-launch-resolution.test.ts": 17, + "src/main/codex/codex-structured-location-support.test.ts": 3, + "src/main/codex/codex-structured-owner-identity.test.ts": 9, + "src/main/codex/codex-structured-prompt-items.test.ts": 39, + "src/main/codex/codex-structured-prompt-replies.test.ts": 40, + "src/main/codex/codex-structured-rewind.test.ts": 34, + "src/main/codex/codex-structured-session-adapter-lifecycle.test.ts": 19, + "src/main/codex/codex-structured-session-adapter.test.ts": 37, + "src/main/codex/codex-structured-session-background-tasks.test.ts": 76, + "src/main/codex/codex-structured-session-cancel.test.ts": 164, + "src/main/codex/codex-structured-session-close.test.ts": 20, + "src/main/codex/codex-structured-session-options.test.ts": 12, + "src/main/codex/codex-structured-session-shutdown.test.ts": 62, + "src/main/codex/codex-structured-thread-open.test.ts": 19, + "src/main/codex/codex-structured-turn-processes.integration.test.ts": 85, + "src/main/codex/codex-subagent-execution-projection.test.ts": 16, + "src/main/codex/codex-subagent-executions.test.ts": 21, + "src/main/codex/codex-subagent-roster.test.ts": 40, + "src/main/codex/codex-tool-identity-translation.test.ts": 14, + "src/main/codex/codex-trust-config-concurrent-launch.test.ts": 70, + "src/main/codex/codex-trust-config-mutation-queue.test.ts": 10, + "src/main/codex/codex-trust-config-rollback.test.ts": 16, + "src/main/codex/codex-trust-grant-host.test.ts": 12, + "src/main/codex/codex-trust-grant-ledger.test.ts": 8, + "src/main/codex/codex-trust-grant-main-thread-boundary.test.ts": 15, + "src/main/codex/codex-trust-grant-telemetry.test.ts": 8, + "src/main/codex/codex-tui-rollout-proof.test.ts": 33, + "src/main/codex/codex-turn-ordinals.test.ts": 45, + "src/main/codex/codex-user-hook-trust-rebase-client.test.ts": 13, + "src/main/codex/codex-user-hook-trust-rebase.test.ts": 17, + "src/main/codex/codex-wsl-hook-install-plan.test.ts": 11, + "src/main/codex/config-plugin-registration-promotion.test.ts": 193, + "src/main/codex/config-settings-baseline-upgrade.test.ts": 61, + "src/main/codex/config-settings-promotion.test.ts": 227, + "src/main/codex/config-sync-stall.test.ts": 47, + "src/main/codex/config-toml-hook-trust-scaling.test.ts": 38, + "src/main/codex/config-toml-trust-api-parity.test.ts": 13, + "src/main/codex/config-toml-trust-hash.test.ts": 7, + "src/main/codex/config-toml-trust-hook-read.test.ts": 16, + "src/main/codex/config-toml-trust-hook-removal.test.ts": 26, + "src/main/codex/config-toml-trust-hook-upsert.test.ts": 44, + "src/main/codex/config-toml-trust-key.test.ts": 10, + "src/main/codex/config-toml-trust-paths.test.ts": 10, + "src/main/codex/config-toml-trust-project.test.ts": 27, + "src/main/codex/hook-service-concurrent-launch-install.test.ts": 618, + "src/main/codex/hook-service-legacy-cleanup.test.ts": 601, + "src/main/codex/hook-service-managed-install.test.ts": 99, + "src/main/codex/hook-service-runtime-trust-repair.test.ts": 117, + "src/main/codex/hook-service-trust-grant.test.ts": 123, + "src/main/codex/hook-service-user-hook-mirroring.test.ts": 186, + "src/main/codex/hook-service-wsl-runtime.test.ts": 113, + "src/main/codex/hook-trust-promotion.test.ts": 225, + "src/main/codex/managed-home-shell-preflight.test.ts": 28, + "src/main/codex/retained-codex-hook-state.test.ts": 8, + "src/main/codex/sta-4735-hook-trust-provenance-overwrite.test.ts": 12, + "src/main/codex/sta-4737-unreadable-is-not-absent.test.ts": 58, + "src/main/codex/sta-4823-shared-state-survives-a-failed-read.test.ts": 94, + "src/main/codex/wsl-codex-session-bridge.test.ts": 82, + "src/main/command-code/hook-service.test.ts": 239, + "src/main/computer/computer-action-verification-normalization.test.ts": 4, + "src/main/computer/computer-provider-lifecycle.test.ts": 5, + "src/main/computer/computer-provider-unavailable-message.test.ts": 3, + "src/main/computer/computer-sidecar-diagnostics.test.ts": 8, + "src/main/computer/desktop-script-provider-action-errors.test.ts": 68, + "src/main/computer/desktop-script-provider-actions.test.ts": 131, + "src/main/computer/desktop-script-provider-bridge.test.ts": 6, + "src/main/computer/desktop-script-provider-cache-lifecycle.test.ts": 96, + "src/main/computer/desktop-script-provider-cache.test.ts": 115, + "src/main/computer/desktop-script-provider-client.test.ts": 154, + "src/main/computer/desktop-script-provider-errors.test.ts": 161, + "src/main/computer/desktop-script-provider-paste-validation.test.ts": 105, + "src/main/computer/desktop-script-provider-runtime-host-routing.test.ts": 44, + "src/main/computer/desktop-script-runtime-host.test.ts": 106, + "src/main/computer/desktop-script-serve-channel.test.ts": 9, + "src/main/computer/macos-computer-use-permission-status.test.ts": 38, + "src/main/computer/macos-computer-use-permissions.test.ts": 24, + "src/main/computer/macos-native-provider-client.test.ts": 557, + "src/main/computer/macos-native-provider-paste-validation.test.ts": 99, + "src/main/computer/macos-native-provider-socket.test.ts": 14, + "src/main/computer/sidecar-client.test.ts": 28, + "src/main/computer/windows-powershell-execution-policy.test.ts": 7, + "src/main/copilot/hook-service.test.ts": 83, + "src/main/crash-reporting/crash-breadcrumb-renderer-attribution.test.ts": 29, + "src/main/crash-reporting/crash-breadcrumb-store-leak.test.ts": 60, + "src/main/crash-reporting/crash-breadcrumb-store-orphan-cleanup.test.ts": 45, + "src/main/crash-reporting/crash-breadcrumb-store.test.ts": 34, + "src/main/crash-reporting/crash-report-copy-text.test.ts": 8, + "src/main/crash-reporting/crash-report-store.test.ts": 351, + "src/main/crash-reporting/crashpad-capture.test.ts": 60, + "src/main/crash-reporting/durable-crash-breadcrumb.test.ts": 11, + "src/main/crash-reporting/expected-teardown-state.test.ts": 9, + "src/main/crash-reporting/gpu-crash-diagnostics.test.ts": 14, + "src/main/crash-reporting/gpu-crash-fallback-decision.test.ts": 13, + "src/main/crash-reporting/gpu-crash-fallback-field-sessions.test.ts": 9, + "src/main/crash-reporting/gpu-fallback-engagement.test.ts": 14, + "src/main/crash-reporting/gpu-fallback-recovered-launch.test.ts": 10, + "src/main/crash-reporting/gpu-fallback-restart-prompt.test.ts": 8, + "src/main/crash-reporting/main-process-lifecycle-identity.test.ts": 6, + "src/main/crash-reporting/minidump-crash-signature.test.ts": 30, + "src/main/crash-reporting/pre-gone-host-memory.test.ts": 18, + "src/main/crash-reporting/process-gone-classification.test.ts": 12, + "src/main/crash-reporting/process-gone-dedupe.test.ts": 6, + "src/main/crash-reporting/process-gone-diagnostics.test.ts": 19, + "src/main/crash-reporting/process-gone-killed-one-ordering.test.ts": 40, + "src/main/crash-reporting/process-gone-recorder.test.ts": 599, + "src/main/crash-reporting/process-gone-sibling-correlation.test.ts": 514, + "src/main/crash-reporting/renderer-recovery-circuit-breaker.test.ts": 10, + "src/main/crash-reporting/self-initiated-tree-kill-log.test.ts": 26, + "src/main/crash-reporting/suppressed-process-gone-breadcrumb.test.ts": 5, + "src/main/cursor/hook-service.test.ts": 173, + "src/main/daemon/agent-startup-prompt-latency.node-pty.test.ts": 1916, + "src/main/daemon/bash-prompt-command-composition.test.ts": 3541, + "src/main/daemon/client.test.ts": 392, + "src/main/daemon/cold-restore-payload-cache.test.ts": 9, + "src/main/daemon/daemon-adoption-telemetry-event.test.ts": 18, + "src/main/daemon/daemon-audit-eligibility-event.test.ts": 30, + "src/main/daemon/daemon-authenticated-client-activity.test.ts": 16, + "src/main/daemon/daemon-background-transient-facts.test.ts": 12, + "src/main/daemon/daemon-bundle-staleness.test.ts": 347, + "src/main/daemon/daemon-checkpoint-session-queue.test.ts": 204, + "src/main/daemon/daemon-client-notify-settlement.test.ts": 10, + "src/main/daemon/daemon-client-rpc-request.test.ts": 20, + "src/main/daemon/daemon-durable-history-ownership-seed.test.ts": 25, + "src/main/daemon/daemon-endpoint-ownership.test.ts": 26, + "src/main/daemon/daemon-endpoint-publish.test.ts": 92, + "src/main/daemon/daemon-endpoint-windows.test.ts": 8, + "src/main/daemon/daemon-entry-path-layouts.test.ts": 319, + "src/main/daemon/daemon-entry.test.ts": 15, + "src/main/daemon/daemon-errors.test.ts": 7, + "src/main/daemon/daemon-file-log.test.ts": 25, + "src/main/daemon/daemon-foreground-confirmation-protocol.test.ts": 7, + "src/main/daemon/daemon-health-endpoint-entry.test.ts": 9, + "src/main/daemon/daemon-health-socket-cleanup.test.ts": 16, + "src/main/daemon/daemon-health.test.ts": 3195, + "src/main/daemon/daemon-host-relocation.test.ts": 140, + "src/main/daemon/daemon-idle-shutdown.test.ts": 204, + "src/main/daemon/daemon-incarnation-evidence-main-thread.test.ts": 13, + "src/main/daemon/daemon-incarnation-evidence.test.ts": 23, + "src/main/daemon/daemon-init-child-readiness.test.ts": 406, + "src/main/daemon/daemon-init-child-startup-failure.test.ts": 406, + "src/main/daemon/daemon-init-endpoint-adoption.test.ts": 868, + "src/main/daemon/daemon-init-live-session-preservation.test.ts": 541, + "src/main/daemon/daemon-init-packaged-bundle-staleness.test.ts": 353, + "src/main/daemon/daemon-init-provider-installation.test.ts": 398, + "src/main/daemon/daemon-init-replacement-reporting.test.ts": 247, + "src/main/daemon/daemon-init-restart-sequence.test.ts": 576, + "src/main/daemon/daemon-init-wedged-daemon-grace.test.ts": 284, + "src/main/daemon/daemon-lifecycle-event.test.ts": 8, + "src/main/daemon/daemon-main.test.ts": 43, + "src/main/daemon/daemon-native-pty-exception.test.ts": 12, + "src/main/daemon/daemon-pid-file-parse.test.ts": 9, + "src/main/daemon/daemon-preflight-client-replacement.test.ts": 234, + "src/main/daemon/daemon-process-inspection.test.ts": 22, + "src/main/daemon/daemon-protocol-version.test.ts": 9, + "src/main/daemon/daemon-pty-adapter-cold-restore-reanchor.test.ts": 489, + "src/main/daemon/daemon-pty-adapter-cold-restore-seed.test.ts": 297, + "src/main/daemon/daemon-pty-adapter-concurrent-recovery.test.ts": 142, + "src/main/daemon/daemon-pty-adapter-daemon-recovery.test.ts": 505, + "src/main/daemon/daemon-pty-adapter-history-checkpoints.test.ts": 775, + "src/main/daemon/daemon-pty-adapter-history-recovery.test.ts": 580, + "src/main/daemon/daemon-pty-adapter-inventory-respawn.test.ts": 87, + "src/main/daemon/daemon-pty-adapter-protocol-compatibility.test.ts": 73, + "src/main/daemon/daemon-pty-adapter-replacement-exit-race.test.ts": 53, + "src/main/daemon/daemon-pty-adapter-session-adoption.test.ts": 557, + "src/main/daemon/daemon-pty-adapter-steady-state-compat.test.ts": 7, + "src/main/daemon/daemon-pty-adapter.test.ts": 873, + "src/main/daemon/daemon-pty-router-history-handoff.test.ts": 575, + "src/main/daemon/daemon-pty-router.test.ts": 445, + "src/main/daemon/daemon-pty-startup-delivery.test.ts": 1989, + "src/main/daemon/daemon-pty-upgrade-adoption.test.ts": 120, + "src/main/daemon/daemon-pty-write-settlement-recovery.test.ts": 11, + "src/main/daemon/daemon-ready-identity.test.ts": 8, + "src/main/daemon/daemon-reattach-checkpoint-isolation.test.ts": 10265, + "src/main/daemon/daemon-respawn-throttle.test.ts": 9, + "src/main/daemon/daemon-restore-scrollback-depth.test.ts": 2078, + "src/main/daemon/daemon-self-retirement-respawn.test.ts": 148, + "src/main/daemon/daemon-server-async-spawn-cancellation.test.ts": 354, + "src/main/daemon/daemon-server-attach-only.test.ts": 59, + "src/main/daemon/daemon-server-attachment-lifecycle.test.ts": 214, + "src/main/daemon/daemon-server-error-handling.test.ts": 179, + "src/main/daemon/daemon-server-kill-attribution.test.ts": 20, + "src/main/daemon/daemon-server.test.ts": 702, + "src/main/daemon/daemon-session-owner-resolution.test.ts": 22, + "src/main/daemon/daemon-session-scrollback-window.test.ts": 54, + "src/main/daemon/daemon-shutdown-final-checkpoint-caller-deadline.test.ts": 1702, + "src/main/daemon/daemon-spawner.test.ts": 74, + "src/main/daemon/daemon-stream-data-batcher.test.ts": 108, + "src/main/daemon/daemon-stream-droppability-lifecycle.test.ts": 46, + "src/main/daemon/daemon-stream-droppable-membership.test.ts": 40, + "src/main/daemon/daemon-tcc-attribution-main-thread.test.ts": 14, + "src/main/daemon/daemon-tcc-attribution.test.ts": 8, + "src/main/daemon/daemon-transport-attachment-release.test.ts": 114, + "src/main/daemon/degraded-daemon-pty-provider.test.ts": 58, + "src/main/daemon/hangul-cell-width-agreement.test.ts": 37, + "src/main/daemon/headless-emulator-fidelity.fuzz.test.ts": 17026, + "src/main/daemon/headless-emulator-restored-osc-links.test.ts": 50, + "src/main/daemon/headless-emulator-unicode-width.test.ts": 15, + "src/main/daemon/headless-emulator-wide-char-repaint.test.ts": 1773, + "src/main/daemon/headless-emulator-wide-char-snapshot.test.ts": 186, + "src/main/daemon/headless-emulator.test.ts": 114, + "src/main/daemon/headless-osc-link-ranges.test.ts": 119, + "src/main/daemon/hibernation-cold-restore-repro.test.ts": 73, + "src/main/daemon/history-manager-disabled-sessions-leak.test.ts": 58, + "src/main/daemon/history-manager.test.ts": 326, + "src/main/daemon/history-reader-memory.test.ts": 332, + "src/main/daemon/history-reader.test.ts": 85, + "src/main/daemon/issue-6814-daemon-failure-classification.test.ts": 6043, + "src/main/daemon/macos-login-session-death-watch.test.ts": 13, + "src/main/daemon/ndjson.test.ts": 173, + "src/main/daemon/node-pty-error-hints.test.ts": 6, + "src/main/daemon/osc7-file-uri.test.ts": 8, + "src/main/daemon/osc7-uri-extraction.test.ts": 8, + "src/main/daemon/post-ready-flush-gate.test.ts": 16, + "src/main/daemon/pty-session-id.test.ts": 16, + "src/main/daemon/pty-subprocess-cwd-cancel-identity.test.ts": 12, + "src/main/daemon/pty-subprocess-env-inheritance.test.ts": 58, + "src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts": 64, + "src/main/daemon/pty-subprocess-foreground-identity.test.ts": 303, + "src/main/daemon/pty-subprocess-foreground-scan-cadence.test.ts": 42, + "src/main/daemon/pty-subprocess-git-credential-guard.test.ts": 32, + "src/main/daemon/pty-subprocess-handle-lifecycle.test.ts": 138, + "src/main/daemon/pty-subprocess-io-failure-cleanup.test.ts": 3575, + "src/main/daemon/pty-subprocess-io-failure-native.test.ts": 2227, + "src/main/daemon/pty-subprocess-managed-agent-env.test.ts": 81, + "src/main/daemon/pty-subprocess-windows-shell-launch.test.ts": 66, + "src/main/daemon/pty-subprocess-wsl-launch.test.ts": 52, + "src/main/daemon/pty-subprocess.test.ts": 58, + "src/main/daemon/reattach-snapshot.test.ts": 649, + "src/main/daemon/repro-12101-mouse-tracking-survives-agent-death.test.ts": 61, + "src/main/daemon/repro-7329-remote-snapshot-corruption.test.ts": 57, + "src/main/daemon/session-pending-output.test.ts": 145, + "src/main/daemon/session-shell-recovery.test.ts": 122, + "src/main/daemon/session-terminal-control.test.ts": 46, + "src/main/daemon/session.test.ts": 279, + "src/main/daemon/shell-ready-bash-wrapper.test.ts": 338, + "src/main/daemon/slow-daemon-session-verification.test.ts": 13535, + "src/main/daemon/startup-device-attributes-responder.test.ts": 14, + "src/main/daemon/terminal-checkpoint-serializer.test.ts": 37, + "src/main/daemon/terminal-checkpoint-writer-bounds.test.ts": 91, + "src/main/daemon/terminal-cursor-line-context.test.ts": 26, + "src/main/daemon/terminal-history-incremental-restore.test.ts": 316, + "src/main/daemon/terminal-history-large-checkpoint-cold-restore.test.ts": 9348, + "src/main/daemon/terminal-history-log.test.ts": 13, + "src/main/daemon/terminal-history-permissions.test.ts": 87, + "src/main/daemon/terminal-history-restorable-retention.test.ts": 7, + "src/main/daemon/terminal-history-seed-chunks.test.ts": 7, + "src/main/daemon/terminal-history-seed-segments.test.ts": 22, + "src/main/daemon/terminal-history-seed-transfer-registry.test.ts": 8, + "src/main/daemon/terminal-history-session-tombstone-retry.test.ts": 10, + "src/main/daemon/terminal-host-agent-session.test.ts": 36, + "src/main/daemon/terminal-host-attach-only.test.ts": 28, + "src/main/daemon/terminal-host-cheap-tier-ps-scan-volume.test.ts": 59, + "src/main/daemon/terminal-host-concurrent-create.test.ts": 53, + "src/main/daemon/terminal-host-cwd-readability.test.ts": 30, + "src/main/daemon/terminal-host-non-agent-foreground.test.ts": 17, + "src/main/daemon/terminal-host-process-inspection-cheap-tier.test.ts": 29, + "src/main/daemon/terminal-host-process-inspection.test.ts": 18, + "src/main/daemon/terminal-host-pty-owner-backend.test.ts": 19, + "src/main/daemon/terminal-host-readiness-reporting.test.ts": 23, + "src/main/daemon/terminal-host-session-reaping-leak.test.ts": 30, + "src/main/daemon/terminal-host-startup.test.ts": 45, + "src/main/daemon/terminal-host-teardown-recreate.test.ts": 185, + "src/main/daemon/terminal-host-wsl-context.test.ts": 45, + "src/main/daemon/terminal-host.test.ts": 152, + "src/main/daemon/terminal-session-teardown.test.ts": 16, + "src/main/daemon/terminal-shell-lifecycle-scanner.test.ts": 21, + "src/main/daemon/terminal-shell-recovery-barrier.test.ts": 703, + "src/main/daemon/terminal-shell-recovery-clean-exit-retirement.test.ts": 26, + "src/main/daemon/terminal-snapshot-color-parity.test.ts": 109, + "src/main/daemon/terminal-snapshot-osc8-roundtrip.test.ts": 57, + "src/main/daemon/terminal-snapshot-serialize-roundtrip.test.ts": 130, + "src/main/daemon/windows-conpty-warmup.test.ts": 12, + "src/main/daemon/wsl-cold-restore-cwd.test.ts": 7, + "src/main/destination-serialized-local-rename.test.ts": 330, + "src/main/devin/hook-config-json.test.ts": 11, + "src/main/devin/hook-service.test.ts": 43, + "src/main/diagnostics/main-thread-churn-probe.test.ts": 13, + "src/main/dock/unread-badge.test.ts": 9, + "src/main/droid/hook-service.test.ts": 81, + "src/main/durable-file-write-syscall-proof.test.ts": 125, + "src/main/durable-file-write.test.ts": 89, + "src/main/emulator/android/adb-devices.test.ts": 10, + "src/main/emulator/android/android-app-control.test.ts": 7, + "src/main/emulator/android/android-capability-operations.test.ts": 10, + "src/main/emulator/android/android-device-inventory.test.ts": 12, + "src/main/emulator/android/android-input-commands.test.ts": 5, + "src/main/emulator/android/android-input-mapping.test.ts": 9, + "src/main/emulator/android/android-logcat.test.ts": 5, + "src/main/emulator/android/android-permissions.test.ts": 8, + "src/main/emulator/android/android-sdk-discovery.test.ts": 7, + "src/main/emulator/android/avd-manager.test.ts": 11, + "src/main/emulator/android/scrcpy-server-deploy.test.ts": 7, + "src/main/emulator/android/scrcpy-video-frame-parser.test.ts": 11, + "src/main/emulator/android/uiautomator-tree.test.ts": 12, + "src/main/emulator/backends/android-emulator-backend.test.ts": 24, + "src/main/emulator/backends/ios-emulator-backend.test.ts": 88, + "src/main/emulator/emulator-availability.test.ts": 7, + "src/main/emulator/emulator-bridge.test.ts": 122, + "src/main/emulator/emulator-gesture-sender.test.ts": 131, + "src/main/emulator/emulator-session-registry.test.ts": 5, + "src/main/emulator/emulator-start-lease-registry.test.ts": 62, + "src/main/emulator/mjpeg-frame-parser.test.ts": 8, + "src/main/emulator/mjpeg-frame-stream.test.ts": 34, + "src/main/emulator/scrcpy-video-registry.test.ts": 11, + "src/main/emulator/serve-sim-accessibility-tree.test.ts": 90, + "src/main/emulator/serve-sim-ax-normalization.test.ts": 12, + "src/main/emulator/serve-sim-detached-session.test.ts": 11, + "src/main/emulator/serve-sim-execution.test.ts": 25, + "src/main/emulator/serve-sim-helper-processes.test.ts": 8, + "src/main/emulator/serve-sim-runtime-materializer.test.ts": 49, + "src/main/emulator/simctl-simulator-devices.test.ts": 11, + "src/main/ephemeral-vm-recipe-runner.test.ts": 296, + "src/main/ephemeral-vm-resume-integrity.test.ts": 11, + "src/main/ephemeral-vm-runtime-service.test.ts": 522, + "src/main/ephemeral-vm-runtime-ssh-cleanup.test.ts": 36, + "src/main/external-editor-launch.test.ts": 19, + "src/main/fish-history-session.test.ts": 22, + "src/main/gemini/hook-service.test.ts": 15, + "src/main/ghostty/discovery.test.ts": 14, + "src/main/ghostty/index.test.ts": 20, + "src/main/ghostty/mapper-extended.test.ts": 11, + "src/main/ghostty/mapper.test.ts": 11, + "src/main/ghostty/parser.test.ts": 8, + "src/main/ghostty/theme-import.test.ts": 14, + "src/main/ghostty/theme-resolution.test.ts": 10, + "src/main/git-bash.test.ts": 13, + "src/main/git/add-sparse-worktree.test.ts": 22, + "src/main/git/admission-tier-plumbing.test.ts": 10, + "src/main/git/branch-rename.test.ts": 8, + "src/main/git/canonical-repo-key.test.ts": 6, + "src/main/git/check-ignored-paths.test.ts": 15, + "src/main/git/coalesced-probe.test.ts": 9, + "src/main/git/command-runner/gh-exec-file-deadline.test.ts": 20, + "src/main/git/command-runner/gh-spawn-boundary.test.ts": 566, + "src/main/git/command-runner/git-admission-output-parity.test.ts": 120, + "src/main/git/command-runner/git-admission-span.test.ts": 175, + "src/main/git/command-runner/git-admission-storm-measurement.test.ts": 25859, + "src/main/git/command-runner/git-command-timeout-behavior.test.ts": 1728, + "src/main/git/command-runner/git-command-timeout.test.ts": 8, + "src/main/git/command-runner/git-exec-admission-lifetime.test.ts": 375, + "src/main/git/command-runner/git-spawn-admission-lifetime.test.ts": 15, + "src/main/git/command-runner/git-stream-admission-lifetime.test.ts": 120, + "src/main/git/command-runner/git-subprocess-admission.test.ts": 448, + "src/main/git/command-runner/hosted-cli-deadline-log.test.ts": 22, + "src/main/git/command-runner/wsl-host-failure.test.ts": 17, + "src/main/git/commit-object-ref.test.ts": 8, + "src/main/git/commit.test.ts": 6, + "src/main/git/exact-ref-probe.test.ts": 17, + "src/main/git/fetch-error-classification.test.ts": 9, + "src/main/git/fork-remote-refspec.test.ts": 14, + "src/main/git/fork-remote-stale-branch-refspec.test.ts": 10, + "src/main/git/gh-rate-limit-breaker.test.ts": 20, + "src/main/git/git-capability-state.test.ts": 14, + "src/main/git/git-status-read-lease-owner.test.ts": 15, + "src/main/git/git-upstream-status-read-owner.test.ts": 18, + "src/main/git/hosted-remote-url.test.ts": 6, + "src/main/git/huge-folder-ignore.test.ts": 20, + "src/main/git/local-repo-ref-maintenance.test.ts": 98, + "src/main/git/pack-refs-lock-ownership.test.ts": 36, + "src/main/git/porcelain-v1-records.test.ts": 140, + "src/main/git/remote-ref-probe-cache.test.ts": 13, + "src/main/git/remote-url-probe.test.ts": 9, + "src/main/git/remote.test.ts": 51, + "src/main/git/remove-worktree-branch-cleanup.test.ts": 16, + "src/main/git/remove-worktree-clean-preflight.test.ts": 14, + "src/main/git/remove-worktree.test.ts": 35, + "src/main/git/repo-api-parity.test.ts": 6, + "src/main/git/repo-branch-conflict-batched-probe.test.ts": 11, + "src/main/git/repo-branch-conflict-real-git.test.ts": 157, + "src/main/git/repo-branch-conflict.test.ts": 20, + "src/main/git/repo-clone-path.test.ts": 6, + "src/main/git/repo-default-base-timeout.test.ts": 11, + "src/main/git/repo-default-remote.test.ts": 11, + "src/main/git/repo-detection.test.ts": 348, + "src/main/git/repo-ref-maintenance-real-git.test.ts": 1092, + "src/main/git/repo-remote-drift-real.test.ts": 103, + "src/main/git/repo-remote-drift.test.ts": 8, + "src/main/git/repo-search-ref-compat.test.ts": 10, + "src/main/git/repo-username.test.ts": 30, + "src/main/git/repo.test.ts": 2330, + "src/main/git/runner-command-exec.test.ts": 530, + "src/main/git/runner-gh-host-args.test.ts": 8, + "src/main/git/runner-gh-rate-limit-breaker.test.ts": 32, + "src/main/git/runner-windows-host-environment.test.ts": 88, + "src/main/git/runner-wsl-direct-read.test.ts": 1710, + "src/main/git/runner-wsl-gh-fallback.test.ts": 857, + "src/main/git/runner-wsl-linked-gitdir-timeout.test.ts": 20, + "src/main/git/runner-wsl-login-shell-capture.test.ts": 18, + "src/main/git/runner-wsl-read-routing.test.ts": 15, + "src/main/git/runner.test.ts": 32, + "src/main/git/settled-diff-cache-bounds.test.ts": 7, + "src/main/git/source-control/bulk-pathspec-command-line-budget.test.ts": 128, + "src/main/git/source-control/resolve-git-dir.test.ts": 6, + "src/main/git/source-control/wsl-tracked-pathspec-banner.test.ts": 20, + "src/main/git/status-branch-compare-real-ref.test.ts": 137, + "src/main/git/status-branch-compare.test.ts": 19, + "src/main/git/status-branch-line-total-exec-contract.test.ts": 751, + "src/main/git/status-branch-line-total-real-git.test.ts": 728, + "src/main/git/status-branch-line-total-relay-parity.test.ts": 301, + "src/main/git/status-conflict-operations.test.ts": 19, + "src/main/git/status-conflict-overlap.bench.test.ts": 25, + "src/main/git/status-cquoted-paths.test.ts": 79, + "src/main/git/status-diff-settled-cache.test.ts": 152, + "src/main/git/status-diff.test.ts": 37, + "src/main/git/status-discard-and-bulk-staging.test.ts": 73, + "src/main/git/status-discard-symlink.test.ts": 429, + "src/main/git/status-line-stats-host-paths.test.ts": 14, + "src/main/git/status-pathspec-literals.test.ts": 352, + "src/main/git/status-porcelain-parser.test.ts": 7, + "src/main/git/status-read-coalescing.test.ts": 37, + "src/main/git/status-shared-symlinks.test.ts": 825, + "src/main/git/status-submodule-path-cache.test.ts": 894, + "src/main/git/status-submodule.test.ts": 45, + "src/main/git/status-symlink-probe-budget.test.ts": 44, + "src/main/git/status-upstream-negative-cache.test.ts": 990, + "src/main/git/status-upstream-probe-churn.test.ts": 42, + "src/main/git/status-upstream-ref.test.ts": 13, + "src/main/git/status-wsl-pathspecs.test.ts": 14, + "src/main/git/status.test.ts": 72, + "src/main/git/upstream-deferred-fork-remote-real.test.ts": 49, + "src/main/git/upstream.test.ts": 179, + "src/main/git/worktree-add-creation-config.test.ts": 38, + "src/main/git/worktree-add-local-base-refresh.test.ts": 16, + "src/main/git/worktree-add-local-base-suggestion.test.ts": 15, + "src/main/git/worktree-add-timeout-override.test.ts": 12, + "src/main/git/worktree-base-divergence-real-git.test.ts": 1158, + "src/main/git/worktree-base-divergence.test.ts": 39, + "src/main/git/worktree-base-ref-probe.test.ts": 16, + "src/main/git/worktree-common-dir-comparison.test.ts": 6, + "src/main/git/worktree-configured-paths-concurrency.test.ts": 27, + "src/main/git/worktree-create-preparation-real-git.test.ts": 1004, + "src/main/git/worktree-created-description-real-git.test.ts": 1472, + "src/main/git/worktree-deferred-removal-real-git.test.ts": 269, + "src/main/git/worktree-diff-stamp-guest-gitdir.test.ts": 6, + "src/main/git/worktree-diff-stamp-host-paths.test.ts": 8, + "src/main/git/worktree-git-capabilities.test.ts": 13, + "src/main/git/worktree-graph-listing.test.ts": 13, + "src/main/git/worktree-include-file.test.ts": 83, + "src/main/git/worktree-list-paths.test.ts": 228, + "src/main/git/worktree-list-porcelain.test.ts": 26, + "src/main/git/worktree-listing-created-sparse-distro.test.ts": 9, + "src/main/git/worktree-listing-sparse-distro.test.ts": 8, + "src/main/git/worktree-move.test.ts": 12, + "src/main/git/worktree-mutation-route-invalidation.test.ts": 25, + "src/main/git/worktree-porcelain-parsing.test.ts": 14, + "src/main/git/worktree-preparation-base-oid.test.ts": 14, + "src/main/git/worktree-remove-branch-deletion.test.ts": 17, + "src/main/git/worktree-scan-cache-annotation-reuse.test.ts": 8, + "src/main/git/worktree-scan-cache-sharing.test.ts": 52, + "src/main/git/worktree-separate-git-dir.test.ts": 350, + "src/main/git/worktree-shared-directories.test.ts": 726, + "src/main/git/worktree-sparse-checkout-cache.test.ts": 31, + "src/main/git/worktree-sparse-checkout.test.ts": 276, + "src/main/git/worktree-sparse-state-host-paths.test.ts": 5, + "src/main/git/worktree-symlink-detection.test.ts": 11, + "src/main/git/wsl-direct-git-read-commands.test.ts": 8, + "src/main/git/wsl-linked-worktree-git-route-invalidation.test.ts": 9, + "src/main/git/wsl-linked-worktree-git-routing.test.ts": 57, + "src/main/git/wsl-process-group-termination.test.ts": 8, + "src/main/gitea/client.test.ts": 145, + "src/main/gitea/pull-request-creation.test.ts": 82, + "src/main/gitea/pull-request-mappers.test.ts": 5, + "src/main/gitea/repository-ref.test.ts": 19, + "src/main/github/auth-diagnose.test.ts": 10, + "src/main/github/client-create-pr.test.ts": 39, + "src/main/github/client-file-viewed.test.ts": 10, + "src/main/github/client-issue-origin-preference.test.ts": 17, + "src/main/github/client-issue-source.test.ts": 28, + "src/main/github/client-merge-queue-auto-merge.test.ts": 70, + "src/main/github/client-merged-pr-visibility.test.ts": 19, + "src/main/github/client-pr-branch-discovery.test.ts": 16, + "src/main/github/client-pr-check-details.test.ts": 36, + "src/main/github/client-pr-checks.test.ts": 42, + "src/main/github/client-pr-comment-reactions.test.ts": 13, + "src/main/github/client-pr-conflict-summary.test.ts": 26, + "src/main/github/client-pr-fallback-number.test.ts": 23, + "src/main/github/client-pr-linked-lookup.test.ts": 18, + "src/main/github/client-pr-local-runtime.test.ts": 32, + "src/main/github/client-pr-push-target.test.ts": 24, + "src/main/github/client-pr-state.test.ts": 10, + "src/main/github/client-rate-limit-block.test.ts": 12, + "src/main/github/client-ssh-provider-execution-boundary.test.ts": 23, + "src/main/github/client-stack-merge-guard.test.ts": 32, + "src/main/github/client-starred.test.ts": 12, + "src/main/github/client-tracked-upstream-fork-owner.test.ts": 21, + "src/main/github/client-tracked-upstream-snapshot.test.ts": 232, + "src/main/github/client-work-item-check-summary.test.ts": 12, + "src/main/github/client-work-items-query-paging.test.ts": 35, + "src/main/github/client-work-items.test.ts": 18, + "src/main/github/comment-reactions.test.ts": 3, + "src/main/github/conflict-summary.test.ts": 33, + "src/main/github/default-branch-stale-pr.test.ts": 11, + "src/main/github/gh-utils-concurrency.test.ts": 6, + "src/main/github/gh-utils.test.ts": 88, + "src/main/github/github-api-repository.test.ts": 113, + "src/main/github/github-enterprise-repository.test.ts": 36, + "src/main/github/github-pr-stack.test.ts": 46, + "src/main/github/github-remote-identity-parsing.test.ts": 9, + "src/main/github/github-repository-identity.fork-owner-repo.test.ts": 13, + "src/main/github/github-repository-identity.signed-cache.test.ts": 9, + "src/main/github/github-repository-identity.ssh-host-alias.test.ts": 33, + "src/main/github/issues.test.ts": 18, + "src/main/github/pr-head-tracking-ref.test.ts": 13, + "src/main/github/pr-refresh-coordinator-active-burst-pacing.test.ts": 271, + "src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts": 192, + "src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts": 175, + "src/main/github/pr-refresh-coordinator-rate-limit-budget.test.ts": 223, + "src/main/github/pr-refresh-coordinator-refresh-events.test.ts": 185, + "src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts": 163, + "src/main/github/pr-refresh-error-classification.test.ts": 8, + "src/main/github/pr-refresh-queue-growth-bound.test.ts": 439, + "src/main/github/pr-refresh-validation-backoff.test.ts": 14, + "src/main/github/pr-review-comment-lines.test.ts": 6, + "src/main/github/pr-start-point-compare-base.test.ts": 15, + "src/main/github/pr-start-point.test.ts": 20, + "src/main/github/project-view-host-auth.test.ts": 16, + "src/main/github/project-view.test.ts": 22, + "src/main/github/project-view/mutations.test.ts": 14, + "src/main/github/project-view/project-field-mutations.test.ts": 6, + "src/main/github/project-view/project-view-table.test.ts": 11, + "src/main/github/project-view/repository-field-options.test.ts": 5, + "src/main/github/rate-limit.test.ts": 19, + "src/main/github/review-head-remote.test.ts": 11, + "src/main/github/stacked-pr-creation.test.ts": 23, + "src/main/github/work-item-details-api-parity.test.ts": 7, + "src/main/github/work-item-details-concurrency.test.ts": 7, + "src/main/github/work-item-details-enterprise-host.test.ts": 20, + "src/main/github/work-item-details-file-viewed.test.ts": 18, + "src/main/github/work-item-details-pr-files.test.ts": 22, + "src/main/github/work-item-details.test.ts": 29, + "src/main/gitlab/client-mr-auth-rate-limit.test.ts": 14, + "src/main/gitlab/client-mr-branch-lookup.test.ts": 27, + "src/main/gitlab/client-mr-job-ci.test.ts": 12, + "src/main/gitlab/client-mr-listing.test.ts": 15, + "src/main/gitlab/client-mr-review-actions.test.ts": 11, + "src/main/gitlab/client-work-items.test.ts": 16, + "src/main/gitlab/client.test.ts": 19, + "src/main/gitlab/gitlab-known-host-probe-wsl-fallback.test.ts": 15, + "src/main/gitlab/gitlab-known-host-probe.test.ts": 23, + "src/main/gitlab/gl-utils.test.ts": 41, + "src/main/gitlab/issues.test.ts": 23, + "src/main/gitlab/mappers-workitem.test.ts": 5, + "src/main/gitlab/mappers.test.ts": 23, + "src/main/gitlab/merge-request-creation.test.ts": 11, + "src/main/gitlab/mr-head-tracking-ref.test.ts": 10, + "src/main/gitlab/project-ref-parser.test.ts": 12, + "src/main/gitlab/work-item-details.test.ts": 31, + "src/main/global-fetch-call-site-audit.test.ts": 135, + "src/main/grok-accounts/status.test.ts": 6, + "src/main/grok/grok-hook-config-file.test.ts": 99, + "src/main/grok/grok-hook-owners.test.ts": 26, + "src/main/grok/grok-hook-remnant-removal.test.ts": 15, + "src/main/grok/hook-service.test.ts": 38, + "src/main/grok/windows-grok-hook-script.test.ts": 9, + "src/main/grok/windows-hook-launcher-chain.test.ts": 9, + "src/main/hang-watchdog/hang-detection-marker.test.ts": 7, + "src/main/hang-watchdog/hang-watchdog-detection-loop.test.ts": 11, + "src/main/hang-watchdog/hang-watchdog-worker-path.test.ts": 8, + "src/main/hang-watchdog/main-thread-hang-telemetry.test.ts": 6, + "src/main/hang-watchdog/main-thread-hang-watchdog-entry.test.ts": 19, + "src/main/hang-watchdog/main-thread-hang-watchdog.test.ts": 26, + "src/main/headless-automation-dispatcher-source-boundary.test.ts": 4, + "src/main/hermes/hook-service.test.ts": 196, + "src/main/hooks-effective-hook-resolution.test.ts": 173, + "src/main/hooks-issue-command.test.ts": 237, + "src/main/hooks-orca-yaml-parsing.test.ts": 37, + "src/main/hooks-runner.test.ts": 103, + "src/main/hooks-setup-runner-script.test.ts": 171, + "src/main/hooks.test.ts": 316, + "src/main/host-tree-removal-asar.electron.test.ts": 285, + "src/main/host/deferred-secret-protection-report.test.ts": 16, + "src/main/host/electron-secret-store.test.ts": 11, + "src/main/host/secret-protection-report.test.ts": 13, + "src/main/i18n/main-i18n-lazy-locale.test.ts": 270, + "src/main/ipc/agent-hooks.test.ts": 472, + "src/main/ipc/agent-pane-authority-ownership.test.ts": 4, + "src/main/ipc/ai-vault-scan-coalescing.test.ts": 322, + "src/main/ipc/ai-vault.test.ts": 159, + "src/main/ipc/app.test.ts": 24, + "src/main/ipc/automations-external-scope.test.ts": 110, + "src/main/ipc/bounded-warning-dedupe.test.ts": 8, + "src/main/ipc/browser-client-page-metadata-ipc.test.ts": 19, + "src/main/ipc/browser-preview-tool-authorization.test.ts": 81, + "src/main/ipc/browser-session-profile-ipc.test.ts": 10, + "src/main/ipc/browser-tab-registration-wait.test.ts": 12, + "src/main/ipc/browser.test.ts": 83, + "src/main/ipc/cli-appimage-stale-registration.test.ts": 214, + "src/main/ipc/cli.test.ts": 62, + "src/main/ipc/codex-config-sync.test.ts": 13, + "src/main/ipc/command-path-resolver.test.ts": 13, + "src/main/ipc/computer-use-permissions.test.ts": 9, + "src/main/ipc/crash-reporting-renderer-breadcrumbs.test.ts": 39, + "src/main/ipc/crash-reporting-renderer-error-report-attribution.test.ts": 12, + "src/main/ipc/crash-reporting-replay-guard-wedge-burst.test.ts": 24, + "src/main/ipc/crash-reporting.test.ts": 43, + "src/main/ipc/created-worktree-reconciliation.test.ts": 72, + "src/main/ipc/created-worktree-root-prune.test.ts": 19, + "src/main/ipc/dashboard-payload-validation.test.ts": 74, + "src/main/ipc/dashboard-popout.test.ts": 13, + "src/main/ipc/deferred-emoji-shortcode-dataset.test.ts": 65, + "src/main/ipc/developer-permissions.test.ts": 17, + "src/main/ipc/diagnostics.test.ts": 20, + "src/main/ipc/doc-preview-grant-ipc.test.ts": 10, + "src/main/ipc/dropped-path-resolution.test.ts": 19, + "src/main/ipc/emulator-stream-listener-cleanup.test.ts": 12, + "src/main/ipc/ephemeral-vm-provision-cancel.test.ts": 58, + "src/main/ipc/ephemeral-vm-provisioned-root-ref.test.ts": 447, + "src/main/ipc/ephemeral-vm-runtime-handler-cleanup.test.ts": 94, + "src/main/ipc/ephemeral-vm.test.ts": 595, + "src/main/ipc/feedback-image-attachments.test.ts": 67, + "src/main/ipc/feedback.test.ts": 132, + "src/main/ipc/filesystem-allowed-roots.test.ts": 334, + "src/main/ipc/filesystem-auth.test.ts": 678, + "src/main/ipc/filesystem-branch-compare-diff.test.ts": 24, + "src/main/ipc/filesystem-commit-message-generation.test.ts": 32, + "src/main/ipc/filesystem-commit-message-model-discovery.test.ts": 25, + "src/main/ipc/filesystem-conflict-operation-routing.test.ts": 14, + "src/main/ipc/filesystem-download-transfers.test.ts": 106, + "src/main/ipc/filesystem-git-commit-dispatch.test.ts": 18, + "src/main/ipc/filesystem-git-status-staging.test.ts": 82, + "src/main/ipc/filesystem-import-ssh-ops.test.ts": 23, + "src/main/ipc/filesystem-import-ssh-path-safety.test.ts": 18, + "src/main/ipc/filesystem-import-ssh.test.ts": 31, + "src/main/ipc/filesystem-import.test.ts": 64, + "src/main/ipc/filesystem-list-files-git-directory-expansion.test.ts": 325, + "src/main/ipc/filesystem-list-files-git-fallback-real.test.ts": 271, + "src/main/ipc/filesystem-list-files-install-rg.test.ts": 23, + "src/main/ipc/filesystem-list-files.test.ts": 253, + "src/main/ipc/filesystem-markdown-document-listing.test.ts": 17, + "src/main/ipc/filesystem-mutations.test.ts": 49, + "src/main/ipc/filesystem-path-containment-remote-enoent.test.ts": 8, + "src/main/ipc/filesystem-pull-request-field-generation.test.ts": 18, + "src/main/ipc/filesystem-search-file-paths.test.ts": 257, + "src/main/ipc/filesystem-search-git.test.ts": 150, + "src/main/ipc/filesystem-search-rg-timeout.test.ts": 50, + "src/main/ipc/filesystem-watcher-canonical-root-paths.test.ts": 320, + "src/main/ipc/filesystem-watcher-dormant-rearm.test.ts": 170, + "src/main/ipc/filesystem-watcher-event-batch.test.ts": 9, + "src/main/ipc/filesystem-watcher-ignore.test.ts": 7, + "src/main/ipc/filesystem-watcher-large-batch.test.ts": 150, + "src/main/ipc/filesystem-watcher-local-events.test.ts": 240, + "src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts": 818, + "src/main/ipc/filesystem-watcher-native-capacity.test.ts": 67, + "src/main/ipc/filesystem-watcher-real.test.ts": 488, + "src/main/ipc/filesystem-watcher-remote-batch.test.ts": 27, + "src/main/ipc/filesystem-watcher-remote-cancellation.test.ts": 324, + "src/main/ipc/filesystem-watcher-remote-capacity.test.ts": 31, + "src/main/ipc/filesystem-watcher-remote-rearm.test.ts": 20, + "src/main/ipc/filesystem-watcher-removal-deadline.test.ts": 129, + "src/main/ipc/filesystem-watcher-terminal-resync.test.ts": 21, + "src/main/ipc/filesystem-watcher-unwatchable-roots.test.ts": 24, + "src/main/ipc/filesystem-watcher-wsl.test.ts": 35, + "src/main/ipc/filesystem-watcher.test.ts": 1789, + "src/main/ipc/filesystem.test.ts": 70, + "src/main/ipc/floating-workspace-directory.test.ts": 23, + "src/main/ipc/folder-repo-git-upgrade.test.ts": 2257, + "src/main/ipc/git-status-upstream-ref-watch-request.test.ts": 13, + "src/main/ipc/github-ipc-channel-parity.test.ts": 13, + "src/main/ipc/github-issue-source-preference.test.ts": 16, + "src/main/ipc/github-pr-refresh-routing.test.ts": 31, + "src/main/ipc/github-repo-access-guards.test.ts": 24, + "src/main/ipc/github-ssh-connection-routing.test.ts": 10, + "src/main/ipc/github-star-telemetry.test.ts": 17, + "src/main/ipc/github-work-item-args.test.ts": 12, + "src/main/ipc/github-wsl-runtime-routing.test.ts": 21, + "src/main/ipc/gitlab-repo-access.test.ts": 6, + "src/main/ipc/gitlab.test.ts": 27, + "src/main/ipc/hosted-review.test.ts": 23, + "src/main/ipc/jira-cancellable-requests.test.ts": 12, + "src/main/ipc/keybindings.test.ts": 8, + "src/main/ipc/linear.test.ts": 9, + "src/main/ipc/local-log-tail.test.ts": 9, + "src/main/ipc/local-network-connection-test.test.ts": 84, + "src/main/ipc/local-worktree-runtime-options.test.ts": 10, + "src/main/ipc/macos-keyboard-layout-change-notifications.test.ts": 13, + "src/main/ipc/macos-keyboard-layout-snapshot.test.ts": 7, + "src/main/ipc/markdown-documents.test.ts": 5, + "src/main/ipc/minimax-credentials.test.ts": 17, + "src/main/ipc/mobile.test.ts": 66, + "src/main/ipc/native-chat-subscribe-lifecycle.test.ts": 18, + "src/main/ipc/native-chat.test.ts": 1703, + "src/main/ipc/notebook.test.ts": 13, + "src/main/ipc/notifications-custom-sound.test.ts": 13, + "src/main/ipc/notifications-delivery-gating.test.ts": 19, + "src/main/ipc/notifications-message-formatting.test.ts": 32, + "src/main/ipc/notifications-mobile-fanout.test.ts": 17, + "src/main/ipc/notifications-permission-onboarding.test.ts": 27, + "src/main/ipc/notifications-retention-lifecycle.test.ts": 27, + "src/main/ipc/orca-profile-auth-handlers.test.ts": 16, + "src/main/ipc/orca-profile-org-members-handlers.test.ts": 10, + "src/main/ipc/orca-profiles.test.ts": 28, + "src/main/ipc/parcel-watcher-child-launch.test.ts": 14, + "src/main/ipc/parcel-watcher-child-registry.test.ts": 60, + "src/main/ipc/parcel-watcher-crash-fuse.test.ts": 4, + "src/main/ipc/parcel-watcher-disconnect-termination.test.ts": 46, + "src/main/ipc/parcel-watcher-entry-path.test.ts": 8, + "src/main/ipc/parcel-watcher-event-cancellation.test.ts": 12, + "src/main/ipc/parcel-watcher-event-delivery.test.ts": 8, + "src/main/ipc/parcel-watcher-process-entry.test.ts": 83, + "src/main/ipc/parcel-watcher-process.test.ts": 395, + "src/main/ipc/parcel-watcher-root-path-rewrite.test.ts": 19, + "src/main/ipc/parcel-watcher-shallow-subscription.test.ts": 11, + "src/main/ipc/parcel-watcher-supervisor-capacity-wait.test.ts": 340, + "src/main/ipc/parcel-watcher-supervisor-capacity.test.ts": 134, + "src/main/ipc/parcel-watcher-unsubscribe-timeout.test.ts": 38, + "src/main/ipc/pet-bundle.test.ts": 6, + "src/main/ipc/pet.test.ts": 35, + "src/main/ipc/plugin-marketplaces.test.ts": 13, + "src/main/ipc/plugins.test.ts": 18, + "src/main/ipc/preflight-agent-detection-no-subprocess.test.ts": 22, + "src/main/ipc/preflight-agent-detection.test.ts": 42, + "src/main/ipc/preflight-agent-refresh.test.ts": 21, + "src/main/ipc/preflight-command-exec.test.ts": 8, + "src/main/ipc/preflight-host-cli-status.test.ts": 27, + "src/main/ipc/preflight-remote-ssh.test.ts": 16, + "src/main/ipc/preflight-wsl-agent-detection.test.ts": 55, + "src/main/ipc/preflight-wsl-command.test.ts": 17, + "src/main/ipc/pty-activation-inventory-scope.test.ts": 23, + "src/main/ipc/pty-agent-session-write-gate.test.ts": 141, + "src/main/ipc/pty-applied-size-reporting.test.ts": 48, + "src/main/ipc/pty-buffer-snapshot-dispatch.test.ts": 64, + "src/main/ipc/pty-codex-account-attribution.test.ts": 46, + "src/main/ipc/pty-controller-owner-recovery.test.ts": 53, + "src/main/ipc/pty-controller-ownership-routing.test.ts": 37, + "src/main/ipc/pty-controller-process-inventory.test.ts": 17, + "src/main/ipc/pty-controller-spawn-admission.test.ts": 49, + "src/main/ipc/pty-cumulative-ack-accounting.test.ts": 95, + "src/main/ipc/pty-daemon-controller-teardown.test.ts": 44, + "src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts": 72, + "src/main/ipc/pty-daemon-spawn-codex-auth.test.ts": 82, + "src/main/ipc/pty-daemon-spawn-session-identity.test.ts": 60, + "src/main/ipc/pty-daemon-spawn-wsl-runtime.test.ts": 67, + "src/main/ipc/pty-daemon-ssh-lease-lifecycle.test.ts": 48, + "src/main/ipc/pty-dead-owner-respawn.test.ts": 44, + "src/main/ipc/pty-delivery-health-heal.test.ts": 117, + "src/main/ipc/pty-dispatcher-handshake-osc-answers.test.ts": 83, + "src/main/ipc/pty-encoding.test.ts": 7, + "src/main/ipc/pty-global-renderer-credit.test.ts": 134, + "src/main/ipc/pty-hidden-at-spawn-mark.test.ts": 120, + "src/main/ipc/pty-hidden-delivery-gate.test.ts": 8, + "src/main/ipc/pty-ipc-hidden-delivery-gate.test.ts": 67, + "src/main/ipc/pty-ipc-producer-flow-control.test.ts": 57, + "src/main/ipc/pty-listener-teardown-and-orphans.test.ts": 99, + "src/main/ipc/pty-login-shell-startup-commands.test.ts": 113, + "src/main/ipc/pty-management.test.ts": 46, + "src/main/ipc/pty-output-batching-drain.test.ts": 66, + "src/main/ipc/pty-output-drain-rounds.test.ts": 110, + "src/main/ipc/pty-pane-claim-arbitration.test.ts": 137, + "src/main/ipc/pty-pane-materialization-race.test.ts": 501, + "src/main/ipc/pty-pane-reservation-settlement.test.ts": 71, + "src/main/ipc/pty-pending-data-drain-queue-differential.test.ts": 955, + "src/main/ipc/pty-pending-data-drain-queue.test.ts": 19, + "src/main/ipc/pty-pending-data-drain-scheduler-differential.test.ts": 19, + "src/main/ipc/pty-pending-output-cap.test.ts": 97, + "src/main/ipc/pty-pending-projection-admissions.test.ts": 86, + "src/main/ipc/pty-persisted-incarnation-repair.test.ts": 38, + "src/main/ipc/pty-producer-flow-control.test.ts": 16, + "src/main/ipc/pty-renderer-inflight-credit.test.ts": 156, + "src/main/ipc/pty-renderer-lifecycle-delivery-reset.test.ts": 77, + "src/main/ipc/pty-renderer-liveness-guard.test.ts": 82, + "src/main/ipc/pty-renderer-send-failure-recovery.test.ts": 51, + "src/main/ipc/pty-restore-record-seeding.test.ts": 156, + "src/main/ipc/pty-restored-appimage-cli-shim-refresh.test.ts": 14, + "src/main/ipc/pty-runtime-kill-and-exit.test.ts": 105, + "src/main/ipc/pty-runtime-ssh-binding-persistence.test.ts": 40, + "src/main/ipc/pty-serializer-settlement-mapping.test.ts": 146, + "src/main/ipc/pty-session-liveness-and-ownership.test.ts": 42, + "src/main/ipc/pty-spawn-codex-home-unavailable.test.ts": 43, + "src/main/ipc/pty-spawn-cwd-fallback.test.ts": 69, + "src/main/ipc/pty-spawn-env-agent-overlays.test.ts": 116, + "src/main/ipc/pty-spawn-env-codex-home-routing.test.ts": 132, + "src/main/ipc/pty-spawn-env-codex-resume-provenance.test.ts": 118, + "src/main/ipc/pty-spawn-env-terminal-basics.test.ts": 1128, + "src/main/ipc/pty-spawn-runtime-handle-binding.test.ts": 135, + "src/main/ipc/pty-ssh-stop-verdict.test.ts": 92, + "src/main/ipc/pty-ssh-undelivered-kill.test.ts": 149, + "src/main/ipc/pty-startup-barrier-and-listing.test.ts": 60, + "src/main/ipc/pty-startup-barrier-ordering.test.ts": 3, + "src/main/ipc/pty-startup-swap-window-presence.test.ts": 81, + "src/main/ipc/pty-windows-shell-selection.test.ts": 131, + "src/main/ipc/pty-write-ipc-validation.test.ts": 125, + "src/main/ipc/pty-wsl-cwd-validation.test.ts": 75, + "src/main/ipc/pty/delivery/attached-pty-size.test.ts": 13, + "src/main/ipc/pty/ipc/spawn-commit-ssh-lease-cardinality.test.ts": 111, + "src/main/ipc/pty/ipc/spawn-push-target-materialization-real-git.test.ts": 258, + "src/main/ipc/pty/ipc/spawn-push-target-materialization.test.ts": 11, + "src/main/ipc/pty/ipc/spawn-reattach-size-cache.test.ts": 14, + "src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts": 25, + "src/main/ipc/pty/pane/launch-authority.test.ts": 9, + "src/main/ipc/pty/pane/stable-pane-absence-death-certificate.test.ts": 26, + "src/main/ipc/pty/pane/stable-pane-relay-absence-respawn.test.ts": 14, + "src/main/ipc/pty/register-headless-runtime.test.ts": 6, + "src/main/ipc/pty/runtime/queried-host-kinds.test.ts": 8, + "src/main/ipc/pty/runtime/spawn-commit-pty-size.test.ts": 7, + "src/main/ipc/rate-limits.test.ts": 7, + "src/main/ipc/readdir-error-diagnostics.test.ts": 10, + "src/main/ipc/register-core-handlers/register-core-handlers.test.ts": 20, + "src/main/ipc/remote-watcher-event-batch.test.ts": 29, + "src/main/ipc/remote-workspace-cache.test.ts": 9, + "src/main/ipc/remote-workspace-patch-queue.test.ts": 173, + "src/main/ipc/remote-workspace-snapshot-normalization.test.ts": 10, + "src/main/ipc/remote-workspace-stale-resync.test.ts": 162, + "src/main/ipc/remote-workspace.test.ts": 29, + "src/main/ipc/renderer-shutdown-checkpoint.test.ts": 18, + "src/main/ipc/renderer-terminal-serializer-readiness.test.ts": 11, + "src/main/ipc/repos-add-linked-worktree.test.ts": 46, + "src/main/ipc/repos-create.test.ts": 34, + "src/main/ipc/repos-execution-host-catalog.test.ts": 22, + "src/main/ipc/repos-local-add-and-project-setup.test.ts": 54, + "src/main/ipc/repos-local-clone-lifecycle.test.ts": 245, + "src/main/ipc/repos-nested-import.test.ts": 51, + "src/main/ipc/repos-nested-scan.test.ts": 36, + "src/main/ipc/repos-picker.test.ts": 8, + "src/main/ipc/repos-remote-base-ref-queries.test.ts": 30, + "src/main/ipc/repos-remote-client-events.test.ts": 1415, + "src/main/ipc/repos-remote-git-username.test.ts": 6, + "src/main/ipc/repos-remote.test.ts": 66, + "src/main/ipc/repos-sparse-presets.test.ts": 27, + "src/main/ipc/repos/remote-repo-registration.test.ts": 7, + "src/main/ipc/rg-availability.test.ts": 9, + "src/main/ipc/runtime-environment-browser-client-host-handler.test.ts": 14, + "src/main/ipc/runtime-environment-capability-evidence.test.ts": 9, + "src/main/ipc/runtime-environment-diagnostics-broadcast.test.ts": 4, + "src/main/ipc/runtime-environment-federated-read-routing.test.ts": 34, + "src/main/ipc/runtime-environment-federated-read-transport.bench.test.ts": 10, + "src/main/ipc/runtime-environment-removal-storage.test.ts": 565, + "src/main/ipc/runtime-environment-request-connections.test.ts": 1282, + "src/main/ipc/runtime-environment-revision-guard.test.ts": 7, + "src/main/ipc/runtime-environment-status-connection.test.ts": 441, + "src/main/ipc/runtime-environment-status-recovery.test.ts": 23, + "src/main/ipc/runtime-environment-support-routing.test.ts": 62, + "src/main/ipc/runtime-environment-transport-routing-tailscale-hint.test.ts": 853, + "src/main/ipc/runtime-environments-call-routing.test.ts": 187, + "src/main/ipc/runtime-environments-capability-cache.test.ts": 129, + "src/main/ipc/runtime-environments-pairing.test.ts": 66, + "src/main/ipc/runtime-environments-status-diagnostics.test.ts": 37, + "src/main/ipc/runtime-environments-subscription-lifecycle.test.ts": 35, + "src/main/ipc/runtime-environments-subscription-routing.test.ts": 26, + "src/main/ipc/runtime-environments-subscription-teardown.test.ts": 38, + "src/main/ipc/runtime-subscribe-lifecycle.test.ts": 10, + "src/main/ipc/runtime-watcher-pending-assignment.test.ts": 281, + "src/main/ipc/runtime-watcher-process-pool.test.ts": 46, + "src/main/ipc/runtime.test.ts": 23, + "src/main/ipc/settings.test.ts": 30, + "src/main/ipc/shallow-watch-delivery-probe.test.ts": 28, + "src/main/ipc/shell.test.ts": 47, + "src/main/ipc/skill-cloud-install-ipc-schemas.test.ts": 13, + "src/main/ipc/skill-cloud-ipc-handlers.test.ts": 12, + "src/main/ipc/skill-install-management-ipc-handlers.test.ts": 19, + "src/main/ipc/skill-install-progress-ipc.test.ts": 7, + "src/main/ipc/skill-ipc-main-window.test.ts": 9, + "src/main/ipc/skills.test.ts": 17, + "src/main/ipc/source-control-ai-linked-issue.test.ts": 14, + "src/main/ipc/speech.test.ts": 12, + "src/main/ipc/ssh-app-shutdown.test.ts": 45, + "src/main/ipc/ssh-browse.test.ts": 586, + "src/main/ipc/ssh-disconnect-cancellation.test.ts": 1118, + "src/main/ipc/ssh-handler-reregistration.test.ts": 49, + "src/main/ipc/ssh-passphrase.test.ts": 6, + "src/main/ipc/ssh-pty-closed-generation-ranges.test.ts": 46, + "src/main/ipc/ssh-pty-consumer-identity.test.ts": 175, + "src/main/ipc/ssh-pty-legacy-projection.test.ts": 18, + "src/main/ipc/ssh-pty-model-admission-generation-scope.test.ts": 16, + "src/main/ipc/ssh-pty-model-admission.test.ts": 15, + "src/main/ipc/ssh-pty-output-exit-deadline.test.ts": 23, + "src/main/ipc/ssh-pty-output-generation-guard.test.ts": 10, + "src/main/ipc/ssh-pty-output-intake.test.ts": 57, + "src/main/ipc/ssh-pty-output-model-migration.test.ts": 25, + "src/main/ipc/ssh-pty-remote-source-range-consumers.test.ts": 20, + "src/main/ipc/ssh-pty-source-ack-coalescer.test.ts": 14, + "src/main/ipc/ssh-pty-source-ack-session-contract.test.ts": 13, + "src/main/ipc/ssh-pty-source-obligation-coordinator.test.ts": 14, + "src/main/ipc/ssh-pty-source-obligation-ledger.test.ts": 82, + "src/main/ipc/ssh-relay-reset-resume.test.ts": 908, + "src/main/ipc/ssh-state-broadcast-fanout.test.ts": 25, + "src/main/ipc/ssh-target-registry.test.ts": 1633, + "src/main/ipc/ssh-terminate-sessions.test.ts": 144, + "src/main/ipc/ssh.test.ts": 61, + "src/main/ipc/telemetry.test.ts": 13, + "src/main/ipc/terminal-git-credential-guard.test.ts": 13, + "src/main/ipc/terminal-preview-output-stream.test.ts": 9, + "src/main/ipc/terminal-preview.test.ts": 155, + "src/main/ipc/terminal-render-desync-evidence.test.ts": 35, + "src/main/ipc/tui-agent-detection-commands.test.ts": 5, + "src/main/ipc/ui.test.ts": 12, + "src/main/ipc/usage-provider-handlers.test.ts": 6, + "src/main/ipc/watched-worktree-catalog-notification.test.ts": 7, + "src/main/ipc/watcher-event-root-path-rewrite.test.ts": 18, + "src/main/ipc/watcher-removal-gate.test.ts": 12, + "src/main/ipc/workspace-cleanup-activity.test.ts": 33, + "src/main/ipc/workspace-cleanup-broad-scan.test.ts": 55, + "src/main/ipc/workspace-cleanup-execution-host-routing.test.ts": 28, + "src/main/ipc/workspace-cleanup-local-git-routing.test.ts": 9, + "src/main/ipc/workspace-cleanup-snapshot-ipc.test.ts": 33, + "src/main/ipc/workspace-cleanup.test.ts": 4276, + "src/main/ipc/workspace-create-error-classifier.test.ts": 10, + "src/main/ipc/workspace-ports.test.ts": 21, + "src/main/ipc/workspace-space.test.ts": 18, + "src/main/ipc/worktree-base-directory-change-collector.test.ts": 8, + "src/main/ipc/worktree-base-directory-event-filter.test.ts": 18, + "src/main/ipc/worktree-base-directory-poller-marker-fanout.test.ts": 142, + "src/main/ipc/worktree-base-directory-poller.test.ts": 2628, + "src/main/ipc/worktree-base-directory-watch-targets.test.ts": 319, + "src/main/ipc/worktree-base-directory-watcher.test.ts": 109, + "src/main/ipc/worktree-create-lineage.test.ts": 16, + "src/main/ipc/worktree-folder-rename-target.test.ts": 8, + "src/main/ipc/worktree-git-common-polling.test.ts": 1088, + "src/main/ipc/worktree-git-common-watch.test.ts": 2677, + "src/main/ipc/worktree-git-status-ref-watch.test.ts": 10, + "src/main/ipc/worktree-head-identity-reader-concurrency.test.ts": 29, + "src/main/ipc/worktree-head-identity-reader-incremental.test.ts": 104, + "src/main/ipc/worktree-head-identity-reader.test.ts": 38, + "src/main/ipc/worktree-head-identity-refresh.test.ts": 28, + "src/main/ipc/worktree-include-copy-budget.test.ts": 53, + "src/main/ipc/worktree-logic-created-agent.test.ts": 5, + "src/main/ipc/worktree-logic-wsl.test.ts": 21, + "src/main/ipc/worktree-logic.test.ts": 53, + "src/main/ipc/worktree-metadata-merge.test.ts": 5, + "src/main/ipc/worktree-path-deduplication.test.ts": 34, + "src/main/ipc/worktree-push-target-cleanup.test.ts": 20, + "src/main/ipc/worktree-push-target-reconciliation-real-git.test.ts": 930, + "src/main/ipc/worktree-push-target-reconciliation.test.ts": 19, + "src/main/ipc/worktree-push-target-refspec-migration.test.ts": 11, + "src/main/ipc/worktree-push-target-refspec-real-git.test.ts": 2112, + "src/main/ipc/worktree-push-target-remote-scan.test.ts": 14, + "src/main/ipc/worktree-push-target-setup.test.ts": 16, + "src/main/ipc/worktree-remote-push-target-materialization.test.ts": 31, + "src/main/ipc/worktree-remote-ssh-branch-conflict.test.ts": 11, + "src/main/ipc/worktree-symlink-reconciliation.real.test.ts": 49, + "src/main/ipc/worktree-symlinks.test.ts": 123, + "src/main/ipc/worktree-watcher-removal-binding.test.ts": 78, + "src/main/ipc/worktrees-authoritative-local-metadata-pruning.test.ts": 210, + "src/main/ipc/worktrees-create-execution-host-routing.test.ts": 45, + "src/main/ipc/worktrees-create-metadata-persistence.test.ts": 60, + "src/main/ipc/worktrees-delete-pty-teardown.test.ts": 53, + "src/main/ipc/worktrees-detected-scan-cache.test.ts": 97, + "src/main/ipc/worktrees-discovery-metadata-backfill.test.ts": 51, + "src/main/ipc/worktrees-existing-branch-checkout.test.ts": 71, + "src/main/ipc/worktrees-forget-local.test.ts": 39, + "src/main/ipc/worktrees-issue-command-overrides.test.ts": 25, + "src/main/ipc/worktrees-lineage-hydration.test.ts": 33, + "src/main/ipc/worktrees-listing-fallback-rows.test.ts": 39, + "src/main/ipc/worktrees-local-base-ref-resolution.test.ts": 82, + "src/main/ipc/worktrees-local-create-flow.test.ts": 76, + "src/main/ipc/worktrees-orphan-directory-cleanup.test.ts": 35, + "src/main/ipc/worktrees-preserved-branch-fork-remote.test.ts": 28, + "src/main/ipc/worktrees-removal-recovery.test.ts": 75, + "src/main/ipc/worktrees-remove-archive-hooks.test.ts": 35, + "src/main/ipc/worktrees-remove-host-disambiguation.test.ts": 32, + "src/main/ipc/worktrees-remove-preflight.test.ts": 33, + "src/main/ipc/worktrees-setup-launch-sparse-checkout.test.ts": 57, + "src/main/ipc/worktrees-ssh-base-ref-resolution.test.ts": 61, + "src/main/ipc/worktrees-ssh-branch-conflict-suffixing.test.ts": 51, + "src/main/ipc/worktrees-ssh-create-base-prefetch.test.ts": 124, + "src/main/ipc/worktrees-ssh-fork-push-target-remote.test.ts": 73, + "src/main/ipc/worktrees-ssh-local-base-refresh.test.ts": 86, + "src/main/ipc/worktrees-ssh-pr-head-fetch.test.ts": 43, + "src/main/ipc/worktrees-ssh-provider-authority.test.ts": 35, + "src/main/ipc/worktrees-ssh-repo-owner-resolution.test.ts": 42, + "src/main/ipc/worktrees-ssh-setup-launch.test.ts": 48, + "src/main/ipc/worktrees-windows.test.ts": 72, + "src/main/ipc/worktrees-wsl-runtime-routing.test.ts": 84, + "src/main/ipc/worktrees/listing/detected-provider-listing-meta-index.test.ts": 19, + "src/main/ipc/worktrees/listing/detected-scan-failure-authority.test.ts": 29, + "src/main/ipc/worktrees/listing/detected-worktree-classification.test.ts": 21, + "src/main/ipc/worktrees/listing/detected-worktree-scan-hygiene-gate.test.ts": 13, + "src/main/ipc/worktrees/listing/register-sparse-checkout-cache-invalidation.test.ts": 9, + "src/main/jira/adf-markdown.test.ts": 10, + "src/main/jira/attachment-image-cache-generation.test.ts": 11, + "src/main/jira/attachment-image-cache.test.ts": 9, + "src/main/jira/attachment-images.test.ts": 31, + "src/main/jira/client.test.ts": 302, + "src/main/jira/issues.test.ts": 62, + "src/main/jira/jira-issue-mutations.test.ts": 29, + "src/main/jira/jira-issue-summary-timeout.test.ts": 33, + "src/main/jira/jira-search-abort.test.ts": 24, + "src/main/keybindings/keybinding-file.test.ts": 53, + "src/main/keybindings/keybinding-service.test.ts": 39, + "src/main/kimi/hook-service.test.ts": 8, + "src/main/kimi/kimi-hook-config-toml.test.ts": 25, + "src/main/kimi/kimi-runtime-home.test.ts": 15, + "src/main/lib/unread-response-body.test.ts": 51, + "src/main/line-editor-ready-output-scanner.test.ts": 7, + "src/main/linear/client.test.ts": 97, + "src/main/linear/issue-context-client.test.ts": 23, + "src/main/linear/issue-context-current.test.ts": 6, + "src/main/linear/issue-context-errors.test.ts": 6, + "src/main/linear/issue-context-includes.test.ts": 27, + "src/main/linear/issue-context-inline-media.test.ts": 6, + "src/main/linear/issue-context-relations.test.ts": 10, + "src/main/linear/issue-context.test.ts": 16, + "src/main/linear/issue-list-filter.test.ts": 8, + "src/main/linear/issue-relation-write.test.ts": 19, + "src/main/linear/issue-search-summary.test.ts": 6, + "src/main/linear/issues.test.ts": 41, + "src/main/linear/mappers.test.ts": 8, + "src/main/linear/mcp-issue-list-pagination.test.ts": 56, + "src/main/linear/mcp-issue-list.test.ts": 93, + "src/main/linear/projects.test.ts": 306, + "src/main/linear/teams.test.ts": 20, + "src/main/linux-lid-sleep-assertion.test.ts": 14, + "src/main/linux-package-install-command.test.ts": 36, + "src/main/linux-package-install-diagnostic.test.ts": 17, + "src/main/linux-package-update-recovery.test.ts": 178, + "src/main/linux-update-package-type.test.ts": 86, + "src/main/local-builds/local-build-candidate.test.ts": 107, + "src/main/local-builds/local-build-compatibility-contract.test.ts": 6, + "src/main/local-builds/local-build-feed-server.test.ts": 68, + "src/main/local-downloaded-folder-promotion.test.ts": 101, + "src/main/local-worktree-filesystem.test.ts": 269, + "src/main/local-worktree-metadata-prune-gate.test.ts": 9, + "src/main/local-worktree-path-presence.test.ts": 18, + "src/main/local-worktree-removal-recovery.test.ts": 22, + "src/main/localhost-worktree-label-proxy.test.ts": 72, + "src/main/macos-full-disk-access-status.test.ts": 8, + "src/main/macos-press-and-hold-default.test.ts": 13, + "src/main/macos-system-sleep-assertion.test.ts": 20, + "src/main/macos-tcc-prompt-notice.test.ts": 16, + "src/main/macos-tcc-prompt-watch.test.ts": 22, + "src/main/main-process-tree-kill-gate.test.ts": 7, + "src/main/memory/collector-windows-sweep.test.ts": 198, + "src/main/memory/collector.test.ts": 239, + "src/main/memory/host-memory.test.ts": 21, + "src/main/memory/hydrate-local-pty-registry.test.ts": 873, + "src/main/memory/process-memory-metric.test.ts": 6, + "src/main/memory/windows-process-sample-parsing.test.ts": 86, + "src/main/menu/gpu-acceleration-about-panel.test.ts": 9, + "src/main/menu/register-app-menu.test.ts": 21, + "src/main/mimo/hook-service.test.ts": 11, + "src/main/minimax/minimax-api-key-store.test.ts": 30, + "src/main/minimax/minimax-cookie-store.test.ts": 38, + "src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts": 293, + "src/main/native-chat/agent-session-journal/journal-crash-boundary.test.ts": 97, + "src/main/native-chat/agent-session-journal/journal-cursor.test.ts": 9, + "src/main/native-chat/agent-session-journal/journal-database.test.ts": 95, + "src/main/native-chat/agent-session-journal/journal-epoch-replacement.test.ts": 38, + "src/main/native-chat/agent-session-journal/journal-file-format-remnant.test.ts": 94, + "src/main/native-chat/agent-session-journal/journal-handle-ownership.test.ts": 7709, + "src/main/native-chat/agent-session-journal/journal-item-identity.test.ts": 10, + "src/main/native-chat/agent-session-journal/journal-legacy-import.test.ts": 181, + "src/main/native-chat/agent-session-journal/journal-reducer.test.ts": 45, + "src/main/native-chat/agent-session-journal/journal-row-schema-version.test.ts": 28, + "src/main/native-chat/agent-session-journal/journal-row-schema.test.ts": 32, + "src/main/native-chat/agent-session-journal/journal-row-writer.test.ts": 28, + "src/main/native-chat/agent-session-journal/journal-store-close.test.ts": 85, + "src/main/native-chat/agent-session-journal/journal-store-schema.test.ts": 85, + "src/main/native-chat/agent-session-journal/journal-store.test.ts": 290, + "src/main/native-chat/agent-session-journal/journal-subagent-liveness.test.ts": 835, + "src/main/native-chat/agent-session-wire/agent-session-delta-coalescer-protected.test.ts": 46, + "src/main/native-chat/agent-session-wire/agent-session-delta-coalescer.test.ts": 20, + "src/main/native-chat/agent-session-wire/agent-session-history-forward-read-budget.test.ts": 477, + "src/main/native-chat/agent-session-wire/agent-session-history-page-grouping-parity.test.ts": 253, + "src/main/native-chat/agent-session-wire/agent-session-history-page-scaling.test.ts": 17, + "src/main/native-chat/agent-session-wire/agent-session-history-page.test.ts": 1870, + "src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts": 178, + "src/main/native-chat/agent-session-wire/provider-frame-activity.test.ts": 10, + "src/main/native-chat/agent-session-wire/provider-frame-disposition.test.ts": 15, + "src/main/native-chat/agent-session-wire/provider-turn-activity-routing.test.ts": 22, + "src/main/native-chat/agent-session-wire/structured-agent-session-acquisition-options.test.ts": 564, + "src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.test.ts": 15, + "src/main/native-chat/agent-session-wire/structured-agent-session-adapter.test.ts": 14, + "src/main/native-chat/agent-session-wire/structured-agent-session-adopted-import.test.ts": 165, + "src/main/native-chat/agent-session-wire/structured-agent-session-claude-options-round-trip.test.ts": 248, + "src/main/native-chat/agent-session-wire/structured-agent-session-close-retry.test.ts": 52, + "src/main/native-chat/agent-session-wire/structured-agent-session-command-publication.test.ts": 45, + "src/main/native-chat/agent-session-wire/structured-agent-session-event-sink.test.ts": 24, + "src/main/native-chat/agent-session-wire/structured-agent-session-eviction.test.ts": 16, + "src/main/native-chat/agent-session-wire/structured-agent-session-forget-status.test.ts": 30, + "src/main/native-chat/agent-session-wire/structured-agent-session-grouped-prompt.test.ts": 68, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff-flow-runner.test.ts": 99, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff-operation-guard.test.ts": 144, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff-options.test.ts": 587, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff-owner-close.test.ts": 5, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff-reverse.test.ts": 6, + "src/main/native-chat/agent-session-wire/structured-agent-session-handoff.test.ts": 207, + "src/main/native-chat/agent-session-wire/structured-agent-session-holds.test.ts": 215, + "src/main/native-chat/agent-session-wire/structured-agent-session-host-handoff.test.ts": 103, + "src/main/native-chat/agent-session-wire/structured-agent-session-host-runtime-state.test.ts": 7, + "src/main/native-chat/agent-session-wire/structured-agent-session-host.test.ts": 1194, + "src/main/native-chat/agent-session-wire/structured-agent-session-journal-handles.test.ts": 66, + "src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts": 832, + "src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.test.ts": 7, + "src/main/native-chat/agent-session-wire/structured-agent-session-lease-renewer.test.ts": 156, + "src/main/native-chat/agent-session-wire/structured-agent-session-live-tui-restart-survival.test.ts": 431, + "src/main/native-chat/agent-session-wire/structured-agent-session-option-settlement.test.ts": 189, + "src/main/native-chat/agent-session-wire/structured-agent-session-processless-reservation.test.ts": 115, + "src/main/native-chat/agent-session-wire/structured-agent-session-proven-dead-retry.test.ts": 130, + "src/main/native-chat/agent-session-wire/structured-agent-session-provider-restore.test.ts": 288, + "src/main/native-chat/agent-session-wire/structured-agent-session-read-restore.test.ts": 290, + "src/main/native-chat/agent-session-wire/structured-agent-session-readable-restorer.test.ts": 7, + "src/main/native-chat/agent-session-wire/structured-agent-session-recovery-exits.test.ts": 361, + "src/main/native-chat/agent-session-wire/structured-agent-session-recovery-resolution.test.ts": 180, + "src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts": 8912, + "src/main/native-chat/agent-session-wire/structured-agent-session-restart-reconcile.test.ts": 6, + "src/main/native-chat/agent-session-wire/structured-agent-session-restart-restore-gate.test.ts": 6, + "src/main/native-chat/agent-session-wire/structured-agent-session-restart-restore.test.ts": 59, + "src/main/native-chat/agent-session-wire/structured-agent-session-restart-status-publication.test.ts": 137, + "src/main/native-chat/agent-session-wire/structured-agent-session-reveal.test.ts": 19, + "src/main/native-chat/agent-session-wire/structured-agent-session-rewind.test.ts": 1652, + "src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.test.ts": 34, + "src/main/native-chat/agent-session-wire/structured-agent-session-send-settlement.test.ts": 20, + "src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts": 515, + "src/main/native-chat/agent-session-wire/structured-agent-session-settled-attach-retry.test.ts": 304, + "src/main/native-chat/agent-session-wire/structured-agent-session-stale-turn-verdict.test.ts": 13, + "src/main/native-chat/agent-session-wire/structured-agent-session-status-feed.test.ts": 581, + "src/main/native-chat/agent-session-wire/structured-agent-session-subscribers.test.ts": 314, + "src/main/native-chat/agent-session-wire/structured-agent-session-surface-lifetime.test.ts": 1169, + "src/main/native-chat/agent-session-wire/structured-agent-session-task-queue.test.ts": 64, + "src/main/native-chat/agent-session-wire/structured-agent-session-teardown-handoff-drain.test.ts": 251, + "src/main/native-chat/agent-session-wire/structured-agent-session-turns.test.ts": 47, + "src/main/native-chat/agent-session-wire/structured-agent-session-unexpected-exit.test.ts": 15, + "src/main/native-chat/agent-session-wire/structured-agent-session-wedged-profile-migration.test.ts": 985, + "src/main/native-chat/agent-session-wire/structured-agent-session-wire-admission.test.ts": 569, + "src/main/native-chat/agent-session-wire/structured-conversation-command-admission.test.ts": 6, + "src/main/native-chat/agent-session-wire/structured-conversation-command.test.ts": 831, + "src/main/native-chat/agent-session-wire/structured-conversation-replacements.test.ts": 8, + "src/main/native-chat/agent-session-wire/structured-rewind-claude-proof.test.ts": 9, + "src/main/native-chat/agent-session-wire/structured-rewind-journal-body.test.ts": 16, + "src/main/native-chat/agent-session-wire/structured-session-compaction.test.ts": 21, + "src/main/native-chat/agent-session-wire/structured-tui-transcript-catchup.test.ts": 420, + "src/main/native-chat/agent-session-wire/unhandled-provider-frame.test.ts": 21, + "src/main/native-chat/claude-structured-managed-account-support.test.ts": 11, + "src/main/native-chat/host-readable-transcript-path-fs-gate.test.ts": 213, + "src/main/native-chat/host-readable-transcript-path.test.ts": 20, + "src/main/native-chat/native-chat-file-provenance.test.ts": 10, + "src/main/native-chat/session-file-resolver-claude-roots.test.ts": 6, + "src/main/native-chat/session-file-resolver-codex-roots.test.ts": 4, + "src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts": 21, + "src/main/native-chat/session-file-resolver-wsl.test.ts": 21, + "src/main/native-chat/session-file-resolver.test.ts": 87, + "src/main/native-chat/structured-agent-session-create-support.test.ts": 8, + "src/main/native-chat/structured-agent-session-history-adoption.test.ts": 9, + "src/main/native-chat/subagent-entry-id-bounds.test.ts": 7, + "src/main/native-chat/transcript-fallback-id.test.ts": 6, + "src/main/native-chat/transcript-interruption-message.test.ts": 7, + "src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts": 7, + "src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts": 6, + "src/main/native-chat/transcript-line-decoders-codex-skill-context.test.ts": 12, + "src/main/native-chat/transcript-line-decoders.grok.test.ts": 13, + "src/main/native-chat/transcript-line-decoders.omp.test.ts": 21, + "src/main/native-chat/transcript-read-cache-refusal-recovery.test.ts": 31, + "src/main/native-chat/transcript-read-cache-wsl-stall.test.ts": 14, + "src/main/native-chat/transcript-read-cache.test.ts": 55, + "src/main/native-chat/transcript-reader-codex-history-mode.test.ts": 21, + "src/main/native-chat/transcript-reader-wsl-stall.test.ts": 20, + "src/main/native-chat/transcript-reader.test.ts": 31, + "src/main/native-chat/transcript-stream-lines.test.ts": 8, + "src/main/native-chat/transcript-tail-reader-cancellation.test.ts": 115, + "src/main/native-chat/transcript-tail-reader-wsl-gate.test.ts": 17, + "src/main/native-chat/transcript-turn-lifecycle.test.ts": 11, + "src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts": 71, + "src/main/native-chat/transcript-watch-error.test.ts": 2068, + "src/main/native-chat/transcript-watch-liveness.test.ts": 639, + "src/main/native-chat/transcript-watch-resolve-poll.test.ts": 82, + "src/main/native-chat/transcript-watch-unflushed-settle.test.ts": 94, + "src/main/native-chat/transcript-watch-unsubscribe-race.test.ts": 26, + "src/main/native-chat/transcript-watch-wsl-exact-path.test.ts": 21, + "src/main/native-chat/transcript-watch-wsl-stall.test.ts": 21, + "src/main/native-chat/transcript-watch.test.ts": 3183, + "src/main/native-chat/transcript-window-tool-attribution.test.ts": 13, + "src/main/native-chat/wsl-codex-session-path-scan.test.ts": 24, + "src/main/native-chat/wsl-transcript-fs-access.test.ts": 29, + "src/main/native-chat/wsl-transcript-fs-gate.test.ts": 463, + "src/main/native-chat/wsl-transcript-fs-process-client.test.ts": 33, + "src/main/native-chat/wsl-transcript-fs-process-operations.test.ts": 5, + "src/main/native-chat/wsl-transcript-fs-route-quarantine.test.ts": 22, + "src/main/native-chat/wsl-transcript-fs-route.test.ts": 6, + "src/main/native-chat/wsl-transcript-running-observer.test.ts": 65, + "src/main/network/electron-proxy-request-guard.test.ts": 314, + "src/main/network/macos-system-resolver-health.test.ts": 18, + "src/main/network/macos-tailscale-dns-diagnostic.test.ts": 8, + "src/main/network/proxy-settings-session.test.ts": 779, + "src/main/network/proxy-settings.test.ts": 67, + "src/main/notifications/desktop-away-state.test.ts": 6, + "src/main/observability/architecture.test.ts": 10, + "src/main/observability/bundle.test.ts": 294, + "src/main/observability/diagnostic-upload-http.test.ts": 8, + "src/main/observability/instrumentation.test.ts": 252, + "src/main/observability/local-file-sink-memory.test.ts": 126, + "src/main/observability/local-file-sink.test.ts": 27, + "src/main/observability/redactor-environment-lines.test.ts": 486, + "src/main/observability/redactor.test.ts": 27, + "src/main/observability/tracer.test.ts": 27, + "src/main/opencode-usage/scanner-windows-data-directory.test.ts": 23, + "src/main/opencode-usage/scanner-wsl-gate.test.ts": 18, + "src/main/opencode-usage/scanner.test.ts": 224, + "src/main/opencode-usage/store.test.ts": 28, + "src/main/opencode/hook-plugin-background-child-completion.test.ts": 197, + "src/main/opencode/hook-plugin-child-attention.test.ts": 421, + "src/main/opencode/hook-plugin-fail-open-ownership.test.ts": 460, + "src/main/opencode/hook-plugin-lifecycle-delivery.test.ts": 591, + "src/main/opencode/hook-plugin-message-part-throttle.test.ts": 137, + "src/main/opencode/hook-plugin-module-contract.test.ts": 146, + "src/main/opencode/hook-service.test.ts": 50, + "src/main/opencode/opencode-data-directory.test.ts": 6, + "src/main/orca-profiles/profile-artifact-cloud-cleanup.test.ts": 142, + "src/main/orca-profiles/profile-cloud-auth-config.test.ts": 7, + "src/main/orca-profiles/profile-cloud-auth-status.test.ts": 7, + "src/main/orca-profiles/profile-cloud-client.test.ts": 73, + "src/main/orca-profiles/profile-cloud-dev-service.test.ts": 52, + "src/main/orca-profiles/profile-cloud-org-members-client.test.ts": 9, + "src/main/orca-profiles/profile-cloud-org-members-service.test.ts": 33, + "src/main/orca-profiles/profile-cloud-pkce.test.ts": 184, + "src/main/orca-profiles/profile-cloud-service-auth-retry.test.ts": 42, + "src/main/orca-profiles/profile-cloud-service-refresh.test.ts": 148, + "src/main/orca-profiles/profile-cloud-service.test.ts": 45, + "src/main/orca-profiles/profile-cloud-session-mutation.test.ts": 14, + "src/main/orca-profiles/profile-cloud-session-refresh.test.ts": 12, + "src/main/orca-profiles/profile-cloud-session-store.test.ts": 71, + "src/main/orca-profiles/profile-index-store.test.ts": 188, + "src/main/orca-profiles/profile-project-presence.test.ts": 59, + "src/main/orca-profiles/profile-project-session-field-disposition.test.ts": 9, + "src/main/orca-profiles/profile-project-session-state.test.ts": 13, + "src/main/orca-profiles/profile-project-state-file.test.ts": 7, + "src/main/orca-profiles/profile-project-transfer.test.ts": 238, + "src/main/orca-profiles/profile-ui-scope.test.ts": 4, + "src/main/orcad/electron-serve-browser-process.test.ts": 2342, + "src/main/orcad/electron-serve-provider-selection.test.ts": 427, + "src/main/orcad/electron-sidecar-method-routing.test.ts": 7, + "src/main/orcad/electron-sidecar-tab-registry.test.ts": 12, + "src/main/orcad/external-chromium-browser-session.test.ts": 16, + "src/main/orcad/main-preflight-order.test.ts": 8, + "src/main/orcad/native-host-abi.test.ts": 15, + "src/main/orcad/node-pty-prebuilt-slot.test.ts": 12, + "src/main/orcad/node-pty-precondition.test.ts": 290, + "src/main/orcad/orcad-app-paths.test.ts": 10, + "src/main/orcad/orcad-bind-address.test.ts": 11, + "src/main/orcad/orcad-browser-provider.test.ts": 53, + "src/main/orcad/orcad-bundle-native-load-order.test.ts": 3283, + "src/main/orcad/orcad-daemon-supervision.test.ts": 10, + "src/main/orcad/orcad-forked-child-paths.test.ts": 8, + "src/main/orcad/orcad-health.test.ts": 10, + "src/main/orcad/orcad-instance-lock.test.ts": 15, + "src/main/orcad/orcad-launch-contract.test.ts": 13, + "src/main/orcad/orcad-native-preflight.test.ts": 6, + "src/main/orcad/orcad-push-startup.test.ts": 704, + "src/main/own-chromium-tree-kill-guard.test.ts": 20, + "src/main/persistence-async-write-syscalls.test.ts": 5455, + "src/main/persistence-automations.test.ts": 593, + "src/main/persistence-clipboard-selection-migration.test.ts": 65, + "src/main/persistence-cohort-and-identity-migration.test.ts": 67, + "src/main/persistence-cross-host-pane-identity.test.ts": 36, + "src/main/persistence-deregistered-repo-residue.test.ts": 132, + "src/main/persistence-duplicate-repo-id-host-scope.test.ts": 792, + "src/main/persistence-feature-interaction-broadcast.benchmark.test.ts": 126, + "src/main/persistence-floating-terminal-trust.test.ts": 91, + "src/main/persistence-flush-and-save-scheduling.test.ts": 226, + "src/main/persistence-folder-workspace-notes.test.ts": 124, + "src/main/persistence-host-admitted-terminal-membership.test.ts": 32, + "src/main/persistence-host-partitioned-sessions.test.ts": 173, + "src/main/persistence-host-partitioned-ssh-pty-bindings.test.ts": 31, + "src/main/persistence-initial-load.test.ts": 171, + "src/main/persistence-layout-binding-recovery.test.ts": 46, + "src/main/persistence-load-repair-durability.test.ts": 65, + "src/main/persistence-loading-store-extraction.test.ts": 93, + "src/main/persistence-loading-store-write-risks.test.ts": 34, + "src/main/persistence-native-chat-tab-view-mode.test.ts": 17, + "src/main/persistence-pane-identity-migration.test.ts": 27776, + "src/main/persistence-protected-secret-fail-closed.test.ts": 2755, + "src/main/persistence-protected-secret-write-race.test.ts": 698, + "src/main/persistence-proxy-secret-recovery.test.ts": 1089, + "src/main/persistence-pty-binding-leaf-tab-resolution.test.ts": 33, + "src/main/persistence-pty-binding-reconciliation.test.ts": 54, + "src/main/persistence-remote-session-startup.test.ts": 58, + "src/main/persistence-repo-lifecycle.test.ts": 807, + "src/main/persistence-right-sidebar-tab.test.ts": 7, + "src/main/persistence-settings-ui-defaults.test.ts": 137, + "src/main/persistence-settings-update.test.ts": 124, + "src/main/persistence-single-serialize.test.ts": 1727, + "src/main/persistence-source-control-ai-migration.test.ts": 108, + "src/main/persistence-split-pane-incarnation.test.ts": 65, + "src/main/persistence-ssh-lease-reattach-reclaim.test.ts": 46, + "src/main/persistence-ssh-lease-tombstone-retention.test.ts": 44, + "src/main/persistence-ssh-pending-pty-kill.test.ts": 570, + "src/main/persistence-ssh-readoption-automation-migration.test.ts": 1520, + "src/main/persistence-ssh-remote-pty-binding-replay.test.ts": 41, + "src/main/persistence-ssh-remote-pty-leases.test.ts": 129, + "src/main/persistence-ssh-targets-and-pane-keys.test.ts": 72, + "src/main/persistence-terminal-option-key-migration.test.ts": 47, + "src/main/persistence-ui-state.test.ts": 3109, + "src/main/persistence-update-repo.test.ts": 252, + "src/main/persistence-workspace-pinned-automation-fence.test.ts": 1660, + "src/main/persistence-workspace-repin-automation-owner.test.ts": 1100, + "src/main/persistence-workspace-session-scrollback.test.ts": 101, + "src/main/persistence-workspace-status-workflow.test.ts": 67, + "src/main/persistence-worktree-card-properties.test.ts": 43, + "src/main/persistence-worktree-deletion-fencing.test.ts": 88, + "src/main/persistence-worktree-lineage-and-backups.test.ts": 499, + "src/main/persistence-worktree-meta-and-folder-workspaces.test.ts": 83, + "src/main/persistence-worktree-name-retirement.test.ts": 3387, + "src/main/persistence-worktree-visibility.test.ts": 100, + "src/main/persistence/applying-settings/settings-update-terminal-contrast.test.ts": 8, + "src/main/persistence/applying-settings/terminal-settings-migrations.test.ts": 4, + "src/main/persistence/leasing-ssh-ptys/ssh-pty-binding-cleanup.test.ts": 22, + "src/main/persistence/loading-store/metadata-lineage-batch-pruning.test.ts": 46, + "src/main/persistence/loading-store/normalize-loaded-global-settings.test.ts": 17, + "src/main/persistence/loading-store/normalize-loaded-project-catalog.test.ts": 12, + "src/main/persistence/loading-store/persisted-state-redundancy.test.ts": 461, + "src/main/persistence/loading-store/secret-sentinel-substitution.test.ts": 14, + "src/main/persistence/loading-store/state-write-round-trip.test.ts": 72, + "src/main/persistence/loading-store/store-prune-gate-signals.test.ts": 32, + "src/main/persistence/loading-store/store-runtime-authored-session-writes.test.ts": 29, + "src/main/persistence/loading-store/workspace-session-partitions.test.ts": 16, + "src/main/persistence/loading-store/workspace-session-terminal-binding-replay.test.ts": 13, + "src/main/persistence/loading-store/worktree-meta-alias-projection.test.ts": 345, + "src/main/persistence/loading-store/worktree-meta-write-normalization.test.ts": 8, + "src/main/persistence/restoring-sessions/pane-alias-normalization-scaling.test.ts": 11, + "src/main/persistence/restoring-sessions/pane-alias-normalization.test.ts": 6, + "src/main/persistence/restoring-sessions/pane-key-remapping.test.ts": 9, + "src/main/persistence/restoring-sessions/session-owner-fields.test.ts": 10, + "src/main/persistence/restoring-sessions/session-worktree-ownership.test.ts": 21, + "src/main/persistence/restoring-sessions/terminal-layout-normalization.test.ts": 6, + "src/main/persistence/restoring-sessions/workspace-pane-normalization-index.test.ts": 10, + "src/main/persistence/runtime-authored-workspace-session-fields.test.ts": 5, + "src/main/persistence/scheduling-automations/automation-run-operations.test.ts": 16, + "src/main/persistence/tracking-repos/local-worktree-metadata-scan-expectation.test.ts": 21, + "src/main/persistence/tracking-repos/missing-local-worktree-metadata-pruning.test.ts": 107, + "src/main/persistence/tracking-repos/probeable-local-worktree-metadata-candidates.test.ts": 13, + "src/main/persistence/tracking-repos/project-host-compatibility.test.ts": 11, + "src/main/persistence/tracking-repos/worktree-metadata-normalization.test.ts": 9, + "src/main/pi/agent-status-extension-omp-lifecycle.test.ts": 839, + "src/main/pi/agent-status-extension-source.test.ts": 1483, + "src/main/pi/agent-status-omp-approval-forwarding.test.ts": 487, + "src/main/pi/agent-status-owner-recovery.test.ts": 432, + "src/main/pi/agent-status-ui-prompt.test.ts": 623, + "src/main/pi/titlebar-extension-overlay-path.test.ts": 9, + "src/main/pi/titlebar-extension-service.test.ts": 147, + "src/main/pi/titlebar-extension-source.test.ts": 618, + "src/main/plugins/plugin-atomic-file-write.test.ts": 1988, + "src/main/plugins/plugin-audit-log-scaling.test.ts": 412, + "src/main/plugins/plugin-audit-log.test.ts": 17, + "src/main/plugins/plugin-bundled-bootstrap-coordinator.test.ts": 62, + "src/main/plugins/plugin-bundled-bootstrap.test.ts": 104, + "src/main/plugins/plugin-command-registry.test.ts": 32, + "src/main/plugins/plugin-content-pack-registry.test.ts": 30, + "src/main/plugins/plugin-content-safety.test.ts": 66, + "src/main/plugins/plugin-dev-watcher.test.ts": 106, + "src/main/plugins/plugin-discovery.test.ts": 40, + "src/main/plugins/plugin-enablement.test.ts": 14, + "src/main/plugins/plugin-host-conformance.test.ts": 30, + "src/main/plugins/plugin-host-methods.test.ts": 22, + "src/main/plugins/plugin-host-process.test.ts": 13, + "src/main/plugins/plugin-host-runtime.test.ts": 11, + "src/main/plugins/plugin-install-trust.test.ts": 51, + "src/main/plugins/plugin-install.test.ts": 334, + "src/main/plugins/plugin-kill-list-content-revocation.test.ts": 52, + "src/main/plugins/plugin-kill-list-service.test.ts": 68, + "src/main/plugins/plugin-language-pack-registry.test.ts": 22, + "src/main/plugins/plugin-launch-content.test.ts": 84, + "src/main/plugins/plugin-list-projection.test.ts": 15, + "src/main/plugins/plugin-marketplace-installer.test.ts": 98, + "src/main/plugins/plugin-marketplace-service.test.ts": 136, + "src/main/plugins/plugin-marketplace-store.test.ts": 241, + "src/main/plugins/plugin-panel-controller.test.ts": 25, + "src/main/plugins/plugin-panel-navigation-guard.test.ts": 7, + "src/main/plugins/plugin-panel-owner-lifecycle.test.ts": 4, + "src/main/plugins/plugin-panel-sessions.test.ts": 7, + "src/main/plugins/plugin-private-marketplace.integration.test.ts": 321, + "src/main/plugins/plugin-secrets-store.test.ts": 33, + "src/main/plugins/plugin-service-integrity.test.ts": 70, + "src/main/plugins/plugin-service-reconciliation.test.ts": 314, + "src/main/plugins/plugin-startup-budget.test.ts": 373, + "src/main/plugins/plugin-storage-store.test.ts": 8, + "src/main/plugins/plugin-vm-recipe-registry.test.ts": 40, + "src/main/plugins/plugin-worker-controller.test.ts": 77, + "src/main/plugins/plugin-worker-env.test.ts": 6, + "src/main/plugins/plugin-worker-manager.test.ts": 17, + "src/main/plugins/plugin-worker-output-buffer.test.ts": 12, + "src/main/plugins/plugin-worker-supervision.integration.test.ts": 8191, + "src/main/ports/advertised-url-watcher-pid-validation.test.ts": 10, + "src/main/ports/advertised-url-watcher.test.ts": 22, + "src/main/ports/local-workspace-port-scanner.test.ts": 44, + "src/main/ports/port-scan-command-client.test.ts": 1741, + "src/main/ports/port-scan-command-execution.test.ts": 5223, + "src/main/ports/port-scan-command-import-boundary.test.ts": 6, + "src/main/ports/ssh-advertised-url-enrichment.test.ts": 13, + "src/main/ports/workspace-port-ownership.test.ts": 9, + "src/main/powershell-osc133-bootstrap.test.ts": 14, + "src/main/project-groups/folder-workspace-path-status.test.ts": 19, + "src/main/project-groups/nested-repo-discovery.test.ts": 103, + "src/main/project-groups/nested-repo-import-target.test.ts": 10, + "src/main/project-groups/nested-repo-import.test.ts": 17, + "src/main/project-runtime-git-options.test.ts": 15, + "src/main/protected-secret-persistence.test.ts": 20, + "src/main/providers/agent-foreground-process-batch.test.ts": 15, + "src/main/providers/agent-foreground-process-pi.test.ts": 12, + "src/main/providers/agent-foreground-process-ps-scan-volume.test.ts": 24, + "src/main/providers/agent-foreground-process-real-rows.test.ts": 13, + "src/main/providers/agent-foreground-process-remote-evidence.test.ts": 11, + "src/main/providers/agent-foreground-process-windows-job-rejection.test.ts": 4, + "src/main/providers/agent-foreground-process.test.ts": 30, + "src/main/providers/execution-host-provider-dispatch.test.ts": 12, + "src/main/providers/local-pty-foreground-inspection-cheap-tier.test.ts": 10, + "src/main/providers/local-pty-provider-foreground-process.test.ts": 110, + "src/main/providers/local-pty-provider-io-events.test.ts": 31, + "src/main/providers/local-pty-provider-session-inventory.test.ts": 29, + "src/main/providers/local-pty-provider-shell-readiness.test.ts": 39, + "src/main/providers/local-pty-provider-shutdown.test.ts": 351, + "src/main/providers/local-pty-provider-spawn-cwd-safety.test.ts": 21, + "src/main/providers/local-pty-provider-spawn-env.test.ts": 40, + "src/main/providers/local-pty-provider-spawn-session.test.ts": 33, + "src/main/providers/local-pty-provider-windows-shell-launch.test.ts": 72, + "src/main/providers/local-pty-shell-ready-marker-scan.test.ts": 8, + "src/main/providers/local-pty-shell-ready-startup-command.test.ts": 11, + "src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts": 411, + "src/main/providers/local-pty-shell-startup-command.node-pty.test.ts": 52, + "src/main/providers/local-pty-utils-windows-fallback.test.ts": 9, + "src/main/providers/local-pty-utils.test.ts": 12, + "src/main/providers/macos-login-session-pty-probe.test.ts": 12, + "src/main/providers/macos-tcc-login-shell.test.ts": 77, + "src/main/providers/posix-pane-foreground-fingerprint.test.ts": 9, + "src/main/providers/process-cwd.test.ts": 100, + "src/main/providers/provider-dispatch.test.ts": 38, + "src/main/providers/pty-default-cwd.test.ts": 9, + "src/main/providers/pty-process-inspection.test.ts": 15, + "src/main/providers/pty-process-list-admission.test.ts": 76, + "src/main/providers/pty-repaint-wide-char-buffer.node-pty.test.ts": 1855, + "src/main/providers/settled-pty-writer-census.test.ts": 79, + "src/main/providers/ssh-filesystem-dispatch.test.ts": 9, + "src/main/providers/ssh-filesystem-doc-preview.test.ts": 7, + "src/main/providers/ssh-filesystem-download.test.ts": 145, + "src/main/providers/ssh-filesystem-provider-capabilities.test.ts": 15, + "src/main/providers/ssh-filesystem-provider-download-folder.test.ts": 22, + "src/main/providers/ssh-filesystem-provider-range.test.ts": 13, + "src/main/providers/ssh-filesystem-provider-stream.test.ts": 52, + "src/main/providers/ssh-filesystem-provider-watch-waiters.test.ts": 437, + "src/main/providers/ssh-filesystem-provider.test.ts": 59, + "src/main/providers/ssh-filesystem-watch-notifications.test.ts": 11, + "src/main/providers/ssh-git-dispatch.test.ts": 4, + "src/main/providers/ssh-git-provider-api.test.ts": 8, + "src/main/providers/ssh-git-provider-commit-message.test.ts": 18, + "src/main/providers/ssh-git-provider-diff.test.ts": 32, + "src/main/providers/ssh-git-provider-exec.test.ts": 26, + "src/main/providers/ssh-git-provider-merge.test.ts": 9, + "src/main/providers/ssh-git-provider-remote-sync.test.ts": 21, + "src/main/providers/ssh-git-provider-staging.test.ts": 16, + "src/main/providers/ssh-git-provider-status-lease.test.ts": 24, + "src/main/providers/ssh-git-provider-status.test.ts": 22, + "src/main/providers/ssh-git-provider-upstream-lease.test.ts": 16, + "src/main/providers/ssh-git-provider-worktree.test.ts": 29, + "src/main/providers/ssh-git-worktree-list-dedupe.test.ts": 32, + "src/main/providers/ssh-pty-inspect-observation-identity.test.ts": 6, + "src/main/providers/ssh-pty-live-source-restore-respawn.test.ts": 15, + "src/main/providers/ssh-pty-notification-rejection-routing.test.ts": 162, + "src/main/providers/ssh-pty-notification-routing.test.ts": 22, + "src/main/providers/ssh-pty-provider-agent-session-create-operation.test.ts": 35, + "src/main/providers/ssh-pty-provider-claim-incarnation.test.ts": 7, + "src/main/providers/ssh-pty-provider-exit-race.test.ts": 19, + "src/main/providers/ssh-pty-provider-process-events.test.ts": 18, + "src/main/providers/ssh-pty-provider-reattach-incarnation.test.ts": 9, + "src/main/providers/ssh-pty-provider-spawn.test.ts": 33, + "src/main/providers/ssh-pty-provider-terminal-repair.test.ts": 36, + "src/main/providers/ssh-pty-provider.test.ts": 31, + "src/main/providers/ssh-pty-reattach-absence-discrimination.test.ts": 23, + "src/main/providers/ssh-pty-relay-absence-verdict.test.ts": 19, + "src/main/providers/ssh-pty-source-delivery-ledger.test.ts": 14, + "src/main/providers/ssh-pty-write.test.ts": 25, + "src/main/providers/ssh-worktree-catalog-authority.test.ts": 29, + "src/main/providers/stable-foreground-process.test.ts": 5, + "src/main/providers/windows-agent-foreground-process-scan-volume.test.ts": 37, + "src/main/providers/windows-cached-agent-revalidation.test.ts": 11, + "src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts": 7, + "src/main/providers/windows-console-attached-processes.test.ts": 16, + "src/main/providers/windows-foreground-process-inspection-cost.test.ts": 15, + "src/main/providers/windows-foreground-process-rows.test.ts": 13, + "src/main/providers/windows-powershell-executable.test.ts": 18, + "src/main/providers/windows-powershell.test.ts": 8, + "src/main/providers/windows-pty-job-membership.test.ts": 13, + "src/main/providers/windows-shell-args.test.ts": 48, + "src/main/providers/windows-shell-fallback-chain.test.ts": 10, + "src/main/providers/working-directory-validation.test.ts": 45, + "src/main/provisioned-root-ssh-adoption.test.ts": 140, + "src/main/proxy-guarded-fetch-call-site-audit.test.ts": 76, + "src/main/pty-descendant-termination-job-coverage.test.ts": 5, + "src/main/pty-descendant-termination.test.ts": 59, + "src/main/pty/appimage-terminal-env.test.ts": 6, + "src/main/pty/build-mode-env.test.ts": 6, + "src/main/pty/codex-home-wsl-env.test.ts": 5, + "src/main/pty/codex-preflight-profile-path-rewrite.test.ts": 134, + "src/main/pty/codex-shell-launch-preflight.test.ts": 4086, + "src/main/pty/conda-activation-env.test.ts": 8, + "src/main/pty/legacy-terminal-shim-dir.test.ts": 1197, + "src/main/pty/legacy-terminal-windows-tombstone.test.ts": 21, + "src/main/pty/node-pty-master-fd-retirement.test.ts": 5158, + "src/main/pty/node-pty-self-exit-pseudoconsole-close.test.ts": 6, + "src/main/pty/omp-shell-wrapper-alias-safety.test.ts": 16, + "src/main/pty/omp-sqlite-overlay.test.ts": 5, + "src/main/pty/overlay-mirror.test.ts": 17, + "src/main/pty/posix-pty-foreground-group.test.ts": 9, + "src/main/pty/posix-pty-process-groups.integration.test.ts": 106, + "src/main/pty/posix-pty-process-groups.test.ts": 10, + "src/main/pty/shell-startup-env.test.ts": 19, + "src/main/pty/terminal-color-env.test.ts": 8, + "src/main/pty/windows-environment-path-main-loop.test.ts": 367, + "src/main/pty/windows-environment-path.test.ts": 17, + "src/main/pty/windows-path-registry-change.test.ts": 6, + "src/main/pty/windows-path-registry-fallback.test.ts": 7, + "src/main/pty/windows-path-registry-reader.test.ts": 9, + "src/main/pty/wsl-orca-env.test.ts": 20, + "src/main/pwsh.test.ts": 24, + "src/main/quit-path-durable-write-blocking.test.ts": 1453, + "src/main/quit-teardown-agent-browser-daemons.test.ts": 6, + "src/main/quit-teardown-deadline.test.ts": 11, + "src/main/quit-teardown-start-gate.test.ts": 8, + "src/main/rate-limits/account-runtime-target-sync.test.ts": 12, + "src/main/rate-limits/antigravity-usage-mirror.test.ts": 7, + "src/main/rate-limits/auth-filesystem-operation.test.ts": 11, + "src/main/rate-limits/claude-fetcher-cli-fallback.test.ts": 86, + "src/main/rate-limits/claude-fetcher-fable-usage.test.ts": 65, + "src/main/rate-limits/claude-fetcher-keychain-credentials.test.ts": 100, + "src/main/rate-limits/claude-fetcher-managed-account-usage.test.ts": 50, + "src/main/rate-limits/claude-oauth-usage-error.test.ts": 44, + "src/main/rate-limits/claude-pty.test.ts": 64, + "src/main/rate-limits/claude-usage-error-classification.test.ts": 9, + "src/main/rate-limits/claude-usage-refresh-plan.test.ts": 7, + "src/main/rate-limits/codex-auth-presence.test.ts": 17, + "src/main/rate-limits/codex-fetcher-auth-errors.test.ts": 15, + "src/main/rate-limits/codex-fetcher-backend.test.ts": 76, + "src/main/rate-limits/codex-fetcher-probe-shutdown.test.ts": 38, + "src/main/rate-limits/codex-fetcher-process-contract.test.ts": 43, + "src/main/rate-limits/codex-fetcher-pty-settle.test.ts": 38, + "src/main/rate-limits/codex-fetcher-rpc-exit-diagnostics.test.ts": 175, + "src/main/rate-limits/codex-fetcher-runtime-pairing.test.ts": 17, + "src/main/rate-limits/codex-fetcher-session-supplement.test.ts": 20, + "src/main/rate-limits/codex-fetcher.test.ts": 86, + "src/main/rate-limits/codex-probe-termination.test.ts": 16, + "src/main/rate-limits/codex-pty-rate-limit-probe.test.ts": 10, + "src/main/rate-limits/codex-pty-status-parser.test.ts": 7, + "src/main/rate-limits/codex-rate-limit-window-classification.test.ts": 11, + "src/main/rate-limits/codex-rpc-rate-limit-probe.test.ts": 60, + "src/main/rate-limits/gemini-bucket-formatting.test.ts": 6, + "src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts": 10, + "src/main/rate-limits/gemini-usage-fetcher.test.ts": 19, + "src/main/rate-limits/grok-auth.test.ts": 41, + "src/main/rate-limits/grok-fetcher.test.ts": 27, + "src/main/rate-limits/hidden-pty-cleanup.test.ts": 12, + "src/main/rate-limits/initial-account-rate-limit-target.test.ts": 14, + "src/main/rate-limits/kimi-fetcher-wsl-home.test.ts": 12, + "src/main/rate-limits/kimi-fetcher.test.ts": 34, + "src/main/rate-limits/minimax/minimax-fetcher.test.ts": 46, + "src/main/rate-limits/minimax/minimax-request-context.test.ts": 26, + "src/main/rate-limits/opencode-go-usage-fetcher.test.ts": 34, + "src/main/rate-limits/service-account-target-selection.test.ts": 142, + "src/main/rate-limits/service-antigravity-usage.test.ts": 11, + "src/main/rate-limits/service-inactive-account-previews.test.ts": 27, + "src/main/rate-limits/service-live-claude-usage.test.ts": 37, + "src/main/rate-limits/service-minimax-usage.test.ts": 17, + "src/main/rate-limits/service-refresh-orchestration.test.ts": 29, + "src/main/rate-limits/service-window-activation.test.ts": 22, + "src/main/refused-tree-kill-root-termination.test.ts": 24, + "src/main/remote-agent-trust-presets.test.ts": 13, + "src/main/remote-worktree-history-cleanup.test.ts": 12, + "src/main/repo-git-remote-identity-enrichment.test.ts": 22, + "src/main/repo-git-remote-identity.test.ts": 23, + "src/main/repo-git-username-enrichment.test.ts": 11, + "src/main/repo-icon-autodetect.test.ts": 242, + "src/main/repo-icon-file-detection.test.ts": 70, + "src/main/repo-icon-source-href.test.ts": 73, + "src/main/repo-maintenance-idle-gate.test.ts": 5, + "src/main/repo-worktrees.test.ts": 20, + "src/main/runtime/agent-prompt-receipt-correlation.test.ts": 65, + "src/main/runtime/agent-prompt-request-correlation.test.ts": 9, + "src/main/runtime/agent-prompt-submission-runtime.test.ts": 1762, + "src/main/runtime/agent-prompt-submission-verification.test.ts": 125, + "src/main/runtime/agent-prompt-submission-windows-submit-delay.test.ts": 192, + "src/main/runtime/agent-session-backup-recovery.test.ts": 116, + "src/main/runtime/agent-session-claim-identity.test.ts": 12, + "src/main/runtime/agent-session-conversation-name-store.test.ts": 76, + "src/main/runtime/agent-session-eviction-settlement-latch.test.ts": 9, + "src/main/runtime/agent-session-handoff-lease-transitions.test.ts": 8, + "src/main/runtime/agent-session-launch-env-admission.test.ts": 165, + "src/main/runtime/agent-session-launch-env-backfill.test.ts": 40, + "src/main/runtime/agent-session-lease-renewal.test.ts": 221, + "src/main/runtime/agent-session-orphan-child-reaper.test.ts": 12, + "src/main/runtime/agent-session-process-identity-probe-windows-batch.test.ts": 5, + "src/main/runtime/agent-session-process-identity-probe.test.ts": 17, + "src/main/runtime/agent-session-provider-handle-transition.test.ts": 7, + "src/main/runtime/agent-session-pty-write-enforcement.test.ts": 2113, + "src/main/runtime/agent-session-pty-write-gate.test.ts": 15, + "src/main/runtime/agent-session-record-conversation-name.test.ts": 14, + "src/main/runtime/agent-session-record-options.test.ts": 33, + "src/main/runtime/agent-session-record-store-security.test.ts": 23, + "src/main/runtime/agent-session-record-store.test.ts": 744, + "src/main/runtime/agent-session-record-unsupported-schema.test.ts": 26, + "src/main/runtime/agent-session-recovery-publish-fault.test.ts": 25, + "src/main/runtime/agent-session-reservation-admission.test.ts": 9, + "src/main/runtime/agent-session-restart-handoff-adjudication.test.ts": 13, + "src/main/runtime/agent-session-resume-args.test.ts": 5, + "src/main/runtime/agent-session-spawn-token-process-scan.test.ts": 8, + "src/main/runtime/agent-session-spawn-token-readback.test.ts": 9, + "src/main/runtime/agent-session-unreadable-record-salvage.test.ts": 68, + "src/main/runtime/agent-terminal-launch-trust-host.test.ts": 27, + "src/main/runtime/antigravity-readiness-transcripts.test.ts": 9380, + "src/main/runtime/automation-change-publication.test.ts": 4436, + "src/main/runtime/browser-client-download-transfers.test.ts": 80, + "src/main/runtime/browser-execution-host-key-resolution.test.ts": 25, + "src/main/runtime/browser-host-capability-selection.test.ts": 12, + "src/main/runtime/browser-host-client-page-adoption-grants.test.ts": 9, + "src/main/runtime/browser-host-client-page-adoption.test.ts": 9, + "src/main/runtime/browser-host-client-page-creation.test.ts": 28, + "src/main/runtime/browser-host-command-ledger-capacity.test.ts": 14, + "src/main/runtime/browser-host-command-ledger.test.ts": 30, + "src/main/runtime/browser-host-file-channel-admission.test.ts": 7, + "src/main/runtime/browser-host-lease-download-transfer-cleanup.test.ts": 114, + "src/main/runtime/browser-host-lease-fenced-page-release.test.ts": 30, + "src/main/runtime/browser-host-lease-placement-retirement.test.ts": 33, + "src/main/runtime/browser-host-lease-registry.test.ts": 47, + "src/main/runtime/browser-host-page-placement-replacement.test.ts": 11, + "src/main/runtime/browser-host-page-placement.test.ts": 16, + "src/main/runtime/browser-host-page-reconciliation-executor.test.ts": 44, + "src/main/runtime/browser-host-page-reconciliation-orchestration.test.ts": 245, + "src/main/runtime/browser-host-page-reconciliation-plan.test.ts": 22, + "src/main/runtime/browser-host-page-reconciliation-preconditions.test.ts": 7, + "src/main/runtime/browser-host-page-retirement.test.ts": 9, + "src/main/runtime/browser-network-tunnel-paired-runtime.integration.test.ts": 411, + "src/main/runtime/browser-screencast-driver-attribution.test.ts": 503, + "src/main/runtime/browser-screencast-driver-scope.test.ts": 5, + "src/main/runtime/browser-screencast-ghost-subscriber-eviction.test.ts": 14, + "src/main/runtime/browser-screencast-remote-viewer-retention.test.ts": 379, + "src/main/runtime/browser-session-tab-selection-snapshot.test.ts": 12, + "src/main/runtime/browser-tab-create-caller-navigation.test.ts": 641, + "src/main/runtime/browser-tab-create-publication.test.ts": 998, + "src/main/runtime/claude-agent-teams-pty-exit-leak.test.ts": 23, + "src/main/runtime/claude-agent-teams-service.test.ts": 29, + "src/main/runtime/claude-agent-teams-shim-env.test.ts": 41, + "src/main/runtime/claude-structured-session-integration.test.ts": 943, + "src/main/runtime/cli-terminal-create-host-session-binding.test.ts": 22, + "src/main/runtime/client-hosted-browser-page-persistence.integration.test.ts": 39, + "src/main/runtime/client-hosted-browser-page-persistence.test.ts": 24, + "src/main/runtime/client-hosted-browser-row-hydration-census.test.ts": 427, + "src/main/runtime/client-hosted-browser-row-projection.test.ts": 9, + "src/main/runtime/client-hosted-browser-row-publication.test.ts": 30, + "src/main/runtime/client-hosted-browser-row-push.integration.test.ts": 41, + "src/main/runtime/client-hosted-page-reconciliation-hold.integration.test.ts": 16, + "src/main/runtime/client-hosted-page-reconciliation-window.test.ts": 23, + "src/main/runtime/client-session-tab-selection.test.ts": 11, + "src/main/runtime/decorative-title-fact-emission.test.ts": 4, + "src/main/runtime/exit-provenance-audit.test.ts": 394, + "src/main/runtime/expired-ssh-lease-pane-candidacy.test.ts": 22, + "src/main/runtime/external-worktree-paired-client-discovery.integration.test.ts": 203, + "src/main/runtime/fetch-remote-cache.test.ts": 271, + "src/main/runtime/file-watcher-host.test.ts": 600, + "src/main/runtime/fit-override-integration.test.ts": 56, + "src/main/runtime/folder-workspace-pty-identity.test.ts": 54, + "src/main/runtime/graph-sync-deletion-fence.test.ts": 43, + "src/main/runtime/graph-sync-live-daemon-pty-tab-preservation.test.ts": 95, + "src/main/runtime/graph-sync-mobile-snapshot-gating.test.ts": 94, + "src/main/runtime/graph-sync-payload-partition.test.ts": 33, + "src/main/runtime/headless-tab-group-split-layout.test.ts": 15, + "src/main/runtime/headless-tab-order-stability.test.ts": 29, + "src/main/runtime/headless-terminal-dispose-write-ordering.test.ts": 25, + "src/main/runtime/headless-terminal-query-reply-policy.test.ts": 4, + "src/main/runtime/headless-terminal-split-layout.test.ts": 9, + "src/main/runtime/hidden-output-restored-provider-snapshot.test.ts": 74, + "src/main/runtime/host-terminal-close-persistence-durability.test.ts": 59, + "src/main/runtime/linear-save-issue.test.ts": 34, + "src/main/runtime/managed-worktree-create-execution-host.test.ts": 24, + "src/main/runtime/missing-worktree-terminal-reconciliation.test.ts": 14, + "src/main/runtime/mobile-agent-status-permission-renewal.test.ts": 34, + "src/main/runtime/mobile-notification-dismissal-read-failure.test.ts": 4, + "src/main/runtime/mobile-notification-dismissal-store.test.ts": 12, + "src/main/runtime/mobile-notification-replay.test.ts": 11, + "src/main/runtime/mobile-pairing-qr.test.ts": 1086, + "src/main/runtime/mobile-pairing-userdata-path.test.ts": 1112, + "src/main/runtime/mobile-presence-lock.test.ts": 230, + "src/main/runtime/mobile-rpc-allowlist.test.ts": 84, + "src/main/runtime/mobile-session-tabs-agent-status-heartbeat.test.ts": 21, + "src/main/runtime/mobile-session-tabs-churn-coalescing.test.ts": 26, + "src/main/runtime/mobile-session-tabs-notify-coalescer.test.ts": 17, + "src/main/runtime/mobile-session-terminal-persistence-retirement.test.ts": 19, + "src/main/runtime/mobile-session-terminal-retirement-proof.test.ts": 5, + "src/main/runtime/mobile-session-terminal-retirement.test.ts": 12, + "src/main/runtime/mobile-subscribe-integration.test.ts": 303, + "src/main/runtime/multi-client-navigation-isolation.integration.test.ts": 551, + "src/main/runtime/opencode-finished-session-authority.test.ts": 356, + "src/main/runtime/orca-runtime-agent-session-operation.test.ts": 84, + "src/main/runtime/orca-runtime-agent-skill-share.test.ts": 224, + "src/main/runtime/orca-runtime-automations.test.ts": 35, + "src/main/runtime/orca-runtime-browser-client-hosted.test.ts": 778, + "src/main/runtime/orca-runtime-browser-ghost-session-row-close.test.ts": 416, + "src/main/runtime/orca-runtime-browser-headless.test.ts": 450, + "src/main/runtime/orca-runtime-browser-screencast-fanout.test.ts": 316, + "src/main/runtime/orca-runtime-browser.test.ts": 604, + "src/main/runtime/orca-runtime-create-base-prefetch.test.ts": 27, + "src/main/runtime/orca-runtime-emulator-folder-workspace.test.ts": 76, + "src/main/runtime/orca-runtime-exact-worker-provider-session.test.ts": 8, + "src/main/runtime/orca-runtime-files-mobile-explorer-reads.test.ts": 15, + "src/main/runtime/orca-runtime-files-preview-budget.test.ts": 104, + "src/main/runtime/orca-runtime-files-rename-authority.test.ts": 29, + "src/main/runtime/orca-runtime-files-search.test.ts": 25, + "src/main/runtime/orca-runtime-files-ssh-chunk-reads.test.ts": 68, + "src/main/runtime/orca-runtime-files-ssh-rearm.test.ts": 60, + "src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts": 62, + "src/main/runtime/orca-runtime-files-terminal-artifact-io.test.ts": 99, + "src/main/runtime/orca-runtime-files-terminal-link-host-translation.test.ts": 30, + "src/main/runtime/orca-runtime-files-terminal-path-resolution.test.ts": 22, + "src/main/runtime/orca-runtime-files-watch-host-scope.test.ts": 15, + "src/main/runtime/orca-runtime-files-watch.test.ts": 106, + "src/main/runtime/orca-runtime-git-branch-diff.test.ts": 9, + "src/main/runtime/orca-runtime-git-diff-budget.test.ts": 25, + "src/main/runtime/orca-runtime-git.test.ts": 31, + "src/main/runtime/orca-runtime-headless-hydration-repo-gate.test.ts": 32, + "src/main/runtime/orca-runtime-hook-agent-status-projection.test.ts": 297, + "src/main/runtime/orca-runtime-linked-issue-live-meta.integration.test.ts": 17, + "src/main/runtime/orca-runtime-mobile-agent-status-title-truthfulness.test.ts": 44, + "src/main/runtime/orca-runtime-mobile-close-preserved-resurrection.test.ts": 191, + "src/main/runtime/orca-runtime-module-size.test.ts": 20, + "src/main/runtime/orca-runtime-path-candidate-history.test.ts": 16, + "src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts": 27, + "src/main/runtime/orca-runtime-provider-reattach-launch-identity.test.ts": 21, + "src/main/runtime/orca-runtime-skill-recovery.test.ts": 20, + "src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts": 21, + "src/main/runtime/orca-runtime-structured-agent-session-launch-args.test.ts": 20, + "src/main/runtime/orca-runtime-structured-claude-account-gate.test.ts": 17, + "src/main/runtime/orca-runtime-structured-claude-gate-wiring.test.ts": 19, + "src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts": 13, + "src/main/runtime/orca-runtime-structured-session-restore.test.ts": 59, + "src/main/runtime/orca-runtime-structured-status-sink-wiring.test.ts": 26, + "src/main/runtime/orca-runtime-structured-tui-tab-binding.test.ts": 302, + "src/main/runtime/orca-runtime-tab-id-collision.test.ts": 18, + "src/main/runtime/orca-runtime-tail-wait-memo.test.ts": 89, + "src/main/runtime/orca-runtime-terminal-close-continuity.test.ts": 231, + "src/main/runtime/orca-runtime-terminal-create-idempotency.test.ts": 77, + "src/main/runtime/orca-runtime-terminal-cwd.test.ts": 62, + "src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts": 78, + "src/main/runtime/orca-runtime-terminal-retirement-host-partition.test.ts": 54, + "src/main/runtime/orca-runtime-terminal-retirement.test.ts": 255, + "src/main/runtime/orca-runtime-terminal-split-authority.test.ts": 384, + "src/main/runtime/orca-runtime-test-fragment-coverage.test.ts": 5, + "src/main/runtime/orca-runtime.test.ts": 12320, + "src/main/runtime/orchestration-codex-completion-title.test.ts": 664, + "src/main/runtime/orchestration-compatibility-authority.test.ts": 42, + "src/main/runtime/orchestration-dispatch-mailbox-delivery.test.ts": 190, + "src/main/runtime/orchestration-mailbox-cold-park-idle.test.ts": 119, + "src/main/runtime/orchestration-mailbox-crash-recovery.test.ts": 155, + "src/main/runtime/orchestration-mailbox-detached-routing.test.ts": 534, + "src/main/runtime/orchestration-mailbox-filtered-waiters.test.ts": 270, + "src/main/runtime/orchestration-mailbox-notification-consistency.test.ts": 1187, + "src/main/runtime/orchestration-mailbox-pointer-cli-command.test.ts": 107, + "src/main/runtime/orchestration-mailbox-pty-write-gate.test.ts": 162, + "src/main/runtime/orchestration-mailbox-routing-races.test.ts": 642, + "src/main/runtime/orchestration-mailbox-transport-settlement.test.ts": 224, + "src/main/runtime/orchestration-message-delivery-identity.test.ts": 244, + "src/main/runtime/orchestration-messages-fake-parity.test.ts": 55, + "src/main/runtime/orchestration-structured-chat-lease.test.ts": 8461, + "src/main/runtime/orchestration-worker-workspace-resolution.test.ts": 57, + "src/main/runtime/orchestration/adopted-structured-pointer-delivery.test.ts": 13, + "src/main/runtime/orchestration/cli-command.test.ts": 7, + "src/main/runtime/orchestration/coordinator-decision-gates.test.ts": 62, + "src/main/runtime/orchestration/coordinator-dispatch-unobserved-prompt.test.ts": 59, + "src/main/runtime/orchestration/coordinator-drift-probe-coalescing.test.ts": 186, + "src/main/runtime/orchestration/coordinator-escalation-triage.test.ts": 28, + "src/main/runtime/orchestration/coordinator-stale-base-flag.test.ts": 9, + "src/main/runtime/orchestration/coordinator.test.ts": 3333, + "src/main/runtime/orchestration/db-empty-dispatch-shortcircuit.benchmark.test.ts": 64, + "src/main/runtime/orchestration/db-heartbeat-straggler-guard.test.ts": 31, + "src/main/runtime/orchestration/db-message-timestamp.test.ts": 18, + "src/main/runtime/orchestration/db-messages.test.ts": 115, + "src/main/runtime/orchestration/db-stopping-worker-task-guard.test.ts": 97, + "src/main/runtime/orchestration/db-task-create-readiness.test.ts": 174, + "src/main/runtime/orchestration/db-task-dispatch-invariant.test.ts": 521, + "src/main/runtime/orchestration/db-task-dispatch-lifecycle-guards.test.ts": 716, + "src/main/runtime/orchestration/db-task-dispatch-races.test.ts": 172, + "src/main/runtime/orchestration/db-undelivered-mailboxes.test.ts": 44, + "src/main/runtime/orchestration/db.test.ts": 836, + "src/main/runtime/orchestration/db/attempt-outcome-projection.test.ts": 285, + "src/main/runtime/orchestration/db/decision-gate-lifecycle.test.ts": 46, + "src/main/runtime/orchestration/db/dispatch-context/dispatch-capability.test.ts": 51, + "src/main/runtime/orchestration/db/dispatch-depth.test.ts": 320, + "src/main/runtime/orchestration/db/dispatch-mailbox-consumer-fencing.test.ts": 107, + "src/main/runtime/orchestration/db/dispatch-row-writer-boundary.test.ts": 587, + "src/main/runtime/orchestration/db/federated-worker-report-outcome.test.ts": 8, + "src/main/runtime/orchestration/db/federation/federated-dispatch-observation-fence.test.ts": 28, + "src/main/runtime/orchestration/db/federation/remote-dispatch-attachment-release.test.ts": 75, + "src/main/runtime/orchestration/db/hot-path-statement-compilation.test.ts": 74, + "src/main/runtime/orchestration/db/legacy-question-identity.test.ts": 7, + "src/main/runtime/orchestration/db/lifecycle-rejection-marker.test.ts": 8, + "src/main/runtime/orchestration/db/lifecycle-transition-boundary.test.ts": 6, + "src/main/runtime/orchestration/db/lifecycle-transition.test.ts": 76, + "src/main/runtime/orchestration/db/pane-key-match.test.ts": 5, + "src/main/runtime/orchestration/db/row-column-lists.test.ts": 72, + "src/main/runtime/orchestration/db/run-list-cursor.test.ts": 5, + "src/main/runtime/orchestration/db/schema/federated-home-run-migration.test.ts": 128, + "src/main/runtime/orchestration/db/schema/structured-pointer-schema-migration.test.ts": 60, + "src/main/runtime/orchestration/db/writer-run-required.test.ts": 54, + "src/main/runtime/orchestration/dispatch-consumer-generation-migration.test.ts": 143, + "src/main/runtime/orchestration/dispatch-creator-identity-migration.test.ts": 416, + "src/main/runtime/orchestration/dispatch-failure-idempotency.test.ts": 48, + "src/main/runtime/orchestration/federation-ack-checkpoints.test.ts": 6, + "src/main/runtime/orchestration/federation-acknowledgment-integrity.test.ts": 74, + "src/main/runtime/orchestration/federation-acknowledgment-migration.test.ts": 67, + "src/main/runtime/orchestration/federation-lifecycle-settlement.test.ts": 4, + "src/main/runtime/orchestration/federation-sync.test.ts": 196, + "src/main/runtime/orchestration/federation-terminal-recovery.test.ts": 31, + "src/main/runtime/orchestration/formatter.test.ts": 10, + "src/main/runtime/orchestration/groups.test.ts": 8, + "src/main/runtime/orchestration/lifecycle-caller-edges.test.ts": 76, + "src/main/runtime/orchestration/lifecycle-reconciliation.test.ts": 237, + "src/main/runtime/orchestration/lightweight-run-worker-exit-escalation.test.ts": 182, + "src/main/runtime/orchestration/mailbox-pointer-eligibility.test.ts": 67, + "src/main/runtime/orchestration/mailbox-pointer-release-query.test.ts": 120, + "src/main/runtime/orchestration/mailbox-pointer-stage.test.ts": 129, + "src/main/runtime/orchestration/mailbox-pointer-submit.test.ts": 447, + "src/main/runtime/orchestration/message-batch-atomicity.test.ts": 107, + "src/main/runtime/orchestration/mutation-receipt-capacity.test.ts": 155, + "src/main/runtime/orchestration/nested-worker-depth-migration.test.ts": 233, + "src/main/runtime/orchestration/orchestration-adopted-run-binding.test.ts": 643, + "src/main/runtime/orchestration/orchestration-all-start-versions-migration.test.ts": 1759, + "src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts": 517, + "src/main/runtime/orchestration/orchestration-db-permissions.test.ts": 37, + "src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts": 408, + "src/main/runtime/orchestration/orchestration-federated-legacy-probe.test.ts": 309, + "src/main/runtime/orchestration/orchestration-legacy-coordinator-authority-db.test.ts": 371, + "src/main/runtime/orchestration/orchestration-legacy-question-migration-db.test.ts": 445, + "src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts": 1251, + "src/main/runtime/orchestration/orchestration-legacy-worker-terminal-recovery.test.ts": 6, + "src/main/runtime/orchestration/orchestration-mutation-question-db.test.ts": 75, + "src/main/runtime/orchestration/orchestration-peer-capability-cache.test.ts": 11, + "src/main/runtime/orchestration/orchestration-reset-db.test.ts": 84, + "src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts": 184, + "src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts": 32, + "src/main/runtime/orchestration/orchestration-version-skew-migration.test.ts": 539, + "src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts": 352, + "src/main/runtime/orchestration/preamble.test.ts": 75, + "src/main/runtime/orchestration/r1-identity-migration.test.ts": 109, + "src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts": 148, + "src/main/runtime/orchestration/settled-question-threads-migration.test.ts": 51, + "src/main/runtime/orchestration/setup-completion-signal.test.ts": 10, + "src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts": 44, + "src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts": 14, + "src/main/runtime/orchestration/structured-pointer-operation-id.test.ts": 13, + "src/main/runtime/orchestration/structured-session-pointer-delivery.test.ts": 14, + "src/main/runtime/orchestration/structured-worker-direct-mailbox-target.test.ts": 9, + "src/main/runtime/orchestration/structured-worker-group-addressing.test.ts": 17, + "src/main/runtime/orchestration/structured-worker-journal-archive.test.ts": 32, + "src/main/runtime/orchestration/task-deps-flag.test.ts": 15, + "src/main/runtime/orchestration/worker-attention-context.test.ts": 10, + "src/main/runtime/orchestration/worker-output-archive-bounding.test.ts": 58, + "src/main/runtime/orchestration/worker-output-archive.test.ts": 18, + "src/main/runtime/orchestration/worker-output-cursor.test.ts": 8, + "src/main/runtime/orchestration/worker-provider-session.test.ts": 7, + "src/main/runtime/orchestration/worker-start-unobserved-prompt-settlement.test.ts": 63, + "src/main/runtime/orchestration/worker-transcript-payload.test.ts": 15, + "src/main/runtime/orchestration/worker-transcript-read.test.ts": 58, + "src/main/runtime/orchestration/worker-transcript-remote-read.test.ts": 60, + "src/main/runtime/pairing-endpoint.test.ts": 24, + "src/main/runtime/pty-exit-agent-status-reconciliation.test.ts": 35, + "src/main/runtime/pty-exit-per-pty-map-reaper-ratchet.test.ts": 13, + "src/main/runtime/pty-inventory-liveness-verdict.test.ts": 58, + "src/main/runtime/pty-inventory-partial-relay-liveness.test.ts": 31, + "src/main/runtime/pty-shell-ownership-mirror.test.ts": 1507, + "src/main/runtime/pty-transcript-prune-wait-cache.test.ts": 24, + "src/main/runtime/pty-waiver-source-invariant.test.ts": 9, + "src/main/runtime/public-ssh-state.test.ts": 7, + "src/main/runtime/push/desktop-push-service-unreadable-outbox.test.ts": 32, + "src/main/runtime/push/desktop-push-service.test.ts": 79, + "src/main/runtime/push/push-agent-state.test.ts": 9, + "src/main/runtime/push/push-cleanup-auth-expiry.test.ts": 50, + "src/main/runtime/push/push-delivery-policy.test.ts": 9, + "src/main/runtime/push/push-device-registration-persistence.test.ts": 20, + "src/main/runtime/push/push-dispatcher.test.ts": 13, + "src/main/runtime/push/push-gateway-client.test.ts": 149, + "src/main/runtime/push/push-gateway-session.test.ts": 136, + "src/main/runtime/push/push-host-proof-vector.test.ts": 19, + "src/main/runtime/push/push-host-proof.test.ts": 100, + "src/main/runtime/push/push-outcome-counters.test.ts": 6, + "src/main/runtime/push/push-policy-pipeline.integration.test.ts": 38, + "src/main/runtime/push/push-preferences.test.ts": 10, + "src/main/runtime/push/push-registration-races.test.ts": 65, + "src/main/runtime/push/push-registration-rpc.test.ts": 45, + "src/main/runtime/push/push-unpair-persistence.test.ts": 37, + "src/main/runtime/push/push-unregister-outbox.test.ts": 12, + "src/main/runtime/quarter-circle-title-send-authorization.test.ts": 16463, + "src/main/runtime/recent-pty-output-buffer.test.ts": 63, + "src/main/runtime/relay/desktop-relay-service-broker-liveness.test.ts": 10, + "src/main/runtime/relay/desktop-relay-service.test.ts": 12, + "src/main/runtime/relay/mobile-relay-e2ee.integration.test.ts": 83, + "src/main/runtime/relay/relay-auth-coordinator-recovery.test.ts": 18, + "src/main/runtime/relay/relay-auth-coordinator.test.ts": 1169, + "src/main/runtime/relay/relay-auth-host-close-reason.test.ts": 67, + "src/main/runtime/relay/relay-control-client.test.ts": 287, + "src/main/runtime/relay/relay-control-close-reason.test.ts": 40, + "src/main/runtime/relay/relay-control-origin.test.ts": 22, + "src/main/runtime/relay/relay-control-request-retirement.test.ts": 23, + "src/main/runtime/relay/relay-demand-ledger.test.ts": 12, + "src/main/runtime/relay/relay-host-proof.test.ts": 40, + "src/main/runtime/relay/relay-http-client.test.ts": 86, + "src/main/runtime/relay/relay-region-correction.test.ts": 89, + "src/main/runtime/relay/relay-region-preference.test.ts": 90, + "src/main/runtime/relay/relay-region-probe-log.test.ts": 79, + "src/main/runtime/relay/relay-region-refresh.test.ts": 17, + "src/main/runtime/relay/relay-renewal-jitter.test.ts": 506, + "src/main/runtime/relay/relay-revoke-outbox.test.ts": 13, + "src/main/runtime/relay/relay-session-broker.test.ts": 405, + "src/main/runtime/remote-agent-session-host-authority.integration.test.ts": 138, + "src/main/runtime/remote-browser-screencast-frame-admission.test.ts": 47, + "src/main/runtime/remote-desktop-driver.test.ts": 99, + "src/main/runtime/remote-runtime-close-intent.integration.test.ts": 90, + "src/main/runtime/remote-runtime-request-connection.integration.test.ts": 260, + "src/main/runtime/remote-server-updater.test.ts": 9, + "src/main/runtime/renderer-browser-session-reconciliation.test.ts": 11, + "src/main/runtime/repo-icon-fork-backfill.test.ts": 16, + "src/main/runtime/repo-worktree-admin-fingerprint.test.ts": 835, + "src/main/runtime/repo-worktree-resolution-scan.test.ts": 4, + "src/main/runtime/repo-worktree-row-resolution.test.ts": 16, + "src/main/runtime/retained-tail-redraw-window.equivalence.test.ts": 329, + "src/main/runtime/rpc/core-typed-method-contract.test.ts": 9, + "src/main/runtime/rpc/dispatcher-browser-client-automation.test.ts": 23, + "src/main/runtime/rpc/dispatcher-computer-errors.test.ts": 20, + "src/main/runtime/rpc/dispatcher-feature-interactions.test.ts": 17, + "src/main/runtime/rpc/dispatcher-request-parsing.test.ts": 18, + "src/main/runtime/rpc/e2ee-channel-text-backpressure.test.ts": 55, + "src/main/runtime/rpc/e2ee-channel-v2.test.ts": 81, + "src/main/runtime/rpc/e2ee-channel.test.ts": 196, + "src/main/runtime/rpc/e2ee-crypto.test.ts": 41, + "src/main/runtime/rpc/e2ee-integration.test.ts": 57, + "src/main/runtime/rpc/errors.test.ts": 22, + "src/main/runtime/rpc/methods/accounts.test.ts": 21, + "src/main/runtime/rpc/methods/agent-hooks.test.ts": 12, + "src/main/runtime/rpc/methods/agent-session.test.ts": 38, + "src/main/runtime/rpc/methods/agent-skill-sharing-capability-grant.test.ts": 25, + "src/main/runtime/rpc/methods/ai-vault.test.ts": 156, + "src/main/runtime/rpc/methods/artifact-sharing-capability-grant.test.ts": 28, + "src/main/runtime/rpc/methods/artifacts.test.ts": 280, + "src/main/runtime/rpc/methods/automation-scoped-list-methods.test.ts": 31, + "src/main/runtime/rpc/methods/automations.test.ts": 45, + "src/main/runtime/rpc/methods/browser-client-host-attach-adoption.test.ts": 236, + "src/main/runtime/rpc/methods/browser-client-host-reconciliation.test.ts": 26, + "src/main/runtime/rpc/methods/browser-client-host.test.ts": 30, + "src/main/runtime/rpc/methods/browser-client-page-metadata.test.ts": 20, + "src/main/runtime/rpc/methods/browser-network-tunnel.test.ts": 233, + "src/main/runtime/rpc/methods/browser-tab-create-schema.test.ts": 12, + "src/main/runtime/rpc/methods/browser.test.ts": 58, + "src/main/runtime/rpc/methods/client-events.test.ts": 7, + "src/main/runtime/rpc/methods/client-native-chat-settings.test.ts": 22, + "src/main/runtime/rpc/methods/client-ui-pairing-local-fields.test.ts": 41, + "src/main/runtime/rpc/methods/client-ui-task-resume-state.test.ts": 26, + "src/main/runtime/rpc/methods/client-ui.test.ts": 96, + "src/main/runtime/rpc/methods/clipboard.test.ts": 44, + "src/main/runtime/rpc/methods/computer-actions.test.ts": 15, + "src/main/runtime/rpc/methods/computer.test.ts": 33, + "src/main/runtime/rpc/methods/diagnostics.test.ts": 8, + "src/main/runtime/rpc/methods/file-watch-event-batcher.test.ts": 26, + "src/main/runtime/rpc/methods/files-doc-preview.test.ts": 13, + "src/main/runtime/rpc/methods/files-list-all-page-size.test.ts": 17, + "src/main/runtime/rpc/methods/files-path-search.test.ts": 150, + "src/main/runtime/rpc/methods/files-preview-transport-budget.test.ts": 17, + "src/main/runtime/rpc/methods/files-terminal-path-resolution.test.ts": 20, + "src/main/runtime/rpc/methods/files-watch-cancellation.test.ts": 68, + "src/main/runtime/rpc/methods/files-watch-cleanup.test.ts": 64, + "src/main/runtime/rpc/methods/files.test.ts": 196, + "src/main/runtime/rpc/methods/git-diff-transport-budget.test.ts": 50, + "src/main/runtime/rpc/methods/git.test.ts": 72, + "src/main/runtime/rpc/methods/github-pr-refresh-reason.test.ts": 14, + "src/main/runtime/rpc/methods/github.test.ts": 65, + "src/main/runtime/rpc/methods/gitlab.test.ts": 58, + "src/main/runtime/rpc/methods/host-capabilities.test.ts": 10, + "src/main/runtime/rpc/methods/hosted-review.test.ts": 25, + "src/main/runtime/rpc/methods/jira.test.ts": 34, + "src/main/runtime/rpc/methods/linear-agent-access.test.ts": 77, + "src/main/runtime/rpc/methods/linear-agent-project-access.test.ts": 708, + "src/main/runtime/rpc/methods/linear.test.ts": 47, + "src/main/runtime/rpc/methods/native-chat-rpc-block-sanitize.test.ts": 6, + "src/main/runtime/rpc/methods/native-chat.test.ts": 29, + "src/main/runtime/rpc/methods/notification-preferences.test.ts": 6, + "src/main/runtime/rpc/methods/notification-reconnect-cooldown.test.ts": 10, + "src/main/runtime/rpc/methods/orchestration-dispatch-error-codes.test.ts": 105, + "src/main/runtime/rpc/methods/orchestration-structured-worker-abandon.test.ts": 69, + "src/main/runtime/rpc/methods/orchestration-structured-worker-lifecycle.test.ts": 14, + "src/main/runtime/rpc/methods/orchestration-structured-worker-redrive.test.ts": 88, + "src/main/runtime/rpc/methods/orchestration-structured-worker-session.test.ts": 16, + "src/main/runtime/rpc/methods/orchestration-structured-worker-start-failure.test.ts": 13, + "src/main/runtime/rpc/methods/orchestration-worker-mode-opacity.test.ts": 89, + "src/main/runtime/rpc/methods/orchestration-worker-start-mode-selection.test.ts": 188, + "src/main/runtime/rpc/methods/orchestration-worker-start-mode.test.ts": 9, + "src/main/runtime/rpc/methods/orchestration-worker-support-unknown.test.ts": 8, + "src/main/runtime/rpc/methods/orchestration/cli-runtime-boundary.test.ts": 96, + "src/main/runtime/rpc/methods/orchestration/federation/federated-attach-receipt.test.ts": 5, + "src/main/runtime/rpc/methods/orchestration/federation/federated-fleet-snapshot.test.ts": 30, + "src/main/runtime/rpc/methods/orchestration/federation/federated-message-targeting.test.ts": 68, + "src/main/runtime/rpc/methods/orchestration/federation/federated-release-safety.test.ts": 105, + "src/main/runtime/rpc/methods/orchestration/federation/federated-transport-safety.test.ts": 31, + "src/main/runtime/rpc/methods/orchestration/federation/federated-worker-start-receipt.test.ts": 50, + "src/main/runtime/rpc/methods/orchestration/federation/federation-agent-launch.test.ts": 60, + "src/main/runtime/rpc/methods/orchestration/federation/federation-control-mail.test.ts": 281, + "src/main/runtime/rpc/methods/orchestration/federation/federation-effects.test.ts": 4, + "src/main/runtime/rpc/methods/orchestration/federation/federation-folder-placement.test.ts": 35, + "src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts": 1435, + "src/main/runtime/rpc/methods/orchestration/federation/federation-liveness-verdict.test.ts": 279, + "src/main/runtime/rpc/methods/orchestration/federation/federation-output.test.ts": 755, + "src/main/runtime/rpc/methods/orchestration/federation/federation-setup.test.ts": 129, + "src/main/runtime/rpc/methods/orchestration/federation/federation-start-prompt-budget.test.ts": 31, + "src/main/runtime/rpc/methods/orchestration/federation/federation-start-schema.test.ts": 11, + "src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts": 1549, + "src/main/runtime/rpc/methods/orchestration/gates/gate-run-authorization.test.ts": 676, + "src/main/runtime/rpc/methods/orchestration/gates/gates.test.ts": 164, + "src/main/runtime/rpc/methods/orchestration/messaging/ask.test.ts": 430, + "src/main/runtime/rpc/methods/orchestration/messaging/check-superseded-terminal.test.ts": 136, + "src/main/runtime/rpc/methods/orchestration/messaging/check-worker-consumer-fencing.test.ts": 242, + "src/main/runtime/rpc/methods/orchestration/messaging/check-worker-federated-attachment.test.ts": 453, + "src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts": 473, + "src/main/runtime/rpc/methods/orchestration/messaging/recipient-routing.test.ts": 238, + "src/main/runtime/rpc/methods/orchestration/messaging/send-dispatch-authority.test.ts": 138, + "src/main/runtime/rpc/methods/orchestration/messaging/send-group.test.ts": 387, + "src/main/runtime/rpc/methods/orchestration/messaging/send-invalid-type.test.ts": 55, + "src/main/runtime/rpc/methods/orchestration/messaging/send-receipt-plumbing.test.ts": 64, + "src/main/runtime/rpc/methods/orchestration/messaging/send-unbound-terminals.test.ts": 49, + "src/main/runtime/rpc/methods/orchestration/messaging/send.test.ts": 570, + "src/main/runtime/rpc/methods/orchestration/messaging/settled-dispatch-mail.test.ts": 100, + "src/main/runtime/rpc/methods/orchestration/runs/migration-behavior.test.ts": 156, + "src/main/runtime/rpc/methods/orchestration/runs/run-receipt.test.ts": 7, + "src/main/runtime/rpc/methods/orchestration/runs/runs.test.ts": 329, + "src/main/runtime/rpc/methods/orchestration/runs/tasks-dispatch.test.ts": 394, + "src/main/runtime/rpc/methods/orchestration/worker/agent-status-producer-census.test.ts": 1032, + "src/main/runtime/rpc/methods/orchestration/worker/composed-workers.test.ts": 364, + "src/main/runtime/rpc/methods/orchestration/worker/context-only-dispatch-retry.test.ts": 84, + "src/main/runtime/rpc/methods/orchestration/worker/fleet-status-observed-identity.test.ts": 15, + "src/main/runtime/rpc/methods/orchestration/worker/fleet-status-terminal-identity.test.ts": 9, + "src/main/runtime/rpc/methods/orchestration/worker/legacy-dispatch-projection.test.ts": 154, + "src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-observation.test.ts": 133, + "src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-release.test.ts": 105, + "src/main/runtime/rpc/methods/orchestration/worker/self-dispatch-nesting-depth.test.ts": 55, + "src/main/runtime/rpc/methods/orchestration/worker/structured-worker-launch-seed-options.test.ts": 10, + "src/main/runtime/rpc/methods/orchestration/worker/worker-interactive-wait.test.ts": 111, + "src/main/runtime/rpc/methods/orchestration/worker/worker-launch-preferences.test.ts": 32, + "src/main/runtime/rpc/methods/orchestration/worker/worker-list-pagination.test.ts": 215, + "src/main/runtime/rpc/methods/orchestration/worker/worker-list-run-scope-rpc.test.ts": 81, + "src/main/runtime/rpc/methods/orchestration/worker/worker-observation.test.ts": 16, + "src/main/runtime/rpc/methods/orchestration/worker/worker-output.test.ts": 36, + "src/main/runtime/rpc/methods/orchestration/worker/worker-read-projection.test.ts": 60, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-archive.test.ts": 213, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-inventory.test.ts": 285, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-liveness-verdict.test.ts": 17, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-mobile-report.test.ts": 283, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-ownership-guard.test.ts": 151, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release-recovery.test.ts": 390, + "src/main/runtime/rpc/methods/orchestration/worker/worker-release.test.ts": 651, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-budgets.test.ts": 5, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-outcome-classification.test.ts": 8, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-prompt-budget.test.ts": 70, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-prompt-contract.test.ts": 355, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-terminal-target.test.ts": 106, + "src/main/runtime/rpc/methods/orchestration/worker/worker-start-turn-observation.test.ts": 10, + "src/main/runtime/rpc/methods/orchestration/worker/worker-stop-capability.test.ts": 14, + "src/main/runtime/rpc/methods/orchestration/worker/worker-stop-exit-race.test.ts": 109, + "src/main/runtime/rpc/methods/orchestration/worker/worker-stop-liveness-verdict.test.ts": 136, + "src/main/runtime/rpc/methods/orchestration/worker/worker-terminal-custody-at-creation.test.ts": 505, + "src/main/runtime/rpc/methods/orchestration/worker/workers-new-worktree.test.ts": 735, + "src/main/runtime/rpc/methods/orchestration/worker/workers-recovery.test.ts": 238, + "src/main/runtime/rpc/methods/paired-caller-host-id.test.ts": 8, + "src/main/runtime/rpc/methods/pairing.test.ts": 29, + "src/main/runtime/rpc/methods/plugins.test.ts": 13, + "src/main/runtime/rpc/methods/preflight.test.ts": 14, + "src/main/runtime/rpc/methods/project-host-setup-self-host-stamp.test.ts": 16, + "src/main/runtime/rpc/methods/repo-badge-color.test.ts": 14, + "src/main/runtime/rpc/methods/repo.test.ts": 48, + "src/main/runtime/rpc/methods/runtime-client-capabilities.test.ts": 14, + "src/main/runtime/rpc/methods/session-tab-agent-capability-mutations.test.ts": 25, + "src/main/runtime/rpc/methods/session-tab-agent-status-projection.test.ts": 14, + "src/main/runtime/rpc/methods/session-tab-browser-placement-mutations.test.ts": 18, + "src/main/runtime/rpc/methods/session-tab-browser-placement-projection.test.ts": 14, + "src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts": 239, + "src/main/runtime/rpc/methods/session-tabs-inventory-rpc.test.ts": 41, + "src/main/runtime/rpc/methods/session-tabs-move-validation.test.ts": 41, + "src/main/runtime/rpc/methods/session-tabs-retirement-proof-delta.test.ts": 20, + "src/main/runtime/rpc/methods/session-tabs-schemas.test.ts": 18, + "src/main/runtime/rpc/methods/session-tabs-structured-restore.test.ts": 13, + "src/main/runtime/rpc/methods/session-tabs-unsubscribe.test.ts": 16, + "src/main/runtime/rpc/methods/session-tabs.test.ts": 37, + "src/main/runtime/rpc/methods/skills.test.ts": 26, + "src/main/runtime/rpc/methods/speech.test.ts": 20, + "src/main/runtime/rpc/methods/ssh.test.ts": 17, + "src/main/runtime/rpc/methods/structured-agent-session-admission.test.ts": 47, + "src/main/runtime/rpc/methods/structured-agent-session-adoption-replay.test.ts": 87, + "src/main/runtime/rpc/methods/structured-agent-session-background-task-capability.test.ts": 24, + "src/main/runtime/rpc/methods/structured-agent-session-hold.test.ts": 954, + "src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts": 6, + "src/main/runtime/rpc/methods/structured-agent-session-precommit-refusal.test.ts": 23, + "src/main/runtime/rpc/methods/structured-agent-session-turn-item-capability.test.ts": 20, + "src/main/runtime/rpc/methods/structured-agent-session.test.ts": 127, + "src/main/runtime/rpc/methods/structured-worker-read-cursor.test.ts": 10, + "src/main/runtime/rpc/methods/structured-worker-stop-receipt.test.ts": 56, + "src/main/runtime/rpc/methods/structured-worker-tab-retirement.test.ts": 37, + "src/main/runtime/rpc/methods/terminal-create-idempotency.test.ts": 14, + "src/main/runtime/rpc/methods/terminal-legacy-stream-id-characterization.test.ts": 165, + "src/main/runtime/rpc/methods/terminal-manifest-characterization.test.ts": 40, + "src/main/runtime/rpc/methods/terminal-navigation.test.ts": 15, + "src/main/runtime/rpc/methods/terminal-read-screen-cursor.test.ts": 10, + "src/main/runtime/rpc/methods/terminal-stream-extraction-characterization.test.ts": 20, + "src/main/runtime/rpc/methods/terminal/terminal-inspect-process-params.test.ts": 7, + "src/main/runtime/rpc/methods/updater.test.ts": 7, + "src/main/runtime/rpc/methods/workspace-cleanup-ui-schema.test.ts": 17, + "src/main/runtime/rpc/methods/workspace-ports.test.ts": 19, + "src/main/runtime/rpc/methods/worktree-catalog-snapshot-method.test.ts": 17, + "src/main/runtime/rpc/methods/worktree-create-args.test.ts": 14, + "src/main/runtime/rpc/methods/worktree-create-navigation.test.ts": 26, + "src/main/runtime/rpc/methods/worktree-github-pr-suppression.test.ts": 20, + "src/main/runtime/rpc/methods/worktree-missing-terminal-teardown.test.ts": 13, + "src/main/runtime/rpc/methods/worktree-retired-names.test.ts": 12, + "src/main/runtime/rpc/methods/worktree-rm-host-qualification.test.ts": 26, + "src/main/runtime/rpc/methods/worktree-rm-pty-waiver.test.ts": 10, + "src/main/runtime/rpc/methods/worktree-schemas.test.ts": 16, + "src/main/runtime/rpc/methods/worktree.test.ts": 70, + "src/main/runtime/rpc/mobile-auth-acl-critical-path.test.ts": 419, + "src/main/runtime/rpc/mobile-clipboard-image-provenance.test.ts": 25, + "src/main/runtime/rpc/mobile-e2ee-outbound-memory-budget.test.ts": 7, + "src/main/runtime/rpc/mobile-e2ee-v2-desktop-session.test.ts": 33, + "src/main/runtime/rpc/mobile-e2ee-v2-key-schedule.test.ts": 46, + "src/main/runtime/rpc/mobile-socket-wiring.test.ts": 117, + "src/main/runtime/rpc/orchestration-11745-regression-verification.test.ts": 1475, + "src/main/runtime/rpc/orchestration-commit-notify-characterization.test.ts": 326, + "src/main/runtime/rpc/orchestration-contract-fence.test.ts": 121, + "src/main/runtime/rpc/orchestration-current-authority-precedence.test.ts": 657, + "src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher.test.ts": 3011, + "src/main/runtime/rpc/orchestration-legacy-coordinator-race.test.ts": 963, + "src/main/runtime/rpc/orchestration-legacy-fence-jurisdiction.test.ts": 3247, + "src/main/runtime/rpc/orchestration-legacy-question-takeover.test.ts": 293, + "src/main/runtime/rpc/orchestration-legacy-run-routing.test.ts": 363, + "src/main/runtime/rpc/orchestration-legacy-takeover-current-authority.test.ts": 152, + "src/main/runtime/rpc/orchestration-legacy-takeover-delivery.test.ts": 199, + "src/main/runtime/rpc/orchestration-legacy-takeover-dispatcher.test.ts": 498, + "src/main/runtime/rpc/orchestration-mutation-executor.test.ts": 186, + "src/main/runtime/rpc/orchestration-mutation-ledger.test.ts": 304, + "src/main/runtime/rpc/orchestration-mutation-request-show-legacy.test.ts": 94, + "src/main/runtime/rpc/orchestration-mutation-request-show.test.ts": 158, + "src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts": 293, + "src/main/runtime/rpc/orchestration-task-dispatch-invariant.test.ts": 332, + "src/main/runtime/rpc/relay-transport.test.ts": 146, + "src/main/runtime/rpc/remote-runtime-server-heartbeat-missed-probe-tolerance.test.ts": 23, + "src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts": 9, + "src/main/runtime/rpc/runtime-client-capabilities.test.ts": 6, + "src/main/runtime/rpc/runtime-close-attribution-topology.test.ts": 33, + "src/main/runtime/rpc/runtime-close-attribution.test.ts": 19, + "src/main/runtime/rpc/schemas.test.ts": 38, + "src/main/runtime/rpc/streaming.test.ts": 16, + "src/main/runtime/rpc/terminal-agent-prompt-send.test.ts": 20, + "src/main/runtime/rpc/terminal-agent-send-guard.test.ts": 39, + "src/main/runtime/rpc/terminal-geometry-stale-handle.test.ts": 22, + "src/main/runtime/rpc/terminal-lease-stale-handle-survival.test.ts": 168, + "src/main/runtime/rpc/terminal-list-host-scope-transport.test.ts": 11, + "src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts": 563, + "src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts": 1720, + "src/main/runtime/rpc/terminal-multiplex-desktop-resize-routing.test.ts": 695, + "src/main/runtime/rpc/terminal-multiplex-end-verdict.test.ts": 328, + "src/main/runtime/rpc/terminal-multiplex-escape-tail.test.ts": 27, + "src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts": 22, + "src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts": 294, + "src/main/runtime/rpc/terminal-multiplex-input-write-rejection.test.ts": 809, + "src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts": 455, + "src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts": 446, + "src/main/runtime/rpc/terminal-multiplex-resync-replay-trim.test.ts": 16, + "src/main/runtime/rpc/terminal-multiplex-round-robin.test.ts": 6, + "src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts": 644, + "src/main/runtime/rpc/terminal-multiplex-source-range-admission.test.ts": 799, + "src/main/runtime/rpc/terminal-multiplex-subscribe-slot-recovery.test.ts": 177, + "src/main/runtime/rpc/terminal-opencode-send-guard.integration.test.ts": 42, + "src/main/runtime/rpc/terminal-output-batching.test.ts": 366, + "src/main/runtime/rpc/terminal-output-frame-chunks-equivalence.test.ts": 14856, + "src/main/runtime/rpc/terminal-output-frame-source-ranges.test.ts": 11, + "src/main/runtime/rpc/terminal-prompt-delivery-receipt.test.ts": 318, + "src/main/runtime/rpc/terminal-provider-snapshot-sequence.test.ts": 220, + "src/main/runtime/rpc/terminal-requested-snapshot-unavailability.test.ts": 531, + "src/main/runtime/rpc/terminal-send-agent-session-lease.test.ts": 27, + "src/main/runtime/rpc/terminal-send-launch-draft-resolution.test.ts": 18, + "src/main/runtime/rpc/terminal-send.test.ts": 284, + "src/main/runtime/rpc/terminal-source-range-ledger.test.ts": 11, + "src/main/runtime/rpc/terminal-stream-byte-length.test.ts": 5718, + "src/main/runtime/rpc/terminal-subscribe-blank-mount.test.ts": 66, + "src/main/runtime/rpc/terminal-subscribe-buffer.test.ts": 605, + "src/main/runtime/rpc/terminal-subscribe-lease-only.test.ts": 66, + "src/main/runtime/rpc/terminal-subscribe-mount-replay.test.ts": 226, + "src/main/runtime/rpc/terminal-subscribe-ownership.test.ts": 226, + "src/main/runtime/rpc/terminal-subscribe-reconnect-rebind.test.ts": 139, + "src/main/runtime/rpc/terminal-subscribe-relay-drop-lease-survival.test.ts": 178, + "src/main/runtime/rpc/terminal-subscribe-renderer-recovery-output.test.ts": 66, + "src/main/runtime/rpc/unix-socket-transport.test.ts": 8, + "src/main/runtime/rpc/unpaired-device-auth-throttle.test.ts": 9, + "src/main/runtime/rpc/worktree-catalog-snapshot.test.ts": 8, + "src/main/runtime/rpc/ws-fallback-port-store.test.ts": 4, + "src/main/runtime/rpc/ws-transport-accept-order.test.ts": 17, + "src/main/runtime/rpc/ws-transport-static-web.test.ts": 6055, + "src/main/runtime/rpc/ws-transport-transient-packet-loss.test.ts": 635, + "src/main/runtime/rpc/ws-transport.test.ts": 976, + "src/main/runtime/runtime-binary-message-router.test.ts": 4, + "src/main/runtime/runtime-browser-client-automation.test.ts": 19, + "src/main/runtime/runtime-browser-client-page-adoption.test.ts": 32, + "src/main/runtime/runtime-browser-client-page-creation.test.ts": 20, + "src/main/runtime/runtime-browser-client-page-recovery.test.ts": 28, + "src/main/runtime/runtime-browser-client-page-restored-recovery.test.ts": 14, + "src/main/runtime/runtime-browser-network-execution-host.test.ts": 7, + "src/main/runtime/runtime-browser-page-registry.test.ts": 13, + "src/main/runtime/runtime-client-settings-minimax-projection.test.ts": 7, + "src/main/runtime/runtime-extraction-regressions.test.ts": 20, + "src/main/runtime/runtime-file-target-connection-field-ratchet.test.ts": 11, + "src/main/runtime/runtime-file-target-execution-host.test.ts": 33, + "src/main/runtime/runtime-folder-workspace.test.ts": 10, + "src/main/runtime/runtime-git-api-contract.test.ts": 7, + "src/main/runtime/runtime-git-branch-compare-admission.test.ts": 7, + "src/main/runtime/runtime-git-command-target.test.ts": 8, + "src/main/runtime/runtime-git-conflict-operation-routing.test.ts": 11, + "src/main/runtime/runtime-git-execution-host-ownership.test.ts": 10, + "src/main/runtime/runtime-git-generation-admission.test.ts": 11, + "src/main/runtime/runtime-git-status-admission.test.ts": 14, + "src/main/runtime/runtime-git-sync-commands.test.ts": 10, + "src/main/runtime/runtime-git-target-execution-host.test.ts": 33, + "src/main/runtime/runtime-graph-reload-lifecycle.test.ts": 10, + "src/main/runtime/runtime-hook-agent-row-selection.test.ts": 11, + "src/main/runtime/runtime-linear-read-commands.test.ts": 9, + "src/main/runtime/runtime-local-worktree-materialization.test.ts": 7, + "src/main/runtime/runtime-local-worktree-terminal-startup.test.ts": 6, + "src/main/runtime/runtime-managed-worktree-metadata-sweep.test.ts": 29, + "src/main/runtime/runtime-managed-worktree-metadata.test.ts": 7, + "src/main/runtime/runtime-managed-worktree-queries.test.ts": 22, + "src/main/runtime/runtime-metadata-ownership-watch.test.ts": 32, + "src/main/runtime/runtime-metadata.test.ts": 38, + "src/main/runtime/runtime-mobile-agent-status-builder.test.ts": 5, + "src/main/runtime/runtime-mobile-file-path-search.test.ts": 15, + "src/main/runtime/runtime-owned-terminal-publication-lineage.test.ts": 28, + "src/main/runtime/runtime-project-group-controller-folder-delete.test.ts": 7, + "src/main/runtime/runtime-project-host-setup-controller.test.ts": 16, + "src/main/runtime/runtime-remote-fetch-ref-maintenance.test.ts": 63, + "src/main/runtime/runtime-remove-project-host-scope.test.ts": 23, + "src/main/runtime/runtime-resolved-worktree-cache.test.ts": 6, + "src/main/runtime/runtime-rpc-browser-host-admission.test.ts": 31, + "src/main/runtime/runtime-rpc-device-revocation.test.ts": 99, + "src/main/runtime/runtime-rpc-long-poll-transport.test.ts": 1931, + "src/main/runtime/runtime-rpc-metadata-lifecycle.test.ts": 29, + "src/main/runtime/runtime-rpc-mobile-method-allowlist.test.ts": 53, + "src/main/runtime/runtime-rpc-mobile-native-chat-settings.test.ts": 5, + "src/main/runtime/runtime-rpc-mobile-terminal-streaming.test.ts": 575, + "src/main/runtime/runtime-rpc-orchestration-db-migration.test.ts": 56, + "src/main/runtime/runtime-rpc-pairing-mode-persistence.test.ts": 139, + "src/main/runtime/runtime-rpc-pairing-offer.test.ts": 71, + "src/main/runtime/runtime-rpc-relay-pairing.test.ts": 431, + "src/main/runtime/runtime-rpc-request-authorization.test.ts": 83, + "src/main/runtime/runtime-rpc-startup-failure.test.ts": 19, + "src/main/runtime/runtime-rpc-terminal-list.test.ts": 641, + "src/main/runtime/runtime-rpc-websocket-bind-host.test.ts": 104, + "src/main/runtime/runtime-rpc-websocket-long-poll-caps.test.ts": 67, + "src/main/runtime/runtime-rpc-worktree-queries.test.ts": 73, + "src/main/runtime/runtime-search-line-fragments.test.ts": 9, + "src/main/runtime/runtime-skill-install-authority.test.ts": 4, + "src/main/runtime/runtime-skill-install-commands.test.ts": 8, + "src/main/runtime/runtime-skill-install-queries.test.ts": 15, + "src/main/runtime/runtime-socket-sweep.test.ts": 8, + "src/main/runtime/runtime-terminal-idle-polls.test.ts": 14, + "src/main/runtime/runtime-terminal-orphan-topology-validation.test.ts": 17, + "src/main/runtime/runtime-terminal-spawn-push-target-materialization.test.ts": 9, + "src/main/runtime/runtime-worktree-agent-rows-structured.test.ts": 13, + "src/main/runtime/runtime-worktree-agent-sources.test.ts": 8, + "src/main/runtime/runtime-worktree-agent-startup.test.ts": 11, + "src/main/runtime/runtime-worktree-ps-summaries.test.ts": 7, + "src/main/runtime/runtime-worktree-selection.test.ts": 8, + "src/main/runtime/runtime-worktree-structured-agent-rows-liveness.test.ts": 87, + "src/main/runtime/saved-structured-agent-session-restoration.test.ts": 5, + "src/main/runtime/selected-review-branch.test.ts": 11, + "src/main/runtime/session-tabs-inventory-publication.test.ts": 47, + "src/main/runtime/settled-worker-process-replacement.test.ts": 96, + "src/main/runtime/structured-agent-session-close.test.ts": 10, + "src/main/runtime/structured-agent-session-integration-replay.test.ts": 400, + "src/main/runtime/structured-agent-session-integration.test.ts": 816, + "src/main/runtime/structured-agent-session-pty-binding.test.ts": 16, + "src/main/runtime/structured-agent-session-rollback-compatibility.test.ts": 39, + "src/main/runtime/structured-agent-session-runtime-exit.test.ts": 348, + "src/main/runtime/structured-agent-session-runtime.test.ts": 33, + "src/main/runtime/structured-agent-session-support-probe.test.ts": 25, + "src/main/runtime/structured-claude-auth-policy-wiring.test.ts": 8, + "src/main/runtime/structured-conversation-tab-replacement.test.ts": 6, + "src/main/runtime/structured-session-worktree-teardown.test.ts": 2436, + "src/main/runtime/structured-tui-exit-proof.test.ts": 14, + "src/main/runtime/structured-tui-idle-evidence.test.ts": 5, + "src/main/runtime/structured-tui-process-identity.test.ts": 26, + "src/main/runtime/structured-tui-recovery-claim-match.test.ts": 13, + "src/main/runtime/structured-worker-agent-presence.test.ts": 6, + "src/main/runtime/structured-worker-authority.test.ts": 7, + "src/main/runtime/structured-worker-child-identity-env.test.ts": 6, + "src/main/runtime/structured-worker-hook-attestation.test.ts": 7, + "src/main/runtime/structured-worker-identity.test.ts": 17, + "src/main/runtime/structured-worker-mail-routing.test.ts": 9, + "src/main/runtime/structured-worker-takeover-pane-key.test.ts": 6, + "src/main/runtime/structured-worker-terminal-read.test.ts": 14, + "src/main/runtime/structured-worker-terminal-refusal.test.ts": 8, + "src/main/runtime/terminal-ansi-pending-retention.test.ts": 464, + "src/main/runtime/terminal-focus-navigation-coalescer.test.ts": 21, + "src/main/runtime/terminal-identity-probe.test.ts": 8, + "src/main/runtime/terminal-interactive-wait-visibility.test.ts": 6198, + "src/main/runtime/terminal-leaf-tab-resolution.test.ts": 6, + "src/main/runtime/terminal-list-execution-host-scope.test.ts": 3128, + "src/main/runtime/terminal-list-payload-size.test.ts": 172, + "src/main/runtime/terminal-list-stale-leaf-liveness.test.ts": 42, + "src/main/runtime/terminal-mobile-subscribe-tab-mount.test.ts": 26, + "src/main/runtime/terminal-model-query-authority.test.ts": 9, + "src/main/runtime/terminal-orphan-owner.test.ts": 7, + "src/main/runtime/terminal-orphan-topology.test.ts": 12, + "src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts": 46, + "src/main/runtime/terminal-projection.test.ts": 6, + "src/main/runtime/terminal-pty-exit-waiter.test.ts": 28, + "src/main/runtime/terminal-query-responder.test.ts": 178, + "src/main/runtime/terminal-restore-record-seed.test.ts": 69, + "src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts": 23, + "src/main/runtime/terminal-retirement-proof-publication.test.ts": 77, + "src/main/runtime/terminal-send-stale-leaf-liveness.test.ts": 180, + "src/main/runtime/terminal-subscribe-exit-waiter-leak.test.ts": 24, + "src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts": 870, + "src/main/runtime/terminal-tail-buffer.test.ts": 5851, + "src/main/runtime/terminal-tail-row-retention.test.ts": 167, + "src/main/runtime/terminal-tail-sentinel-index.test.ts": 4042, + "src/main/runtime/terminal-tail-whitespace.test.ts": 11, + "src/main/runtime/terminal-vertical-control-scan.test.ts": 8, + "src/main/runtime/terminal-wait-detection.test.ts": 20, + "src/main/runtime/terminal-wait-results.test.ts": 6, + "src/main/runtime/wait-blocked-check-state.test.ts": 112, + "src/main/runtime/wait-blocked-keyword-carry-retention.test.ts": 12, + "src/main/runtime/windows-default-route-interfaces.test.ts": 9, + "src/main/runtime/windows-drive-listing.test.ts": 12, + "src/main/runtime/windows-firewall-remote-scope.test.ts": 19, + "src/main/runtime/windows-mobile-firewall.test.ts": 14, + "src/main/runtime/workspace-session-membership-scaling.test.ts": 13, + "src/main/runtime/worktree-launch-host-repo.test.ts": 10, + "src/main/runtime/worktree-list-host-scope.test.ts": 32, + "src/main/runtime/worktree-path-selector-wsl-posix.test.ts": 46, + "src/main/runtime/worktree-ps-degraded-repo-scan.test.ts": 94, + "src/main/runtime/worktree-ps-host-scope.test.ts": 40, + "src/main/runtime/worktree-rm-id-selector-path-spelling.test.ts": 47, + "src/main/runtime/worktree-scan-admin-fingerprint-gate.test.ts": 531, + "src/main/runtime/worktree-scan-execution-host-routing.test.ts": 33, + "src/main/runtime/worktree-teardown-unstopped-pty.test.ts": 204, + "src/main/runtime/worktree-teardown.test.ts": 235, + "src/main/runtime/worktree-terminal-mutation-lock.test.ts": 13, + "src/main/serve-update-handoff.app-environment.test.ts": 907, + "src/main/serve-update-handoff.test.ts": 8, + "src/main/server/serve-readiness.test.ts": 10, + "src/main/server/serve-stdout-boundary.test.ts": 8, + "src/main/shell-prompt-readiness-probe.test.ts": 28, + "src/main/shell-startup-identity-scanner.test.ts": 6, + "src/main/shell-startup-output-scanner.test.ts": 24, + "src/main/shell-wrapper-content-address.test.ts": 9, + "src/main/shell-wrapper-generated-file-snapshot.test.ts": 40, + "src/main/skills/agent-skill-selection.test.ts": 22, + "src/main/skills/claude-plugin-skill-sources-wsl.test.ts": 6, + "src/main/skills/claude-plugin-skill-sources.test.ts": 7, + "src/main/skills/discovery.test.ts": 108, + "src/main/skills/skill-bundle-artifacts.test.ts": 39, + "src/main/skills/skill-bundle-creation.test.ts": 628, + "src/main/skills/skill-bundle-install-service.test.ts": 196, + "src/main/skills/skill-bundle-observability-summary.test.ts": 4, + "src/main/skills/skill-bundle-ssh-relay-service.test.ts": 105, + "src/main/skills/skill-candidate-concurrency.test.ts": 29, + "src/main/skills/skill-client-mediated-transfer-cancellation.test.ts": 20, + "src/main/skills/skill-client-mediated-transfer.test.ts": 29, + "src/main/skills/skill-cloud-direct-upload.test.ts": 125, + "src/main/skills/skill-cloud-grant-installation.test.ts": 15, + "src/main/skills/skill-cloud-install-target.test.ts": 6, + "src/main/skills/skill-cloud-request.test.ts": 58, + "src/main/skills/skill-cloud-service.test.ts": 73, + "src/main/skills/skill-delete/plan.test.ts": 102, + "src/main/skills/skill-delete/recovery.test.ts": 86, + "src/main/skills/skill-delete/service.test.ts": 618, + "src/main/skills/skill-delete/staging-visibility.test.ts": 21, + "src/main/skills/skill-delete/wsl-enumeration-protocol.test.ts": 12, + "src/main/skills/skill-discovery-concurrency.test.ts": 88, + "src/main/skills/skill-discovery-order.test.ts": 286, + "src/main/skills/skill-discovery-target.test.ts": 21, + "src/main/skills/skill-discovery-wsl-plugins.test.ts": 22, + "src/main/skills/skill-discovery-wsl-script-roundtrip.test.ts": 49, + "src/main/skills/skill-discovery-wsl.test.ts": 40, + "src/main/skills/skill-freshness-eligibility.test.ts": 10, + "src/main/skills/skill-freshness-inventory-limits.test.ts": 6, + "src/main/skills/skill-freshness-inventory.test.ts": 2182, + "src/main/skills/skill-git-tree-identity.test.ts": 40, + "src/main/skills/skill-install-destinations.test.ts": 26, + "src/main/skills/skill-install-discovery-verification.test.ts": 17, + "src/main/skills/skill-install-lock-release.test.ts": 7, + "src/main/skills/skill-install-lock.test.ts": 241, + "src/main/skills/skill-install-management-service.test.ts": 39, + "src/main/skills/skill-install-provenance.test.ts": 19, + "src/main/skills/skill-install-recovery.test.ts": 304, + "src/main/skills/skill-install-request-service.test.ts": 299, + "src/main/skills/skill-install-service.test.ts": 944, + "src/main/skills/skill-install-transaction.test.ts": 949, + "src/main/skills/skill-operation-observability.test.ts": 92, + "src/main/skills/skill-package-creation.test.ts": 312, + "src/main/skills/skill-package-deterministic-gzip.test.ts": 527, + "src/main/skills/skill-package-download.test.ts": 107, + "src/main/skills/skill-package-identity.test.ts": 86, + "src/main/skills/skill-package-tar.test.ts": 259, + "src/main/skills/skill-placement-alias-repair.test.ts": 43, + "src/main/skills/skill-placement-copy-drift.test.ts": 38, + "src/main/skills/skill-placement-reconciliation.test.ts": 74, + "src/main/skills/skill-placement-transaction.test.ts": 97, + "src/main/skills/skill-plugin-cache-scan.test.ts": 599, + "src/main/skills/skill-provider-destinations.test.ts": 8, + "src/main/skills/skill-provider-runtime-roots.test.ts": 9, + "src/main/skills/skill-remote-error-category-parity.test.ts": 15, + "src/main/skills/skill-remote-install-cancellation.test.ts": 4, + "src/main/skills/skill-remote-install-service.test.ts": 32, + "src/main/skills/skill-remove-transaction.test.ts": 577, + "src/main/skills/skill-root-file-walk.test.ts": 27, + "src/main/skills/skill-runtime-capability.test.ts": 9, + "src/main/skills/skill-scan-coalescer.test.ts": 12, + "src/main/skills/skill-share-preparation-service.test.ts": 237, + "src/main/skills/skill-ssh-relay-service.test.ts": 61, + "src/main/skills/skill-transaction-startup-recovery.test.ts": 206, + "src/main/skills/skill-update-convergence.test.ts": 32, + "src/main/skills/skill-update-outcome.test.ts": 49, + "src/main/skills/skill-update-registration.test.ts": 14, + "src/main/skills/skill-update-run.test.ts": 28, + "src/main/skills/skill-upload-session-admission-regression.test.ts": 24, + "src/main/skills/skill-upload-session-service.test.ts": 157, + "src/main/skills/skill-wsl-install-filesystem.test.ts": 9, + "src/main/skills/skill-wsl-provider-detection.test.ts": 10, + "src/main/source-control/forge-provider.test.ts": 52, + "src/main/source-control/hosted-review-azure-devops.integration.test.ts": 153, + "src/main/source-control/hosted-review-base-ref-suffix.test.ts": 12, + "src/main/source-control/hosted-review-bitbucket.integration.test.ts": 368, + "src/main/source-control/hosted-review-branch-cache.test.ts": 186, + "src/main/source-control/hosted-review-creation-eligibility.test.ts": 26, + "src/main/source-control/hosted-review-creation-gitlab-self-hosted.test.ts": 14, + "src/main/source-control/hosted-review-creation-shared-symlinks.test.ts": 17, + "src/main/source-control/hosted-review-creation.test.ts": 29, + "src/main/source-control/hosted-review-dirty-preflight-wsl-paths.test.ts": 5, + "src/main/source-control/hosted-review-execution-host-routing.test.ts": 35, + "src/main/source-control/hosted-review-gitea.integration.test.ts": 321, + "src/main/source-control/hosted-review.test.ts": 14, + "src/main/source-control/pull-request-linked-issue.test.ts": 10, + "src/main/source-control/repo-default-branch.test.ts": 18, + "src/main/source-control/stacked-hosted-review-creation.test.ts": 7, + "src/main/speech/model-catalog.test.ts": 5, + "src/main/speech/model-manager-download-error.test.ts": 17, + "src/main/speech/model-manager-download-resume.test.ts": 3020, + "src/main/speech/model-manager-progress-callback.test.ts": 9, + "src/main/speech/model-manager-stream-cleanup.test.ts": 30, + "src/main/speech/model-manager-windows-path.test.ts": 584, + "src/main/speech/model-manager.test.ts": 45, + "src/main/speech/openai-api-key-store.test.ts": 18, + "src/main/speech/openai-transcription-client.test.ts": 5, + "src/main/speech/speech-model-deletion.test.ts": 8, + "src/main/speech/speech-model-download-response.test.ts": 7, + "src/main/speech/stt-offline-audio-chunker.test.ts": 164, + "src/main/speech/stt-service.test.ts": 24, + "src/main/speech/stt-worker-model-config.test.ts": 8, + "src/main/speech/stt-worker.test.ts": 7, + "src/main/sqlite/sqlite-read-failure.test.ts": 16, + "src/main/sqlite/sync-database.test.ts": 1184, + "src/main/ssh-expired-lease-pane-readoption.test.ts": 58, + "src/main/ssh-reattach-pane-cardinality.test.ts": 181, + "src/main/ssh/orcad-activation-gate.test.ts": 8, + "src/main/ssh/orcad-activation-record.test.ts": 9, + "src/main/ssh/orcad-remote-deploy.test.ts": 63, + "src/main/ssh/orcad-remote-gc.test.ts": 11, + "src/main/ssh/orcad-remote-launch.test.ts": 15, + "src/main/ssh/orcad-remote-rollback.test.ts": 61, + "src/main/ssh/orcad-remote-shell-commands.integration.test.ts": 1254, + "src/main/ssh/orcad-state-snapshot.test.ts": 7, + "src/main/ssh/orcad-update-plan.test.ts": 10, + "src/main/ssh/relay-native-dependency-coverage.test.ts": 333, + "src/main/ssh/relay-protocol-backpressure.test.ts": 31, + "src/main/ssh/relay-protocol.test.ts": 79, + "src/main/ssh/relay-socket-path-limit-shell.integration.test.ts": 77, + "src/main/ssh/relay-socket-path-limit.test.ts": 17, + "src/main/ssh/remote-install-coexistence.test.ts": 22, + "src/main/ssh/remote-install-model.test.ts": 13, + "src/main/ssh/removed-ssh-target-tombstone-retention.test.ts": 7, + "src/main/ssh/sftp-namespace-resolution.test.ts": 36, + "src/main/ssh/sftp-stream-late-error.test.ts": 27, + "src/main/ssh/sftp-upload.test.ts": 139, + "src/main/ssh/ssh-agent-identity-filter.test.ts": 14, + "src/main/ssh/ssh-channel-multiplexer-backpressure.test.ts": 41, + "src/main/ssh/ssh-channel-multiplexer-saturation-wedge.test.ts": 14, + "src/main/ssh/ssh-channel-multiplexer-settlement.test.ts": 8, + "src/main/ssh/ssh-channel-multiplexer.test.ts": 52, + "src/main/ssh/ssh-config-alias-claim.test.ts": 12, + "src/main/ssh/ssh-config-host-picker.test.ts": 199, + "src/main/ssh/ssh-config-loader-regression.test.ts": 80, + "src/main/ssh/ssh-config-loader.test.ts": 77, + "src/main/ssh/ssh-config-parser-host-patterns.test.ts": 7, + "src/main/ssh/ssh-config-parser.test.ts": 262, + "src/main/ssh/ssh-config-resolver.test.ts": 12, + "src/main/ssh/ssh-connect-attempt-cancellation.test.ts": 7, + "src/main/ssh/ssh-connection-auth-fallback.test.ts": 125, + "src/main/ssh/ssh-connection-channel-open.test.ts": 746, + "src/main/ssh/ssh-connection-direct-startup.test.ts": 32, + "src/main/ssh/ssh-connection-file-transfer.test.ts": 17, + "src/main/ssh/ssh-connection-generation.test.ts": 9, + "src/main/ssh/ssh-connection-github-probe.test.ts": 16, + "src/main/ssh/ssh-connection-gssapi-fallback.test.ts": 127, + "src/main/ssh/ssh-connection-host-key-store-wiring.test.ts": 246, + "src/main/ssh/ssh-connection-host-key-verification.test.ts": 66, + "src/main/ssh/ssh-connection-manager-registry.test.ts": 67, + "src/main/ssh/ssh-connection-manager.test.ts": 9, + "src/main/ssh/ssh-connection-reconnect-ladder.test.ts": 335, + "src/main/ssh/ssh-connection-sftp-namespace.test.ts": 834, + "src/main/ssh/ssh-connection-sftp-wire.test.ts": 2413, + "src/main/ssh/ssh-connection-store.test.ts": 36, + "src/main/ssh/ssh-connection-system-transport.test.ts": 58, + "src/main/ssh/ssh-connection-utils.test.ts": 83, + "src/main/ssh/ssh-connection.test.ts": 72, + "src/main/ssh/ssh-control-socket.test.ts": 15, + "src/main/ssh/ssh-file-stream-inactivity-deadline.test.ts": 17, + "src/main/ssh/ssh-file-transfer-abort.test.ts": 16, + "src/main/ssh/ssh-g-config-resolution.test.ts": 67, + "src/main/ssh/ssh-git-response-stream-reader.test.ts": 11, + "src/main/ssh/ssh-git-stream-idle-timer.test.ts": 52, + "src/main/ssh/ssh-host-key-decision.test.ts": 19, + "src/main/ssh/ssh-host-key-store.test.ts": 134, + "src/main/ssh/ssh-host-key-verifier.test.ts": 21, + "src/main/ssh/ssh-known-hosts-source.test.ts": 57, + "src/main/ssh/ssh-known-hosts.test.ts": 23, + "src/main/ssh/ssh-multi-factor-authentication.test.ts": 1247, + "src/main/ssh/ssh-multi-key-authentication.test.ts": 30, + "src/main/ssh/ssh-multiplexer-transport-writer.test.ts": 26, + "src/main/ssh/ssh-orphan-relay-pty-sweep.test.ts": 21, + "src/main/ssh/ssh-orphan-sweep-pane-state-verdicts.test.ts": 21, + "src/main/ssh/ssh-owner-recovery-retry.test.ts": 15, + "src/main/ssh/ssh-pending-pty-kill-replay.test.ts": 13, + "src/main/ssh/ssh-port-forward.test.ts": 25, + "src/main/ssh/ssh-port-scanner.test.ts": 31, + "src/main/ssh/ssh-posix-command-wrapper.test.ts": 27, + "src/main/ssh/ssh-provider-authority.test.ts": 18, + "src/main/ssh/ssh-proxy-command.test.ts": 15, + "src/main/ssh/ssh-pty-consumer-recovery.test.ts": 10, + "src/main/ssh/ssh-pty-consumer-session.test.ts": 13, + "src/main/ssh/ssh-pty-recovery-retention-budget.test.ts": 4, + "src/main/ssh/ssh-pty-retired-source-deliveries.test.ts": 11, + "src/main/ssh/ssh-reconnect-error-classification.test.ts": 10, + "src/main/ssh/ssh-reconnect-ladder.test.ts": 8, + "src/main/ssh/ssh-relay-build-toolchain.test.ts": 12, + "src/main/ssh/ssh-relay-cross-version-isolation.test.ts": 72, + "src/main/ssh/ssh-relay-deploy-helpers.test.ts": 296, + "src/main/ssh/ssh-relay-deploy-incumbent-verdict.test.ts": 16, + "src/main/ssh/ssh-relay-deploy-staged-upload.test.ts": 166, + "src/main/ssh/ssh-relay-deploy.test.ts": 209, + "src/main/ssh/ssh-relay-endpoint-incumbent-shell.integration.test.ts": 2169, + "src/main/ssh/ssh-relay-endpoint-incumbent.test.ts": 15, + "src/main/ssh/ssh-relay-endpoint-takeover.test.ts": 15, + "src/main/ssh/ssh-relay-gc-retry.test.ts": 82, + "src/main/ssh/ssh-relay-install-lock.test.ts": 4, + "src/main/ssh/ssh-relay-install-namespace.test.ts": 16, + "src/main/ssh/ssh-relay-native-deps-cache-deploy.test.ts": 1273, + "src/main/ssh/ssh-relay-native-deps-cache-shell.test.ts": 323, + "src/main/ssh/ssh-relay-native-deps-cache.test.ts": 24, + "src/main/ssh/ssh-relay-native-deps-install-staged-upload.test.ts": 659, + "src/main/ssh/ssh-relay-native-deps-install.test.ts": 13644, + "src/main/ssh/ssh-relay-native-deps-probe-verdict.test.ts": 254, + "src/main/ssh/ssh-relay-node-headers.test.ts": 2346, + "src/main/ssh/ssh-relay-node-pty-repair.test.ts": 15, + "src/main/ssh/ssh-relay-node-pty-spawn-repair.test.ts": 25, + "src/main/ssh/ssh-relay-orphan-abandon-paths.test.ts": 117, + "src/main/ssh/ssh-relay-pty-master-cloexec-install.test.ts": 1911, + "src/main/ssh/ssh-relay-reset.test.ts": 27, + "src/main/ssh/ssh-relay-sentinel-copy-budget.test.ts": 21, + "src/main/ssh/ssh-relay-session-agent-hooks.integration.test.ts": 596, + "src/main/ssh/ssh-relay-session-data-delivery.test.ts": 55, + "src/main/ssh/ssh-relay-session-incarnation.test.ts": 19, + "src/main/ssh/ssh-relay-session-managed-hooks.test.ts": 14, + "src/main/ssh/ssh-relay-session-model-migration.test.ts": 170, + "src/main/ssh/ssh-relay-session-orphan-sweep.test.ts": 43, + "src/main/ssh/ssh-relay-session-pending-kill-replay.test.ts": 14, + "src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts": 120, + "src/main/ssh/ssh-relay-session-recovery-durability.test.ts": 1066, + "src/main/ssh/ssh-relay-session-recovery-races.test.ts": 325, + "src/main/ssh/ssh-relay-session-rejected-delivery.test.ts": 793, + "src/main/ssh/ssh-relay-session-relay-loss.test.ts": 37, + "src/main/ssh/ssh-relay-session-terminal-error.test.ts": 29, + "src/main/ssh/ssh-relay-session.test.ts": 311, + "src/main/ssh/ssh-relay-sftp-namespace-install.test.ts": 198, + "src/main/ssh/ssh-relay-superseded-endpoints.test.ts": 17, + "src/main/ssh/ssh-relay-upload-stage-commands.test.ts": 39445, + "src/main/ssh/ssh-relay-versioned-install.test.ts": 85, + "src/main/ssh/ssh-remote-cli-dispatch-refusal-passthrough.test.ts": 16, + "src/main/ssh/ssh-remote-cli-format.test.ts": 7, + "src/main/ssh/ssh-remote-cli-host-passthrough.test.ts": 39, + "src/main/ssh/ssh-remote-cli-launcher.test.ts": 10, + "src/main/ssh/ssh-remote-cli-terminal-host-scope.test.ts": 20, + "src/main/ssh/ssh-remote-commands.test.ts": 11428, + "src/main/ssh/ssh-remote-linear-activity-output.test.ts": 4, + "src/main/ssh/ssh-remote-linear-cli.test.ts": 53, + "src/main/ssh/ssh-remote-linear-list-issues.test.ts": 14, + "src/main/ssh/ssh-remote-linear-relation-write.test.ts": 15, + "src/main/ssh/ssh-remote-linear-save-issue.test.ts": 24, + "src/main/ssh/ssh-remote-linear-truncation-output.test.ts": 7, + "src/main/ssh/ssh-remote-node-resolution.test.ts": 118, + "src/main/ssh/ssh-remote-node-toolchain-probe.test.ts": 8, + "src/main/ssh/ssh-remote-orca-cli.test.ts": 140, + "src/main/ssh/ssh-remote-orchestration-compatibility.test.ts": 289, + "src/main/ssh/ssh-remote-orchestration-send.test.ts": 8, + "src/main/ssh/ssh-remote-platform-detection.test.ts": 27, + "src/main/ssh/ssh-remote-platform.test.ts": 16, + "src/main/ssh/ssh-remote-powershell.test.ts": 72, + "src/main/ssh/ssh-remote-windows-command-line-limit.test.ts": 23, + "src/main/ssh/ssh-request-outcome-verdict.test.ts": 5, + "src/main/ssh/ssh-security-key-identity.test.ts": 41, + "src/main/ssh/ssh-session-limit-error.test.ts": 5, + "src/main/ssh/ssh-system-fallback.test.ts": 90, + "src/main/ssh/ssh-system-transport.integration.test.ts": 637, + "src/main/ssh/ssh-target-id-migration.test.ts": 6, + "src/main/ssh/ssh-target-readoption.test.ts": 10, + "src/main/ssh/system-ssh-binary.test.ts": 12, + "src/main/ssh/system-ssh-dynamic-forward-process.test.ts": 61, + "src/main/ssh/system-ssh-forward-process.test.ts": 40, + "src/main/ssh/system-ssh-sftp-args.test.ts": 8, + "src/main/ssh/system-ssh-sftp-path.test.ts": 11, + "src/main/ssh/system-ssh-windows-upload.test.ts": 112, + "src/main/ssh/system-ssh-windows-write-capabilities.test.ts": 8, + "src/main/ssh/vscode-ssh-authority.test.ts": 8, + "src/main/star-nag/service-direct-star.test.ts": 14, + "src/main/star-nag/service-force-show-races.test.ts": 17, + "src/main/star-nag/service-outcome-telemetry.test.ts": 11, + "src/main/star-nag/service-prompt-moments.test.ts": 18, + "src/main/star-nag/service-threshold-prompt.test.ts": 15, + "src/main/startup/bootstrap-fatal-exit-guard.test.ts": 6, + "src/main/startup/branch-rename-hook-structured-session.test.ts": 10, + "src/main/startup/cli-launch-redirect.test.ts": 11, + "src/main/startup/configure-process-dev-parent-shutdown.test.ts": 65, + "src/main/startup/configure-process.test.ts": 123, + "src/main/startup/desktop-startup-ordering.test.ts": 14, + "src/main/startup/dev-education-suppression.test.ts": 10, + "src/main/startup/dev-instance-identity.test.ts": 7, + "src/main/startup/ensure-virtual-display.test.ts": 5092, + "src/main/startup/first-window-startup-services.test.ts": 16, + "src/main/startup/gpu-fallback-marker.test.ts": 7, + "src/main/startup/gpu-fallback-switches.test.ts": 6, + "src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts": 55, + "src/main/startup/headless-pty-hydration-ordering.test.ts": 10, + "src/main/startup/host-port-bootstrap-wiring.test.ts": 11, + "src/main/startup/hydrate-shell-path.test.ts": 228, + "src/main/startup/hydrate-shell-path.windows.test.ts": 284, + "src/main/startup/legacy-worker-renderer-recovery.test.ts": 16, + "src/main/startup/login-shell-environment.test.ts": 67, + "src/main/startup/main-process-error-guards.test.ts": 29, + "src/main/startup/main-process-ready-phase-ordering.test.ts": 9, + "src/main/startup/main-window-structured-status-filter.test.ts": 7, + "src/main/startup/os-opened-markdown-delivery.test.ts": 10, + "src/main/startup/os-opened-markdown-files.test.ts": 116, + "src/main/startup/os-opened-markdown-wiring.test.ts": 5, + "src/main/startup/pre-gone-crash-sampling-wiring.test.ts": 5, + "src/main/startup/renderer-heap-headroom.test.ts": 10, + "src/main/startup/run-electron-vite-dev-web.test.ts": 432, + "src/main/startup/run-electron-vite-dev.test.ts": 943, + "src/main/startup/secret-protection-report-deferral-wiring.test.ts": 6, + "src/main/startup/secure-dns-census.test.ts": 407, + "src/main/startup/serve-desktop-activation-wiring.test.ts": 9, + "src/main/startup/serve-desktop-activation.test.ts": 5, + "src/main/startup/serve-mode-argv-cli-redirect-order.test.ts": 9, + "src/main/startup/serve-mode-argv.test.ts": 32, + "src/main/startup/serve-options.test.ts": 20, + "src/main/startup/serve-signal-handlers.test.ts": 4, + "src/main/startup/single-instance-lock-exit.electron.test.ts": 275, + "src/main/startup/single-instance-lock-headless-exit.test.ts": 11, + "src/main/startup/single-instance-lock.test.ts": 19, + "src/main/startup/skill-share-deep-link-state.test.ts": 8, + "src/main/startup/startup-diagnostics.test.ts": 7, + "src/main/startup/window-all-closed-quit-policy.test.ts": 5, + "src/main/startup/windows-desktop-shell-path-startup.test.ts": 8, + "src/main/startup/windows-install-dir-acl-probe.test.ts": 20, + "src/main/startup/windows-install-dir-acl-recovery.test.ts": 460, + "src/main/startup/windows-install-dir-acl-startup-wiring.test.ts": 6, + "src/main/startup/windows-install-dir-package-acl-repair.test.ts": 43, + "src/main/startup/windows-shell-path-hydration.test.ts": 315, + "src/main/startup/windows-shell-path-ownership.test.ts": 10, + "src/main/startup/windows-user-data-acl.test.ts": 15, + "src/main/startup/wsl-cli-reconciliation-startup-barrier.test.ts": 9, + "src/main/stats/agent-session-transition-recorder.test.ts": 23, + "src/main/stats/collector-async-save.test.ts": 375, + "src/main/stats/stats-title-detection-independence.test.ts": 6, + "src/main/synthetic-title-frame-routing.test.ts": 4, + "src/main/synthetic-title-spinner.test.ts": 4, + "src/main/synthetic-title-visibility.test.ts": 5, + "src/main/system-fonts.test.ts": 16, + "src/main/system-power-lifecycle.test.ts": 8, + "src/main/system-resume-broadcast.test.ts": 24, + "src/main/telemetry/burst-cap.test.ts": 27, + "src/main/telemetry/classify-error.test.ts": 7, + "src/main/telemetry/client-lifecycle.test.ts": 17, + "src/main/telemetry/client.test.ts": 42, + "src/main/telemetry/cohort-classifier.test.ts": 13, + "src/main/telemetry/consent.test.ts": 15, + "src/main/telemetry/install-id.test.ts": 7, + "src/main/telemetry/onboarding-cohort-classifier.test.ts": 12, + "src/main/telemetry/onboarding-feature-setup-validator.test.ts": 18, + "src/main/telemetry/validator-warn-cache.test.ts": 30, + "src/main/telemetry/validator.test.ts": 20, + "src/main/terminal-history-async-delete.test.ts": 289, + "src/main/terminal-history-gc-fs-call-count.test.ts": 54, + "src/main/terminal-history-gc.test.ts": 877, + "src/main/terminal-history-tombstone-retry.test.ts": 4798, + "src/main/terminal-history.test.ts": 34, + "src/main/text-generation/agent-failure-output.test.ts": 11, + "src/main/text-generation/commit-message-agent-environment.test.ts": 31, + "src/main/text-generation/commit-message-command-backslash-mode.test.ts": 6, + "src/main/text-generation/commit-message-text-generation-branch-name.test.ts": 16, + "src/main/text-generation/commit-message-text-generation-cancellation.test.ts": 439, + "src/main/text-generation/commit-message-text-generation-failure-sanitization.test.ts": 15, + "src/main/text-generation/commit-message-text-generation-generated-output.test.ts": 15, + "src/main/text-generation/commit-message-text-generation-linked-issue.test.ts": 14, + "src/main/text-generation/commit-message-text-generation-local-subprocess.test.ts": 28, + "src/main/text-generation/commit-message-text-generation-model-discovery.test.ts": 48, + "src/main/text-generation/commit-message-text-generation-regression.test.ts": 10, + "src/main/text-generation/commit-message-text-generation-remote-execution.test.ts": 30, + "src/main/text-generation/commit-message-text-generation-settings.test.ts": 19, + "src/main/text-generation/pull-request-context-errors.test.ts": 25, + "src/main/text-generation/pull-request-context.test.ts": 30, + "src/main/tray/system-tray.test.ts": 92, + "src/main/tray/tray-attention-icon.test.ts": 13, + "src/main/tray/tray-dev-badge.test.ts": 11, + "src/main/update-install-exit-watchdog.test.ts": 12, + "src/main/updater-changelog.test.ts": 7, + "src/main/updater-events.test.ts": 204, + "src/main/updater-lifecycle-diagnostics.test.ts": 5, + "src/main/updater-linux-package-recovery-actions.test.ts": 2079, + "src/main/updater-nudge.test.ts": 12, + "src/main/updater-prerelease-feed-readiness.test.ts": 114, + "src/main/updater-prerelease-feed.test.ts": 148, + "src/main/updater-release-builds.test.ts": 30, + "src/main/updater-test-harness.leaked-timers.test.ts": 2596, + "src/main/updater-test-module-loader.test.ts": 508, + "src/main/updater.build-channel-selection.test.ts": 1674, + "src/main/updater.check-failure.test.ts": 1910, + "src/main/updater.check-preflight.test.ts": 2719, + "src/main/updater.check-settlement.test.ts": 2274, + "src/main/updater.fallback.test.ts": 22, + "src/main/updater.headless-serve-install.test.ts": 272, + "src/main/updater.install-failure-cause.test.ts": 2517, + "src/main/updater.linux-externally-managed.test.ts": 1400, + "src/main/updater.linux-root-package-install.test.ts": 1772, + "src/main/updater.nudge-campaign.test.ts": 1490, + "src/main/updater.prerelease-fallback.test.ts": 4396, + "src/main/updater.publishing-window-feed.test.ts": 3071, + "src/main/updater.quit-and-install.test.ts": 2199, + "src/main/updater.startup-scheduling.test.ts": 1573, + "src/main/usage-worktree-canonicalizer.test.ts": 10, + "src/main/usage-worktree-metadata.test.ts": 9, + "src/main/usage/highest-usage-key.test.ts": 14, + "src/main/usage/usage-breakdown-scaling.test.ts": 55, + "src/main/usage/usage-calendar-range.test.ts": 8, + "src/main/usage/usage-provider-store-lifecycle.test.ts": 293, + "src/main/usage/usage-worktree-refs.test.ts": 7, + "src/main/warp-themes/discovery.test.ts": 30, + "src/main/warp-themes/index.test.ts": 447, + "src/main/warp-themes/manual-warp-theme-files.test.ts": 36, + "src/main/warp-themes/parser-runner.test.ts": 9, + "src/main/warp-themes/parser.test.ts": 23, + "src/main/warp-themes/theme-file-scanner.test.ts": 22, + "src/main/win32-utils-async-acl.test.ts": 16, + "src/main/win32-utils.test.ts": 12, + "src/main/window/attach-main-window-services.test.ts": 195, + "src/main/window/clipboard-dashboard-popout-access.test.ts": 15, + "src/main/window/clipboard-file-copy.test.ts": 11, + "src/main/window/clipboard-image-temp-file.test.ts": 8, + "src/main/window/clipboard-image-thumbnail.test.ts": 8, + "src/main/window/clipboard-ipc-handlers.test.ts": 94, + "src/main/window/clipboard-remote-file-copy.test.ts": 18, + "src/main/window/clipboard-remote-file-staging-fs.test.ts": 23, + "src/main/window/clipboard-remote-file-staging.test.ts": 37, + "src/main/window/clipboard-runtime-owned-ssh-paste.test.ts": 15, + "src/main/window/clipboard-text-write-verify.test.ts": 8, + "src/main/window/clipboard-windows-image-file.test.ts": 17, + "src/main/window/createMainWindow-close-confirmation.test.ts": 29, + "src/main/window/createMainWindow-markdown-editor-focus.test.ts": 21, + "src/main/window/createMainWindow-recovery-reload-watchdog.test.ts": 48, + "src/main/window/createMainWindow-renderer-crash-recovery.test.ts": 51, + "src/main/window/createMainWindow-startup-reveal.test.ts": 35, + "src/main/window/createMainWindow-system-resume-relay.test.ts": 11, + "src/main/window/createMainWindow-terminal-focus-shortcuts.test.ts": 46, + "src/main/window/createMainWindow-tray-minimize-close.test.ts": 31, + "src/main/window/createMainWindow-zoom-and-tab-switch-shortcuts.test.ts": 17, + "src/main/window/createMainWindow.test.ts": 35, + "src/main/window/dashboard-popout-window.test.ts": 26, + "src/main/window/editable-context-menu.test.ts": 20, + "src/main/window/focus-existing-window.test.ts": 10, + "src/main/window/foreground-activation-policy.test.ts": 15, + "src/main/window/history-gc-profile-worktree-ids.test.ts": 13, + "src/main/window/history-gc-worktree-ids.test.ts": 8, + "src/main/window/macos-app-activation.test.ts": 5, + "src/main/window/macos-tahoe-release.test.ts": 6, + "src/main/window/main-window-visibility.test.ts": 5, + "src/main/window/main-window-webview-security.test.ts": 12, + "src/main/window/mobile-markdown-request-relay.test.ts": 26, + "src/main/window/privileged-window-navigation.test.ts": 14, + "src/main/window/renderer-document-navigation.test.ts": 9, + "src/main/window/renderer-publication-throttle.test.ts": 5, + "src/main/window/renderer-recovery-prompt.test.ts": 21, + "src/main/window/renderer-recovery-reload-watchdog.test.ts": 16, + "src/main/window/runtime-renderer-notification-sender.test.ts": 9, + "src/main/window/session-tab-close-request-relay.test.ts": 40, + "src/main/window/terminal-tab-close-request-relay.test.ts": 24, + "src/main/window/updater-package-recovery-ipc.test.ts": 14, + "src/main/window/window-close-decision.test.ts": 4, + "src/main/windows-descendant-exit-verification.test.ts": 17, + "src/main/windows-process-tree-kill.test.ts": 7, + "src/main/windows-pty-root-identity.test.ts": 13, + "src/main/windows/windows-command-line-recovery-health.test.ts": 10, + "src/main/windows/windows-process-table-cim-scan.test.ts": 6, + "src/main/windows/windows-process-table.test.ts": 64, + "src/main/windows/windows-process-tree-command-line-patch.test.ts": 11, + "src/main/windows/windows-pty-job.test.ts": 14, + "src/main/workspace-cleanup-removal-snapshot-prune.test.ts": 13, + "src/main/workspace-cleanup-scan-snapshot.test.ts": 88, + "src/main/workspace-space-analysis-capacity.test.ts": 38, + "src/main/workspace-space-analysis-du-timeout.test.ts": 236, + "src/main/workspace-space-analysis-snapshot.test.ts": 66, + "src/main/workspace-space-analysis.test.ts": 227, + "src/main/workspace-space-repo-scan.test.ts": 5, + "src/main/workspace-space-scan-control.test.ts": 36, + "src/main/worktree-create-base-prefetch.test.ts": 225, + "src/main/worktree-create-base.test.ts": 5, + "src/main/worktree-create-candidates.test.ts": 5, + "src/main/worktree-create-execution-host-route.test.ts": 10, + "src/main/worktree-create-preparation-cancellation.test.ts": 29, + "src/main/worktree-create-preparation-claim.test.ts": 8, + "src/main/worktree-create-preparation-wsl-root.test.ts": 13, + "src/main/worktree-create-preparation.test.ts": 110, + "src/main/worktree-create-timing.test.ts": 7, + "src/main/worktree-identity-persistence.test.ts": 207, + "src/main/worktree-lineage-pruning.test.ts": 12, + "src/main/worktree-metadata-ownership.test.ts": 4, + "src/main/worktree-name-retirement.test.ts": 18, + "src/main/worktree-removal-authority.test.ts": 10, + "src/main/worktree-removal-execution-host-route.test.ts": 6, + "src/main/worktree-removal-repo-owner.test.ts": 7, + "src/main/worktree-removal-safety.test.ts": 18, + "src/main/worktree-removal-session-partition-fencing.test.ts": 1223, + "src/main/worktree-retirement-backfill-scan.test.ts": 20, + "src/main/worktree-retirement-backfill-stall.test.ts": 15, + "src/main/worktree-retirement-discovery-wsl.test.ts": 32, + "src/main/worktree-retirement-discovery.test.ts": 37, + "src/main/worktree-retirement-namespace.test.ts": 28, + "src/main/worktree-root-preparation.test.ts": 11, + "src/main/worktree-trash.test.ts": 53, + "src/main/wsl-availability-missing-kernel.test.ts": 10, + "src/main/wsl-bash-command.test.ts": 4, + "src/main/wsl-distro-list-output.test.ts": 8, + "src/main/wsl-distro-list-single-flight.test.ts": 14, + "src/main/wsl-fish-history-cleanup.test.ts": 15, + "src/main/wsl-interop-spawn-directory.test.ts": 10, + "src/main/wsl-running-distros.test.ts": 125, + "src/main/wsl-unc-delete-symlink-repro.test.ts": 7, + "src/main/wsl-unc-delete.test.ts": 12, + "src/main/wsl.test.ts": 35, + "src/main/wsl/wsl-guest-environment.test.ts": 52, + "src/main/wsl/wsl-invocation-boundary.test.ts": 6, + "src/main/wsl/wsl-probe-failure-semantics.test.ts": 587, + "src/main/wsl/wsl-runner.test.ts": 32, + "src/main/wsl/wsl-w1-w3-contract.test.ts": 26, + "src/preload/api/platform-bridge.test.ts": 12, + "src/preload/app-restart-checkpoint-routing.test.ts": 651, + "src/preload/browser-client-page-renderer-requests.test.ts": 22, + "src/preload/browser-find-subscriptions.test.ts": 11, + "src/preload/browser-window-close.test.ts": 6, + "src/preload/close-active-tab-payload-admission.test.ts": 5, + "src/preload/doc-preview-link-interception.test.ts": 29, + "src/preload/pty-snapshot-capability-ipc.test.ts": 652, + "src/preload/renderer-heap-statistics-reader.test.ts": 5, + "src/preload/renderer-process-memory-reader.test.ts": 7, + "src/preload/renderer-restart-wiring.test.ts": 13, + "src/preload/runtime-environment-subscriptions.test.ts": 11, + "src/preload/ssh-authority-forwarding.test.ts": 682, + "src/preload/updater-package-recovery.test.ts": 273, + "src/preload/usage-provider-api.test.ts": 9, + "src/relay/agent-exec-handler-windows.test.ts": 11, + "src/relay/agent-exec-handler.test.ts": 28, + "src/relay/agent-hook-envelope-publication.test.ts": 215, + "src/relay/agent-hook-integration.test.ts": 116, + "src/relay/agent-hook-retired-pane-suppression.test.ts": 125, + "src/relay/agent-hook-server-codex-subagent-transcript.test.ts": 1068, + "src/relay/agent-hook-server.test.ts": 1481, + "src/relay/ai-vault-handler.test.ts": 208, + "src/relay/ai-vault-service-client.test.ts": 28, + "src/relay/ai-vault-service-restart-policy.test.ts": 10, + "src/relay/ai-vault-service-spawn.test.ts": 9, + "src/relay/context.test.ts": 5, + "src/relay/dispatcher-capacity-degradation.test.ts": 112, + "src/relay/dispatcher-client-close-cause.test.ts": 15, + "src/relay/dispatcher-client-writer.test.ts": 13, + "src/relay/dispatcher-frame-guard-regressions.test.ts": 7, + "src/relay/dispatcher-json-payload.test.ts": 51, + "src/relay/dispatcher-notification-ownership.test.ts": 5, + "src/relay/dispatcher-silent-client-reaper.test.ts": 19, + "src/relay/dispatcher-structured-error.test.ts": 10, + "src/relay/dispatcher-timeout.test.ts": 19, + "src/relay/dispatcher-writer-admission.test.ts": 94, + "src/relay/dispatcher.test.ts": 198, + "src/relay/external-automation-provider-catalog.test.ts": 27, + "src/relay/external-automations-handler-log-path.test.ts": 24, + "src/relay/external-automations-handler.test.ts": 21, + "src/relay/fs-handler-doc-preview.test.ts": 16, + "src/relay/fs-handler-file-range-dispatch.test.ts": 79, + "src/relay/fs-handler-file-range.test.ts": 61, + "src/relay/fs-handler-git-search.test.ts": 7, + "src/relay/fs-handler-install-rg.test.ts": 9, + "src/relay/fs-handler-list-files-cancel.test.ts": 28, + "src/relay/fs-handler-list-files-ignored.test.ts": 648, + "src/relay/fs-handler-list-files-result-limit.test.ts": 12, + "src/relay/fs-handler-readdir-fallback.test.ts": 8, + "src/relay/fs-handler-ripgrep-fallback.test.ts": 19, + "src/relay/fs-handler-stream.test.ts": 475, + "src/relay/fs-handler.test.ts": 172, + "src/relay/fs-list-files-cancel.integration.test.ts": 26, + "src/relay/fs-list-files-large-response.integration.test.ts": 87, + "src/relay/fs-list-files-scan-coordinator.test.ts": 14, + "src/relay/fs-path-metadata-requests.test.ts": 10, + "src/relay/fs-path-metadata-symlink-concurrency.test.ts": 11, + "src/relay/fs-search-line-fragments.test.ts": 84, + "src/relay/fs-stream-pty-echo-backpressure.integration.test.ts": 135, + "src/relay/git-branch-delete-refusal-parity.test.ts": 10, + "src/relay/git-exec-validator.test.ts": 18, + "src/relay/git-handler-blob-readers.test.ts": 9, + "src/relay/git-handler-branch-cleanup.test.ts": 19, + "src/relay/git-handler-branch-compare.test.ts": 58, + "src/relay/git-handler-branch-diff-equivalence.test.ts": 908, + "src/relay/git-handler-branch-diff.test.ts": 174, + "src/relay/git-handler-check-ignore.test.ts": 8, + "src/relay/git-handler-diff-read-coalescing.test.ts": 103, + "src/relay/git-handler-file-diff.test.ts": 1278, + "src/relay/git-handler-fork-remote-exec.test.ts": 75, + "src/relay/git-handler-pull-reconciliation.test.ts": 712, + "src/relay/git-handler-push-target.test.ts": 16, + "src/relay/git-handler-remote-sync.test.ts": 1241, + "src/relay/git-handler-staging.test.ts": 402, + "src/relay/git-handler-status-ops.test.ts": 63, + "src/relay/git-handler-submodule-cache-invalidation.test.ts": 8, + "src/relay/git-handler-submodule-ops.test.ts": 47, + "src/relay/git-handler-submodule-status-cancellation.test.ts": 11, + "src/relay/git-handler-termination.test.ts": 9, + "src/relay/git-handler-utils.test.ts": 4, + "src/relay/git-handler-working-tree-changes.test.ts": 1001, + "src/relay/git-handler-worktree-clean.test.ts": 9, + "src/relay/git-handler-worktree-git-capabilities.test.ts": 12, + "src/relay/git-handler-worktree-inspection.test.ts": 819, + "src/relay/git-handler-worktree-list-authority.test.ts": 12, + "src/relay/git-handler-worktree-list.test.ts": 11, + "src/relay/git-handler-worktree-ops.test.ts": 27, + "src/relay/git-handler-worktree-paths.test.ts": 5, + "src/relay/git-handler-worktree-provisioning.test.ts": 723, + "src/relay/git-handler.test.ts": 638, + "src/relay/git-porcelain-local-parity.test.ts": 21, + "src/relay/git-push-target-local-parity.test.ts": 21, + "src/relay/git-response-pty-echo-backpressure.integration.test.ts": 751, + "src/relay/git-response-stream-ownership.test.ts": 18, + "src/relay/git-status-branch-line-total.test.ts": 197, + "src/relay/git-status-upstream-negative-cache.test.ts": 260, + "src/relay/git-stdout-stream.test.ts": 21, + "src/relay/git-working-file-read.test.ts": 27, + "src/relay/hermes-run-correlation.test.ts": 8, + "src/relay/hermes-run-history.test.ts": 13, + "src/relay/integration.test.ts": 1222, + "src/relay/legacy-relay-publication-ledger.test.ts": 7, + "src/relay/managed-hook-installer.test.ts": 13, + "src/relay/node-pty-binding-survey.test.ts": 132, + "src/relay/node-pty-unavailable-diagnosis.test.ts": 13, + "src/relay/plugin-overlay-env.test.ts": 14, + "src/relay/plugin-overlay.test.ts": 24, + "src/relay/plugin-source-limit.test.ts": 8, + "src/relay/port-scan-handler.test.ts": 70, + "src/relay/preflight-handler.test.ts": 18, + "src/relay/protocol-backpressure.test.ts": 51, + "src/relay/protocol-handshake.test.ts": 7, + "src/relay/protocol-json-payload.test.ts": 178, + "src/relay/pty-handler-attach-replay.test.ts": 49, + "src/relay/pty-handler-dispose-lifecycle.test.ts": 37, + "src/relay/pty-handler-grace-timer.test.ts": 19, + "src/relay/pty-handler-inventory-process-evidence.test.ts": 30, + "src/relay/pty-handler-output-drain-differential.test.ts": 135, + "src/relay/pty-handler-output-streaming.test.ts": 420, + "src/relay/pty-handler-ownership-attestation.test.ts": 87, + "src/relay/pty-handler-resize-stale-pty.test.ts": 30, + "src/relay/pty-handler-retired-pane-surface.test.ts": 82, + "src/relay/pty-handler-revive.test.ts": 91, + "src/relay/pty-handler-shell-resolution.test.ts": 33, + "src/relay/pty-handler-shutdown-signals.test.ts": 54, + "src/relay/pty-handler-source-publication.test.ts": 260, + "src/relay/pty-handler-spawn-admission.test.ts": 127, + "src/relay/pty-handler-spawn-cwd.test.ts": 46, + "src/relay/pty-handler-spawn-environment.test.ts": 108, + "src/relay/pty-handler-startup-command-delivery.test.ts": 111, + "src/relay/pty-handler-windows-child-process-evidence.test.ts": 35, + "src/relay/pty-replay-buffer-equivalence.test.ts": 77, + "src/relay/pty-shell-launch.test.ts": 312, + "src/relay/pty-shell-utils.test.ts": 58, + "src/relay/pty-source-credit-ledger.test.ts": 122, + "src/relay/pty-source-credit-scheduler.test.ts": 12, + "src/relay/pty-source-sent-boundaries.test.ts": 88, + "src/relay/relay-command-env.test.ts": 14, + "src/relay/relay-daemon-fatal-reap.test.ts": 540, + "src/relay/relay-diagnostic-log.test.ts": 7, + "src/relay/relay-endpoint-credential-publication.test.ts": 2634, + "src/relay/relay-filesystem-watch-registry.test.ts": 128, + "src/relay/relay-grace-branch.test.ts": 7, + "src/relay/relay-handshake-roundtrip.test.ts": 317, + "src/relay/relay-launch-options.test.ts": 12, + "src/relay/relay-oversized-notification-survival.test.ts": 67, + "src/relay/relay-pty-consumer-owner-displacement.test.ts": 19, + "src/relay/relay-pty-publication-admission.test.ts": 26, + "src/relay/relay-pty-source-cancellation-exit.test.ts": 54, + "src/relay/relay-pty-source-exit-publication.test.ts": 27, + "src/relay/relay-pty-source-publication.test.ts": 69, + "src/relay/relay-pty-source-recovery-completion.test.ts": 9, + "src/relay/relay-pty-source-recovery-interleavings.test.ts": 25, + "src/relay/relay-pty-source-recovery-window.test.ts": 33, + "src/relay/relay-pty-source-restore-retry.test.ts": 15, + "src/relay/relay-pty-source-send-scheduler.test.ts": 9, + "src/relay/relay-pty-source-superseded-activation.test.ts": 21, + "src/relay/relay-reconnect-listener-credential-gate.test.ts": 28, + "src/relay/relay-watch-root-capacity.test.ts": 15, + "src/relay/relay-watcher-event-emitter.test.ts": 842, + "src/relay/relay-watcher-frame-chunking.test.ts": 632, + "src/relay/relay-watcher-parent-removal.test.ts": 20, + "src/relay/relay-watcher-pending-setup-waiters.test.ts": 233, + "src/relay/relay-watcher-setup-wait.test.ts": 320, + "src/relay/remote-artifact-cli-input.test.ts": 124, + "src/relay/remote-cli-env.test.ts": 5, + "src/relay/remote-cli-stdin.test.ts": 7, + "src/relay/remote-cli-timeout.test.ts": 9, + "src/relay/retired-pane-surfaces.test.ts": 7, + "src/relay/rotating-log-writer.test.ts": 33, + "src/relay/skill-install-handler.test.ts": 306, + "src/relay/skill-upload-multi-relay.integration.test.ts": 549, + "src/relay/ssh-pty-consumer-session-adapter.test.ts": 48, + "src/relay/ssh-pty-source-credit-adapter.test.ts": 29, + "src/relay/subprocess-tree-termination.test.ts": 6, + "src/relay/subprocess.test.ts": 22239, + "src/relay/terminal-history-wsl.test.ts": 11, + "src/relay/terminal-history.test.ts": 14, + "src/relay/windows-port-scan.test.ts": 66, + "src/relay/workspace-session-handler.test.ts": 12, + "src/relay/workspace-snapshot-publication.test.ts": 29, + "src/relay/workspace-space-scan-du-capacity.test.ts": 34, + "src/relay/workspace-space-scan.test.ts": 37, + "src/relay/wsl-agent-hook-relay.test.ts": 45, + "src/relay/wsl-hook-fs-bridge.test.ts": 11, + "src/relay/wsl-install-plugins-handler.test.ts": 20, + "src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts": 13, + "src/renderer/src/app-shell/app-command-handlers-workspace-delete.test.ts": 10, + "src/renderer/src/app-shell/app-root-surface-settings.test.tsx": 25, + "src/renderer/src/app-shell/reconcile-hydrated-workspace-tab-models.test.ts": 6, + "src/renderer/src/app-shell/remote-workspace-unplaced-upload-suppression.test.tsx": 92, + "src/renderer/src/app-shell/shutdown-checkpoint-persist.test.ts": 16, + "src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts": 20, + "src/renderer/src/app-shell/startup-actions-selector.test.ts": 56, + "src/renderer/src/app-shell/use-document-appearance.test.tsx": 33, + "src/renderer/src/app-shell/window-visibility-actions-selector.test.ts": 42, + "src/renderer/src/app-shell/workspace-view-cross-client-sync.test.tsx": 120, + "src/renderer/src/app-startup-routing.test.ts": 13, + "src/renderer/src/assets/mobile-page-qr-layout.test.ts": 5, + "src/renderer/src/assets/rich-markdown-task-list-style.test.ts": 4, + "src/renderer/src/assets/terminal-container-geometry.test.ts": 7, + "src/renderer/src/assets/terminal-scrollbar-style.test.ts": 4, + "src/renderer/src/assets/worktree-card-active-style.test.ts": 4, + "src/renderer/src/components/AgentStateDot.test.ts": 38, + "src/renderer/src/components/AgentWorkingSpinner.test.tsx": 43, + "src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx": 343, + "src/renderer/src/components/NewWorkspaceComposerCard.set-location-warm.test.tsx": 141, + "src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx": 229, + "src/renderer/src/components/NewWorkspaceComposerCard.test.tsx": 1044, + "src/renderer/src/components/QuickOpen.mount-gating.test.tsx": 647, + "src/renderer/src/components/SelectedTextCopyMenu.test.tsx": 49, + "src/renderer/src/components/StarNagCard.test.tsx": 60, + "src/renderer/src/components/StateIndicatorTooltip.test.tsx": 12, + "src/renderer/src/components/TerminalSearch.test.tsx": 106, + "src/renderer/src/components/TerminalTitlebarTabs.test.tsx": 28, + "src/renderer/src/components/TerminalWorkbenchContainer.test.tsx": 40, + "src/renderer/src/components/UpdateCard.error-card.test.tsx": 449, + "src/renderer/src/components/UpdateCard.test.ts": 60, + "src/renderer/src/components/WorktreeBaseFallbackDialog.test.tsx": 146, + "src/renderer/src/components/WorktreeJumpPalette.linear-url.test.tsx": 865, + "src/renderer/src/components/WorktreeJumpPalette.mount-gating.test.tsx": 489, + "src/renderer/src/components/WorktreeJumpPalette.recent-tabs.behavior.test.tsx": 466, + "src/renderer/src/components/WorktreeJumpPalette.recent-tabs.test.tsx": 907, + "src/renderer/src/components/WorktreeJumpPalette.test.tsx": 423, + "src/renderer/src/components/activity/ActivityPrototypePage.filter-focus-shortcut.test.ts": 8, + "src/renderer/src/components/activity/ActivityPrototypePage.test.ts": 21, + "src/renderer/src/components/activity/ActivityPrototypePage.thread-grouping.test.ts": 31, + "src/renderer/src/components/activity/ActivityThreadOptionsMenu.test.tsx": 388, + "src/renderer/src/components/activity/activity-auto-mark-read-loop.react185.test.tsx": 950, + "src/renderer/src/components/activity/activity-clear-completed.test.ts": 28, + "src/renderer/src/components/activity/activity-event-builder-agent-context.test.ts": 13, + "src/renderer/src/components/activity/activity-event-builder.bounded-history.test.ts": 12, + "src/renderer/src/components/activity/activity-event-builder.host-ownership.test.ts": 17, + "src/renderer/src/components/activity/activity-event-builder.identity-reuse.test.ts": 15, + "src/renderer/src/components/activity/activity-event-builder.live-cap.test.ts": 14, + "src/renderer/src/components/activity/activity-portal-churn-budget.test.ts": 8, + "src/renderer/src/components/activity/activity-portal-readiness-decay.react185.test.tsx": 33, + "src/renderer/src/components/activity/activity-portal-readiness-loop.react185.test.tsx": 144, + "src/renderer/src/components/activity/activity-portal-readiness-oscillation.test.ts": 5, + "src/renderer/src/components/activity/activity-portal-readiness-subscription-churn.react185.test.tsx": 131, + "src/renderer/src/components/activity/activity-portal-thread-reconciliation.test.ts": 9, + "src/renderer/src/components/activity/activity-scope-filter.test.ts": 9, + "src/renderer/src/components/activity/activity-terminal-portal-publication-loop.react185.test.tsx": 21, + "src/renderer/src/components/activity/activity-terminal-portal.test.tsx": 19, + "src/renderer/src/components/activity/activity-thread-actions.test.ts": 17, + "src/renderer/src/components/activity/activity-thread-child-agent.test.ts": 7, + "src/renderer/src/components/activity/activity-thread-grouping.search-cache.test.ts": 8, + "src/renderer/src/components/activity/activity-thread-grouping.status-order.test.ts": 17, + "src/renderer/src/components/activity/activity-thread-hover-card.test.tsx": 228, + "src/renderer/src/components/activity/activity-thread-list-pane-collapsible.test.tsx": 218, + "src/renderer/src/components/activity/activity-thread-list-pane.virtualization.test.tsx": 852, + "src/renderer/src/components/activity/activity-thread-presentation.test.ts": 9, + "src/renderer/src/components/activity/activity-thread-virtual-items.test.ts": 12, + "src/renderer/src/components/activity/dev-activity-fixture.test.ts": 7, + "src/renderer/src/components/activity/event-time-clock-refresh.test.tsx": 40, + "src/renderer/src/components/activity/use-activity-thread-action-bindings.test.tsx": 22, + "src/renderer/src/components/activity/useActivityUnreadCount.freshness.test.tsx": 42, + "src/renderer/src/components/activity/useActivityUnreadCount.test.ts": 7, + "src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.test.tsx": 42, + "src/renderer/src/components/agent-session-continuation/agent-session-continuation-selection.test.ts": 5, + "src/renderer/src/components/agent/AgentCombobox.test.tsx": 855, + "src/renderer/src/components/agent/AgentSettingsDialog.test.tsx": 68, + "src/renderer/src/components/agent/agent-combobox-command-state.test.ts": 6, + "src/renderer/src/components/artifacts/ArtifactCollection.test.tsx": 439, + "src/renderer/src/components/artifacts/ArtifactPreview.test.tsx": 69, + "src/renderer/src/components/artifacts/ArtifactPublishButton.test.tsx": 272, + "src/renderer/src/components/artifacts/ArtifactsPage.test.tsx": 1134, + "src/renderer/src/components/artifacts/artifact-display-labels.test.ts": 30, + "src/renderer/src/components/artifacts/artifact-list-search.test.ts": 11, + "src/renderer/src/components/artifacts/artifact-publish-flow.test.ts": 62, + "src/renderer/src/components/artifacts/artifact-published-link-client.test.ts": 13, + "src/renderer/src/components/automations/AutomationDestinationField.test.tsx": 285, + "src/renderer/src/components/automations/AutomationDetail.host.test.tsx": 104, + "src/renderer/src/components/automations/AutomationDetail.test.tsx": 81, + "src/renderer/src/components/automations/AutomationEditorPromptEditor.test.tsx": 35, + "src/renderer/src/components/automations/AutomationHostBadges.test.tsx": 191, + "src/renderer/src/components/automations/AutomationHostFilterNotice.test.tsx": 125, + "src/renderer/src/components/automations/AutomationListEmptyView.test.tsx": 53, + "src/renderer/src/components/automations/AutomationListHostGroups.test.tsx": 71, + "src/renderer/src/components/automations/AutomationListLocalRows.test.tsx": 298, + "src/renderer/src/components/automations/AutomationListSearchField.test.tsx": 74, + "src/renderer/src/components/automations/AutomationListTableHeader.test.tsx": 131, + "src/renderer/src/components/automations/AutomationOwnerConflictNotice.test.tsx": 60, + "src/renderer/src/components/automations/AutomationPromptDisclosure.test.tsx": 334, + "src/renderer/src/components/automations/AutomationRunHistory.test.tsx": 122, + "src/renderer/src/components/automations/AutomationRunsDashboardSurface.test.tsx": 23, + "src/renderer/src/components/automations/AutomationRunsTable.test.tsx": 60, + "src/renderer/src/components/automations/AutomationSchedulePicker.render.test.tsx": 915, + "src/renderer/src/components/automations/AutomationSchedulePicker.test.ts": 839, + "src/renderer/src/components/automations/AutomationTimeField.test.tsx": 248, + "src/renderer/src/components/automations/AutomationsDetailPane.run-count.test.tsx": 115, + "src/renderer/src/components/automations/AutomationsDetailPane.test.tsx": 178, + "src/renderer/src/components/automations/AutomationsListPanel.test.tsx": 271, + "src/renderer/src/components/automations/AutomationsPage.create-destination.test.tsx": 412, + "src/renderer/src/components/automations/AutomationsPage.cross-authority-actions.test.tsx": 137, + "src/renderer/src/components/automations/AutomationsPage.escape-precedence.test.tsx": 232, + "src/renderer/src/components/automations/AutomationsPage.external-scope.test.tsx": 212, + "src/renderer/src/components/automations/AutomationsPage.notice-recovery.test.tsx": 198, + "src/renderer/src/components/automations/AutomationsPage.refresh-selection.test.tsx": 376, + "src/renderer/src/components/automations/AutomationsPage.run-visibility.test.tsx": 133, + "src/renderer/src/components/automations/AutomationsPage.save-visibility.test.tsx": 195, + "src/renderer/src/components/automations/AutomationsPage.strict-mode-save-visibility.test.tsx": 233, + "src/renderer/src/components/automations/AutomationsPage.test.tsx": 644, + "src/renderer/src/components/automations/AutomationsPageBreadcrumb.test.tsx": 62, + "src/renderer/src/components/automations/CreateFromPicker.test.tsx": 75, + "src/renderer/src/components/automations/ExternalAutomationManagers.test.tsx": 247, + "src/renderer/src/components/automations/automation-authority-identity.test.ts": 8, + "src/renderer/src/components/automations/automation-captured-owner.test.ts": 8, + "src/renderer/src/components/automations/automation-create-destination.test.ts": 10, + "src/renderer/src/components/automations/automation-create-projects.test.ts": 6, + "src/renderer/src/components/automations/automation-detail-tab-navigation.test.ts": 16, + "src/renderer/src/components/automations/automation-editor-prompt-options.test.ts": 6, + "src/renderer/src/components/automations/automation-external-target-match.test.ts": 8, + "src/renderer/src/components/automations/automation-host-cache-controller.test.ts": 21, + "src/renderer/src/components/automations/automation-host-cache-health.test.ts": 9, + "src/renderer/src/components/automations/automation-host-cache.test.ts": 18, + "src/renderer/src/components/automations/automation-host-catalog-generation.test.ts": 14, + "src/renderer/src/components/automations/automation-host-catalog-order.test.ts": 19, + "src/renderer/src/components/automations/automation-host-catalog-source.test.ts": 11, + "src/renderer/src/components/automations/automation-host-catalog.test.ts": 64, + "src/renderer/src/components/automations/automation-host-client.test.ts": 7, + "src/renderer/src/components/automations/automation-host-detail-display.test.ts": 4, + "src/renderer/src/components/automations/automation-host-diagnostics.test.ts": 28, + "src/renderer/src/components/automations/automation-host-filter-resolution.test.ts": 28, + "src/renderer/src/components/automations/automation-host-health.test.ts": 7, + "src/renderer/src/components/automations/automation-host-invalidation-window-events.test.ts": 12, + "src/renderer/src/components/automations/automation-host-invalidation.test.ts": 17, + "src/renderer/src/components/automations/automation-host-list-rows.test.ts": 8, + "src/renderer/src/components/automations/automation-host-orphan-entry.test.ts": 22, + "src/renderer/src/components/automations/automation-host-picker-groups.test.ts": 19, + "src/renderer/src/components/automations/automation-host-recovery.test.ts": 5, + "src/renderer/src/components/automations/automation-host-scale-gates.test.ts": 195, + "src/renderer/src/components/automations/automation-host-scheduler.test.ts": 18, + "src/renderer/src/components/automations/automation-host-status-descriptors.test.ts": 24, + "src/renderer/src/components/automations/automation-list-empty-state.test.ts": 18, + "src/renderer/src/components/automations/automation-list-focus-recovery.test.ts": 7, + "src/renderer/src/components/automations/automation-list-keyboard-navigation.test.ts": 8, + "src/renderer/src/components/automations/automation-list-last-run.test.ts": 26, + "src/renderer/src/components/automations/automation-list-row-identity.test.ts": 7, + "src/renderer/src/components/automations/automation-list-search-rows.test.ts": 12, + "src/renderer/src/components/automations/automation-list-search.test.ts": 12, + "src/renderer/src/components/automations/automation-list-view-sort.test.ts": 175, + "src/renderer/src/components/automations/automation-list-view.test.ts": 15, + "src/renderer/src/components/automations/automation-project-groups.test.ts": 8, + "src/renderer/src/components/automations/automation-run-completion-evidence.test.ts": 7, + "src/renderer/src/components/automations/automation-run-context.test.ts": 5, + "src/renderer/src/components/automations/automation-run-history-keyboard-navigation.test.ts": 14, + "src/renderer/src/components/automations/automation-run-open-target.test.ts": 10, + "src/renderer/src/components/automations/automation-run-output-snapshot-equivalence.test.ts": 69, + "src/renderer/src/components/automations/automation-run-output-snapshot.test.ts": 47, + "src/renderer/src/components/automations/automation-run-view-state.test.ts": 11, + "src/renderer/src/components/automations/automation-runs-dashboard-model.test.ts": 6, + "src/renderer/src/components/automations/automation-scoped-list-client.test.ts": 76, + "src/renderer/src/components/automations/automation-setup-decision.test.ts": 8, + "src/renderer/src/components/automations/automation-source-display.test.ts": 10, + "src/renderer/src/components/automations/automation-target-availability.test.ts": 12, + "src/renderer/src/components/automations/automation-usage-model.test.ts": 6, + "src/renderer/src/components/automations/automation-write-invalidation.test.ts": 8, + "src/renderer/src/components/automations/external-automation-display.test.ts": 6, + "src/renderer/src/components/automations/external-automation-list-entries.test.ts": 7, + "src/renderer/src/components/automations/external-automation-run-table-state.test.ts": 9, + "src/renderer/src/components/automations/external-automation-scope-gating.test.ts": 11, + "src/renderer/src/components/automations/external-automation-scope-keys.test.ts": 7, + "src/renderer/src/components/automations/external-automation-source-availability.test.ts": 5, + "src/renderer/src/components/automations/hermes-cron-output-parse.test.ts": 27, + "src/renderer/src/components/automations/use-automation-host-catalog-readoption.test.tsx": 58, + "src/renderer/src/components/automations/use-automation-host-catalog.test.tsx": 56, + "src/renderer/src/components/automations/use-automation-list-focus-recovery.test.tsx": 26, + "src/renderer/src/components/automations/use-automation-list-search.test.tsx": 33, + "src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx": 53, + "src/renderer/src/components/automations/use-external-automation-scope-retention.test.tsx": 19, + "src/renderer/src/components/automations/use-selected-automation-run-history.test.tsx": 33, + "src/renderer/src/components/browser-cookie-import-google-disclosure.test.tsx": 99, + "src/renderer/src/components/browser-favicon.test.tsx": 55, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.chrome-parity.test.tsx": 391, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx": 346, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.download-notices.test.tsx": 295, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx": 551, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.failure-remount.test.tsx": 274, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx": 111, + "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx": 370, + "src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx": 102, + "src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx": 10, + "src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts": 7, + "src/renderer/src/components/browser-pane/annotate/browser-annotation-output.test.ts": 36, + "src/renderer/src/components/browser-pane/annotate/browser-page-annotation-tray.test.tsx": 326, + "src/renderer/src/components/browser-pane/annotate/markup-drawing-model.test.ts": 15, + "src/renderer/src/components/browser-pane/annotate/markup-screenshot-compose.test.ts": 8, + "src/renderer/src/components/browser-pane/annotate/markup-shape-render.test.ts": 7, + "src/renderer/src/components/browser-pane/annotate/use-markup-draw-hint.test.ts": 17, + "src/renderer/src/components/browser-pane/annotate/useGrabMode.test.ts": 369, + "src/renderer/src/components/browser-pane/annotate/useMarkupEditor.test.ts": 26, + "src/renderer/src/components/browser-pane/annotate/useMarkupPointerHandlers.test.ts": 18, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserAddressBar.test.tsx": 79, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserFind.test.tsx": 152, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserPane.remote-link-routing.test.ts": 6, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserPane.render-ipc.test.ts": 6, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserPane.webview-preferences.test.ts": 20, + "src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.test.tsx": 951, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-address-bar-expansion.test.ts": 7, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-address-bar-suggestions.test.ts": 23, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-deferred-lifecycle.test.tsx": 139, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-detected-browsers-summary.test.ts": 4, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-egress-indicator.test.tsx": 234, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-import-hint-visibility.test.ts": 4, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-navigation-control-row.test.tsx": 57, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-pane-page-selection.test.ts": 4, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.adoption-chrome.test.tsx": 1919, + "src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.retention-props.test.tsx": 93, + "src/renderer/src/components/browser-pane/assemble-chrome/context-menu-positioning.test.ts": 10, + "src/renderer/src/components/browser-pane/assemble-chrome/ssh-routed-browser-page-gate.test.tsx": 175, + "src/renderer/src/components/browser-pane/assemble-chrome/use-browser-page-chrome-focus.test.tsx": 118, + "src/renderer/src/components/browser-pane/browser-client-page-metadata-publisher.test.ts": 262, + "src/renderer/src/components/browser-pane/browser-client-page-metadata-route-census.test.ts": 6, + "src/renderer/src/components/browser-pane/browser-client-page-position-driver.test.ts": 47, + "src/renderer/src/components/browser-pane/browser-client-page-renderer-installation.test.ts": 12, + "src/renderer/src/components/browser-pane/browser-client-page-retained-drag-passthrough.test.ts": 57, + "src/renderer/src/components/browser-pane/browser-client-page-retained-registry.test.ts": 52, + "src/renderer/src/components/browser-pane/browser-download-destination-toast.test.ts": 7, + "src/renderer/src/components/browser-pane/browser-reopen-on-server.test.ts": 13, + "src/renderer/src/components/browser-pane/describe-page/browser-annotation-geometry.test.ts": 8, + "src/renderer/src/components/browser-pane/describe-page/browser-artifact-upload.test.ts": 11, + "src/renderer/src/components/browser-pane/describe-page/browser-favicon-url.test.ts": 7, + "src/renderer/src/components/browser-pane/describe-page/browser-overlay-shortcut-target.test.ts": 7, + "src/renderer/src/components/browser-pane/describe-page/browser-page-url-display.test.ts": 15, + "src/renderer/src/components/browser-pane/describe-page/live-browser-url-registry.test.ts": 6, + "src/renderer/src/components/browser-pane/host-guest/browser-automation-visibility.test.ts": 16, + "src/renderer/src/components/browser-pane/host-guest/browser-focus.test.ts": 9, + "src/renderer/src/components/browser-pane/host-guest/browser-guest-eviction-veto.test.ts": 10, + "src/renderer/src/components/browser-pane/host-guest/browser-guest-page-id-identity.test.ts": 6, + "src/renderer/src/components/browser-pane/host-guest/browser-guest-paint-retention.test.ts": 33, + "src/renderer/src/components/browser-pane/host-guest/browser-guest-retention-site-census.test.ts": 592, + "src/renderer/src/components/browser-pane/host-guest/browser-guest-worktree-retention.test.ts": 15, + "src/renderer/src/components/browser-pane/host-guest/browser-keyboard.test.ts": 20, + "src/renderer/src/components/browser-pane/host-guest/browser-page-evicted-guest-recovery.test.ts": 105, + "src/renderer/src/components/browser-pane/host-guest/browser-page-favicon-retention.test.ts": 12, + "src/renderer/src/components/browser-pane/host-guest/browser-page-guest-recovery.test.ts": 338, + "src/renderer/src/components/browser-pane/host-guest/browser-page-paintability.test.ts": 5, + "src/renderer/src/components/browser-pane/host-guest/browser-page-viewport.test.ts": 28, + "src/renderer/src/components/browser-pane/host-guest/browser-page-webview-surface.test.ts": 21, + "src/renderer/src/components/browser-pane/host-guest/browser-page-webview.test.ts": 5, + "src/renderer/src/components/browser-pane/host-guest/browser-page-zoom.test.ts": 16, + "src/renderer/src/components/browser-pane/host-guest/browser-system-resume.test.ts": 14, + "src/renderer/src/components/browser-pane/host-guest/browser-worktree-surface-paintability.test.ts": 6, + "src/renderer/src/components/browser-pane/host-guest/use-browser-page-slot-viewport.test.ts": 21, + "src/renderer/src/components/browser-pane/host-guest/use-browser-page-viewport-scroll-reporting.test.tsx": 35, + "src/renderer/src/components/browser-pane/host-guest/use-webview-drag-passthrough-active.test.tsx": 35, + "src/renderer/src/components/browser-pane/host-guest/webview-registry.test.ts": 44, + "src/renderer/src/components/browser-pane/navigate/browser-address-bar-navigation.test.ts": 12, + "src/renderer/src/components/browser-pane/navigate/browser-download-progress.test.ts": 7, + "src/renderer/src/components/browser-pane/navigate/browser-load-failure-overlay.test.tsx": 150, + "src/renderer/src/components/browser-pane/navigate/browser-notices.test.ts": 19, + "src/renderer/src/components/browser-pane/navigate/browser-page-download-activity.test.ts": 16, + "src/renderer/src/components/browser-pane/navigate/browser-reload-action.test.ts": 9, + "src/renderer/src/components/browser-pane/navigate/browser-webview-load-failure.test.ts": 11, + "src/renderer/src/components/browser-pane/navigate/chromium-error-page-poll-visibility.test.ts": 40, + "src/renderer/src/components/browser-pane/navigate/chromium-error-page-polling.test.ts": 5, + "src/renderer/src/components/browser-pane/navigate/use-browser-page-reload-actions.test.tsx": 19, + "src/renderer/src/components/browser-pane/restored-client-hosted-recovery-window.test.tsx": 308, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-frame-style.test.ts": 7, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-keyboard.test.ts": 4, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-page-input-model.test.ts": 8, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-page-pane.address-bar.test.tsx": 293, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-page-pane.chrome-chords.test.tsx": 434, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-stream-errors.test.ts": 10, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-stream-lifecycle.test.ts": 61, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-stream-restart-scheduler.test.ts": 12, + "src/renderer/src/components/browser-pane/stream-remote/remote-browser-stream-status.test.ts": 6, + "src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-navigation.test.ts": 37, + "src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-stream-activation.test.ts": 54, + "src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx": 2857, + "src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx": 4032, + "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts": 28, + "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-identity.test.ts": 8, + "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts": 67, + "src/renderer/src/components/browser-pane/workspace-doc/doc-preview-webview-attach.test.ts": 14, + "src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.lazy-ref.test.tsx": 29, + "src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts": 29, + "src/renderer/src/components/browser-pane/workspace-doc/workspace-doc-page-pane.test.tsx": 41, + "src/renderer/src/components/browser-profile-user-agent-option.test.tsx": 82, + "src/renderer/src/components/browser-webauthn-account-dialog.test.tsx": 166, + "src/renderer/src/components/cmd-j/palette-activation-focus-routing.test.ts": 7, + "src/renderer/src/components/cmd-j/palette-duplicate-key-ghost-rows.test.tsx": 26, + "src/renderer/src/components/cmd-j/palette-filter-option-list.test.ts": 15, + "src/renderer/src/components/cmd-j/palette-filter-options.test.ts": 23, + "src/renderer/src/components/cmd-j/palette-filter.test.ts": 12, + "src/renderer/src/components/cmd-j/palette-focus-restore-target.test.ts": 11, + "src/renderer/src/components/cmd-j/palette-host-badge.test.ts": 15, + "src/renderer/src/components/cmd-j/palette-list-entry-render-keys.test.ts": 6, + "src/renderer/src/components/cmd-j/palette-live-status.test.tsx": 183, + "src/renderer/src/components/cmd-j/palette-query-tokens.test.ts": 9, + "src/renderer/src/components/cmd-j/palette-results.test.ts": 42, + "src/renderer/src/components/cmd-j/palette-section-render-cap.test.ts": 12, + "src/renderer/src/components/cmd-j/palette-session-age.test.ts": 16, + "src/renderer/src/components/cmd-j/plugin-quick-actions.test.ts": 9, + "src/renderer/src/components/cmd-j/quick-action-context.test.ts": 20, + "src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts": 27, + "src/renderer/src/components/cmd-j/worktree-palette-cache-inputs.test.ts": 5, + "src/renderer/src/components/codex-restart-chip.test.tsx": 120, + "src/renderer/src/components/codex-restart-notice-key.test.ts": 4, + "src/renderer/src/components/comment-code-context-state.test.ts": 5, + "src/renderer/src/components/comment-reply-target-state.test.ts": 6, + "src/renderer/src/components/confirmation-dialog-refresh-boundary.test.ts": 44, + "src/renderer/src/components/confirmation-dialog.test.tsx": 445, + "src/renderer/src/components/confirmation-skip-preference.test.ts": 109, + "src/renderer/src/components/contextual-tours/ContextualTourControl.test.ts": 8, + "src/renderer/src/components/contextual-tours/ContextualTourOverlay.remeasure.test.tsx": 313, + "src/renderer/src/components/contextual-tours/ContextualTourOverlay.test.tsx": 136, + "src/renderer/src/components/contextual-tours/ContextualTourOverlay.visibility.test.tsx": 2421, + "src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.localization.test.tsx": 191, + "src/renderer/src/components/contextual-tours/contextual-tour-floating-position.test.ts": 1879, + "src/renderer/src/components/contextual-tours/contextual-tour-gate.test.ts": 14, + "src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts": 91, + "src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts": 6, + "src/renderer/src/components/contextual-tours/request-contextual-tour-when-ready.test.ts": 15, + "src/renderer/src/components/contextual-tours/use-contextual-tour.test.ts": 8, + "src/renderer/src/components/contextual-tours/workspace-creation-tour-handoff.test.ts": 9, + "src/renderer/src/components/crash-report/CrashReportDialogSurface.overflow.test.tsx": 109, + "src/renderer/src/components/crash-report/crash-report-submit-notice.test.ts": 13, + "src/renderer/src/components/crash-report/use-crash-report-copy.test.tsx": 21, + "src/renderer/src/components/dashboard-popout/AgentKanbanBoard.test.tsx": 469, + "src/renderer/src/components/dashboard-popout/AgentKanbanCard.test.tsx": 393, + "src/renderer/src/components/dashboard-popout/AgentMap.test.tsx": 1741, + "src/renderer/src/components/dashboard-popout/AgentMapCanvas.performance.test.tsx": 75, + "src/renderer/src/components/dashboard-popout/AgentMapFilterPanel.test.tsx": 126, + "src/renderer/src/components/dashboard-popout/AgentMapMotion.test.tsx": 189, + "src/renderer/src/components/dashboard-popout/AgentMapProjectLabel.test.tsx": 106, + "src/renderer/src/components/dashboard-popout/AgentMapRingHover.test.tsx": 289, + "src/renderer/src/components/dashboard-popout/AgentMapStatusGlow.test.tsx": 732, + "src/renderer/src/components/dashboard-popout/AgentMapTimeRangeField.test.tsx": 1678, + "src/renderer/src/components/dashboard-popout/AgentMapWorkspaceContextMenu.boundary.test.ts": 7, + "src/renderer/src/components/dashboard-popout/AgentMapWorkspaceContextMenu.test.tsx": 2066, + "src/renderer/src/components/dashboard-popout/AgentTerminalDialog.test.tsx": 234, + "src/renderer/src/components/dashboard-popout/AgentTerminalPreview.clipboard-routes.test.tsx": 1314, + "src/renderer/src/components/dashboard-popout/AgentTerminalPreview.option-dead-key.test.tsx": 149, + "src/renderer/src/components/dashboard-popout/AgentTerminalPreview.test.tsx": 1273, + "src/renderer/src/components/dashboard-popout/DashboardHostBadge.test.tsx": 64, + "src/renderer/src/components/dashboard-popout/DashboardPopoutRoot.test.tsx": 26, + "src/renderer/src/components/dashboard-popout/agent-board-filtering.test.ts": 8, + "src/renderer/src/components/dashboard-popout/agent-map-filter.test.ts": 9, + "src/renderer/src/components/dashboard-popout/agent-map-glow.performance.test.ts": 12, + "src/renderer/src/components/dashboard-popout/agent-map-hover-containment.test.ts": 7, + "src/renderer/src/components/dashboard-popout/agent-map-label-declutter.test.ts": 12, + "src/renderer/src/components/dashboard-popout/agent-map-layout.test.ts": 152, + "src/renderer/src/components/dashboard-popout/agent-map-lineage-chevron-path.test.ts": 26, + "src/renderer/src/components/dashboard-popout/agent-map-navigation.test.ts": 9, + "src/renderer/src/components/dashboard-popout/agent-map-node-metadata.test.ts": 10, + "src/renderer/src/components/dashboard-popout/agent-map-node-presentation.test.ts": 4, + "src/renderer/src/components/dashboard-popout/agent-map-quick-views.test.ts": 11, + "src/renderer/src/components/dashboard-popout/agent-map-time-filter.test.ts": 6, + "src/renderer/src/components/dashboard-popout/agent-map-workspace-visibility.test.ts": 5, + "src/renderer/src/components/dashboard-popout/agent-map-worktree-active-status.test.ts": 8, + "src/renderer/src/components/dashboard-popout/agent-map-worktree-lineage-layout.test.ts": 161, + "src/renderer/src/components/dashboard-popout/agent-map-worktree-packing.test.ts": 1600, + "src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts": 9, + "src/renderer/src/components/dashboard-popout/preview-grid-claim.test.ts": 18, + "src/renderer/src/components/dashboard-popout/preview-terminal-ime-bridge-kitty-bytes.test.ts": 228, + "src/renderer/src/components/dashboard-popout/preview-terminal-options.test.ts": 11, + "src/renderer/src/components/dashboard-popout/preview-terminal-right-click-paste.test.ts": 63, + "src/renderer/src/components/dashboard-popout/preview-terminal-shortcuts.test.ts": 13, + "src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts": 11, + "src/renderer/src/components/dashboard-popout/terminal-preview-unavailable-message.test.ts": 10, + "src/renderer/src/components/dashboard-popout/useAgentMapFilters.test.tsx": 36, + "src/renderer/src/components/dashboard-popout/useDashboardSnapshot.test.tsx": 59, + "src/renderer/src/components/dashboard/AgentDashboardDrawer.test.tsx": 227, + "src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.test.tsx": 68, + "src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx": 97, + "src/renderer/src/components/dashboard/agent-dashboard-performance-isolation.test.ts": 6, + "src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts": 5, + "src/renderer/src/components/dashboard/agent-row-lineage-model.test.ts": 6, + "src/renderer/src/components/dashboard/agent-row-pane-live-title.test.ts": 7, + "src/renderer/src/components/dashboard/build-dashboard-bucket-counts.allocation.test.ts": 97, + "src/renderer/src/components/dashboard/build-dashboard-bucket-counts.cache.test.ts": 11, + "src/renderer/src/components/dashboard/build-dashboard-bucket-counts.equivalence.test.ts": 40, + "src/renderer/src/components/dashboard/build-dashboard-snapshot-folder-workspace.test.ts": 21, + "src/renderer/src/components/dashboard/build-dashboard-snapshot-orchestration-routing.test.ts": 15, + "src/renderer/src/components/dashboard/build-dashboard-snapshot.rows-cache.test.ts": 30, + "src/renderer/src/components/dashboard/build-dashboard-snapshot.test.ts": 34, + "src/renderer/src/components/dashboard/dashboard-card-context.test.ts": 14, + "src/renderer/src/components/dashboard/dashboard-card-labels.test.ts": 5, + "src/renderer/src/components/dashboard/dashboard-card-terminal-input.test.ts": 11, + "src/renderer/src/components/dashboard/dashboard-worktree-launch-options.test.ts": 19, + "src/renderer/src/components/dashboard/launch-dashboard-agent.test.ts": 7, + "src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts": 10, + "src/renderer/src/components/dashboard/useAgentBucketCounts.gate.test.ts": 46, + "src/renderer/src/components/dashboard/useAgentBucketCounts.test.tsx": 35, + "src/renderer/src/components/dashboard/useDashboardPopoutBridge.test.tsx": 57, + "src/renderer/src/components/dashboard/useLiveDashboardSnapshot.test.ts": 63, + "src/renderer/src/components/dashboard/useRetainedAgents.test.ts": 66, + "src/renderer/src/components/dashboard/useRetainedAgentsSync.test.ts": 6, + "src/renderer/src/components/dictation/DictationIndicator.localization.test.ts": 7, + "src/renderer/src/components/dictation/DictationIndicator.test.tsx": 86, + "src/renderer/src/components/dictation/dictation-audio-meter.test.ts": 5, + "src/renderer/src/components/dictation/dictation-final-segments.test.ts": 5, + "src/renderer/src/components/dictation/dictation-insertion-target.test.ts": 288, + "src/renderer/src/components/dictation/dictation-meter-store.test.tsx": 15, + "src/renderer/src/components/dictation/dictation-stopped-sessions.test.ts": 9, + "src/renderer/src/components/dictation/microphone-devices.test.ts": 16, + "src/renderer/src/components/dictation/use-hold-dictation-gesture.test.tsx": 43, + "src/renderer/src/components/diff-comments/DiffCommentCard.resize.test.tsx": 127, + "src/renderer/src/components/diff-comments/diff-comment-popover-outside-click.test.tsx": 59, + "src/renderer/src/components/diff-comments/diff-comment-popover-position.test.ts": 8, + "src/renderer/src/components/diff-comments/diff-comment-zone-mouse-events.test.ts": 5, + "src/renderer/src/components/diff-comments/useDiffCommentDecorator.commentable-lines.test.tsx": 43, + "src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx": 28, + "src/renderer/src/components/editor/ChangesModeView.test.tsx": 27, + "src/renderer/src/components/editor/CheckRunCopyButton.test.tsx": 70, + "src/renderer/src/components/editor/CheckRunDetailsPanel.copy.test.tsx": 116, + "src/renderer/src/components/editor/ConflictComponents.test.tsx": 4, + "src/renderer/src/components/editor/EditorContent.markdown-classification.test.tsx": 60, + "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx": 49, + "src/renderer/src/components/editor/EditorContent.test.tsx": 37, + "src/renderer/src/components/editor/EditorPanel.markdown-classification-memoization.test.tsx": 462, + "src/renderer/src/components/editor/EditorPanelHeader.test.tsx": 34, + "src/renderer/src/components/editor/EditorPanelMarkdownActionsMenu.test.tsx": 21, + "src/renderer/src/components/editor/EditorPanelShell.header.test.tsx": 18, + "src/renderer/src/components/editor/ExternalFileChangeBanner.test.tsx": 56, + "src/renderer/src/components/editor/ExternalFileChangeCompareDialog.test.tsx": 186, + "src/renderer/src/components/editor/ImageViewer.test.tsx": 105, + "src/renderer/src/components/editor/LargeDiffLoadPrompt.test.tsx": 42, + "src/renderer/src/components/editor/MarkdownPreview.link-routing.interaction.test.tsx": 106, + "src/renderer/src/components/editor/MarkdownPreview.test.ts": 9, + "src/renderer/src/components/editor/MarkdownPreview.toc-visibility-gate.test.tsx": 107, + "src/renderer/src/components/editor/MarkdownTableOfContentsPanel.test.tsx": 23, + "src/renderer/src/components/editor/MonacoEditor.content-owner.test.tsx": 56, + "src/renderer/src/components/editor/MonacoEditor.font-family.test.tsx": 46, + "src/renderer/src/components/editor/NotesSendMenu.test.tsx": 34, + "src/renderer/src/components/editor/ReviewNotesSendMenuContent.test.tsx": 30, + "src/renderer/src/components/editor/RichMarkdownErrorBoundary.lazy-chunk.test.tsx": 104, + "src/renderer/src/components/editor/RichMarkdownLinkBubble.render.test.tsx": 106, + "src/renderer/src/components/editor/RichMarkdownLinkBubble.test.ts": 3, + "src/renderer/src/components/editor/RichMarkdownSearchBar.ime-enter.test.tsx": 187, + "src/renderer/src/components/editor/RichMarkdownSlashMenu.test.tsx": 14, + "src/renderer/src/components/editor/RichMarkdownTableControls.test.tsx": 272, + "src/renderer/src/components/editor/check-annotation-open.test.ts": 9, + "src/renderer/src/components/editor/check-annotation-path.test.ts": 10, + "src/renderer/src/components/editor/check-job-step-status.test.ts": 5, + "src/renderer/src/components/editor/check-run-clipboard-text.test.ts": 8, + "src/renderer/src/components/editor/check-run-details-fix-with-ai.test.ts": 16, + "src/renderer/src/components/editor/check-run-details-tab.test.ts": 7, + "src/renderer/src/components/editor/closed-editor-tab-cache-sweep.test.ts": 10, + "src/renderer/src/components/editor/closed-editor-tab-disposal.test.ts": 48, + "src/renderer/src/components/editor/combined-diff-on-demand-load.test.ts": 9, + "src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-collapsed-work.test.tsx": 96, + "src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-render.test.tsx": 241, + "src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-windowing.test.tsx": 212, + "src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree.test.ts": 13, + "src/renderer/src/components/editor/combined-diff/browse-files/use-combined-diff-file-tree-resize.test.tsx": 44, + "src/renderer/src/components/editor/combined-diff/browse-files/use-combined-diff-tree-navigation.test.ts": 17, + "src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-initial-section-load.test.ts": 5, + "src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-load-scheduler.test.ts": 7, + "src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-connection.test.ts": 11, + "src/renderer/src/components/editor/combined-diff/load-sections/combined-diff-section-load-state.test.ts": 10, + "src/renderer/src/components/editor/combined-diff/remember-view/use-combined-diff-view-restore.test.tsx": 29, + "src/renderer/src/components/editor/combined-diff/resolve-changes/combined-diff-entries.test.ts": 12, + "src/renderer/src/components/editor/combined-diff/resolve-changes/combined-diff-section-cache-match.test.ts": 5, + "src/renderer/src/components/editor/combined-diff/resolve-changes/combined-diff-section-scaling.test.tsx": 157, + "src/renderer/src/components/editor/combined-diff/resolve-changes/use-combined-diff-section-index-map.test.tsx": 23, + "src/renderer/src/components/editor/combined-diff/review-controls/combined-diff-commit-message.test.ts": 17, + "src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-restore-signal-equivalence.test.tsx": 291, + "src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-scrollbar-drag.test.ts": 8, + "src/renderer/src/components/editor/csv-parse.test.ts": 38, + "src/renderer/src/components/editor/details-markdown-html.test.ts": 24, + "src/renderer/src/components/editor/diff-editor-line-number-options.test.ts": 8, + "src/renderer/src/components/editor/diff-editor-shift-wheel-scroll.test.ts": 14, + "src/renderer/src/components/editor/diff-editor-whitespace-options.test.ts": 6, + "src/renderer/src/components/editor/diff-editor-word-wrap-options.test.ts": 4, + "src/renderer/src/components/editor/diff-line-stats.test.ts": 10, + "src/renderer/src/components/editor/diff-model-swap-view-state.test.ts": 8, + "src/renderer/src/components/editor/diff-monaco-model-disposal.test.ts": 12, + "src/renderer/src/components/editor/diff-navigation-context.test.tsx": 45, + "src/renderer/src/components/editor/diff-section-layout.test.ts": 30, + "src/renderer/src/components/editor/diff-section-preview.test.ts": 9, + "src/renderer/src/components/editor/diff-viewer-large-diff-save-action.test.ts": 7, + "src/renderer/src/components/editor/editor-autosave-conflict-flow.test.ts": 37, + "src/renderer/src/components/editor/editor-autosave-controller.test.ts": 40, + "src/renderer/src/components/editor/editor-autosave.test.ts": 17, + "src/renderer/src/components/editor/editor-cmd-save-target.test.ts": 10, + "src/renderer/src/components/editor/editor-content-dirty-state.test.ts": 21, + "src/renderer/src/components/editor/editor-external-watch-path-index.test.ts": 9, + "src/renderer/src/components/editor/editor-file-save-attempt.test.ts": 11, + "src/renderer/src/components/editor/editor-header.test.ts": 8, + "src/renderer/src/components/editor/editor-labels.test.ts": 6, + "src/renderer/src/components/editor/editor-panel-diff-reload.test.ts": 8, + "src/renderer/src/components/editor/editor-panel-draft-selector.test.ts": 20, + "src/renderer/src/components/editor/editor-panel-git-entry-selector.test.ts": 24, + "src/renderer/src/components/editor/editor-panel-render-model.test.ts": 29, + "src/renderer/src/components/editor/editor-path-move-inflight.test.ts": 9, + "src/renderer/src/components/editor/editor-restored-tab-conflict-scan.test.ts": 38, + "src/renderer/src/components/editor/editor-self-write-registry.test.ts": 20, + "src/renderer/src/components/editor/editor-shortcuts.test.ts": 23, + "src/renderer/src/components/editor/export-active-markdown.test.ts": 7, + "src/renderer/src/components/editor/file-editor-word-wrap-options.test.ts": 4, + "src/renderer/src/components/editor/image-viewer-zoom.test.ts": 10, + "src/renderer/src/components/editor/ipynb-code-cell-lines.test.ts": 138, + "src/renderer/src/components/editor/ipynb-parse.test.ts": 26, + "src/renderer/src/components/editor/large-diff-render-limit.test.ts": 103, + "src/renderer/src/components/editor/line-copy-path.test.ts": 5, + "src/renderer/src/components/editor/local-log-tail-decoder.test.ts": 8, + "src/renderer/src/components/editor/markdown-artifact-upload.test.ts": 7, + "src/renderer/src/components/editor/markdown-dirty-state.test.ts": 154, + "src/renderer/src/components/editor/markdown-doc-completions.test.ts": 11, + "src/renderer/src/components/editor/markdown-doc-links.test.ts": 29, + "src/renderer/src/components/editor/markdown-document-list-request.test.ts": 14, + "src/renderer/src/components/editor/markdown-document-worktree-path-selector.test.ts": 758, + "src/renderer/src/components/editor/markdown-export-extract.test.ts": 21, + "src/renderer/src/components/editor/markdown-export-html.test.ts": 5, + "src/renderer/src/components/editor/markdown-frontmatter.test.ts": 7, + "src/renderer/src/components/editor/markdown-heading-slug.test.ts": 6, + "src/renderer/src/components/editor/markdown-internal-links.test.ts": 10, + "src/renderer/src/components/editor/markdown-preview-annotation-shortcut.test.ts": 25, + "src/renderer/src/components/editor/markdown-preview-controls.test.ts": 10, + "src/renderer/src/components/editor/markdown-preview-links.test.ts": 8, + "src/renderer/src/components/editor/markdown-preview-local-images.test.ts": 67, + "src/renderer/src/components/editor/markdown-preview-search-crash.test.tsx": 24, + "src/renderer/src/components/editor/markdown-preview-search.test.ts": 16, + "src/renderer/src/components/editor/markdown-preview-url-transform.test.ts": 14, + "src/renderer/src/components/editor/markdown-reference-link-normalization.test.ts": 25, + "src/renderer/src/components/editor/markdown-render-mode.test.ts": 5, + "src/renderer/src/components/editor/markdown-rich-mode-eligibility-cache.test.ts": 225, + "src/renderer/src/components/editor/markdown-rich-mode.test.ts": 276, + "src/renderer/src/components/editor/markdown-rich-size-limit.test.ts": 7, + "src/renderer/src/components/editor/markdown-round-trip.test.ts": 388, + "src/renderer/src/components/editor/markdown-table-of-contents.test.ts": 57, + "src/renderer/src/components/editor/markdown-toc-collapse-state.test.ts": 5, + "src/renderer/src/components/editor/markdown-toc-visibility-gate.test.ts": 18, + "src/renderer/src/components/editor/mermaid-config.test.ts": 6, + "src/renderer/src/components/editor/monaco-auto-height.test.ts": 42, + "src/renderer/src/components/editor/monaco-codebase-search.test.ts": 8, + "src/renderer/src/components/editor/monaco-conflict-decorations.test.ts": 12, + "src/renderer/src/components/editor/monaco-content-sync.test.ts": 14, + "src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts": 44, + "src/renderer/src/components/editor/monaco-context-menu-paste.test.ts": 59, + "src/renderer/src/components/editor/monaco-find-options.test.ts": 5, + "src/renderer/src/components/editor/monaco-find-widget.test.ts": 12, + "src/renderer/src/components/editor/monaco-large-text-paste.test.ts": 19, + "src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.offset-scan.test.ts": 72, + "src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.test.ts": 15, + "src/renderer/src/components/editor/monaco-markdown-selection-annotation.test.ts": 9, + "src/renderer/src/components/editor/monaco-programmatic-sync.test.ts": 5, + "src/renderer/src/components/editor/monaco-reveal-range.test.ts": 4, + "src/renderer/src/components/editor/monaco-view-state-persistence.test.ts": 5, + "src/renderer/src/components/editor/pdf-scale-preference.test.ts": 8, + "src/renderer/src/components/editor/pdf-view-position.test.ts": 19, + "src/renderer/src/components/editor/pending-editor-focus-request.test.ts": 6, + "src/renderer/src/components/editor/position-stable-node-view-update.test.ts": 7, + "src/renderer/src/components/editor/restored-editor-owner-save-lifecycle.test.ts": 321, + "src/renderer/src/components/editor/rich-markdown-auto-focus.test.ts": 12, + "src/renderer/src/components/editor/rich-markdown-code-block-languages.test.ts": 8, + "src/renderer/src/components/editor/rich-markdown-commands.test.ts": 95, + "src/renderer/src/components/editor/rich-markdown-context-command-routing.test.ts": 56, + "src/renderer/src/components/editor/rich-markdown-cut.test.ts": 142, + "src/renderer/src/components/editor/rich-markdown-details-keyboard.test.ts": 83, + "src/renderer/src/components/editor/rich-markdown-doc-link.test.ts": 7, + "src/renderer/src/components/editor/rich-markdown-editor-click-routing.test.ts": 13, + "src/renderer/src/components/editor/rich-markdown-editor-config.test.ts": 15, + "src/renderer/src/components/editor/rich-markdown-empty-paragraph-delete.test.ts": 57, + "src/renderer/src/components/editor/rich-markdown-html-superscript-link.test.ts": 581, + "src/renderer/src/components/editor/rich-markdown-image-insert-code-block.test.ts": 270, + "src/renderer/src/components/editor/rich-markdown-image-insert.test.ts": 54, + "src/renderer/src/components/editor/rich-markdown-inline-image-paragraph.test.ts": 90, + "src/renderer/src/components/editor/rich-markdown-key-handler.test.ts": 83, + "src/renderer/src/components/editor/rich-markdown-large-text-paste.test.ts": 17, + "src/renderer/src/components/editor/rich-markdown-link-clipboard.test.ts": 7, + "src/renderer/src/components/editor/rich-markdown-link-shortcut.test.ts": 96, + "src/renderer/src/components/editor/rich-markdown-list-continuation.test.ts": 35, + "src/renderer/src/components/editor/rich-markdown-list-tokenizers.test.ts": 11, + "src/renderer/src/components/editor/rich-markdown-local-image.test.ts": 128, + "src/renderer/src/components/editor/rich-markdown-lowlight-cache.test.ts": 80, + "src/renderer/src/components/editor/rich-markdown-lowlight.test.ts": 2575, + "src/renderer/src/components/editor/rich-markdown-normalize.test.ts": 43, + "src/renderer/src/components/editor/rich-markdown-paragraph.test.ts": 9, + "src/renderer/src/components/editor/rich-markdown-paste-handler.test.ts": 8, + "src/renderer/src/components/editor/rich-markdown-paste-image.test.ts": 11, + "src/renderer/src/components/editor/rich-markdown-range-bounds.test.ts": 66, + "src/renderer/src/components/editor/rich-markdown-review-annotations.test.ts": 15, + "src/renderer/src/components/editor/rich-markdown-review-note-layout.test.ts": 6, + "src/renderer/src/components/editor/rich-markdown-review-rail-blocks.test.ts": 203, + "src/renderer/src/components/editor/rich-markdown-review-text-ranges.test.ts": 26, + "src/renderer/src/components/editor/rich-markdown-search-matches-cache.test.ts": 38, + "src/renderer/src/components/editor/rich-markdown-search.test.ts": 10, + "src/renderer/src/components/editor/rich-markdown-serialization-commit.test.ts": 21, + "src/renderer/src/components/editor/rich-markdown-slash-command-filter.test.ts": 7, + "src/renderer/src/components/editor/rich-markdown-source-reconcile.test.ts": 332, + "src/renderer/src/components/editor/rich-markdown-tab-key-handler.test.ts": 130, + "src/renderer/src/components/editor/rich-markdown-table-actions.test.ts": 475, + "src/renderer/src/components/editor/rich-markdown-table-control-layout.test.ts": 4, + "src/renderer/src/components/editor/rich-markdown-table-keyboard.test.ts": 105, + "src/renderer/src/components/editor/rich-markdown-terminal-path-paste.test.ts": 11, + "src/renderer/src/components/editor/rich-markdown-toc-heading-target.test.ts": 11, + "src/renderer/src/components/editor/selection-copy.test.ts": 8, + "src/renderer/src/components/editor/setup-contextual-copy.test.ts": 10, + "src/renderer/src/components/editor/untitled-file-rename-path.test.ts": 6, + "src/renderer/src/components/editor/use-monaco-editor-decorations.doc-link-refresh.test.tsx": 42, + "src/renderer/src/components/editor/use-rich-markdown-table-context-menu.test.tsx": 86, + "src/renderer/src/components/editor/use-rich-markdown-table-control-target.test.tsx": 83, + "src/renderer/src/components/editor/useDiffViewerLargeDiffLifecycle.test.tsx": 16, + "src/renderer/src/components/editor/useEditorCmdSaveRequest.test.tsx": 34, + "src/renderer/src/components/editor/useEditorPanelContentState.test.tsx": 823, + "src/renderer/src/components/editor/useEditorPanelExternalContentEvents.test.tsx": 32, + "src/renderer/src/components/editor/useEditorPanelFileLoadRetry.test.tsx": 53, + "src/renderer/src/components/editor/useEditorPanelRemoteSiblingContentState.test.tsx": 25, + "src/renderer/src/components/editor/useEditorPanelVisibilityContentState.test.tsx": 151, + "src/renderer/src/components/editor/useIpynbCellExecution.test.tsx": 78, + "src/renderer/src/components/editor/useIpynbDocumentEditing.test.tsx": 29, + "src/renderer/src/components/editor/useLinkBubble.test.tsx": 37, + "src/renderer/src/components/editor/useLocalImageSrc.test.ts": 74, + "src/renderer/src/components/editor/useLocalLogTail.test.tsx": 234, + "src/renderer/src/components/editor/useMarkdownDocuments.test.ts": 9, + "src/renderer/src/components/editor/useRichMarkdownEditorInstance.integration.test.tsx": 71, + "src/renderer/src/components/editor/useRichMarkdownEditorInstance.test.tsx": 23, + "src/renderer/src/components/editor/useRichMarkdownPendingFocus.test.ts": 20, + "src/renderer/src/components/editor/useRichMarkdownProgrammaticSync.test.ts": 77, + "src/renderer/src/components/editor/useRichMarkdownReviewController.open-guard.test.ts": 17, + "src/renderer/src/components/editor/useRichMarkdownSearch.reuse.test.tsx": 55, + "src/renderer/src/components/editor/useRichMarkdownSearch.test.tsx": 31, + "src/renderer/src/components/emulator-pane/MobileEmulatorTabIntroCallout.test.tsx": 60, + "src/renderer/src/components/emulator-pane/emulator-device-frame-layout.test.ts": 10, + "src/renderer/src/components/emulator-pane/emulator-device-frame-visibility.test.tsx": 158, + "src/renderer/src/components/emulator-pane/emulator-device-frame.input.test.tsx": 188, + "src/renderer/src/components/emulator-pane/emulator-device-state.test.ts": 5, + "src/renderer/src/components/emulator-pane/emulator-keyboard-paste.test.ts": 16, + "src/renderer/src/components/emulator-pane/emulator-screen-gesture.test.ts": 11, + "src/renderer/src/components/emulator-pane/emulator-screen-stream-content.test.tsx": 43, + "src/renderer/src/components/emulator-pane/mobile-emulator-agent-setup-cli-state.test.ts": 6, + "src/renderer/src/components/emulator-pane/mobile-emulator-agent-setup-visibility.test.ts": 4, + "src/renderer/src/components/emulator-pane/mobile-emulator-hidden-toast.test.tsx": 10, + "src/renderer/src/components/emulator-pane/mobile-emulator-tab-intro-visibility.test.ts": 7, + "src/renderer/src/components/emulator-pane/use-emulator-pane-session.test.tsx": 53, + "src/renderer/src/components/emulator-pane/use-mobile-emulator-tab-intro-actions.test.tsx": 43, + "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.lazy-chunk.test.tsx": 86, + "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.never-landed-reload.test.tsx": 51, + "src/renderer/src/components/error-boundaries/react-185-bystander-attribution.test.tsx": 100, + "src/renderer/src/components/feature-interaction-writer-boundaries.test.ts": 16, + "src/renderer/src/components/feature-tips/CliSkillSetupTerminal.test.tsx": 33, + "src/renderer/src/components/feature-tips/CmdJPaletteFeatureTipVisual.test.tsx": 71, + "src/renderer/src/components/feature-tips/CmdJPaletteTipDialog.test.tsx": 35, + "src/renderer/src/components/feature-tips/VoiceDictationFeatureTipVisual.test.tsx": 72, + "src/renderer/src/components/feature-tips/VoiceDictationTipDialog.test.tsx": 44, + "src/renderer/src/components/feature-tips/feature-tip-cli-install-action.test.ts": 9, + "src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts": 10, + "src/renderer/src/components/feature-tips/feature-tip-startup-gate.test.ts": 12, + "src/renderer/src/components/feature-tips/feature-tip-telemetry.test.ts": 8, + "src/renderer/src/components/feature-wall/AgentCapabilitiesSetupAction.test.ts": 6, + "src/renderer/src/components/feature-wall/ConnectIntegrationsList.test.tsx": 181, + "src/renderer/src/components/feature-wall/FeatureWallSetupWorkflowActions.test.tsx": 74, + "src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts": 113, + "src/renderer/src/components/feature-wall/KeepAwakeCard.test.tsx": 99, + "src/renderer/src/components/feature-wall/browser-animated-visual-sequence.test.ts": 6, + "src/renderer/src/components/feature-wall/feature-wall-animation-visibility.test.tsx": 163, + "src/renderer/src/components/feature-wall/feature-wall-rail-navigation.test.ts": 9, + "src/renderer/src/components/feature-wall/feature-wall-setup-checklist-localized-copy.test.ts": 9, + "src/renderer/src/components/feature-wall/feature-wall-setup-progress.test.ts": 12, + "src/renderer/src/components/feature-wall/feature-wall-shortcut-labels.test.tsx": 72, + "src/renderer/src/components/feature-wall/feature-wall-usage-tracking.test.ts": 13, + "src/renderer/src/components/feature-wall/use-feature-wall-completion.test.ts": 8, + "src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.test.ts": 8, + "src/renderer/src/components/feature-wall/use-integration-connection-status.test.ts": 15, + "src/renderer/src/components/feature-wall/workbench-terminal-storyboard-sequence.test.ts": 9, + "src/renderer/src/components/file-path-cursor-tooltip.test.ts": 5, + "src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx": 35, + "src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.freshness.test.tsx": 163, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.bounds.test.tsx": 365, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.empty-state.test.tsx": 526, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.focus.test.tsx": 615, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.markdown-editor.test.tsx": 782, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx": 878, + "src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tab-lifecycle.test.tsx": 693, + "src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.test.tsx": 84, + "src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.test.tsx": 10, + "src/renderer/src/components/floating-terminal/floating-terminal-panel-bounds.test.ts": 17, + "src/renderer/src/components/floating-terminal/floating-terminal-panel-inputs.test.ts": 6, + "src/renderer/src/components/floating-terminal/floating-terminal-panel-view-state.test.ts": 9, + "src/renderer/src/components/floating-terminal/floating-terminal-trigger-position.test.ts": 17, + "src/renderer/src/components/floating-terminal/floating-workspace-tab-reorder.test.ts": 73, + "src/renderer/src/components/floating-terminal/terminal-pane-handle-registry.test.ts": 11, + "src/renderer/src/components/github-body-draft-state.test.ts": 5, + "src/renderer/src/components/github-checks-tab-state.test.ts": 11, + "src/renderer/src/components/github-enterprise-slug-routing-boundary.test.ts": 6, + "src/renderer/src/components/github-item-dialog-source-boundary.test.ts": 13, + "src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-section-index.test.tsx": 49, + "src/renderer/src/components/github-link-copy-state.test.ts": 6, + "src/renderer/src/components/github-pr-merge-state.test.ts": 25, + "src/renderer/src/components/github-pr-reviewer-display.test.ts": 11, + "src/renderer/src/components/github-project/GhAuthErrorHelp.test.ts": 57, + "src/renderer/src/components/github-project/ProjectRoadmap.test.tsx": 257, + "src/renderer/src/components/github-project/github-project-picker-filter.test.ts": 10, + "src/renderer/src/components/github-project/group-sort.test.ts": 13, + "src/renderer/src/components/github-project/project-dialog-state.test.ts": 10, + "src/renderer/src/components/github-project/project-picker-browse-cache.test.ts": 14, + "src/renderer/src/components/github-project/project-picker-input.test.ts": 10, + "src/renderer/src/components/github-project/project-row-filtering.test.ts": 14, + "src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts": 11, + "src/renderer/src/components/github-project/project-visible-table-cache.test.ts": 6, + "src/renderer/src/components/github/PRFilterPickers.test.ts": 6, + "src/renderer/src/components/github/github-markdown-image-url.test.ts": 5, + "src/renderer/src/components/github/github-mention-option-filter.test.ts": 10, + "src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts": 18, + "src/renderer/src/components/github/github-user-avatar.test.tsx": 66, + "src/renderer/src/components/github/github-work-item-assignee-filter.test.ts": 10, + "src/renderer/src/components/github/github-work-item-label-filter.test.ts": 7, + "src/renderer/src/components/github/pr-comment-code-context.test.ts": 29, + "src/renderer/src/components/github/pr-file-content-size.test.ts": 5, + "src/renderer/src/components/github/repro-8784-ghe-avatar-fallback.test.ts": 13, + "src/renderer/src/components/github/use-image-input.test.ts": 186, + "src/renderer/src/components/hover-reveal-touch-action-visibility.test.ts": 12, + "src/renderer/src/components/jira-create-adf.test.ts": 10, + "src/renderer/src/components/jira-project-picker-filter.test.ts": 11, + "src/renderer/src/components/landing-preflight-dismissal.test.ts": 12, + "src/renderer/src/components/landing-preflight-issues.test.ts": 14, + "src/renderer/src/components/landing-preflight-runtime-boundary.test.ts": 59, + "src/renderer/src/components/linear-api-key-dialog-state.test.ts": 6, + "src/renderer/src/components/linear-issue-attribute-filter-coverage-agreement.test.ts": 1157, + "src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx": 1107, + "src/renderer/src/components/linear-issue-attribute-filter-primary-team.test.ts": 21, + "src/renderer/src/components/linear-issue-attribute-filter-team-ids.test.ts": 22, + "src/renderer/src/components/linear-issue-project-selector.test.tsx": 88, + "src/renderer/src/components/linear-issue-text-draft-state.test.ts": 4, + "src/renderer/src/components/linear-issue-text-save-plan.test.ts": 6, + "src/renderer/src/components/linear-issue-view-storage.test.ts": 11, + "src/renderer/src/components/linear-issue-workspace-api-parity.test.ts": 9, + "src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx": 47, + "src/renderer/src/components/linear-issue-workspace-header.test.ts": 5, + "src/renderer/src/components/linear-issue-workspace-text.test.ts": 7, + "src/renderer/src/components/linear-project-presentation.test.ts": 9, + "src/renderer/src/components/linear-project-search-query.test.ts": 5, + "src/renderer/src/components/linear-project-view-surfaces.test.tsx": 104, + "src/renderer/src/components/linear-scope-selector.test.ts": 8, + "src/renderer/src/components/link-actions/LinkActionPopover.test.tsx": 167, + "src/renderer/src/components/maintenance/update-card/update-card-error-model.test.ts": 13, + "src/renderer/src/components/mobile/MobileHero.test.tsx": 637, + "src/renderer/src/components/mobile/MobilePage.test.tsx": 685, + "src/renderer/src/components/mobile/MobilePageToolbar.test.tsx": 41, + "src/renderer/src/components/mobile/NetworkInterfacePicker.test.tsx": 77, + "src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx": 173, + "src/renderer/src/components/mobile/mobile-page-stage.test.ts": 4, + "src/renderer/src/components/mobile/paired-mobile-devices.test.ts": 5, + "src/renderer/src/components/mobile/use-mobile-page-paired-devices.test.ts": 85, + "src/renderer/src/components/mobile/use-mobile-pairing-address-preference.test.tsx": 35, + "src/renderer/src/components/native-chat/NativeChatAutocompleteMenus.test.tsx": 69, + "src/renderer/src/components/native-chat/NativeChatBackgroundTasksStatus.test.tsx": 142, + "src/renderer/src/components/native-chat/NativeChatComposer.test.tsx": 164, + "src/renderer/src/components/native-chat/NativeChatComposerActions.test.tsx": 64, + "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.test.tsx": 72, + "src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx": 207, + "src/renderer/src/components/native-chat/NativeChatMessageList.provider-frame.test.tsx": 20, + "src/renderer/src/components/native-chat/NativeChatMessageList.stream-render.perf.test.tsx": 893, + "src/renderer/src/components/native-chat/NativeChatMessageList.task-list-frames.test.tsx": 515, + "src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx": 376, + "src/renderer/src/components/native-chat/NativeChatMessageList.tool-stream-cost.test.tsx": 2900, + "src/renderer/src/components/native-chat/NativeChatMessageList.turn-history.test.tsx": 355, + "src/renderer/src/components/native-chat/NativeChatMessageList.turn-indicator.test.tsx": 374, + "src/renderer/src/components/native-chat/NativeChatMessageList.turn-timing.test.tsx": 126, + "src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx": 3124, + "src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx": 180, + "src/renderer/src/components/native-chat/NativeChatMessageTimestamp.test.tsx": 359, + "src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx": 192, + "src/renderer/src/components/native-chat/NativeChatPromptEditor.test.tsx": 137, + "src/renderer/src/components/native-chat/NativeChatQuestionCard.test.tsx": 153, + "src/renderer/src/components/native-chat/NativeChatResolutionReceipt.test.tsx": 268, + "src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsx": 155, + "src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx": 509, + "src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx": 15241, + "src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx": 15300, + "src/renderer/src/components/native-chat/NativeChatSubagentRun.test.tsx": 187, + "src/renderer/src/components/native-chat/NativeChatTaskList.test.tsx": 175, + "src/renderer/src/components/native-chat/NativeChatToolRun.identity.test.tsx": 253, + "src/renderer/src/components/native-chat/NativeChatToolRun.test.tsx": 438, + "src/renderer/src/components/native-chat/NativeChatTranscriptChrome.test.tsx": 107, + "src/renderer/src/components/native-chat/NativeChatView.test.tsx": 58, + "src/renderer/src/components/native-chat/StructuredAgentSessionHandoffChrome.test.tsx": 65, + "src/renderer/src/components/native-chat/StructuredAgentSessionPaneOverlayLayer.test.tsx": 49, + "src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx": 410, + "src/renderer/src/components/native-chat/background-task-roster.test.ts": 21, + "src/renderer/src/components/native-chat/claude-model-switch-confirmation.test.ts": 14, + "src/renderer/src/components/native-chat/claude-terminal-session-options.test.ts": 13, + "src/renderer/src/components/native-chat/native-chat-assembler-merge-parity.test.ts": 8, + "src/renderer/src/components/native-chat/native-chat-attachment-upload.test.ts": 12, + "src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts": 7, + "src/renderer/src/components/native-chat/native-chat-availability.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-composer-autogrow.test.tsx": 178, + "src/renderer/src/components/native-chat/native-chat-composer-composition.test.tsx": 469, + "src/renderer/src/components/native-chat/native-chat-composer-drop-scope.test.tsx": 153, + "src/renderer/src/components/native-chat/native-chat-composer-reveal-focus.test.tsx": 71, + "src/renderer/src/components/native-chat/native-chat-composer-scope-cache.test.ts": 5, + "src/renderer/src/components/native-chat/native-chat-composer-state.test.ts": 25, + "src/renderer/src/components/native-chat/native-chat-diff.test.ts": 13, + "src/renderer/src/components/native-chat/native-chat-dismiss-key.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-draft-cache.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-file-link.test.ts": 10, + "src/renderer/src/components/native-chat/native-chat-font-scale.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-http-link-source-owner.test.ts": 13, + "src/renderer/src/components/native-chat/native-chat-image-paste.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-image-runtime-context.test.ts": 8, + "src/renderer/src/components/native-chat/native-chat-incremental-assembler.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-interactive-prompt.test.ts": 17, + "src/renderer/src/components/native-chat/native-chat-launch-default-adoption.test.ts": 14, + "src/renderer/src/components/native-chat/native-chat-launch-draft-resolution.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-launch-draft-send.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-launch-session-options.test.ts": 5, + "src/renderer/src/components/native-chat/native-chat-layout-actions.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-leaf-routing.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-live-status.test.ts": 13, + "src/renderer/src/components/native-chat/native-chat-message-grouping.test.ts": 8, + "src/renderer/src/components/native-chat/native-chat-message-list-projection.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-noise.test.ts": 8, + "src/renderer/src/components/native-chat/native-chat-pagination.test.ts": 5, + "src/renderer/src/components/native-chat/native-chat-pane-resolution.test.ts": 15, + "src/renderer/src/components/native-chat/native-chat-pending-occurrence.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-pending.test.ts": 36, + "src/renderer/src/components/native-chat/native-chat-pinned-rows.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-preassembled-session-parity.test.ts": 267, + "src/renderer/src/components/native-chat/native-chat-pty-retired-model.test.ts": 12, + "src/renderer/src/components/native-chat/native-chat-pty-session-options.test.ts": 235, + "src/renderer/src/components/native-chat/native-chat-retire-persisted-model.test.ts": 88, + "src/renderer/src/components/native-chat/native-chat-row-height-estimate.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-runtime-owner.test.ts": 7, + "src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts": 1210, + "src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts": 187, + "src/renderer/src/components/native-chat/native-chat-scrape-fallback.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-send-eligibility.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-send.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-session-assembler.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-session-option-enrichment.test.ts": 325, + "src/renderer/src/components/native-chat/native-chat-session-option-labels.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-session-transport.test.ts": 49, + "src/renderer/src/components/native-chat/native-chat-shared-copy-matches-catalog.test.ts": 5, + "src/renderer/src/components/native-chat/native-chat-shortcut.test.ts": 7, + "src/renderer/src/components/native-chat/native-chat-split-shortcut.test.ts": 7, + "src/renderer/src/components/native-chat/native-chat-stop-layering.test.ts": 4, + "src/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsx": 271, + "src/renderer/src/components/native-chat/native-chat-tab-agent-entry.test.ts": 7, + "src/renderer/src/components/native-chat/native-chat-task-list-history.test.ts": 10, + "src/renderer/src/components/native-chat/native-chat-task-list-state.test.ts": 13, + "src/renderer/src/components/native-chat/native-chat-tool-fold.test.ts": 12, + "src/renderer/src/components/native-chat/native-chat-tool-summary.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-transcript-slots.test.ts": 6, + "src/renderer/src/components/native-chat/native-chat-turn-diffs.test.ts": 25, + "src/renderer/src/components/native-chat/native-chat-typing-indicator.test.ts": 11, + "src/renderer/src/components/native-chat/native-chat-typing-redirect.test.ts": 5, + "src/renderer/src/components/native-chat/native-chat-view-state.test.ts": 9, + "src/renderer/src/components/native-chat/native-chat-web-link-actions.test.ts": 18, + "src/renderer/src/components/native-chat/native-chat-working-status-shared-clock.test.tsx": 55, + "src/renderer/src/components/native-chat/native-chat-working-suppression.test.ts": 8, + "src/renderer/src/components/native-chat/structured-agent-session-message-projection.test.ts": 8, + "src/renderer/src/components/native-chat/structured-agent-session-read-transport.test.ts": 24, + "src/renderer/src/components/native-chat/structured-session-takeover-report.test.tsx": 74, + "src/renderer/src/components/native-chat/use-native-chat-composer-app-menu-selection.test.tsx": 65, + "src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx": 83, + "src/renderer/src/components/native-chat/use-native-chat-composer-catalog.test.tsx": 45, + "src/renderer/src/components/native-chat/use-native-chat-composer-keydown.test.tsx": 30, + "src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx": 27, + "src/renderer/src/components/native-chat/use-native-chat-context-menu.test.tsx": 45, + "src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx": 46, + "src/renderer/src/components/native-chat/use-native-chat-hook-status.test.ts": 4, + "src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx": 34, + "src/renderer/src/components/native-chat/use-native-chat-launch-draft-adoption.test.tsx": 51, + "src/renderer/src/components/native-chat/use-native-chat-link-actions.test.tsx": 320, + "src/renderer/src/components/native-chat/use-native-chat-live-session-pending.test.ts": 63, + "src/renderer/src/components/native-chat/use-native-chat-live-session-visibility.test.ts": 75, + "src/renderer/src/components/native-chat/use-native-chat-live-session.test.ts": 158, + "src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx": 26, + "src/renderer/src/components/native-chat/use-native-chat-retained-session.test.ts": 32, + "src/renderer/src/components/native-chat/use-native-chat-send-lifecycle.test.tsx": 17, + "src/renderer/src/components/native-chat/use-native-chat-session-option-command.test.tsx": 30, + "src/renderer/src/components/native-chat/use-native-chat-session-options.test.ts": 248, + "src/renderer/src/components/native-chat/use-native-chat-skills.react.test.tsx": 396, + "src/renderer/src/components/native-chat/use-native-chat-skills.test.ts": 8, + "src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsx": 193, + "src/renderer/src/components/native-chat/use-native-chat-toggle-shortcut.test.ts": 10, + "src/renderer/src/components/native-chat/use-native-chat-transcript-window.options.test.tsx": 88, + "src/renderer/src/components/native-chat/use-structured-agent-session-hold.test.tsx": 199, + "src/renderer/src/components/native-chat/use-structured-agent-session-messages.test.tsx": 24, + "src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx": 870, + "src/renderer/src/components/native-chat/use-structured-agent-session-read.test.tsx": 596, + "src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx": 605, + "src/renderer/src/components/native-chat/use-structured-agent-turn-timing.test.tsx": 23, + "src/renderer/src/components/network/CustomAddressDialog.test.tsx": 72, + "src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.test.tsx": 175, + "src/renderer/src/components/new-workspace/ProjectCombobox.dialog-handoff.test.tsx": 499, + "src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx": 245, + "src/renderer/src/components/new-workspace/ProjectComboboxRow.test.tsx": 65, + "src/renderer/src/components/new-workspace/SetProjectLocationDialog.test.tsx": 999, + "src/renderer/src/components/new-workspace/SmartWorkspaceNameField-provider-boundaries.test.ts": 7, + "src/renderer/src/components/new-workspace/SmartWorkspaceNameField-repo-slug-routing.test.ts": 14, + "src/renderer/src/components/new-workspace/SmartWorkspaceNameField-source-boundaries.test.ts": 5, + "src/renderer/src/components/new-workspace/SmartWorkspaceNameField.ime-enter.test.tsx": 253, + "src/renderer/src/components/new-workspace/SmartWorkspaceNameField.jira-accessibility.test.tsx": 500, + "src/renderer/src/components/new-workspace/project-combobox-matching.test.ts": 29, + "src/renderer/src/components/new-workspace/smart-workspace-command-value.test.ts": 7, + "src/renderer/src/components/new-workspace/smart-workspace-localized-options.test.ts": 59, + "src/renderer/src/components/new-workspace/smart-workspace-source-popover-focus.test.ts": 17, + "src/renderer/src/components/new-workspace/smart-workspace-source-results.test.ts": 16, + "src/renderer/src/components/new-workspace/use-jira-source-connection.test.tsx": 46, + "src/renderer/src/components/new-workspace/use-jira-url-source.test.tsx": 39, + "src/renderer/src/components/new-workspace/use-recent-project-ids.test.ts": 6, + "src/renderer/src/components/onboarding/AgentStep.test.tsx": 78, + "src/renderer/src/components/onboarding/FeatureSetupInlineTerminal.test.tsx": 31, + "src/renderer/src/components/onboarding/NotificationStep.test.tsx": 61, + "src/renderer/src/components/onboarding/OnboardingFlow.test.tsx": 128, + "src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.command-finished.test.tsx": 47, + "src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.test.ts": 4, + "src/renderer/src/components/onboarding/ThemeStep.test.ts": 8, + "src/renderer/src/components/onboarding/WindowsTerminalStep.test.tsx": 51, + "src/renderer/src/components/onboarding/agent-picked-payload.test.ts": 10, + "src/renderer/src/components/onboarding/onboarding-dismiss-target.test.ts": 4, + "src/renderer/src/components/onboarding/onboarding-feature-setup.test.ts": 23, + "src/renderer/src/components/onboarding/onboarding-folder-agent-startup.test.ts": 10, + "src/renderer/src/components/onboarding/onboarding-settings-hydration.test.ts": 5, + "src/renderer/src/components/onboarding/use-onboarding-flow-persistence.test.ts": 34, + "src/renderer/src/components/onboarding/use-onboarding-flow.test.ts": 21, + "src/renderer/src/components/onboarding/windows-terminal-onboarding-telemetry.test.ts": 5, + "src/renderer/src/components/orca-profiles/OrcaProfileSignOutConfirmDialog.test.tsx": 21, + "src/renderer/src/components/pet/PetOverlay.frame-durations.test.tsx": 50, + "src/renderer/src/components/pet/PetOverlay.keyframes.test.tsx": 217, + "src/renderer/src/components/pet/PetOverlay.pet-switch.test.tsx": 37, + "src/renderer/src/components/pet/PetOverlay.pointer-interaction.test.tsx": 75, + "src/renderer/src/components/pet/PetOverlay.test.ts": 5, + "src/renderer/src/components/pet/pet-agent-state.test.ts": 14, + "src/renderer/src/components/pet/pet-blob-cache.test.ts": 12, + "src/renderer/src/components/pet/pet-overlay-hit-area.test.tsx": 49, + "src/renderer/src/components/pet/pet-overlay-position.test.ts": 6, + "src/renderer/src/components/pet/pet-overlay-visibility.test.ts": 6, + "src/renderer/src/components/pet/sprite-animation-css.test.ts": 9, + "src/renderer/src/components/pet/usePetPointerInteraction.test.ts": 23, + "src/renderer/src/components/pet/usePetUrl.test.tsx": 15, + "src/renderer/src/components/ports/WorkspacePortScanner.test.tsx": 215, + "src/renderer/src/components/pr-check-counts.test.ts": 11, + "src/renderer/src/components/pr-checks-fix-prompt.test.ts": 20, + "src/renderer/src/components/pr-comments-resolution-prompt.test.ts": 9, + "src/renderer/src/components/provider-check-classification-parity.test.ts": 14, + "src/renderer/src/components/pull-request-page-host-boundary.test.ts": 17, + "src/renderer/src/components/pull-request-page/cache/file-content.test.ts": 11, + "src/renderer/src/components/pull-request-page/files/combined-diff-section-index.test.tsx": 22, + "src/renderer/src/components/pull-request-page/mentions/options.test.ts": 4, + "src/renderer/src/components/pull-request-page/mentions/query.test.ts": 7, + "src/renderer/src/components/pull-request-page/presentation/state-badge.test.ts": 6, + "src/renderer/src/components/quick-open-file-list.react.test.tsx": 90, + "src/renderer/src/components/quick-open-file-list.test.ts": 9, + "src/renderer/src/components/quick-open-install-rg-guidance.render.test.tsx": 57, + "src/renderer/src/components/quick-open-install-rg-guidance.test.ts": 9, + "src/renderer/src/components/quick-open-search.test.ts": 248, + "src/renderer/src/components/repo/NestedRepoChecklist.test.tsx": 17, + "src/renderer/src/components/repo/NestedRepoScanLimitNotice.test.ts": 5, + "src/renderer/src/components/repo/repo-icon.emoji-centering.test.tsx": 45, + "src/renderer/src/components/repo/repo-icon.test.tsx": 61, + "src/renderer/src/components/right-sidebar/ActionButton.test.tsx": 17, + "src/renderer/src/components/right-sidebar/AiVaultSessionRow.test.tsx": 294, + "src/renderer/src/components/right-sidebar/AiVaultSessionSubagents.test.tsx": 67, + "src/renderer/src/components/right-sidebar/ChecksPanel.review-header.test.tsx": 28, + "src/renderer/src/components/right-sidebar/ChecksPanel.updated-at-metadata.test.tsx": 46, + "src/renderer/src/components/right-sidebar/CommitArea.chevron-spinner.test.tsx": 78, + "src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx": 87, + "src/renderer/src/components/right-sidebar/CommitArea.primary-icons.test.tsx": 92, + "src/renderer/src/components/right-sidebar/CommitArea.test.tsx": 373, + "src/renderer/src/components/right-sidebar/FileExplorerNameFilter.test.tsx": 12, + "src/renderer/src/components/right-sidebar/FileExplorerRow.actions.test.tsx": 19, + "src/renderer/src/components/right-sidebar/FileExplorerToolbar.test.tsx": 45, + "src/renderer/src/components/right-sidebar/FileExplorerViewSwitch.test.tsx": 10, + "src/renderer/src/components/right-sidebar/FileExplorerVirtualRows.row-handlers.test.tsx": 16, + "src/renderer/src/components/right-sidebar/FileExplorerVirtualRowsAddProject.test.tsx": 14, + "src/renderer/src/components/right-sidebar/FolderWorkspacePrChecksPanel.test.tsx": 300, + "src/renderer/src/components/right-sidebar/FolderWorkspacePrChecksRow.test.tsx": 72, + "src/renderer/src/components/right-sidebar/FolderWorkspaceWorktreesPanel.test.tsx": 69, + "src/renderer/src/components/right-sidebar/GitHistoryPanel.test.tsx": 60, + "src/renderer/src/components/right-sidebar/GitHubPRStackMap.test.tsx": 75, + "src/renderer/src/components/right-sidebar/HostedReviewActions.draft.test.tsx": 22, + "src/renderer/src/components/right-sidebar/PluginPanel.test.tsx": 220, + "src/renderer/src/components/right-sidebar/PortsPanel.test.tsx": 1028, + "src/renderer/src/components/right-sidebar/PullRequestComposer.generate-tooltip.test.tsx": 753, + "src/renderer/src/components/right-sidebar/SearchResultItems.test.tsx": 18, + "src/renderer/src/components/right-sidebar/SessionRowTrailingActions.test.tsx": 54, + "src/renderer/src/components/right-sidebar/SourceControl.branch-line-total.test.tsx": 774, + "src/renderer/src/components/right-sidebar/SourceControl.commit-drafts.test.ts": 18, + "src/renderer/src/components/right-sidebar/SourceControl.commit-failure-recovery.test.ts": 9, + "src/renderer/src/components/right-sidebar/SourceControl.commit-generation-records.test.ts": 8, + "src/renderer/src/components/right-sidebar/SourceControl.compare-summary.test.ts": 14, + "src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts": 8, + "src/renderer/src/components/right-sidebar/SourceControl.hosted-review-header-link.test.tsx": 19, + "src/renderer/src/components/right-sidebar/SourceControl.open-file-highlight.test.tsx": 546, + "src/renderer/src/components/right-sidebar/SourceControl.pr-generation-records.test.ts": 15, + "src/renderer/src/components/right-sidebar/SourceControl.preview-open.test.tsx": 833, + "src/renderer/src/components/right-sidebar/SourceControl.push-failure-recovery.test.ts": 8, + "src/renderer/src/components/right-sidebar/SourceControl.remote-action-errors.test.ts": 9, + "src/renderer/src/components/right-sidebar/SourceControl.resolve-conflicts-prompt.test.ts": 8, + "src/renderer/src/components/right-sidebar/SourceControl.virtual-file-list.test.tsx": 2498, + "src/renderer/src/components/right-sidebar/SourceControlAgentActionDialog.test.tsx": 425, + "src/renderer/src/components/right-sidebar/SourceControlAgentActionDialogForm.test.tsx": 74, + "src/renderer/src/components/right-sidebar/SourceControlTextGenerationDialog.test.ts": 94, + "src/renderer/src/components/right-sidebar/active-checks-status.test.ts": 9, + "src/renderer/src/components/right-sidebar/activity-bar-buttons.test.tsx": 59, + "src/renderer/src/components/right-sidebar/activity-bar-overflow.test.ts": 7, + "src/renderer/src/components/right-sidebar/ai-vault-first-prompt-card.test.tsx": 137, + "src/renderer/src/components/right-sidebar/ai-vault-host-scope.test.ts": 32, + "src/renderer/src/components/right-sidebar/ai-vault-original-pane-index.test.ts": 16, + "src/renderer/src/components/right-sidebar/ai-vault-original-pane.test.ts": 7, + "src/renderer/src/components/right-sidebar/ai-vault-scan-issue-state.test.ts": 8, + "src/renderer/src/components/right-sidebar/ai-vault-scope-paths.test.ts": 12, + "src/renderer/src/components/right-sidebar/ai-vault-scope-state.test.ts": 8, + "src/renderer/src/components/right-sidebar/ai-vault-session-continuation.test.ts": 7, + "src/renderer/src/components/right-sidebar/ai-vault-session-deletability.test.ts": 10, + "src/renderer/src/components/right-sidebar/ai-vault-session-display.test.ts": 13, + "src/renderer/src/components/right-sidebar/ai-vault-session-filters.test.ts": 19, + "src/renderer/src/components/right-sidebar/ai-vault-session-identity.test.ts": 9, + "src/renderer/src/components/right-sidebar/ai-vault-session-log-open.test.ts": 15, + "src/renderer/src/components/right-sidebar/ai-vault-session-path-actions.test.ts": 7, + "src/renderer/src/components/right-sidebar/ai-vault-session-projects.test.ts": 22, + "src/renderer/src/components/right-sidebar/ai-vault-session-publication-gate.test.ts": 17, + "src/renderer/src/components/right-sidebar/ai-vault-session-refresh.test.ts": 193, + "src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.test.ts": 9, + "src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.test.ts": 10, + "src/renderer/src/components/right-sidebar/ai-vault-session-resume.test.ts": 19, + "src/renderer/src/components/right-sidebar/ai-vault-session-worktree-map.test.tsx": 61, + "src/renderer/src/components/right-sidebar/ai-vault-session-worktree.test.ts": 19, + "src/renderer/src/components/right-sidebar/ai-vault-view-defaults.test.ts": 10, + "src/renderer/src/components/right-sidebar/ai-vault-view-options-persistence.test.ts": 13, + "src/renderer/src/components/right-sidebar/branch-line-total-request-gate.test.ts": 6, + "src/renderer/src/components/right-sidebar/check-details-resize.test.ts": 4, + "src/renderer/src/components/right-sidebar/checks-entry-refresh.test.ts": 10, + "src/renderer/src/components/right-sidebar/checks-list-expanded-details.test.tsx": 327, + "src/renderer/src/components/right-sidebar/checks-panel-async-result-key.test.ts": 4, + "src/renderer/src/components/right-sidebar/checks-panel-content.test.tsx": 100, + "src/renderer/src/components/right-sidebar/checks-panel-empty-state.test.ts": 28, + "src/renderer/src/components/right-sidebar/checks-panel-git-status-snapshot.test.ts": 14, + "src/renderer/src/components/right-sidebar/checks-panel-hosted-review-click-routing.test.ts": 10, + "src/renderer/src/components/right-sidebar/checks-panel-pr-refresh-breadcrumb.test.ts": 11, + "src/renderer/src/components/right-sidebar/checks-panel-pr-refresh-request.test.ts": 9, + "src/renderer/src/components/right-sidebar/checks-panel-review-creation.test.ts": 16, + "src/renderer/src/components/right-sidebar/checks-panel-review-lookup-authority.test.ts": 11, + "src/renderer/src/components/right-sidebar/checks-panel-review.test.ts": 8, + "src/renderer/src/components/right-sidebar/checks-panel-terminal-worktree.test.ts": 12, + "src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.test.ts": 9, + "src/renderer/src/components/right-sidebar/checks-panel/panel-content-rendering.test.tsx": 90, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-check-and-review-actions.test.tsx": 33, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-create-review.test.tsx": 22, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.test.tsx": 17, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-git-status-effects.test.tsx": 37, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-manual-refresh.test.tsx": 15, + "src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-polling.test.tsx": 38, + "src/renderer/src/components/right-sidebar/coalesced-poll-runner.test.ts": 18, + "src/renderer/src/components/right-sidebar/commit-failure-dialog-state.test.ts": 4, + "src/renderer/src/components/right-sidebar/commit-failure-summary.test.ts": 21, + "src/renderer/src/components/right-sidebar/create-review-draft-title.test.ts": 7, + "src/renderer/src/components/right-sidebar/diff-comments-clear-dialog-state.test.ts": 6, + "src/renderer/src/components/right-sidebar/discard-all-sequence.test.ts": 19, + "src/renderer/src/components/right-sidebar/file-explorer-add-project-action.test.ts": 7, + "src/renderer/src/components/right-sidebar/file-explorer-batch-deletion.test.ts": 140, + "src/renderer/src/components/right-sidebar/file-explorer-deferred-dir-toggle.test.ts": 42, + "src/renderer/src/components/right-sidebar/file-explorer-dir-load-tracker.test.ts": 4, + "src/renderer/src/components/right-sidebar/file-explorer-dir-toggle-timing.test.ts": 11, + "src/renderer/src/components/right-sidebar/file-explorer-directory-listing.test.ts": 5, + "src/renderer/src/components/right-sidebar/file-explorer-drag-scroll-marker.test.tsx": 56, + "src/renderer/src/components/right-sidebar/file-explorer-entries.test.ts": 9, + "src/renderer/src/components/right-sidebar/file-explorer-expanded-dirs-refresh.test.ts": 23, + "src/renderer/src/components/right-sidebar/file-explorer-inline-input-outside-click.test.tsx": 62, + "src/renderer/src/components/right-sidebar/file-explorer-inline-rename-flow.test.tsx": 113, + "src/renderer/src/components/right-sidebar/file-explorer-keyboard-navigation.test.ts": 11, + "src/renderer/src/components/right-sidebar/file-explorer-name-filter-projection.test.ts": 5, + "src/renderer/src/components/right-sidebar/file-explorer-operation-generation.test.ts": 35, + "src/renderer/src/components/right-sidebar/file-explorer-paths.test.ts": 5, + "src/renderer/src/components/right-sidebar/file-explorer-refresh-concurrency.test.ts": 4, + "src/renderer/src/components/right-sidebar/file-explorer-reset.test.ts": 5, + "src/renderer/src/components/right-sidebar/file-explorer-row-projection.test.ts": 8, + "src/renderer/src/components/right-sidebar/file-explorer-runtime-owner-boundary.test.ts": 9, + "src/renderer/src/components/right-sidebar/file-explorer-selection.test.ts": 8, + "src/renderer/src/components/right-sidebar/file-explorer-stale-dir-cache.test.ts": 5, + "src/renderer/src/components/right-sidebar/file-explorer-watch-drive-root.test.ts": 12, + "src/renderer/src/components/right-sidebar/file-explorer-watch-reconcile.test.ts": 193, + "src/renderer/src/components/right-sidebar/file-explorer-watch-refresh-scheduler.test.ts": 37, + "src/renderer/src/components/right-sidebar/file-search-include-pattern.test.ts": 8, + "src/renderer/src/components/right-sidebar/folder-workspace-attached-worktrees.test.ts": 16, + "src/renderer/src/components/right-sidebar/fork-push-target-label.test.ts": 6, + "src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts": 32, + "src/renderer/src/components/right-sidebar/git-status-refresh-branch-line-total.test.ts": 19, + "src/renderer/src/components/right-sidebar/git-status-refresh-scheduler.test.ts": 23, + "src/renderer/src/components/right-sidebar/git-status-refresh.test.ts": 66, + "src/renderer/src/components/right-sidebar/github-pr-link-modal.test.ts": 9, + "src/renderer/src/components/right-sidebar/github-pr-stack-merge.test.ts": 12, + "src/renderer/src/components/right-sidebar/gitlab-mr-merge-state.test.ts": 10, + "src/renderer/src/components/right-sidebar/local-workspace-port-sections.test.ts": 8, + "src/renderer/src/components/right-sidebar/parent-pr-checks-projection-selector.test.ts": 77, + "src/renderer/src/components/right-sidebar/parent-pr-checks-refresh.test.ts": 32, + "src/renderer/src/components/right-sidebar/parent-pr-checks-rows.test.ts": 37, + "src/renderer/src/components/right-sidebar/plugin-panel-activity-items.test.ts": 5, + "src/renderer/src/components/right-sidebar/plugin-panel-bridge-host.test.ts": 45, + "src/renderer/src/components/right-sidebar/plugin-panel-watchdog-visibility.test.ts": 12, + "src/renderer/src/components/right-sidebar/plugin-panel-watchdog.test.ts": 12, + "src/renderer/src/components/right-sidebar/pr-comment-presentation.test.ts": 9, + "src/renderer/src/components/right-sidebar/pr-comment-snapshotted-thread-resolver.test.ts": 11, + "src/renderer/src/components/right-sidebar/pr-comment-thread-resolution.test.ts": 4, + "src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts": 23, + "src/renderer/src/components/right-sidebar/pr-comments-list-selection.test.tsx": 791, + "src/renderer/src/components/right-sidebar/push-target-upstream-refresh-cache.test.ts": 11, + "src/renderer/src/components/right-sidebar/review-cache-entry-selection.test.ts": 11, + "src/renderer/src/components/right-sidebar/right-panel-comment-composer.ime-enter.test.tsx": 103, + "src/renderer/src/components/right-sidebar/right-panel-comment-focus-timers.test.ts": 8, + "src/renderer/src/components/right-sidebar/right-sidebar-activity-visibility.test.ts": 5, + "src/renderer/src/components/right-sidebar/right-sidebar-effective-tab.test.ts": 5, + "src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.test.ts": 5, + "src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx": 126, + "src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.test.ts": 5, + "src/renderer/src/components/right-sidebar/right-sidebar-width.test.ts": 5, + "src/renderer/src/components/right-sidebar/runSourceControlAgentActionStart.test.ts": 11, + "src/renderer/src/components/right-sidebar/search-match-open.test.ts": 12, + "src/renderer/src/components/right-sidebar/search-rows.test.ts": 6, + "src/renderer/src/components/right-sidebar/source-control-action-recipe-match.test.ts": 11, + "src/renderer/src/components/right-sidebar/source-control-actions.test.ts": 5, + "src/renderer/src/components/right-sidebar/source-control-active-open-file-keys.test.ts": 8, + "src/renderer/src/components/right-sidebar/source-control-agent-action-dialog-support.test.ts": 4, + "src/renderer/src/components/right-sidebar/source-control-branch-context-row.test.tsx": 147, + "src/renderer/src/components/right-sidebar/source-control-branch-context-stats.test.ts": 17, + "src/renderer/src/components/right-sidebar/source-control-branch-section-heading.test.tsx": 23, + "src/renderer/src/components/right-sidebar/source-control-commit-eligibility.test.ts": 7, + "src/renderer/src/components/right-sidebar/source-control-commit-message-rows.test.ts": 99, + "src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts": 18, + "src/renderer/src/components/right-sidebar/source-control-create-pr-intent-state.test.ts": 7, + "src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.test.ts": 10, + "src/renderer/src/components/right-sidebar/source-control-created-review-link.test.ts": 6, + "src/renderer/src/components/right-sidebar/source-control-discard-confirmation.test.ts": 17, + "src/renderer/src/components/right-sidebar/source-control-discard-dialog.test.tsx": 168, + "src/renderer/src/components/right-sidebar/source-control-dropdown-items.create-pr-intent.test.ts": 24, + "src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts": 34, + "src/renderer/src/components/right-sidebar/source-control-entry-actions.test.ts": 5, + "src/renderer/src/components/right-sidebar/source-control-entry-context-menu.test.tsx": 17, + "src/renderer/src/components/right-sidebar/source-control-file-filter.test.ts": 6, + "src/renderer/src/components/right-sidebar/source-control-header-toolbar-identity.test.tsx": 100, + "src/renderer/src/components/right-sidebar/source-control-header-toolbar.test.ts": 6, + "src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts": 17, + "src/renderer/src/components/right-sidebar/source-control-hosted-review-push-target.test.ts": 6, + "src/renderer/src/components/right-sidebar/source-control-huge-repo-warning-dismissals.test.ts": 237, + "src/renderer/src/components/right-sidebar/source-control-manual-review-url.test.ts": 13, + "src/renderer/src/components/right-sidebar/source-control-primary-action.create-pr-intent.test.ts": 29, + "src/renderer/src/components/right-sidebar/source-control-primary-action.test.ts": 34, + "src/renderer/src/components/right-sidebar/source-control-push-recovery.test.ts": 8, + "src/renderer/src/components/right-sidebar/source-control-section-order.test.ts": 9, + "src/renderer/src/components/right-sidebar/source-control-split-open.test.ts": 7, + "src/renderer/src/components/right-sidebar/source-control-status-sort.test.ts": 8, + "src/renderer/src/components/right-sidebar/source-control-submodule-expansion.test.ts": 16, + "src/renderer/src/components/right-sidebar/source-control-text-generation-defaults.test.ts": 8, + "src/renderer/src/components/right-sidebar/source-control-too-many-changes-banner.test.tsx": 87, + "src/renderer/src/components/right-sidebar/source-control-tree.test.ts": 13, + "src/renderer/src/components/right-sidebar/source-control-virtual-file-list.test.tsx": 148, + "src/renderer/src/components/right-sidebar/source-control/listing/use-file-projection-work.test.tsx": 31, + "src/renderer/src/components/right-sidebar/source-control/listing/use-store-actions.store-subscriptions.test.tsx": 50, + "src/renderer/src/components/right-sidebar/source-control/review/suppressed-github-pr.test.ts": 10, + "src/renderer/src/components/right-sidebar/source-control/review/use-action-model.test.tsx": 20, + "src/renderer/src/components/right-sidebar/source-control/review/use-hosted-review-state.test.tsx": 38, + "src/renderer/src/components/right-sidebar/status-display.test.ts": 4, + "src/renderer/src/components/right-sidebar/use-checks-panel-terminal-worktree.test.ts": 87, + "src/renderer/src/components/right-sidebar/use-git-status-upstream-ref-watch.test.ts": 11, + "src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx": 47, + "src/renderer/src/components/right-sidebar/use-installed-plugin-route-reconciliation.test.tsx": 23, + "src/renderer/src/components/right-sidebar/use-persisted-ai-vault-view-options.test.tsx": 34, + "src/renderer/src/components/right-sidebar/use-plugin-panel-theme-revision.test.tsx": 47, + "src/renderer/src/components/right-sidebar/use-source-control-ai.test.ts": 6, + "src/renderer/src/components/right-sidebar/use-source-control-branch-compare.test.tsx": 64, + "src/renderer/src/components/right-sidebar/use-source-control-git-history.test.tsx": 45, + "src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.repo-default.test.ts": 28, + "src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.test.ts": 48, + "src/renderer/src/components/right-sidebar/useFileExplorerDragDrop.test.ts": 5, + "src/renderer/src/components/right-sidebar/useFileExplorerHandlers.test.ts": 14, + "src/renderer/src/components/right-sidebar/useFileExplorerKeys.test.ts": 4, + "src/renderer/src/components/right-sidebar/useFileExplorerRowDrag.test.ts": 20, + "src/renderer/src/components/right-sidebar/useFileExplorerTree.refresh-projection-churn.test.tsx": 35, + "src/renderer/src/components/right-sidebar/useFileExplorerTree.stale-dirs.test.tsx": 37, + "src/renderer/src/components/right-sidebar/useFileExplorerVisibleRowProjection.debounce.test.tsx": 110, + "src/renderer/src/components/right-sidebar/useFileExplorerVisibleRowProjection.test.ts": 12, + "src/renderer/src/components/right-sidebar/useFileExplorerWatch.pending-refresh.test.tsx": 27, + "src/renderer/src/components/right-sidebar/useFileExplorerWatch.test.ts": 12, + "src/renderer/src/components/right-sidebar/useFileSearchRunner.test.tsx": 27, + "src/renderer/src/components/right-sidebar/useGitStatusPolling.rerender.test.ts": 57, + "src/renderer/src/components/right-sidebar/useGitStatusPolling.test.ts": 585, + "src/renderer/src/components/right-sidebar/useHostedReviewStackParent.test.tsx": 33, + "src/renderer/src/components/right-sidebar/useSourceControlSelection.test.ts": 9, + "src/renderer/src/components/right-sidebar/useSourceControlSubmoduleStatus.test.tsx": 39, + "src/renderer/src/components/settings/AccountsPane.test.tsx": 508, + "src/renderer/src/components/settings/AdvancedNetworkSettingsSection.test.ts": 9, + "src/renderer/src/components/settings/AdvancedPane.test.tsx": 37, + "src/renderer/src/components/settings/AgentSkillSetupPanel.freshness.test.tsx": 63, + "src/renderer/src/components/settings/AgentSkillSetupPanel.test.tsx": 479, + "src/renderer/src/components/settings/AgentsPane.test.tsx": 445, + "src/renderer/src/components/settings/AppearancePane.test.tsx": 694, + "src/renderer/src/components/settings/ArtifactsSettingsPane.test.tsx": 358, + "src/renderer/src/components/settings/AutomationsSettingsPane.test.tsx": 117, + "src/renderer/src/components/settings/BranchPrefixFeedback.test.tsx": 16, + "src/renderer/src/components/settings/BrowserClientHostedRemoteSetting.test.tsx": 116, + "src/renderer/src/components/settings/BrowserPane.test.ts": 6, + "src/renderer/src/components/settings/BrowserSshWorkspaceRoutingSetting.test.tsx": 111, + "src/renderer/src/components/settings/BrowserUseSkillStep.test.tsx": 11, + "src/renderer/src/components/settings/CliSection.install-failure.test.tsx": 197, + "src/renderer/src/components/settings/CliSection.test.tsx": 87, + "src/renderer/src/components/settings/CliSkillRuntimeSetup.test.tsx": 60, + "src/renderer/src/components/settings/CloudVmSetupGuide.test.tsx": 28, + "src/renderer/src/components/settings/CommitMessageAiPane.test.tsx": 532, + "src/renderer/src/components/settings/DefaultWindowsProjectRuntimeSetting.test.tsx": 28, + "src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx": 283, + "src/renderer/src/components/settings/DiffShowWhitespaceSetting.test.tsx": 57, + "src/renderer/src/components/settings/EditorWordWrapSetting.test.tsx": 74, + "src/renderer/src/components/settings/EphemeralVmRuntimesSection.test.tsx": 128, + "src/renderer/src/components/settings/EphemeralVmsPane.test.tsx": 103, + "src/renderer/src/components/settings/ExperimentalPane.test.tsx": 797, + "src/renderer/src/components/settings/FloatingWorkspacePane.test.tsx": 6, + "src/renderer/src/components/settings/GeneralPane.test.ts": 11, + "src/renderer/src/components/settings/GeneralRemoteServerUpdates.test.tsx": 55, + "src/renderer/src/components/settings/GeneralUpdateSettingsSection.test.tsx": 79, + "src/renderer/src/components/settings/GeneralWorkspaceSettingsSection.test.tsx": 308, + "src/renderer/src/components/settings/GhosttyImportModal.test.ts": 17, + "src/renderer/src/components/settings/GitPane.test.ts": 54, + "src/renderer/src/components/settings/GitPane.test.tsx": 109, + "src/renderer/src/components/settings/GrokAccountsSection.test.tsx": 61, + "src/renderer/src/components/settings/HiddenExperimentalGroup.test.tsx": 85, + "src/renderer/src/components/settings/KagiSessionLinkForm.test.ts": 5, + "src/renderer/src/components/settings/LinearAgentSkillGuide.test.tsx": 49, + "src/renderer/src/components/settings/LinearAgentSkillNotes.test.tsx": 27, + "src/renderer/src/components/settings/LinearAgentSkillPane.test.tsx": 152, + "src/renderer/src/components/settings/LocalNetworkConnectionTest.test.tsx": 227, + "src/renderer/src/components/settings/MobileEmulatorAgentControlRow.test.tsx": 40, + "src/renderer/src/components/settings/MobilePairingConnectionOptions.test.tsx": 243, + "src/renderer/src/components/settings/MobilePairingQrSection.test.tsx": 203, + "src/renderer/src/components/settings/MobilePairingSetupSection.test.tsx": 748, + "src/renderer/src/components/settings/MobilePane.test.tsx": 885, + "src/renderer/src/components/settings/MobilePaneAddressSearch.test.tsx": 31, + "src/renderer/src/components/settings/NativeChatSupportedAgents.test.tsx": 114, + "src/renderer/src/components/settings/NotificationsPane.test.tsx": 55, + "src/renderer/src/components/settings/OpenInMenuSetting.test.ts": 23, + "src/renderer/src/components/settings/OrcaAccountSettingsPane.test.tsx": 140, + "src/renderer/src/components/settings/OrchestrationPane.test.tsx": 266, + "src/renderer/src/components/settings/OrchestrationSkillAgentCoverage.test.tsx": 26, + "src/renderer/src/components/settings/PluginConsentDialog.test.tsx": 450, + "src/renderer/src/components/settings/PluginInstallDialog.test.tsx": 165, + "src/renderer/src/components/settings/PluginKeybindingConsentPreview.test.ts": 7, + "src/renderer/src/components/settings/PluginMarketplaceBrowser.test.tsx": 397, + "src/renderer/src/components/settings/PluginMarketplaceSourceDialog.test.tsx": 195, + "src/renderer/src/components/settings/PluginSettingsRow.test.tsx": 75, + "src/renderer/src/components/settings/PluginsSettingsSection.lifecycle.test.tsx": 748, + "src/renderer/src/components/settings/PrivacyPane.test.ts": 47, + "src/renderer/src/components/settings/ProjectWindowsRuntimeSetting.test.tsx": 159, + "src/renderer/src/components/settings/QuickCommandsList.test.tsx": 122, + "src/renderer/src/components/settings/QuickCommandsPane.test.ts": 8, + "src/renderer/src/components/settings/RemoteServerUpdateDialog.test.tsx": 69, + "src/renderer/src/components/settings/RepositoryForkSyncSection.test.tsx": 62, + "src/renderer/src/components/settings/RepositoryHooksSection.test.ts": 120, + "src/renderer/src/components/settings/RepositoryHostSetupsSection.test.tsx": 354, + "src/renderer/src/components/settings/RepositoryHostSetupsSection.workspace-window.test.tsx": 71, + "src/renderer/src/components/settings/RepositoryIconEmojiPicker.test.tsx": 37, + "src/renderer/src/components/settings/RepositoryIconPicker.github-avatar-refresh.test.tsx": 47, + "src/renderer/src/components/settings/RepositoryPane.test.ts": 837, + "src/renderer/src/components/settings/RepositoryPaneDraftInput.test.tsx": 74, + "src/renderer/src/components/settings/RepositorySourceControlAiSection.test.ts": 73, + "src/renderer/src/components/settings/RepositoryWorktreeDefaultsSection.test.tsx": 130, + "src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts": 17, + "src/renderer/src/components/settings/RuntimeHostAccessForm.test.tsx": 75, + "src/renderer/src/components/settings/RuntimePairingGeneratorForm.test.tsx": 41, + "src/renderer/src/components/settings/RuntimePairingUrlGenerator.test.tsx": 88, + "src/renderer/src/components/settings/Settings.load-performance.test.ts": 7, + "src/renderer/src/components/settings/SettingsConstants.test.ts": 9, + "src/renderer/src/components/settings/SettingsFormControls.font-autocomplete.test.tsx": 359, + "src/renderer/src/components/settings/SettingsFormControls.number-field.test.tsx": 66, + "src/renderer/src/components/settings/SettingsFormControls.segmented-control.test.tsx": 69, + "src/renderer/src/components/settings/SettingsSidebar.test.tsx": 79, + "src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx": 192, + "src/renderer/src/components/settings/ShortcutCommandBlock.test.tsx": 76, + "src/renderer/src/components/settings/ShortcutFilterRail.test.ts": 10, + "src/renderer/src/components/settings/SourceControlActionRepoOverrideNote.test.tsx": 137, + "src/renderer/src/components/settings/SparsePresetSettingsSection.test.tsx": 28, + "src/renderer/src/components/settings/SshTargetCard.test.tsx": 89, + "src/renderer/src/components/settings/SshTargetForm.test.tsx": 363, + "src/renderer/src/components/settings/TaskSourceLinearSetup.test.tsx": 77, + "src/renderer/src/components/settings/TaskSourceProviderCard.test.tsx": 72, + "src/renderer/src/components/settings/TaskSourceShowInTasksStep.test.tsx": 32, + "src/renderer/src/components/settings/TaskSourceSimpleSetup.test.tsx": 40, + "src/renderer/src/components/settings/TasksPane.test.tsx": 282, + "src/renderer/src/components/settings/TerminalAdvancedSection.test.tsx": 85, + "src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts": 48, + "src/renderer/src/components/settings/TerminalContrastSetting.test.tsx": 128, + "src/renderer/src/components/settings/TerminalFontSizeSetting.test.tsx": 62, + "src/renderer/src/components/settings/TerminalPane.pwsh.test.ts": 38, + "src/renderer/src/components/settings/TerminalSettingsPreview.lifecycle.test.tsx": 19, + "src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx": 89, + "src/renderer/src/components/settings/TerminalThemeSections.lifecycle.test.ts": 51, + "src/renderer/src/components/settings/VoicePane.test.tsx": 443, + "src/renderer/src/components/settings/VoiceSpeechModelSection.test.tsx": 85, + "src/renderer/src/components/settings/WorkspaceDirectorySetting.test.tsx": 104, + "src/renderer/src/components/settings/accounts-search.test.ts": 7, + "src/renderer/src/components/settings/agent-availability-settings.test.ts": 19, + "src/renderer/src/components/settings/agent-default-env-draft.test.ts": 9, + "src/renderer/src/components/settings/agent-skill-installed-command-callers.test.ts": 103, + "src/renderer/src/components/settings/appearance-interface-summary.test.ts": 7, + "src/renderer/src/components/settings/appearance-search.test.ts": 315, + "src/renderer/src/components/settings/appearance-status-bar-search.test.ts": 8, + "src/renderer/src/components/settings/appearance-usage-percentage-search.test.ts": 3, + "src/renderer/src/components/settings/browser-cookie-import-label.test.ts": 9, + "src/renderer/src/components/settings/browser-link-routing-localization.test.ts": 222, + "src/renderer/src/components/settings/browser-search.test.ts": 60, + "src/renderer/src/components/settings/browser-session-host-selection.test.ts": 4, + "src/renderer/src/components/settings/cli-install-failure.test.ts": 7, + "src/renderer/src/components/settings/cli-source-control-integration-cards.test.tsx": 41, + "src/renderer/src/components/settings/codex-account-auth-warning.test.ts": 11, + "src/renderer/src/components/settings/codex-config-sync-warning.test.ts": 7, + "src/renderer/src/components/settings/codex-session-source-home-control.test.tsx": 10, + "src/renderer/src/components/settings/developer-permissions-search.test.ts": 280, + "src/renderer/src/components/settings/host-scoped-setting-scope.test.ts": 9, + "src/renderer/src/components/settings/integrations-pane-status.test.ts": 10, + "src/renderer/src/components/settings/jira-integration-card.test.tsx": 71, + "src/renderer/src/components/settings/linear-agent-skill-install-cta.test.tsx": 126, + "src/renderer/src/components/settings/mobile-network-interface-selection.test.ts": 9, + "src/renderer/src/components/settings/mobile-pairing-device-polling.test.ts": 6, + "src/renderer/src/components/settings/mobile-pane-search.test.ts": 9, + "src/renderer/src/components/settings/native-chat-experimental-search-entry.test.ts": 11, + "src/renderer/src/components/settings/osc52-clipboard-copy.test.ts": 7, + "src/renderer/src/components/settings/plugin-install-source.test.ts": 9, + "src/renderer/src/components/settings/provider-account-scope.test.ts": 12, + "src/renderer/src/components/settings/provider-account-visibility.test.ts": 5, + "src/renderer/src/components/settings/provider-rate-limit-scope-panels.test.tsx": 31, + "src/renderer/src/components/settings/repository-hook-settings-draft.test.ts": 7, + "src/renderer/src/components/settings/repository-host-setup-options.test.ts": 6, + "src/renderer/src/components/settings/repository-icon-github.test.ts": 8, + "src/renderer/src/components/settings/repository-source-control-ai-global-ux.test.ts": 52, + "src/renderer/src/components/settings/repository-source-control-ai-persist-queue.test.ts": 10, + "src/renderer/src/components/settings/setting-ownership.test.ts": 13, + "src/renderer/src/components/settings/settings-deep-link-target-watcher.test.ts": 18, + "src/renderer/src/components/settings/settings-form-option-filter.test.ts": 13, + "src/renderer/src/components/settings/settings-project-list.test.ts": 21, + "src/renderer/src/components/settings/settings-search-keywords.test.ts": 122, + "src/renderer/src/components/settings/settings-search.test.ts": 9, + "src/renderer/src/components/settings/settings-setup-guide-progress-hook.test.tsx": 12, + "src/renderer/src/components/settings/settings-setup-guide-progress.test.ts": 5, + "src/renderer/src/components/settings/shortcut-binding-list-mutations.test.ts": 10, + "src/renderer/src/components/settings/shortcut-definition-catalog.test.ts": 269, + "src/renderer/src/components/settings/shortcut-groups.test.ts": 14, + "src/renderer/src/components/settings/shortcut-recording-state.test.ts": 5, + "src/renderer/src/components/settings/shortcut-row-visibility.test.ts": 14, + "src/renderer/src/components/settings/sparse-preset-date.test.ts": 26, + "src/renderer/src/components/settings/sparse-preset-operation-error.test.ts": 3, + "src/renderer/src/components/settings/ssh-target-action-state.test.ts": 4, + "src/renderer/src/components/settings/ssh-target-draft.test.ts": 17, + "src/renderer/src/components/settings/ssh-target-remove.test.ts": 10, + "src/renderer/src/components/settings/ssh-target-save-payload.test.ts": 10, + "src/renderer/src/components/settings/task-source-setup-state.test.ts": 11, + "src/renderer/src/components/settings/task-tracker-integration-cards.test.tsx": 134, + "src/renderer/src/components/settings/terminal-preview-content.test.ts": 7, + "src/renderer/src/components/settings/terminal-search.test.ts": 56, + "src/renderer/src/components/settings/use-debounced-settings-text-draft.test.ts": 35, + "src/renderer/src/components/settings/use-mac-captured-digit-chords.test.ts": 393, + "src/renderer/src/components/settings/use-repository-hook-settings-draft.test.tsx": 34, + "src/renderer/src/components/settings/use-task-source-provider-readiness.test.tsx": 25, + "src/renderer/src/components/settings/useGhosttyImport.test.ts": 17, + "src/renderer/src/components/settings/useWarpThemeImport.test.ts": 31, + "src/renderer/src/components/settings/worktree-symlink-path-filter.test.ts": 11, + "src/renderer/src/components/setup-guide/SetupGuideModal.mount-gating.test.tsx": 1073, + "src/renderer/src/components/setup-guide/use-setup-guide-progress.test.ts": 10, + "src/renderer/src/components/setup-guide/use-setup-guide-telemetry.test.ts": 10, + "src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts": 184, + "src/renderer/src/components/shared/useDaemonActions.test.tsx": 27, + "src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx": 198, + "src/renderer/src/components/sidebar/AddRemoteHostDialog.config-picker.test.tsx": 603, + "src/renderer/src/components/sidebar/AddRemoteHostFields.test.tsx": 39, + "src/renderer/src/components/sidebar/AddRepoCreateStep.test.tsx": 54, + "src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts": 8, + "src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx": 71, + "src/renderer/src/components/sidebar/AddRepoHostSelector.test.tsx": 43, + "src/renderer/src/components/sidebar/AddRepoNestedImportStep.test.tsx": 146, + "src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx": 283, + "src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts": 103, + "src/renderer/src/components/sidebar/AgentDashboardSidebarHost.test.tsx": 40, + "src/renderer/src/components/sidebar/AutoRenameFailedDialog.test.tsx": 229, + "src/renderer/src/components/sidebar/CacheTimer.test.tsx": 15, + "src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx": 68, + "src/renderer/src/components/sidebar/CommentMarkdown.link-click.test.tsx": 134, + "src/renderer/src/components/sidebar/CommentMarkdown.test.tsx": 309, + "src/renderer/src/components/sidebar/DeleteWorktreeDialog.host-context-boundary.test.ts": 7, + "src/renderer/src/components/sidebar/DeleteWorktreeDialog.test.tsx": 523, + "src/renderer/src/components/sidebar/DeleteWorktreeTargetPreview.test.tsx": 105, + "src/renderer/src/components/sidebar/FilterToggleRow.test.tsx": 30, + "src/renderer/src/components/sidebar/ImportedWorktreesVisibilityLine.test.tsx": 34, + "src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.reminder-toast.test.tsx": 214, + "src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.test.tsx": 820, + "src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.update-command.test.tsx": 242, + "src/renderer/src/components/sidebar/MarkdownImageLightbox.test.tsx": 265, + "src/renderer/src/components/sidebar/NewExternalWorktreesInboxLine.test.tsx": 109, + "src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx": 80, + "src/renderer/src/components/sidebar/NoticeHostGlyph.test.tsx": 59, + "src/renderer/src/components/sidebar/OrcaYamlTrustDialog.test.tsx": 24, + "src/renderer/src/components/sidebar/ProjectAddedDialog.test.tsx": 37, + "src/renderer/src/components/sidebar/ProjectGroupDeleteDialog.test.tsx": 194, + "src/renderer/src/components/sidebar/ProjectHeaderActions.test.tsx": 17, + "src/renderer/src/components/sidebar/RemoteFileBrowser.paste.test.tsx": 145, + "src/renderer/src/components/sidebar/RemoveFolderDialog.test.tsx": 15, + "src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx": 65, + "src/renderer/src/components/sidebar/SetupScriptPromptCard.test.ts": 8, + "src/renderer/src/components/sidebar/SetupScriptPromptCardShell.test.tsx": 57, + "src/renderer/src/components/sidebar/Sidebar.test.tsx": 80, + "src/renderer/src/components/sidebar/SidebarAgentsList.test.tsx": 205, + "src/renderer/src/components/sidebar/SidebarFeedbackDialog.test.tsx": 312, + "src/renderer/src/components/sidebar/SidebarGroupByToggle.test.tsx": 62, + "src/renderer/src/components/sidebar/SidebarHeader.test.tsx": 200, + "src/renderer/src/components/sidebar/SidebarNav.test.tsx": 749, + "src/renderer/src/components/sidebar/SidebarRepositoryFilterSection.test.tsx": 507, + "src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.test.tsx": 280, + "src/renderer/src/components/sidebar/SidebarToolbar.test.tsx": 129, + "src/renderer/src/components/sidebar/SidebarWorkspaceFilterSection.test.tsx": 123, + "src/renderer/src/components/sidebar/StatusIndicator.test.ts": 32, + "src/renderer/src/components/sidebar/WorkspaceKanbanCard.host-identity.test.tsx": 30, + "src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.mount-gating.test.tsx": 522, + "src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx": 179, + "src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.task-status-sync.test.tsx": 128, + "src/renderer/src/components/sidebar/WorkspaceKanbanDrawerHeader.test.tsx": 14, + "src/renderer/src/components/sidebar/WorkspaceKanbanLaneCardList.test.tsx": 70, + "src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.test.tsx": 78, + "src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.test.tsx": 80, + "src/renderer/src/components/sidebar/WorkspaceKanbanSettingsMenu.test.tsx": 211, + "src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.test.tsx": 55, + "src/renderer/src/components/sidebar/WorktreeCard.affiliate-list-mode.test.tsx": 80, + "src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx": 3366, + "src/renderer/src/components/sidebar/WorktreeCard.compact-ports-hover-independence.test.tsx": 1185, + "src/renderer/src/components/sidebar/WorktreeCard.hosted-review-refresh.test.tsx": 1294, + "src/renderer/src/components/sidebar/WorktreeCard.lineage.test.tsx": 1219, + "src/renderer/src/components/sidebar/WorktreeCard.merged-pr-display.test.tsx": 1041, + "src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx": 1063, + "src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx": 1918, + "src/renderer/src/components/sidebar/WorktreeCard.quick-actions.test.tsx": 1401, + "src/renderer/src/components/sidebar/WorktreeCard.ssh-reconnect-prompt.test.tsx": 762, + "src/renderer/src/components/sidebar/WorktreeCard.test.ts": 9, + "src/renderer/src/components/sidebar/WorktreeCardAgents.activation.test.tsx": 523, + "src/renderer/src/components/sidebar/WorktreeCardAgents.expansion-remount.test.tsx": 470, + "src/renderer/src/components/sidebar/WorktreeCardAgents.send-target.test.tsx": 808, + "src/renderer/src/components/sidebar/WorktreeCardAgents.test.tsx": 673, + "src/renderer/src/components/sidebar/WorktreeCardAutomationDetailSection.test.tsx": 86, + "src/renderer/src/components/sidebar/WorktreeCardDisplayMenuSection.test.tsx": 76, + "src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx": 305, + "src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx": 99, + "src/renderer/src/components/sidebar/WorktreeCardPorts.test.tsx": 1109, + "src/renderer/src/components/sidebar/WorktreeCardSshHostControl.test.tsx": 281, + "src/renderer/src/components/sidebar/WorktreeCardStatusSlot.test.tsx": 53, + "src/renderer/src/components/sidebar/WorktreeContextMenu.delete-shortcut.test.tsx": 70, + "src/renderer/src/components/sidebar/WorktreeContextMenu.react185-bystander.test.tsx": 733, + "src/renderer/src/components/sidebar/WorktreeContextMenu.test.ts": 31, + "src/renderer/src/components/sidebar/WorktreeDeveloperMenu.test.tsx": 23, + "src/renderer/src/components/sidebar/WorktreeDeveloperMenuReveal.test.tsx": 73, + "src/renderer/src/components/sidebar/WorktreeList.card-memo-stability.test.tsx": 1046, + "src/renderer/src/components/sidebar/WorktreeList.empty-project-rows.test.ts": 1389, + "src/renderer/src/components/sidebar/WorktreeList.folder-workspace-rows.test.ts": 1843, + "src/renderer/src/components/sidebar/WorktreeList.group-headers.test.ts": 1975, + "src/renderer/src/components/sidebar/WorktreeList.lineage-agent-expansion-coupling.test.tsx": 2544, + "src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts": 1842, + "src/renderer/src/components/sidebar/WorktreeList.lineage-child-real-card.test.tsx": 3279, + "src/renderer/src/components/sidebar/WorktreeList.status-lane-lineage-drop.test.tsx": 2529, + "src/renderer/src/components/sidebar/WorktreeMetaDialog.test.tsx": 1032, + "src/renderer/src/components/sidebar/WorktreeOpenInMenu.test.tsx": 30, + "src/renderer/src/components/sidebar/WorktreeParentPickerPopover.test.ts": 21, + "src/renderer/src/components/sidebar/WorktreeTitleInlineRename.begin-editing.test.tsx": 78, + "src/renderer/src/components/sidebar/WorktreeTitleInlineRename.editor-lifecycle.test.tsx": 147, + "src/renderer/src/components/sidebar/WorktreeTitleInlineRename.test.tsx": 24, + "src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx": 2347, + "src/renderer/src/components/sidebar/WorktreeVisibilityHelpPopover.test.tsx": 496, + "src/renderer/src/components/sidebar/WorktreeVisibilitySourceList.test.tsx": 107, + "src/renderer/src/components/sidebar/active-worktree-focus-after-delete.test.ts": 13, + "src/renderer/src/components/sidebar/add-remote-host-ssh-actions.test.ts": 18, + "src/renderer/src/components/sidebar/add-repo-browse-authority.test.ts": 9, + "src/renderer/src/components/sidebar/add-repo-existing-workspaces-telemetry.test.ts": 15, + "src/renderer/src/components/sidebar/add-repo-skip-finalization.test.ts": 6, + "src/renderer/src/components/sidebar/clone-defaults.test.ts": 6, + "src/renderer/src/components/sidebar/create-project-defaults.test.ts": 7, + "src/renderer/src/components/sidebar/default-branch-visible-under-hide-sleeping.test.ts": 12, + "src/renderer/src/components/sidebar/delete-worktree-dirty-change-counts.test.ts": 3, + "src/renderer/src/components/sidebar/delete-worktree-failure-toast.test.tsx": 44, + "src/renderer/src/components/sidebar/delete-worktree-flow.test.ts": 284, + "src/renderer/src/components/sidebar/delete-worktree-parallel-flow.test.ts": 37, + "src/renderer/src/components/sidebar/delete-worktree-toast.test.ts": 8, + "src/renderer/src/components/sidebar/empty-project-placeholder-repos.test.ts": 10, + "src/renderer/src/components/sidebar/focused-agent-row-highlight.test.ts": 10, + "src/renderer/src/components/sidebar/folder-workspace-card-pr-display.test.ts": 18, + "src/renderer/src/components/sidebar/folder-workspace-composer-helpers.test.ts": 10, + "src/renderer/src/components/sidebar/folder-workspace-composer-path-status.test.tsx": 48, + "src/renderer/src/components/sidebar/folder-workspace-composer-submit.test.ts": 61, + "src/renderer/src/components/sidebar/folder-workspace-linked-startup-plan.test.ts": 6, + "src/renderer/src/components/sidebar/host-header-drag.test.tsx": 51, + "src/renderer/src/components/sidebar/host-header-menu-items.test.ts": 8, + "src/renderer/src/components/sidebar/host-rename-remove.test.ts": 8, + "src/renderer/src/components/sidebar/host-section-folder-workspace-counts.test.ts": 7, + "src/renderer/src/components/sidebar/host-section-order.test.ts": 5, + "src/renderer/src/components/sidebar/host-section-rows.test.ts": 12, + "src/renderer/src/components/sidebar/hovered-workspace-delete.test.ts": 115, + "src/renderer/src/components/sidebar/imported-worktrees-card-actions.test.ts": 13, + "src/renderer/src/components/sidebar/imported-worktrees-card-candidates.test.ts": 8, + "src/renderer/src/components/sidebar/linear-agent-skill-runtime.shell-override.test.ts": 5, + "src/renderer/src/components/sidebar/linear-agent-skill-setup-reminders.test.ts": 13, + "src/renderer/src/components/sidebar/local-base-ref-suggestion-toast.test.tsx": 52, + "src/renderer/src/components/sidebar/mobile-sidebar-onboarding-badge.test.ts": 34, + "src/renderer/src/components/sidebar/natural-worktree-ids.test.ts": 8, + "src/renderer/src/components/sidebar/new-external-worktrees-inbox-actions.test.ts": 10, + "src/renderer/src/components/sidebar/new-external-worktrees-inbox-candidates.test.ts": 7, + "src/renderer/src/components/sidebar/pinned-section-worktrees.test.ts": 363, + "src/renderer/src/components/sidebar/preserved-branch-batch-toast.test.tsx": 129, + "src/renderer/src/components/sidebar/preserved-branch-toast.test.tsx": 47, + "src/renderer/src/components/sidebar/project-added-default-checkout.test.ts": 22, + "src/renderer/src/components/sidebar/project-group-header-dom.test.ts": 6, + "src/renderer/src/components/sidebar/project-group-header-drag-commit.test.ts": 12, + "src/renderer/src/components/sidebar/project-group-header-drag-start.test.ts": 21, + "src/renderer/src/components/sidebar/project-group-header-drag.test.ts": 16, + "src/renderer/src/components/sidebar/project-group-header-drop.test.ts": 13, + "src/renderer/src/components/sidebar/project-header-action-selector-lockstep.test.ts": 7, + "src/renderer/src/components/sidebar/project-header-color.test.ts": 7, + "src/renderer/src/components/sidebar/project-header-drag-commit.test.ts": 12, + "src/renderer/src/components/sidebar/project-header-drag-start.test.ts": 11, + "src/renderer/src/components/sidebar/project-header-drag.test.ts": 14, + "src/renderer/src/components/sidebar/project-header-drop.test.ts": 7, + "src/renderer/src/components/sidebar/project-order-manual-default-notice-visibility.test.ts": 7, + "src/renderer/src/components/sidebar/prompt-cache-countdown-clock.test.ts": 11, + "src/renderer/src/components/sidebar/prompt-cache-timer-selection.test.ts": 5, + "src/renderer/src/components/sidebar/remote-file-browser-drive-paths.test.ts": 6, + "src/renderer/src/components/sidebar/remote-file-browser-helpers.test.ts": 39, + "src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.test.ts": 42, + "src/renderer/src/components/sidebar/repo-header-create-state.test.ts": 10, + "src/renderer/src/components/sidebar/sidebar-empty-state-gate.test.ts": 4, + "src/renderer/src/components/sidebar/sidebar-filter-state.test.ts": 9, + "src/renderer/src/components/sidebar/sidebar-host-options.test.ts": 15, + "src/renderer/src/components/sidebar/sidebar-project-drop.test.ts": 10, + "src/renderer/src/components/sidebar/sidebar-resize-handle.test.ts": 8, + "src/renderer/src/components/sidebar/sidebar-workspace-option-items.test.ts": 9, + "src/renderer/src/components/sidebar/sleep-worktree-activation-race.test.ts": 17, + "src/renderer/src/components/sidebar/sleep-worktree-flow.test.ts": 19, + "src/renderer/src/components/sidebar/smart-attention.test.ts": 26, + "src/renderer/src/components/sidebar/smart-sort.test.ts": 17, + "src/renderer/src/components/sidebar/ssh-host-remove-resolution.test.ts": 10, + "src/renderer/src/components/sidebar/ssh-target-duplicate.test.ts": 8, + "src/renderer/src/components/sidebar/ssh-workspace-forget-resolution.test.ts": 5, + "src/renderer/src/components/sidebar/stale-agent-row-unverifiable.test.ts": 13, + "src/renderer/src/components/sidebar/truncated-sidebar-label.test.tsx": 63, + "src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts": 157, + "src/renderer/src/components/sidebar/use-add-repo-hosted-controller.test.ts": 5, + "src/renderer/src/components/sidebar/use-feedback-image-drop.test.tsx": 46, + "src/renderer/src/components/sidebar/use-visible-workspace-kanban-worktree-ids.test.tsx": 28, + "src/renderer/src/components/sidebar/use-workspace-kanban-card-pointer-drag.test.ts": 10, + "src/renderer/src/components/sidebar/use-workspace-kanban-drawer-lingering.test.tsx": 28, + "src/renderer/src/components/sidebar/use-workspace-kanban-outside-dismiss.test.ts": 7, + "src/renderer/src/components/sidebar/use-workspace-kanban-selection.test.tsx": 21, + "src/renderer/src/components/sidebar/use-workspace-reveal-body-redirect.test.tsx": 33, + "src/renderer/src/components/sidebar/use-workspace-status-drop.test.ts": 7, + "src/renderer/src/components/sidebar/use-worktree-activity-status.test.tsx": 23, + "src/renderer/src/components/sidebar/use-worktree-activity-statuses.test.ts": 7, + "src/renderer/src/components/sidebar/use-worktree-card-secondary-details.store-subscriptions.test.tsx": 30, + "src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts": 114, + "src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts": 127, + "src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts": 13, + "src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts": 54, + "src/renderer/src/components/sidebar/useCreateProjectDefaults.test.ts": 30, + "src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts": 105, + "src/renderer/src/components/sidebar/useRenderedSetupScriptPromptState.test.ts": 20, + "src/renderer/src/components/sidebar/useSetupScriptPromptRevalidation.test.tsx": 63, + "src/renderer/src/components/sidebar/useWorkspaceBoardPanel.test.tsx": 68, + "src/renderer/src/components/sidebar/useWorktreeAgentRows.test.ts": 21, + "src/renderer/src/components/sidebar/visible-worktree-activity-inputs.test.ts": 9, + "src/renderer/src/components/sidebar/visible-worktree-indexes.test.ts": 12, + "src/renderer/src/components/sidebar/visible-worktrees.test.ts": 22, + "src/renderer/src/components/sidebar/workspace-board-task-status-sync.test.ts": 26, + "src/renderer/src/components/sidebar/workspace-creator-visibility.test.ts": 6, + "src/renderer/src/components/sidebar/workspace-delete-lineage.test.ts": 9, + "src/renderer/src/components/sidebar/workspace-kanban-area-selection.test.ts": 13, + "src/renderer/src/components/sidebar/workspace-kanban-card-pointer-drag-dom.test.ts": 7, + "src/renderer/src/components/sidebar/workspace-kanban-filtered-drop-index.test.ts": 11, + "src/renderer/src/components/sidebar/workspace-kanban-search.test.ts": 23, + "src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.test.ts": 21, + "src/renderer/src/components/sidebar/workspace-kanban-virtual-lane-layout.test.ts": 13, + "src/renderer/src/components/sidebar/workspace-kanban-worktree-groups.test.ts": 16, + "src/renderer/src/components/sidebar/workspace-lineage-menu-actions.test.ts": 9, + "src/renderer/src/components/sidebar/workspace-status.test.ts": 11, + "src/renderer/src/components/sidebar/worktree-agent-activity-summary.test.ts": 25, + "src/renderer/src/components/sidebar/worktree-agent-freshness-selector.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-agent-orchestration-batch.test.ts": 17, + "src/renderer/src/components/sidebar/worktree-agent-orchestration-index.test.ts": 103, + "src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts": 26, + "src/renderer/src/components/sidebar/worktree-card-agent-ack-inputs.test.tsx": 20, + "src/renderer/src/components/sidebar/worktree-card-agent-summary.test.ts": 31, + "src/renderer/src/components/sidebar/worktree-card-compact-agent-row.stable-message.test.tsx": 84, + "src/renderer/src/components/sidebar/worktree-card-details-hover-state.test.tsx": 26, + "src/renderer/src/components/sidebar/worktree-card-dom-events.test.ts": 6, + "src/renderer/src/components/sidebar/worktree-card-jira-issue-display.test.ts": 6, + "src/renderer/src/components/sidebar/worktree-card-markdown-isolation.test.ts": 344, + "src/renderer/src/components/sidebar/worktree-card-pr-display.test.ts": 11, + "src/renderer/src/components/sidebar/worktree-card-send-target-inputs.test.ts": 7, + "src/renderer/src/components/sidebar/worktree-card-status-inputs.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-card-title-display.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts": 11, + "src/renderer/src/components/sidebar/worktree-delete-host-qualification.test.ts": 173, + "src/renderer/src/components/sidebar/worktree-delete-position-scaling.test.ts": 35, + "src/renderer/src/components/sidebar/worktree-delete-request.test.ts": 9, + "src/renderer/src/components/sidebar/worktree-drag-units.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-filter-visibility.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-header-section-boundaries.test.ts": 6, + "src/renderer/src/components/sidebar/worktree-keyboard-cycle.test.ts": 7, + "src/renderer/src/components/sidebar/worktree-lineage-drag-drop.test.ts": 15, + "src/renderer/src/components/sidebar/worktree-lineage-expansion.performance.test.ts": 159, + "src/renderer/src/components/sidebar/worktree-lineage-projection.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-lineage-toggle-handler-cache.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-list-groups-host-collision.test.ts": 17, + "src/renderer/src/components/sidebar/worktree-list-groups-host-labels.test.ts": 23, + "src/renderer/src/components/sidebar/worktree-list-groups-imported-worktrees.test.ts": 22, + "src/renderer/src/components/sidebar/worktree-list-groups-lineage-nesting.test.ts": 17, + "src/renderer/src/components/sidebar/worktree-list-groups-nested-project-groups.test.ts": 16, + "src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts": 16, + "src/renderer/src/components/sidebar/worktree-list-groups-pending-creations.test.ts": 13, + "src/renderer/src/components/sidebar/worktree-list-groups-pinned-host-labels.test.ts": 11, + "src/renderer/src/components/sidebar/worktree-list-groups-project-groups.test.ts": 16, + "src/renderer/src/components/sidebar/worktree-list-groups-project-host-setups.test.ts": 19, + "src/renderer/src/components/sidebar/worktree-list-groups-project-order.test.ts": 1284, + "src/renderer/src/components/sidebar/worktree-list-groups-section-label-disambiguation.test.ts": 12, + "src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.folder-workspace-lanes.test.ts": 22, + "src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.test.ts": 21, + "src/renderer/src/components/sidebar/worktree-list/grouping/host-labels.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-list/grouping/imported-rows.test.ts": 9, + "src/renderer/src/components/sidebar/worktree-list/listing/host-filtering.test.ts": 9, + "src/renderer/src/components/sidebar/worktree-list/listing/pending-worktree-creation-keys.test.ts": 5, + "src/renderer/src/components/sidebar/worktree-list/listing/review-cache-inputs.test.ts": 6, + "src/renderer/src/components/sidebar/worktree-list/listing/use-visible-worktrees.test.tsx": 35, + "src/renderer/src/components/sidebar/worktree-list/navigation/active-descendant-option.test.ts": 5, + "src/renderer/src/components/sidebar/worktree-list/navigation/folder-reveal.test.ts": 9, + "src/renderer/src/components/sidebar/worktree-list/navigation/render-row-lookup.folder-workspace.test.ts": 5, + "src/renderer/src/components/sidebar/worktree-list/navigation/use-keyboard.host-identity.test.tsx": 24, + "src/renderer/src/components/sidebar/worktree-list/navigation/use-reveal-requests.test.tsx": 124, + "src/renderer/src/components/sidebar/worktree-list/navigation/use-selection-host-collision.test.tsx": 18, + "src/renderer/src/components/sidebar/worktree-list/rows/FolderPathStatusIndicator.test.tsx": 38, + "src/renderer/src/components/sidebar/worktree-list/rows/RepoScanUnavailableIndicator.test.tsx": 57, + "src/renderer/src/components/sidebar/worktree-list/rows/indentation.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-list/rows/option-dom-host-collision.test.ts": 11, + "src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs-owner-host.test.tsx": 39, + "src/renderer/src/components/sidebar/worktree-list/viewport/hard-scroll-up.test.ts": 14, + "src/renderer/src/components/sidebar/worktree-list/viewport/scroll-adjustment.test.ts": 15, + "src/renderer/src/components/sidebar/worktree-list/viewport/sticky-headers.test.ts": 26, + "src/renderer/src/components/sidebar/worktree-list/viewport/use-row-removal-animation.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.test.ts": 45, + "src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.test.tsx": 21, + "src/renderer/src/components/sidebar/worktree-list/viewport/virtual-rows.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-list/viewport/visible-refresh.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-manual-order-catalog.test.ts": 16, + "src/renderer/src/components/sidebar/worktree-manual-order-store-write.test.ts": 22, + "src/renderer/src/components/sidebar/worktree-manual-order.test.ts": 315, + "src/renderer/src/components/sidebar/worktree-meta-updates.test.ts": 14, + "src/renderer/src/components/sidebar/worktree-multi-selection.test.ts": 8, + "src/renderer/src/components/sidebar/worktree-name-suggestions.test.ts": 73, + "src/renderer/src/components/sidebar/worktree-parent-eligibility.test.ts": 9, + "src/renderer/src/components/sidebar/worktree-review-helpers.test.tsx": 22, + "src/renderer/src/components/sidebar/worktree-scroll-to-current-button.test.ts": 4, + "src/renderer/src/components/sidebar/worktree-sidebar-drag-autoscroll.test.ts": 15, + "src/renderer/src/components/sidebar/worktree-sidebar-drag-geometry.test.ts": 27, + "src/renderer/src/components/sidebar/worktree-sidebar-drop-preview.test.ts": 13, + "src/renderer/src/components/sidebar/worktree-sidebar-pointer-drag-dom.test.ts": 7, + "src/renderer/src/components/sidebar/worktree-sidebar-reveal-scroll-settle.test.ts": 3, + "src/renderer/src/components/sidebar/worktree-sidebar-reveal.test.ts": 10, + "src/renderer/src/components/sidebar/worktree-sidebar-row-preference.test.ts": 7, + "src/renderer/src/components/sidebar/worktree-sort-label-ordering.test.ts": 180, + "src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.test.ts": 20, + "src/renderer/src/components/sidebar/worktree-unambiguous-id-index.test.ts": 5, + "src/renderer/src/components/sidebar/worktree-unnest.test.ts": 19, + "src/renderer/src/components/sidebar/worktree-visibility-source-provenance.test.ts": 9, + "src/renderer/src/components/skills/SkillFreshnessNudge.test.tsx": 53, + "src/renderer/src/components/skills/SkillFreshnessStatusPill.test.tsx": 51, + "src/renderer/src/components/skills/SkillFreshnessUpdateDialog.test.tsx": 420, + "src/renderer/src/components/skills/SkillInstallAgentPicker.test.tsx": 226, + "src/renderer/src/components/skills/SkillInstallDialog.test.tsx": 1326, + "src/renderer/src/components/skills/SkillInstallManagementDialog.test.tsx": 1515, + "src/renderer/src/components/skills/SkillInstallTargetFields.test.tsx": 169, + "src/renderer/src/components/skills/SkillInstallWorkspaceCombobox.test.tsx": 537, + "src/renderer/src/components/skills/SkillShareDialog.test.tsx": 649, + "src/renderer/src/components/skills/SkillSharedLinkRow.test.tsx": 388, + "src/renderer/src/components/skills/SkillsPage.test.tsx": 1126, + "src/renderer/src/components/skills/skill-bundle-name.test.ts": 8, + "src/renderer/src/components/skills/skill-bundle-retry-selection.test.ts": 6, + "src/renderer/src/components/skills/skill-delete-copy.test.ts": 23, + "src/renderer/src/components/skills/skill-delete-selection.test.ts": 10, + "src/renderer/src/components/skills/skill-description-length.test.ts": 4, + "src/renderer/src/components/skills/skill-freshness-grouping.test.ts": 18, + "src/renderer/src/components/skills/skill-freshness-skipped-reason.test.ts": 11, + "src/renderer/src/components/skills/skill-install-progress-state.test.tsx": 23, + "src/renderer/src/components/skills/skill-install-provider-groups.test.ts": 11, + "src/renderer/src/components/skills/skill-install-workspace-choices.test.ts": 8, + "src/renderer/src/components/skills/skill-package-checklist-items.test.ts": 12, + "src/renderer/src/components/skills/skill-package-digest.test.ts": 5, + "src/renderer/src/components/skills/skill-package-install-risk.test.ts": 8, + "src/renderer/src/components/skills/skill-share-link.test.ts": 4, + "src/renderer/src/components/skills/skill-share-package-selection.test.ts": 5, + "src/renderer/src/components/skills/skill-share-preview-summary.test.ts": 21, + "src/renderer/src/components/skills/skill-share-selection.test.ts": 6, + "src/renderer/src/components/skills/skill-source-inventory.test.ts": 8, + "src/renderer/src/components/skills/skills-filter.test.ts": 5, + "src/renderer/src/components/source-control/SourceControlActionVariableChips.test.tsx": 56, + "src/renderer/src/components/star-nag/StarNagAgentValueMomentObserver.test.tsx": 86, + "src/renderer/src/components/star-nag/StarNagToastHost.test.tsx": 130, + "src/renderer/src/components/stats/GrokUsagePane.test.tsx": 106, + "src/renderer/src/components/stats/UsageBreakdownSection.test.tsx": 35, + "src/renderer/src/components/stats/UsageTrackingPaneShell.test.tsx": 230, + "src/renderer/src/components/stats/usage-daily-chart.test.tsx": 300, + "src/renderer/src/components/stats/usage-overview-model.test.ts": 202, + "src/renderer/src/components/status-bar/CaffeinateStatusSegment.localization.test.tsx": 645, + "src/renderer/src/components/status-bar/PetStatusSegment.layout.test.ts": 4, + "src/renderer/src/components/status-bar/PortsStatusSegment.host-routing.test.tsx": 100, + "src/renderer/src/components/status-bar/PortsStatusSegment.render-stability.test.tsx": 36, + "src/renderer/src/components/status-bar/ResourceUsageStatusSegment.rows.test.tsx": 62, + "src/renderer/src/components/status-bar/ResourceUsageStatusSegment.session-polling.test.ts": 6, + "src/renderer/src/components/status-bar/RuntimeHostStatusRow.test.tsx": 130, + "src/renderer/src/components/status-bar/SkillUpdateStatusSegment.test.tsx": 64, + "src/renderer/src/components/status-bar/SshStatusSegment.test.ts": 10, + "src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx": 137, + "src/renderer/src/components/status-bar/UsageRosterPanel.test.tsx": 102, + "src/renderer/src/components/status-bar/codex-restart-status-summary.test.ts": 6, + "src/renderer/src/components/status-bar/codex-status-sign-in.test.tsx": 830, + "src/renderer/src/components/status-bar/icons.test.tsx": 16, + "src/renderer/src/components/status-bar/inline-usage-bars.test.tsx": 742, + "src/renderer/src/components/status-bar/mergeSnapshotAndSessions.test.ts": 29, + "src/renderer/src/components/status-bar/ports-status-popover-rows.test.tsx": 50, + "src/renderer/src/components/status-bar/provider-account-sync-key.test.ts": 6, + "src/renderer/src/components/status-bar/provider-segment-monthly-window.test.tsx": 672, + "src/renderer/src/components/status-bar/remote-host-connection-status.test.ts": 10, + "src/renderer/src/components/status-bar/resource-manager-terminal-copy.test.ts": 7, + "src/renderer/src/components/status-bar/resource-manager-worktree-target.test.ts": 5, + "src/renderer/src/components/status-bar/resource-memory-metric-copy.test.ts": 7, + "src/renderer/src/components/status-bar/resource-session-bindings.test.ts": 8, + "src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts": 3, + "src/renderer/src/components/status-bar/resource-session-inventory.test.ts": 9, + "src/renderer/src/components/status-bar/resource-session-kill-confirmation.test.ts": 5, + "src/renderer/src/components/status-bar/resource-session-navigation.test.ts": 10, + "src/renderer/src/components/status-bar/resource-usage-open-slices.test.ts": 10, + "src/renderer/src/components/status-bar/resource-usage-space-scan-ready.test.ts": 7, + "src/renderer/src/components/status-bar/ssh-status-segment-copy.test.ts": 5, + "src/renderer/src/components/status-bar/status-bar-agent-gating.test.ts": 6, + "src/renderer/src/components/status-bar/status-bar-context-menu-policy.test.ts": 4, + "src/renderer/src/components/status-bar/status-bar-copy-localization.test.tsx": 310, + "src/renderer/src/components/status-bar/status-bar-provider-menu-focus.test.tsx": 773, + "src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts": 12, + "src/renderer/src/components/status-bar/status-bar-runtime-groups.test.ts": 12, + "src/renderer/src/components/status-bar/tooltip.test.ts": 51, + "src/renderer/src/components/status-bar/usage-error-copy.test.ts": 5, + "src/renderer/src/components/status-bar/usage-percentage-label.test.ts": 4, + "src/renderer/src/components/status-bar/usage-provider-settings-target.test.ts": 6, + "src/renderer/src/components/status-bar/usage-roster-formatting.test.ts": 6, + "src/renderer/src/components/status-bar/usage-roster-row-state.test.ts": 6, + "src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx": 677, + "src/renderer/src/components/status-bar/workspace-space-breakdown-list.test.tsx": 69, + "src/renderer/src/components/status-bar/workspace-space-delete-host-routing.test.ts": 8, + "src/renderer/src/components/status-bar/workspace-space-format.test.ts": 8, + "src/renderer/src/components/status-bar/workspace-space-layout.test.ts": 5, + "src/renderer/src/components/status-bar/workspace-space-manager-source-boundary.test.ts": 8, + "src/renderer/src/components/status-bar/workspace-space-presentation.test.ts": 133, + "src/renderer/src/components/tab-bar/BrowserTab.test.tsx": 1412, + "src/renderer/src/components/tab-bar/ClientHostedBrowserTabRows.test.tsx": 106, + "src/renderer/src/components/tab-bar/EditorFileTab.test.tsx": 840, + "src/renderer/src/components/tab-bar/EditorFileTabContextMenu.test.tsx": 505, + "src/renderer/src/components/tab-bar/QuickLaunchButton.test.ts": 37, + "src/renderer/src/components/tab-bar/RecentTabSwitcher.test.tsx": 46, + "src/renderer/src/components/tab-bar/SortableTab.rename-shortcut.test.tsx": 739, + "src/renderer/src/components/tab-bar/SortableTab.update-depth-probe.test.tsx": 113, + "src/renderer/src/components/tab-bar/SortableTabContextMenu.test.tsx": 121, + "src/renderer/src/components/tab-bar/TabBar.client-hosted-row-active-state.test.ts": 1116, + "src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts": 2180, + "src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts": 2043, + "src/renderer/src/components/tab-bar/TabBar.windows-shell-remote-runtime.test.ts": 1451, + "src/renderer/src/components/tab-bar/TabBar.windows-shell-ssh-host.test.ts": 1914, + "src/renderer/src/components/tab-bar/TabBar.worktree-write-gate.test.tsx": 1048, + "src/renderer/src/components/tab-bar/TabBar.worktree-write-gate.windows.test.tsx": 477, + "src/renderer/src/components/tab-bar/TabBarCreateEntry.history.test.tsx": 100, + "src/renderer/src/components/tab-bar/TabBarCreateEntry.keyboard.test.tsx": 166, + "src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx": 199, + "src/renderer/src/components/tab-bar/TabBarCreateEntry.tab-results.test.tsx": 171, + "src/renderer/src/components/tab-bar/TabBarCreateEntryRow.test.tsx": 102, + "src/renderer/src/components/tab-bar/TabBarQuickCommandItem.test.tsx": 106, + "src/renderer/src/components/tab-bar/TabBarQuickCommandsMenu.keyboard.test.ts": 134, + "src/renderer/src/components/tab-bar/TabStripScrollIndicator.test.tsx": 63, + "src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx": 36, + "src/renderer/src/components/tab-bar/client-hosted-browser-row-strip-placement.test.ts": 6, + "src/renderer/src/components/tab-bar/drop-indicator.test.ts": 8, + "src/renderer/src/components/tab-bar/editor-tab-local-open-guard.test.ts": 5, + "src/renderer/src/components/tab-bar/group-tab-order.test.ts": 8, + "src/renderer/src/components/tab-bar/middle-button-default-guard.test.ts": 5, + "src/renderer/src/components/tab-bar/native-chat-tab-agent-evidence.test.ts": 9, + "src/renderer/src/components/tab-bar/open-tab-entry-dedupe.test.ts": 8, + "src/renderer/src/components/tab-bar/open-tab-search-retention.test.ts": 20, + "src/renderer/src/components/tab-bar/open-tab-search.test.ts": 35, + "src/renderer/src/components/tab-bar/open-tab-selection-routing.test.ts": 12, + "src/renderer/src/components/tab-bar/query-token-match.test.ts": 7, + "src/renderer/src/components/tab-bar/recent-tab-switching.test.ts": 8, + "src/renderer/src/components/tab-bar/reconcile-order.test.ts": 6, + "src/renderer/src/components/tab-bar/tab-agent-launch-options.test.ts": 15, + "src/renderer/src/components/tab-bar/tab-agent-types-by-tab-id.test.ts": 13, + "src/renderer/src/components/tab-bar/tab-bar-item-surface.client-hosted-active-state.test.tsx": 14, + "src/renderer/src/components/tab-bar/tab-context-menu-consistency.test.tsx": 8, + "src/renderer/src/components/tab-bar/tab-create-entry-action.test.ts": 35, + "src/renderer/src/components/tab-bar/tab-create-entry-classifier.test.ts": 50, + "src/renderer/src/components/tab-bar/tab-create-entry-file-matches.test.ts": 36, + "src/renderer/src/components/tab-bar/tab-create-entry-forced-search.test.ts": 4, + "src/renderer/src/components/tab-bar/tab-create-entry-history-placement.test.ts": 6, + "src/renderer/src/components/tab-bar/tab-create-entry-local-path.test.ts": 285, + "src/renderer/src/components/tab-bar/tab-create-menu-options.test.ts": 16, + "src/renderer/src/components/tab-bar/tab-move-to-pane-column.test.ts": 12, + "src/renderer/src/components/tab-bar/tab-strip-content-resize-observers.test.ts": 11, + "src/renderer/src/components/tab-bar/tab-strip-overflow-navigation.test.ts": 6, + "src/renderer/src/components/tab-bar/tab-strip-pointer-activation.test.tsx": 35, + "src/renderer/src/components/tab-bar/tab-strip-scroll-metrics.test.ts": 6, + "src/renderer/src/components/tab-bar/tab-title-tooltip.test.tsx": 28, + "src/renderer/src/components/tab-bar/terminal-tab-activity-status.test.ts": 46, + "src/renderer/src/components/tab-bar/terminal-tab-spinner-launch-agent.test.ts": 15, + "src/renderer/src/components/tab-bar/use-open-tab-search.test.ts": 221, + "src/renderer/src/components/tab-bar/use-tab-bar-quick-command-search-input.test.ts": 29, + "src/renderer/src/components/tab-bar/windows-shell-launch.test.ts": 5, + "src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts": 7, + "src/renderer/src/components/tab-group/RetainedPaneHost.test.tsx": 75, + "src/renderer/src/components/tab-group/TabGroupPanel.context-menu.test.ts": 7, + "src/renderer/src/components/tab-group/TabGroupSplitLayout.drag.test.tsx": 128, + "src/renderer/src/components/tab-group/TabGroupSplitLayout.test.ts": 10, + "src/renderer/src/components/tab-group/TabPaneColumnSplitDragOverlay.test.tsx": 7, + "src/renderer/src/components/tab-group/tab-drag-pointer.test.ts": 4, + "src/renderer/src/components/tab-group/tab-drag-preview-activation.test.ts": 10, + "src/renderer/src/components/tab-group/tab-drag-retained-guest-passthrough.test.tsx": 60, + "src/renderer/src/components/tab-group/tab-drop-zone.test.ts": 4, + "src/renderer/src/components/tab-group/tab-group-body-anchor.test.ts": 4, + "src/renderer/src/components/tab-group/tab-group-panel-split-target.test.ts": 18, + "src/renderer/src/components/tab-group/tab-insertion.test.ts": 7, + "src/renderer/src/components/tab-group/useTabDragSplit.test.ts": 78, + "src/renderer/src/components/tab-group/useTabGroupCreationCommands.local-shell.test.ts": 679, + "src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.structured-session.test.ts": 269, + "src/renderer/src/components/tab-group/useTabGroupTabCloseCommands.test.tsx": 69, + "src/renderer/src/components/tab-group/useTabGroupWorkspaceModel.focus.test.ts": 1235, + "src/renderer/src/components/task-drawer-source-boundary.test.ts": 8, + "src/renderer/src/components/task-page-cache-selectors.test.ts": 11, + "src/renderer/src/components/task-page-checks-pill.test.ts": 5, + "src/renderer/src/components/task-page-default-repo-selection.test.ts": 18, + "src/renderer/src/components/task-page-empty-state.test.ts": 14, + "src/renderer/src/components/task-page-github-dialog-state-authority.test.ts": 10, + "src/renderer/src/components/task-page-github-issue-creation.test.ts": 5, + "src/renderer/src/components/task-page-github-list-scroll-restore.test.ts": 20, + "src/renderer/src/components/task-page-github-resume-cache.test.ts": 17, + "src/renderer/src/components/task-page-github-reviewer-suggestions.test.ts": 6, + "src/renderer/src/components/task-page-github-status-actions.test.ts": 7, + "src/renderer/src/components/task-page-github-status-state.test.ts": 7, + "src/renderer/src/components/task-page-github-task-kind.test.ts": 15, + "src/renderer/src/components/task-page-github-work-item-filter-membership.test.ts": 10, + "src/renderer/src/components/task-page-github-work-item-mutation-patches.test.ts": 13, + "src/renderer/src/components/task-page-github-work-item-mutation-regressions.test.ts": 22, + "src/renderer/src/components/task-page-github-work-item-mutations.test.ts": 24, + "src/renderer/src/components/task-page-github-work-item-status.test.ts": 9, + "src/renderer/src/components/task-page-gitlab-task-filters.test.ts": 5, + "src/renderer/src/components/task-page-initial-selection-scaling.test.tsx": 41, + "src/renderer/src/components/task-page-jira-cache-selectors.test.ts": 7, + "src/renderer/src/components/task-page-jira-create-fields.test.ts": 9, + "src/renderer/src/components/task-page-jira-grouping.test.ts": 194, + "src/renderer/src/components/task-page-jira-item-source-context.test.ts": 7, + "src/renderer/src/components/task-page-jira-load-state.test.ts": 7, + "src/renderer/src/components/task-page-jira-project-selection.test.ts": 8, + "src/renderer/src/components/task-page-jira-sort-controls.test.tsx": 186, + "src/renderer/src/components/task-page-jira-sorting.test.ts": 151, + "src/renderer/src/components/task-page-linear-in-orca-issues.test.ts": 14, + "src/renderer/src/components/task-page-linear-issue-dialog-popover-scroll.test.ts": 9, + "src/renderer/src/components/task-page-linear-issue-empty-state.test.ts": 6, + "src/renderer/src/components/task-page-linear-issue-grouping.test.ts": 11, + "src/renderer/src/components/task-page-linear-issue-request.test.ts": 6, + "src/renderer/src/components/task-page-linear-team-selection.test.ts": 9, + "src/renderer/src/components/task-page-list-chrome-visibility.test.ts": 7, + "src/renderer/src/components/task-page-localized-options.test.ts": 79, + "src/renderer/src/components/task-page-mutation-page-allocation.test.ts": 8, + "src/renderer/src/components/task-page-new-issue-draft.test.ts": 6, + "src/renderer/src/components/task-page-pagination-page-numbers.test.ts": 7, + "src/renderer/src/components/task-page-pr-check-summary.test.ts": 6, + "src/renderer/src/components/task-page-repo-source-context.test.ts": 10, + "src/renderer/src/components/task-page-repo-source-divergence.test.ts": 5, + "src/renderer/src/components/task-page-source-switch-boundary.test.ts": 9, + "src/renderer/src/components/task-page-task-creation-drafts.test.ts": 10, + "src/renderer/src/components/task-page-task-source-host-availability.test.ts": 11, + "src/renderer/src/components/task-page-work-item-pagination.test.ts": 18, + "src/renderer/src/components/task-page-workspace-composer-boundary.test.ts": 223, + "src/renderer/src/components/task-source-context-summary.test.ts": 17, + "src/renderer/src/components/task-source-provider-availability.test.ts": 10, + "src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx": 135, + "src/renderer/src/components/terminal-pane/MobileDriverOverlay.test.tsx": 12, + "src/renderer/src/components/terminal-pane/PinnedTabCloseDialog.test.tsx": 200, + "src/renderer/src/components/terminal-pane/RunningTerminalCloseDialog.test.tsx": 269, + "src/renderer/src/components/terminal-pane/SessionRestoredBanner.test.tsx": 33, + "src/renderer/src/components/terminal-pane/TerminalAgentSessionForkDialog.test.tsx": 207, + "src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx": 58, + "src/renderer/src/components/terminal-pane/TerminalErrorToast.test.ts": 127, + "src/renderer/src/components/terminal-pane/TerminalPaneHeaderOverlay.test.tsx": 101, + "src/renderer/src/components/terminal-pane/TerminalPaneNativeChatPortal.test.tsx": 24, + "src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.react185.test.tsx": 60, + "src/renderer/src/components/terminal-pane/TerminalProcessExitOverlay.test.tsx": 76, + "src/renderer/src/components/terminal-pane/TerminalRemoteRuntimeReconnectBanner.test.tsx": 70, + "src/renderer/src/components/terminal-pane/TerminalSshReconnectOverlay.test.tsx": 192, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-attention-dispatch.test.ts": 39, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-completion-replay-guard.test.ts": 20, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-dispose-leak.test.ts": 13, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-done-quiet-window.test.ts": 28, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts": 34, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-monitoring-turn-end.test.ts": 19, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts": 18, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts": 44, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts": 24, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-boundary.test.ts": 20, + "src/renderer/src/components/terminal-pane/agent-completion-coordinator-stamped-turn-replay.test.ts": 23, + "src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts": 47, + "src/renderer/src/components/terminal-pane/agent-completion-poll-interval.test.ts": 185, + "src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts": 6, + "src/renderer/src/components/terminal-pane/agent-completion-steady-state-opt-in.test.ts": 11, + "src/renderer/src/components/terminal-pane/agent-hook-terminal-lifecycle.test.ts": 11, + "src/renderer/src/components/terminal-pane/agent-interrupt-inference.test.ts": 30, + "src/renderer/src/components/terminal-pane/agent-process-inspection-queue-rejection-containment.test.ts": 14, + "src/renderer/src/components/terminal-pane/agent-process-inspection-round.test.ts": 18, + "src/renderer/src/components/terminal-pane/agent-question-answered-inference.test.ts": 14, + "src/renderer/src/components/terminal-pane/cache-timer-seeding.test.ts": 8, + "src/renderer/src/components/terminal-pane/codex-auto-approval-notification-suppression.test.ts": 112, + "src/renderer/src/components/terminal-pane/codex-backfill-error-detector.test.ts": 5, + "src/renderer/src/components/terminal-pane/codex-detached-pane-restart.test.ts": 305, + "src/renderer/src/components/terminal-pane/command-code-done-settle.test.ts": 18, + "src/renderer/src/components/terminal-pane/command-code-output-ownership.test.ts": 8, + "src/renderer/src/components/terminal-pane/compose-active-terminal-theme.test.ts": 7, + "src/renderer/src/components/terminal-pane/deferred-reattach-live-data-queue.test.ts": 173, + "src/renderer/src/components/terminal-pane/deferred-split-pane-handoff.test.ts": 15, + "src/renderer/src/components/terminal-pane/desktop-fit-fallback.test.ts": 9, + "src/renderer/src/components/terminal-pane/direct-ssh-hidden-output-restore-unavailable-banner.test.ts": 1045, + "src/renderer/src/components/terminal-pane/edge-wrapped-terminal-http-links.test.ts": 31, + "src/renderer/src/components/terminal-pane/expand-collapse-render-stability.test.ts": 26, + "src/renderer/src/components/terminal-pane/expand-collapse.test.ts": 8, + "src/renderer/src/components/terminal-pane/focus-terminal-pane-event.test.ts": 10, + "src/renderer/src/components/terminal-pane/focused-pane-rim-flash.test.ts": 7, + "src/renderer/src/components/terminal-pane/force-park-buffer-capture.test.ts": 5, + "src/renderer/src/components/terminal-pane/git-bash-console-capacity.test.ts": 12, + "src/renderer/src/components/terminal-pane/hard-wrapped-terminal-http-links.test.ts": 34, + "src/renderer/src/components/terminal-pane/hidden-output-restore-scheduler.test.ts": 16, + "src/renderer/src/components/terminal-pane/hidden-reveal-reconciliation.fuzz.test.ts": 7500, + "src/renderer/src/components/terminal-pane/issue-12112-agent-pane-startup-color-reply-leak.repro.test.ts": 21, + "src/renderer/src/components/terminal-pane/issue-4631-terminal-hover-link-provider.repro.test.ts": 177, + "src/renderer/src/components/terminal-pane/keyboard-handlers-ime-composing-chord.test.tsx": 48, + "src/renderer/src/components/terminal-pane/keyboard-handlers-ime-enter-keyup.test.tsx": 59, + "src/renderer/src/components/terminal-pane/keyboard-handlers-ime.test.tsx": 54, + "src/renderer/src/components/terminal-pane/keyboard-handlers.test.ts": 17, + "src/renderer/src/components/terminal-pane/layout-serialization.test.ts": 19, + "src/renderer/src/components/terminal-pane/manual-terminal-worktree-park-eligibility.test.ts": 8, + "src/renderer/src/components/terminal-pane/merge-captured-leaf-state.test.ts": 16, + "src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.test.ts": 7, + "src/renderer/src/components/terminal-pane/mobile-driver-overlay-focus.test.ts": 5, + "src/renderer/src/components/terminal-pane/mobile-driver-overlay-visibility.test.ts": 6, + "src/renderer/src/components/terminal-pane/mouse-hide-while-typing.test.ts": 6, + "src/renderer/src/components/terminal-pane/native-chat-leaf-title-agent.test.ts": 13, + "src/renderer/src/components/terminal-pane/osc52-clipboard-default-on-notice.test.ts": 58, + "src/renderer/src/components/terminal-pane/osc52-clipboard-toast.test.ts": 69, + "src/renderer/src/components/terminal-pane/osc52-clipboard.test.ts": 122, + "src/renderer/src/components/terminal-pane/override-affected-panes.test.ts": 7, + "src/renderer/src/components/terminal-pane/paired-reconnect-multi-pane-materialization.test.ts": 1727, + "src/renderer/src/components/terminal-pane/pane-agent-session-id.test.ts": 5, + "src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.test.ts": 59, + "src/renderer/src/components/terminal-pane/pane-foreground-inspect-observation-identity.test.ts": 10, + "src/renderer/src/components/terminal-pane/pane-helpers.test.ts": 9, + "src/renderer/src/components/terminal-pane/pane-title-overlay-rects.test.ts": 6, + "src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts": 2029, + "src/renderer/src/components/terminal-pane/parked-terminal-command-status.test.ts": 56, + "src/renderer/src/components/terminal-pane/parse-osc7.test.ts": 5, + "src/renderer/src/components/terminal-pane/pty-buffer-serializer.test.ts": 19, + "src/renderer/src/components/terminal-pane/pty-connection-agent-session-resume.test.ts": 1727, + "src/renderer/src/components/terminal-pane/pty-connection-cold-restore-agent-resume.test.ts": 1162, + "src/renderer/src/components/terminal-pane/pty-connection-cold-restore-repaint.test.ts": 699, + "src/renderer/src/components/terminal-pane/pty-connection-cold-restore-resume-command.test.ts": 709, + "src/renderer/src/components/terminal-pane/pty-connection-command-finished-cleanup.test.ts": 1591, + "src/renderer/src/components/terminal-pane/pty-connection-daemon-snapshot-replay.test.ts": 3135, + "src/renderer/src/components/terminal-pane/pty-connection-deferred-reattach-live-output.test.ts": 1466, + "src/renderer/src/components/terminal-pane/pty-connection-deferred-ssh-passphrase.test.ts": 1225, + "src/renderer/src/components/terminal-pane/pty-connection-deliberate-sleep-guard.test.ts": 1131, + "src/renderer/src/components/terminal-pane/pty-connection-direct-ssh-reattach-retry.test.ts": 1727, + "src/renderer/src/components/terminal-pane/pty-connection-direct-ssh-spawn-retry.test.ts": 1570, + "src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-routing.test.ts": 1216, + "src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-sampling.test.ts": 1659, + "src/renderer/src/components/terminal-pane/pty-connection-foreground-routing-confirmation.test.ts": 624, + "src/renderer/src/components/terminal-pane/pty-connection-foreground-write-path.test.ts": 1254, + "src/renderer/src/components/terminal-pane/pty-connection-fresh-spawn-guards.test.ts": 1432, + "src/renderer/src/components/terminal-pane/pty-connection-hibernation-wake.test.ts": 1373, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-atlas-recovery.test.ts": 1032, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-backlog-reconciliation.test.ts": 939, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-backlog-snapshot.test.ts": 3190, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-codex-queries.test.ts": 1467, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-delivery-gate.test.ts": 1677, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-output-restore.test.ts": 597, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-query-snapshot-restore.test.ts": 1298, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-restore-fit-overflow.test.ts": 589, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-snapshot-live-overlap.test.ts": 814, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-snapshot-resize-signals.test.ts": 1012, + "src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.test.ts": 835, + "src/renderer/src/components/terminal-pane/pty-connection-hook-completion-bell-arbitration.test.ts": 1536, + "src/renderer/src/components/terminal-pane/pty-connection-hook-completion-side-effects.test.ts": 1433, + "src/renderer/src/components/terminal-pane/pty-connection-hook-idle-arbitration.test.ts": 1039, + "src/renderer/src/components/terminal-pane/pty-connection-interrupt-inference.test.ts": 916, + "src/renderer/src/components/terminal-pane/pty-connection-main-side-effect-authority.test.ts": 922, + "src/renderer/src/components/terminal-pane/pty-connection-mode-2031-subscriptions.test.ts": 1134, + "src/renderer/src/components/terminal-pane/pty-connection-notification-settings-gating.test.ts": 869, + "src/renderer/src/components/terminal-pane/pty-connection-parked-ssh-snapshot.test.ts": 1744, + "src/renderer/src/components/terminal-pane/pty-connection-post-dispose-restore-termination.test.ts": 771, + "src/renderer/src/components/terminal-pane/pty-connection-pty-exit-teardown.test.ts": 3670, + "src/renderer/src/components/terminal-pane/pty-connection-queued-startup-consume.test.ts": 705, + "src/renderer/src/components/terminal-pane/pty-connection-reattach-binding.test.ts": 2385, + "src/renderer/src/components/terminal-pane/pty-connection-reattach-mode-reset.test.ts": 1987, + "src/renderer/src/components/terminal-pane/pty-connection-remote-runtime-attach.test.ts": 1224, + "src/renderer/src/components/terminal-pane/pty-connection-remote-snapshot-source-grid.test.ts": 807, + "src/renderer/src/components/terminal-pane/pty-connection-renderer-risk-repaint.test.ts": 1489, + "src/renderer/src/components/terminal-pane/pty-connection-replay-payload-handling.test.ts": 1203, + "src/renderer/src/components/terminal-pane/pty-connection-restored-baseline-shortfall.test.ts": 835, + "src/renderer/src/components/terminal-pane/pty-connection-runtime-owner-spawn-routing.test.ts": 925, + "src/renderer/src/components/terminal-pane/pty-connection-session-liveness.test.ts": 1912, + "src/renderer/src/components/terminal-pane/pty-connection-setup-split-spawn.test.ts": 1083, + "src/renderer/src/components/terminal-pane/pty-connection-sleeping-resume-banner.test.ts": 798, + "src/renderer/src/components/terminal-pane/pty-connection-spawn-left-pane-unbound.test.ts": 897, + "src/renderer/src/components/terminal-pane/pty-connection-split-cwd-resolution.test.ts": 47, + "src/renderer/src/components/terminal-pane/pty-connection-ssh-startup-draft-delivery.test.ts": 698, + "src/renderer/src/components/terminal-pane/pty-connection-stalled-hidden-restore.test.ts": 3547, + "src/renderer/src/components/terminal-pane/pty-connection-startup-command-delivery.test.ts": 876, + "src/renderer/src/components/terminal-pane/pty-connection-task-complete-dispatch.test.ts": 1186, + "src/renderer/src/components/terminal-pane/pty-connection-terminal-input-gating.test.ts": 4079, + "src/renderer/src/components/terminal-pane/pty-connection-typed-agent-identity.test.ts": 1778, + "src/renderer/src/components/terminal-pane/pty-connection-visibility-resume-size.test.ts": 1757, + "src/renderer/src/components/terminal-pane/pty-connection-visible-pane-output-pause-latch.test.ts": 860, + "src/renderer/src/components/terminal-pane/pty-connection-windows-cjk-repaint.test.ts": 1118, + "src/renderer/src/components/terminal-pane/pty-connection-windows-keyboard-reset.test.ts": 1660, + "src/renderer/src/components/terminal-pane/pty-connection/direct-ssh-reattach-recovery.test.ts": 8, + "src/renderer/src/components/terminal-pane/pty-connection/foreground-output-budgets.test.ts": 4, + "src/renderer/src/components/terminal-pane/pty-connection/pane-pty-layout-binding.test.ts": 6, + "src/renderer/src/components/terminal-pane/pty-connection/reattach-payload-context.test.ts": 7, + "src/renderer/src/components/terminal-pane/pty-connection/reattach-payload-ssh-reconnect-model-paint.test.ts": 16, + "src/renderer/src/components/terminal-pane/pty-connection/reattach-success-settle.test.ts": 12, + "src/renderer/src/components/terminal-pane/pty-connection/ssh-session-gone-verdict.test.ts": 5, + "src/renderer/src/components/terminal-pane/pty-connection/terminal-tab-id.test.ts": 6, + "src/renderer/src/components/terminal-pane/pty-connection/unbound-pane-spawn-recovery.test.ts": 12, + "src/renderer/src/components/terminal-pane/pty-dispatcher-delivery-interest.test.ts": 98, + "src/renderer/src/components/terminal-pane/pty-dispatcher-delivery-resync.test.ts": 56, + "src/renderer/src/components/terminal-pane/pty-dispatcher-pi-routing.test.ts": 317, + "src/renderer/src/components/terminal-pane/pty-dispatcher-push-reattach.test.ts": 89, + "src/renderer/src/components/terminal-pane/pty-input-write-queue.test.ts": 750, + "src/renderer/src/components/terminal-pane/pty-model-restore-channel.test.ts": 10, + "src/renderer/src/components/terminal-pane/pty-pre-handler-buffer-warn-eviction.test.ts": 17, + "src/renderer/src/components/terminal-pane/pty-pre-handler-buffer.test.ts": 47, + "src/renderer/src/components/terminal-pane/pty-preconnect-input-buffer.test.ts": 50, + "src/renderer/src/components/terminal-pane/pty-renderer-delivery-claims.test.ts": 10, + "src/renderer/src/components/terminal-pane/pty-shutdown-data-suspension.test.ts": 10, + "src/renderer/src/components/terminal-pane/pty-shutdown-exit-deferral.test.ts": 68, + "src/renderer/src/components/terminal-pane/pty-shutdown-output-queue.test.ts": 1985, + "src/renderer/src/components/terminal-pane/pty-side-effect-pending-census.test.ts": 161, + "src/renderer/src/components/terminal-pane/pty-size-reassertion.test.ts": 12, + "src/renderer/src/components/terminal-pane/pty-size-reconcile.test.ts": 14, + "src/renderer/src/components/terminal-pane/pty-transport-connect-spawn.test.ts": 383, + "src/renderer/src/components/terminal-pane/pty-transport-detach-attach-handoff.test.ts": 238, + "src/renderer/src/components/terminal-pane/pty-transport-eager-buffer-replay.test.ts": 321, + "src/renderer/src/components/terminal-pane/pty-transport-handler-suspension.test.ts": 311, + "src/renderer/src/components/terminal-pane/pty-transport-input-write.test.ts": 759, + "src/renderer/src/components/terminal-pane/pty-transport-output-side-effects.test.ts": 310, + "src/renderer/src/components/terminal-pane/pty-transport-pi-coalesce.test.ts": 169, + "src/renderer/src/components/terminal-pane/pty-transport-pi-spinner.test.ts": 165, + "src/renderer/src/components/terminal-pane/pty-transport-reattach-admission.test.ts": 298, + "src/renderer/src/components/terminal-pane/pty-transport-recycled-pty-incarnation.test.ts": 157, + "src/renderer/src/components/terminal-pane/pty-transport-spawn-errors.test.ts": 148, + "src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.test.ts": 22, + "src/renderer/src/components/terminal-pane/remote-desktop-viewport-claim.test.ts": 5, + "src/renderer/src/components/terminal-pane/remote-execution-host-pty.test.ts": 5, + "src/renderer/src/components/terminal-pane/remote-hidden-output-restore-outcomes.test.ts": 1241, + "src/renderer/src/components/terminal-pane/remote-hidden-output-restore-unavailable-banner.repro.test.ts": 1012, + "src/renderer/src/components/terminal-pane/remote-pane-layout-push.test.ts": 10, + "src/renderer/src/components/terminal-pane/remote-runtime-connect-failure-recovery.test.ts": 1731, + "src/renderer/src/components/terminal-pane/remote-runtime-error-surface-dismissal.test.ts": 1369, + "src/renderer/src/components/terminal-pane/remote-runtime-outage-toast-flood-and-stuck-reconnect.test.ts": 2153, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-batching.test.ts": 72, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-deadline-reattach.test.ts": 2204, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-latched-pane-retention.test.ts": 4200, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-query-reply-immediate.test.ts": 2087, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-recovery-state.test.ts": 26, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-snapshot-escape-tail.test.ts": 1389, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-snapshot-source-grid.test.ts": 1605, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-activation-inventory-fallback.test.ts": 2053, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-attach-subscription.test.ts": 3506, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-create-handoff.test.ts": 2653, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-create-outcome-recovery.test.ts": 2166, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-end-verdict.test.ts": 1292, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-expired-pane-recovery.test.ts": 2463, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-host-session-launch.test.ts": 2411, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-host-surface-replacement.test.ts": 5095, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-input-coalescing.test.ts": 3404, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-input-fallback.test.ts": 2595, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-pane-handle-resolution.test.ts": 2613, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-pending-host-surface-attach.test.ts": 1727, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-snapshot-replay.test.ts": 1654, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-split-leaf-activation.test.ts": 2871, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stale-handle-recovery.test.ts": 2313, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-sticky-replacement-policy.test.ts": 2038, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts": 3273, + "src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-web-mirror-recovery.test.ts": 2711, + "src/renderer/src/components/terminal-pane/remote-runtime-resubscribe-failure-recovery-routing.test.ts": 1695, + "src/renderer/src/components/terminal-pane/renderer-owned-agent-status-registry.test.ts": 8, + "src/renderer/src/components/terminal-pane/replay-guard.test.ts": 37, + "src/renderer/src/components/terminal-pane/replayed-scrollback-store-release.test.ts": 5, + "src/renderer/src/components/terminal-pane/repro-8299-shift-space-input-source.test.ts": 14, + "src/renderer/src/components/terminal-pane/repro-8832-url-next-line.test.ts": 19, + "src/renderer/src/components/terminal-pane/resolve-split-cwd.test.ts": 13, + "src/renderer/src/components/terminal-pane/restored-snapshot-coverage.test.ts": 5, + "src/renderer/src/components/terminal-pane/session-restored-banner-pane-state.test.tsx": 64, + "src/renderer/src/components/terminal-pane/shell-ready-marker-scan.test.ts": 6, + "src/renderer/src/components/terminal-pane/shutdown-buffer-captures.test.ts": 9, + "src/renderer/src/components/terminal-pane/sleeping-record-park-exemption.test.ts": 7, + "src/renderer/src/components/terminal-pane/split-right-white-screen.test.ts": 8, + "src/renderer/src/components/terminal-pane/ssh-pane-connect-gate.test.ts": 9, + "src/renderer/src/components/terminal-pane/ssh-reattach-model-restore.test.ts": 16, + "src/renderer/src/components/terminal-pane/ssh-reconnect-model-paint-gate.test.ts": 7, + "src/renderer/src/components/terminal-pane/stale-document-visibility.test.ts": 34, + "src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.test.ts": 15, + "src/renderer/src/components/terminal-pane/terminal-agent-session-continuation.test.ts": 13, + "src/renderer/src/components/terminal-pane/terminal-agent-session-fork.test.ts": 152, + "src/renderer/src/components/terminal-pane/terminal-alternate-screen-parse.test.ts": 30, + "src/renderer/src/components/terminal-pane/terminal-appearance.test.ts": 28, + "src/renderer/src/components/terminal-pane/terminal-bracketed-paste.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-capability-replies.test.ts": 66, + "src/renderer/src/components/terminal-pane/terminal-captured-input-dispatch.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-cjk-cursor-cell-placement.test.ts": 68, + "src/renderer/src/components/terminal-pane/terminal-clipboard-event-paste.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-clipboard-paste.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-cold-park-exempt-flip.react185.test.tsx": 37, + "src/renderer/src/components/terminal-pane/terminal-cold-park-pre-gate-loop.react185.test.tsx": 64, + "src/renderer/src/components/terminal-pane/terminal-cold-park-recheck-deadlines.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-cold-park-subscription-narrowing.react185.test.tsx": 28, + "src/renderer/src/components/terminal-pane/terminal-cold-park-tab-model-identity.react185.test.tsx": 63, + "src/renderer/src/components/terminal-pane/terminal-cold-park-timer-rearm.test.tsx": 69, + "src/renderer/src/components/terminal-pane/terminal-cold-park-verdict-loop.test.tsx": 39, + "src/renderer/src/components/terminal-pane/terminal-cold-park-withheld-tabs.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-command-lifecycle.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-context-menu-dismiss.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-copy-rejection-handling.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-ctrl-arrow-conpty.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-ctrl-enter.test.ts": 15, + "src/renderer/src/components/terminal-pane/terminal-cursor-appearance-precedence.test.ts": 88, + "src/renderer/src/components/terminal-pane/terminal-cursor-inactive-style.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-dead-session-reconcile.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-delivery-watchdog.test.ts": 50, + "src/renderer/src/components/terminal-pane/terminal-drop-handler.test.ts": 34, + "src/renderer/src/components/terminal-pane/terminal-drop-image-path.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-drop-internal-handler.test.ts": 28, + "src/renderer/src/components/terminal-pane/terminal-drop-path-writer.test.ts": 21, + "src/renderer/src/components/terminal-pane/terminal-drop-runtime-owner.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-drop-shell.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-drop-upload-report.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-drop-write-failure.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-error-accumulation.test.ts": 15, + "src/renderer/src/components/terminal-pane/terminal-error-remote-closed-localization.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-eviction-exempt-tabs.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts": 17, + "src/renderer/src/components/terminal-pane/terminal-fit-restore.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-freeze-report.test.ts": 27, + "src/renderer/src/components/terminal-pane/terminal-handle-copy.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-handle-links.test.ts": 36, + "src/renderer/src/components/terminal-pane/terminal-hidden-restore-scrollback.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-hidden-worktree-retention.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-http-link-activation.test.ts": 4, + "src/renderer/src/components/terminal-pane/terminal-http-link-source-owner.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-http-url-extraction.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-ime-candidate-key-release-guard.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.test.ts": 687, + "src/renderer/src/components/terminal-pane/terminal-ime-composition-route.test.ts": 23, + "src/renderer/src/components/terminal-pane/terminal-ime-composition-tracker.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-ime-composition-transaction-ownership.test.ts": 17, + "src/renderer/src/components/terminal-pane/terminal-ime-deferred-chord.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-ime-deferred-newline.test.ts": 28, + "src/renderer/src/components/terminal-pane/terminal-ime-forwarder-space-claim.test.ts": 89, + "src/renderer/src/components/terminal-pane/terminal-ime-hangul-syllable-flush.test.ts": 261, + "src/renderer/src/components/terminal-pane/terminal-ime-hangul-terminating-digit.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-ime-input-context-refresh.test.ts": 22, + "src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-ime-macos-keybinding-dict-trace.test.ts": 134, + "src/renderer/src/components/terminal-pane/terminal-ime-native-text-forwarder.test.ts": 35, + "src/renderer/src/components/terminal-pane/terminal-ime-substituted-text-commit.test.ts": 160, + "src/renderer/src/components/terminal-pane/terminal-ime-won-composition-order.test.ts": 160, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-adversarial.test.ts": 1021, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-cancelled-preedit-visibility.test.ts": 152, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-cancel.test.ts": 133, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-composition-deduplication.test.ts": 446, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-consumed-key-commit.test.ts": 157, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-korean-enter-commit-order.test.ts": 222, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-linux-native-trace-replay.test.ts": 2429, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-midline-preedit-tail.test.ts": 270, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.ts": 111, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.ts": 206, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-transaction-events.test.ts": 277, + "src/renderer/src/components/terminal-pane/terminal-ime-xterm-windows-resumed-preedit-trace.test.ts": 273, + "src/renderer/src/components/terminal-pane/terminal-input-host-platform.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-input-quarantine.test.ts": 13, + "src/renderer/src/components/terminal-pane/terminal-ios-hangul-preedit-coexistence.test.ts": 295, + "src/renderer/src/components/terminal-pane/terminal-ios-hangul-preedit-device-trace.test.ts": 302, + "src/renderer/src/components/terminal-pane/terminal-ios-hangul-preedit.test.ts": 353, + "src/renderer/src/components/terminal-pane/terminal-jis-yen-input.test.ts": 4, + "src/renderer/src/components/terminal-pane/terminal-keyboard-event-handlers-focus.test.ts": 17, + "src/renderer/src/components/terminal-pane/terminal-keyboard-pane-resolution.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-keyboard-protocol-pane-agent.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-kitty-csi-u-encoding.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-layout-duplicate-pty-replay.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-layout-leaf-detach.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-layout-leaf-ids.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-layout-overlay-focus.test.tsx": 27, + "src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership-depth.test.ts": 67, + "src/renderer/src/components/terminal-pane/terminal-layout-pty-ownership.test.ts": 26, + "src/renderer/src/components/terminal-pane/terminal-link-action-routing.test.ts": 29, + "src/renderer/src/components/terminal-pane/terminal-link-activation.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-link-click-fallback.test.ts": 66, + "src/renderer/src/components/terminal-pane/terminal-link-file-open-routing.test.ts": 52, + "src/renderer/src/components/terminal-pane/terminal-link-open-hints.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-link-osc-file-targets.test.ts": 64, + "src/renderer/src/components/terminal-pane/terminal-link-osc-url-routing.test.ts": 27, + "src/renderer/src/components/terminal-pane/terminal-link-pointer-gesture.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-link-provider-link-detection.test.ts": 91, + "src/renderer/src/components/terminal-pane/terminal-link-pty-mouse-suppression.test.ts": 35, + "src/renderer/src/components/terminal-pane/terminal-link-remote-runtime-ssh-open.test.ts": 32, + "src/renderer/src/components/terminal-pane/terminal-link-worktree-root-activation.test.ts": 40, + "src/renderer/src/components/terminal-pane/terminal-link-wsl-path-mapping.test.ts": 42, + "src/renderer/src/components/terminal-pane/terminal-linkifier-click-priming.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-live-layout-reconciliation.test.ts": 19, + "src/renderer/src/components/terminal-pane/terminal-non-latin-control-chord.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-option-kitty-release.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-output-visibility.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-pane-attention-subscriptions.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-pane-close-identity.test.ts": 4, + "src/renderer/src/components/terminal-pane/terminal-pane-hook-order-parity.test.ts": 111, + "src/renderer/src/components/terminal-pane/terminal-pane-host-state-memo.test.ts": 21, + "src/renderer/src/components/terminal-pane/terminal-pane-host-state.test.ts": 13, + "src/renderer/src/components/terminal-pane/terminal-pane-listener-order-parity.test.ts": 196, + "src/renderer/src/components/terminal-pane/terminal-pane-recovery-unsettled-fallback.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-pane-recovery.test.ts": 44, + "src/renderer/src/components/terminal-pane/terminal-pane-split-completion.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts": 20, + "src/renderer/src/components/terminal-pane/terminal-pane-split-with-inherited-cwd.test.ts": 17, + "src/renderer/src/components/terminal-pane/terminal-pane-split-writer-paths.test.ts": 15, + "src/renderer/src/components/terminal-pane/terminal-pane-store-subscription-budget.test.tsx": 128, + "src/renderer/src/components/terminal-pane/terminal-pane-surface-ownership.test.ts": 23, + "src/renderer/src/components/terminal-pane/terminal-pane-tab-detach.test.ts": 25, + "src/renderer/src/components/terminal-pane/terminal-park-verdict-flip-telemetry.test.ts": 19, + "src/renderer/src/components/terminal-pane/terminal-park-verdict-worktree-driver-loop.test.tsx": 45, + "src/renderer/src/components/terminal-pane/terminal-parked-pane-candidates.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-parked-tab-eviction-exemption.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers-batch-sync.test.ts": 92, + "src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts": 69, + "src/renderer/src/components/terminal-pane/terminal-parked-watcher-partial-reconciliation.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-parked-watcher-registry.test.ts": 6, + "src/renderer/src/components/terminal-pane/terminal-parked-watcher-sleep-preserved-exit.test.ts": 13, + "src/renderer/src/components/terminal-pane/terminal-parked-watcher-sole-newborn-exit.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-parking-e2e-overrides.test.ts": 2548, + "src/renderer/src/components/terminal-pane/terminal-parser-handler-guard.test.ts": 18, + "src/renderer/src/components/terminal-pane/terminal-paste-coordinator.test.ts": 39, + "src/renderer/src/components/terminal-pane/terminal-paste-executor-default-yield.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-paste-multiline-policy.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-paste-operation-order.test.ts": 30, + "src/renderer/src/components/terminal-pane/terminal-paste-payload-metadata.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-paste-runtime.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-paste-target-state.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-process-exit-restart.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-programmatic-text-paste.test.ts": 84, + "src/renderer/src/components/terminal-pane/terminal-pty-ack-gate.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-pty-paste-writer.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-quick-command-dispatch.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-remote-runtime-recovery-ui-state.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-render-desync-frame.test.ts": 4, + "src/renderer/src/components/terminal-pane/terminal-render-desync-sentinel.test.ts": 395, + "src/renderer/src/components/terminal-pane/terminal-render-desync-weight-probe.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-renderer-policy.test.ts": 11, + "src/renderer/src/components/terminal-pane/terminal-replay-application-continuity.test.ts": 266, + "src/renderer/src/components/terminal-pane/terminal-replay-cursor-state.test.ts": 70, + "src/renderer/src/components/terminal-pane/terminal-restore-sgr-latch.test.ts": 62, + "src/renderer/src/components/terminal-pane/terminal-restored-viewport.test.ts": 25, + "src/renderer/src/components/terminal-pane/terminal-retention-exempt-growth.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-runtime-host-link-routing.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-selection-copy.test.ts": 10, + "src/renderer/src/components/terminal-pane/terminal-shortcut-ctrl-arrow.test.ts": 13, + "src/renderer/src/components/terminal-pane/terminal-shortcut-option-compose.test.ts": 29, + "src/renderer/src/components/terminal-pane/terminal-shortcut-policy.test.ts": 27, + "src/renderer/src/components/terminal-pane/terminal-shortcut-select-all.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-shutdown-layout-capture.test.ts": 139, + "src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts": 17, + "src/renderer/src/components/terminal-pane/terminal-snapshot-replay-paint.test.ts": 287, + "src/renderer/src/components/terminal-pane/terminal-startup-command-classifier.test.ts": 7, + "src/renderer/src/components/terminal-pane/terminal-startup-grid-settle.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-tab-agent-type-index.test.ts": 5, + "src/renderer/src/components/terminal-pane/terminal-tab-lookup.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-title-evidence.test.ts": 12, + "src/renderer/src/components/terminal-pane/terminal-title-tracker-parity.test.ts": 31, + "src/renderer/src/components/terminal-pane/terminal-unified-tab-lookup.test.ts": 16, + "src/renderer/src/components/terminal-pane/terminal-url-link-click.test.ts": 29, + "src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.test.ts": 14, + "src/renderer/src/components/terminal-pane/terminal-user-input-signal.test.ts": 15, + "src/renderer/src/components/terminal-pane/terminal-view-attributes-publisher.test.ts": 23, + "src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts": 31, + "src/renderer/src/components/terminal-pane/terminal-webgl-atlas-recovery.test.ts": 8, + "src/renderer/src/components/terminal-pane/terminal-webgl-diagnostics-breadcrumbs.test.ts": 9, + "src/renderer/src/components/terminal-pane/terminal-windows-shift-enter.test.ts": 19, + "src/renderer/src/components/terminal-pane/terminal-worktree-path-link.test.ts": 6, + "src/renderer/src/components/terminal-pane/title-agent-identity.test.ts": 6, + "src/renderer/src/components/terminal-pane/use-manual-terminal-worktree-parking.test.ts": 3, + "src/renderer/src/components/terminal-pane/use-mobile-overlay-ticks.perf.test.tsx": 31, + "src/renderer/src/components/terminal-pane/use-notification-dispatch.test.ts": 44, + "src/renderer/src/components/terminal-pane/use-parked-terminal-watcher-synchronization.react185.test.tsx": 59, + "src/renderer/src/components/terminal-pane/use-system-prefers-dark.test.ts": 7, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-active-pty-reporting.test.ts": 15, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-file-drop.test.ts": 22, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-paste-events.test.ts": 43, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-sync-fit-registration.test.ts": 14, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-visibility-resume.test.ts": 22, + "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects-window-focus-recovery.test.ts": 28, + "src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.test.ts": 19, + "src/renderer/src/components/terminal-pane/use-terminal-park-mount-intent.react185.test.tsx": 26, + "src/renderer/src/components/terminal-pane/use-terminal-scroll-visibility-memory.test.ts": 9, + "src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts": 99, + "src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts": 55, + "src/renderer/src/components/terminal-pane/use-visible-terminal-tab-claim.test.ts": 5, + "src/renderer/src/components/terminal-pane/useSessionRestoredBannerDismiss.test.tsx": 29, + "src/renderer/src/components/terminal-pane/useTerminalFontZoom.test.ts": 10, + "src/renderer/src/components/terminal-pane/wrapped-terminal-link-ranges.test.ts": 25, + "src/renderer/src/components/terminal-pane/xterm-bypass-policy-interrupt.test.ts": 13, + "src/renderer/src/components/terminal-pane/xterm-bypass-policy-ios.test.ts": 14, + "src/renderer/src/components/terminal-pane/xterm-bypass-policy-non-mac.test.ts": 14, + "src/renderer/src/components/terminal-pane/xterm-bypass-policy.test.ts": 9, + "src/renderer/src/components/terminal-pane/xterm-write-buffer-stall.repro.test.ts": 24, + "src/renderer/src/components/terminal-parked-watcher-sync-entries.test.tsx": 37, + "src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.test.tsx": 660, + "src/renderer/src/components/terminal-quick-commands/terminal-quick-command-agent-options.test.ts": 10, + "src/renderer/src/components/terminal-quick-commands/terminal-quick-command-dialog-draft.test.ts": 11, + "src/renderer/src/components/terminal-scrollback-decoration-eviction.test.ts": 414, + "src/renderer/src/components/terminal-search-decoration-leak.test.ts": 1942, + "src/renderer/src/components/terminal-search-long-wrapped-line.test.ts": 4994, + "src/renderer/src/components/terminal-search-safe-find.test.ts": 7, + "src/renderer/src/components/terminal-workspace-keydown.test.ts": 246, + "src/renderer/src/components/terminal-workspace-surface-ids.test.tsx": 87, + "src/renderer/src/components/terminal/activation-deferred-tab-admission.test.ts": 13, + "src/renderer/src/components/terminal/active-terminal-repair-loop.react185.test.tsx": 38, + "src/renderer/src/components/terminal/active-terminal-repair.test.ts": 6, + "src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts": 24, + "src/renderer/src/components/terminal/background-terminal-worktree-visibility.test.ts": 14, + "src/renderer/src/components/terminal/cold-activation-deferral-stranding.test.ts": 37, + "src/renderer/src/components/terminal/initial-terminal-structured-launch.test.tsx": 26, + "src/renderer/src/components/terminal/initial-terminal-wiring.test.ts": 4, + "src/renderer/src/components/terminal/initial-terminal.test.ts": 4, + "src/renderer/src/components/terminal/pty-running-work-probe-child-evidence.test.ts": 9, + "src/renderer/src/components/terminal/running-terminal-close-guard.test.ts": 18, + "src/renderer/src/components/terminal/split-group-mount.test.ts": 8, + "src/renderer/src/components/terminal/tab-type-cycle.test.ts": 9, + "src/renderer/src/components/terminal/terminal-close-confirm-keyboard-vs-mouse.test.ts": 12, + "src/renderer/src/components/terminal/terminal-close-copy-kind.test.ts": 8, + "src/renderer/src/components/terminal/terminal-close-incarnation.test.ts": 5, + "src/renderer/src/components/terminal/terminal-provider-snapshot-bound-pty-ids.test.ts": 74, + "src/renderer/src/components/terminal/terminal-provider-snapshot-capability-resettlement.test.ts": 15, + "src/renderer/src/components/terminal/terminal-provider-snapshot-capability.test.ts": 15, + "src/renderer/src/components/terminal/terminal-tab-actions-kill-all.test.ts": 15, + "src/renderer/src/components/terminal/terminal-tab-actions-structured-session.test.ts": 17, + "src/renderer/src/components/terminal/terminal-tab-actions-unified-close.test.ts": 32, + "src/renderer/src/components/terminal/terminal-tab-actions.test.ts": 32, + "src/renderer/src/components/terminal/terminal-tab-bulk-actions.test.ts": 12, + "src/renderer/src/components/terminal/terminal-tab-close-running-confirm.test.ts": 21, + "src/renderer/src/components/terminal/unsaved-close-queue.test.ts": 5, + "src/renderer/src/components/terminal/use-terminal-provider-snapshot-capability.test.tsx": 50, + "src/renderer/src/components/terminal/use-worktree-files.test.tsx": 52, + "src/renderer/src/components/terminal/window-close-running-work.test.ts": 23, + "src/renderer/src/components/ui/color-picker.test.tsx": 24, + "src/renderer/src/components/ui/popover-wheel-scroll.test.tsx": 243, + "src/renderer/src/components/ui/repo-multi-combobox.test.ts": 6, + "src/renderer/src/components/ui/scroll-area.test.tsx": 39, + "src/renderer/src/components/ui/slider.test.tsx": 111, + "src/renderer/src/components/ui/switch.test.tsx": 29, + "src/renderer/src/components/unexpected-signout/unexpected-signout-card.test.tsx": 206, + "src/renderer/src/components/unexpected-signout/unexpected-signout-visibility.test.ts": 9, + "src/renderer/src/components/use-github-task-search-commit.test.ts": 35, + "src/renderer/src/components/use-task-creation-draft-retention.test.ts": 25, + "src/renderer/src/components/use-terminal-create-actions.test.tsx": 22, + "src/renderer/src/components/use-terminal-editor-close-foundation.window-close.test.tsx": 28, + "src/renderer/src/components/use-terminal-window-lifecycle.lazy-ref.test.tsx": 16, + "src/renderer/src/components/use-worktree-jump-palette-browser-ownership.test.ts": 37, + "src/renderer/src/components/window-close-request-coordinator.test.ts": 19, + "src/renderer/src/components/workspace-cleanup/WorkspaceCleanupDialog.mount-gating.test.tsx": 100, + "src/renderer/src/components/workspace-cleanup/WorkspaceCleanupDialog.stale-while-revalidate.test.tsx": 1307, + "src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-browse-state.test.tsx": 43, + "src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-facet-rows.test.tsx": 58, + "src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-git-evidence.test.tsx": 60, + "src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-row-order.test.ts": 26, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-active-facets.test.ts": 11, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal-late-settlement.test.ts": 15, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal-snapshot-batch.test.ts": 63, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.test.ts": 31, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-candidate-list.test.tsx": 47, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-candidate-row-data.test.ts": 6, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-candidate-row.test.tsx": 214, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-confirm-remove.test.tsx": 118, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-deletion-phases.test.ts": 9, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-dialog-notices.test.tsx": 59, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx": 357, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-filter.test.ts": 39, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-sort.test.ts": 12, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-filter-bar.test.tsx": 237, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-flat-list.test.tsx": 280, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-git-evidence.test.ts": 16, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-presentation.test.ts": 27, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-candidates.test.ts": 7, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-settlement.test.ts": 9, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-timeout-recovery.test.ts": 26, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-scanned-host-confirmation-removal.test.tsx": 470, + "src/renderer/src/components/workspace-cleanup/workspace-cleanup-sort-header.test.tsx": 82, + "src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx": 83, + "src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.test.tsx": 122, + "src/renderer/src/components/workspace-surface-projection.test.ts": 39, + "src/renderer/src/components/worktree-creation/WorktreeCreationPanel.test.tsx": 74, + "src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx": 1645, + "src/renderer/src/components/worktree-jump-palette-primitives.test.tsx": 131, + "src/renderer/src/components/worktree-jump-palette-quick-action-availability.test.tsx": 20, + "src/renderer/src/components/worktree-jump-palette-sleeping-filter.test.ts": 9, + "src/renderer/src/components/worktree-jump-palette-source-context-boundary.test.ts": 4, + "src/renderer/src/components/worktree-jump-palette-status-inputs.test.ts": 6, + "src/renderer/src/hooks/agent-hook-completion-background-turn-notifications.test.ts": 134, + "src/renderer/src/hooks/agent-hook-completion-fresh-working.test.ts": 109, + "src/renderer/src/hooks/agent-hook-completion-notifications-import.test.ts": 92, + "src/renderer/src/hooks/agent-hook-completion-notifications.test.ts": 264, + "src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts": 18, + "src/renderer/src/hooks/automation-agent-status-entry-change.test.ts": 7, + "src/renderer/src/hooks/automation-dispatch-unverifiable-loss.test.ts": 137, + "src/renderer/src/hooks/automations-changed-event-attribution.test.ts": 1424, + "src/renderer/src/hooks/composer-branch-selection.test.ts": 11, + "src/renderer/src/hooks/composer-drop-owner.test.ts": 4, + "src/renderer/src/hooks/composer-drop-upload-result.test.ts": 8, + "src/renderer/src/hooks/composer-native-file-drop.test.ts": 9, + "src/renderer/src/hooks/composer-state/composer-drop-listener.test.ts": 26, + "src/renderer/src/hooks/composer-state/draft-target-sync.test.ts": 24, + "src/renderer/src/hooks/composer-state/full-creation-execution.test.ts": 23, + "src/renderer/src/hooks/composer-state/full-creation-structured-launch.test.ts": 7, + "src/renderer/src/hooks/composer-state/full-submit-orchestration.test.ts": 22, + "src/renderer/src/hooks/composer-state/multiple-create-reset.test.ts": 29, + "src/renderer/src/hooks/composer-state/provider-runtime-sync.test.ts": 18, + "src/renderer/src/hooks/composer-state/quick-creation-request.test.ts": 10, + "src/renderer/src/hooks/direct-ssh-host-hydration.test.ts": 24, + "src/renderer/src/hooks/direct-ssh-reconnect-coordinator.test.ts": 30, + "src/renderer/src/hooks/direct-ssh-reconnect-rollout.test.ts": 11, + "src/renderer/src/hooks/direct-ssh-reconnect-tokens.test.ts": 6, + "src/renderer/src/hooks/direct-ssh-runtime-wake-isolation.test.tsx": 43, + "src/renderer/src/hooks/direct-ssh-state-routing.test.ts": 16, + "src/renderer/src/hooks/direct-ssh-worktree-refresh-scheduler.test.ts": 27, + "src/renderer/src/hooks/fork-push-warning.test.ts": 5, + "src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts": 18, + "src/renderer/src/hooks/installed-agent-skill-discovery.test.ts": 9, + "src/renderer/src/hooks/ipc-events/agent-status-pane-routing-index.test.ts": 79, + "src/renderer/src/hooks/ipc-events/agent-status-pending-retry-gate.test.ts": 272, + "src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts": 186, + "src/renderer/src/hooks/ipc-events/browser-automation-bootstrap-lease.test.ts": 10, + "src/renderer/src/hooks/ipc-events/browser-request-ipc-bridge.profile-switch.test.ts": 7, + "src/renderer/src/hooks/ipc-events/browser-state-open-link-profile.test.ts": 8, + "src/renderer/src/hooks/ipc-events/direct-ssh-hydration-fanout.test.ts": 12, + "src/renderer/src/hooks/ipc-events/direct-ssh-hydration-target-metadata.test.ts": 21, + "src/renderer/src/hooks/ipc-events/normalize-agent-status-event.test.ts": 6, + "src/renderer/src/hooks/ipc-events/orca-profile-auth-ipc-bridge.test.ts": 111, + "src/renderer/src/hooks/ipc-events/os-markdown-file-open-bridge.test.ts": 16, + "src/renderer/src/hooks/ipc-events/runtime-reconnect-host-status.test.ts": 32, + "src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts": 15, + "src/renderer/src/hooks/ipc-events/updater-status-ipc-bridge.test.ts": 6, + "src/renderer/src/hooks/ipc-tab-switch-group-order-hydration.test.ts": 11, + "src/renderer/src/hooks/ipc-tab-switch-structured-tab-cycle.test.ts": 11, + "src/renderer/src/hooks/ipc-tab-switch.test.ts": 17, + "src/renderer/src/hooks/legacy-worker-terminal-recovery-event.test.ts": 15, + "src/renderer/src/hooks/macos-tcc-prompt-notice-subscription.test.ts": 23, + "src/renderer/src/hooks/metadata-request-cache.test.ts": 142, + "src/renderer/src/hooks/mobile-terminal-reveal-tab-adoption.test.ts": 2166, + "src/renderer/src/hooks/modal-return-focus-action.test.ts": 7, + "src/renderer/src/hooks/programmatic-scroll-marks.test.ts": 9, + "src/renderer/src/hooks/remote-workspace-deferred-placement-retry.test.ts": 14, + "src/renderer/src/hooks/remote-workspace-session-merge-close-tombstones.test.ts": 13, + "src/renderer/src/hooks/remote-workspace-session-merge-closed-terminal-tombstone.test.ts": 39, + "src/renderer/src/hooks/remote-workspace-session-merge-local-survival.test.ts": 12, + "src/renderer/src/hooks/remote-workspace-snapshot-apply-deferred-session-write.test.ts": 76, + "src/renderer/src/hooks/remote-workspace-snapshot-arrival-coordinator.test.ts": 8, + "src/renderer/src/hooks/remote-workspace-snapshot-duplicate-tab-repair.test.ts": 68, + "src/renderer/src/hooks/remote-workspace-snapshot-fresh-client-tab-seeding.test.ts": 19, + "src/renderer/src/hooks/remote-workspace-snapshot-local-tab-survival.test.ts": 108, + "src/renderer/src/hooks/remote-workspace-snapshot-unplaced-tab-adoption.test.ts": 80, + "src/renderer/src/hooks/remote-workspace-target-sync.test.ts": 45, + "src/renderer/src/hooks/runtime-client-events-sync.test.ts": 66, + "src/renderer/src/hooks/runtime-project-refresh-scheduler.test.ts": 51, + "src/renderer/src/hooks/shortcut-label-cache.test.tsx": 65, + "src/renderer/src/hooks/ssh-reconnect-pane-retry.test.ts": 7, + "src/renderer/src/hooks/structured-session-completion-focus.test.ts": 1364, + "src/renderer/src/hooks/unpaired-device-auth-notification.test.ts": 6, + "src/renderer/src/hooks/use-audio-capture.capture-loss.test.ts": 31, + "src/renderer/src/hooks/use-clipboard-text-copy-feedback.test.ts": 28, + "src/renderer/src/hooks/use-now.test.ts": 35, + "src/renderer/src/hooks/use-palette-search-evaluation-context.test.ts": 29, + "src/renderer/src/hooks/use-task-page-github-work-item-mutation-host.test.ts": 7, + "src/renderer/src/hooks/use-terminal-quick-command-hosts.test.ts": 59, + "src/renderer/src/hooks/use-window-stream-visibility.test.ts": 37, + "src/renderer/src/hooks/useActiveProjectSkillRuntime.test.tsx": 57, + "src/renderer/src/hooks/useAgentDetectionTarget.test.ts": 11, + "src/renderer/src/hooks/useAppMenuPaste.test.tsx": 29, + "src/renderer/src/hooks/useAppMenuSelectionActions.test.tsx": 13, + "src/renderer/src/hooks/useAutoAckViewedAgent.away.test.ts": 95, + "src/renderer/src/hooks/useAutoAckViewedAgent.clock-skew.test.ts": 55, + "src/renderer/src/hooks/useAutoAckViewedAgent.floating-panel.test.ts": 38, + "src/renderer/src/hooks/useAutoAckViewedAgent.test.ts": 63, + "src/renderer/src/hooks/useAutomationDispatchEvents.test.ts": 692, + "src/renderer/src/hooks/useComposerState-decisions.test.ts": 9, + "src/renderer/src/hooks/useComposerState-host-context-boundaries.test.ts": 21, + "src/renderer/src/hooks/useComposerState-host-retarget.test.ts": 7, + "src/renderer/src/hooks/useComposerState.integration.test.ts": 39, + "src/renderer/src/hooks/useDetectedAgents.test.tsx": 43, + "src/renderer/src/hooks/useEditorExternalWatch-complexity.test.ts": 6402, + "src/renderer/src/hooks/useEditorExternalWatch-self-move.test.ts": 27, + "src/renderer/src/hooks/useEditorExternalWatch-subscriptions.test.tsx": 24, + "src/renderer/src/hooks/useEditorExternalWatch-targets.test.ts": 21, + "src/renderer/src/hooks/useEditorExternalWatch-wsl-repro.test.ts": 21, + "src/renderer/src/hooks/useEditorExternalWatch.test.ts": 54, + "src/renderer/src/hooks/useEphemeralVmRecipeOptions.test.tsx": 33, + "src/renderer/src/hooks/useGitHubSlugMetadata.test.tsx": 47, + "src/renderer/src/hooks/useGlobalFileDrop.test.ts": 15, + "src/renderer/src/hooks/useInstalledAgentSkills.react.test.tsx": 90, + "src/renderer/src/hooks/useInstalledAgentSkills.test.ts": 17, + "src/renderer/src/hooks/useIpcEvents-agent-dashboard-shortcut.test.ts": 9, + "src/renderer/src/hooks/useIpcEvents-agent-status-batch-projection.test.ts": 1128, + "src/renderer/src/hooks/useIpcEvents-agent-status-connection-attribution.test.ts": 1955, + "src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts": 1349, + "src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts": 2111, + "src/renderer/src/hooks/useIpcEvents-agent-status-queue-ordering.test.ts": 1997, + "src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-hydration.test.ts": 2101, + "src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-replay.test.ts": 1690, + "src/renderer/src/hooks/useIpcEvents-agent-status-ssh-authority.test.ts": 3444, + "src/renderer/src/hooks/useIpcEvents-agent-status-turn-completion.test.ts": 1433, + "src/renderer/src/hooks/useIpcEvents-browser-certificate-failure.test.ts": 1719, + "src/renderer/src/hooks/useIpcEvents-browser-navigation.test.ts": 1370, + "src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts": 1568, + "src/renderer/src/hooks/useIpcEvents-cli-worktree-activation.test.ts": 1650, + "src/renderer/src/hooks/useIpcEvents-client-hosted-browser-rows.test.ts": 1604, + "src/renderer/src/hooks/useIpcEvents-close-routing-active-browser-tab.test.ts": 1787, + "src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts": 2510, + "src/renderer/src/hooks/useIpcEvents-close-routing-floating-guest.test.ts": 1438, + "src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts": 2721, + "src/renderer/src/hooks/useIpcEvents-cmd-j-digit-chord.test.ts": 1311, + "src/renderer/src/hooks/useIpcEvents-lifecycle.test.ts": 1102, + "src/renderer/src/hooks/useIpcEvents-new-workspace-shortcut.test.ts": 12, + "src/renderer/src/hooks/useIpcEvents-rate-limit-hydration.test.ts": 1455, + "src/renderer/src/hooks/useIpcEvents-repos-changed-catalogs.test.ts": 1275, + "src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts": 17, + "src/renderer/src/hooks/useIpcEvents-session-tab-close-request.test.ts": 1943, + "src/renderer/src/hooks/useIpcEvents-silent-terminal-adoption.test.ts": 1945, + "src/renderer/src/hooks/useIpcEvents-ssh-disconnect-cleanup.test.ts": 1453, + "src/renderer/src/hooks/useIpcEvents-terminal-create-surfacing.test.ts": 2086, + "src/renderer/src/hooks/useIpcEvents-updater-status.test.ts": 1582, + "src/renderer/src/hooks/useIpcEvents-zoom-routing.test.ts": 1559, + "src/renderer/src/hooks/useIssueMetadata.test.tsx": 27, + "src/renderer/src/hooks/useLinearProviderConnected.test.tsx": 19, + "src/renderer/src/hooks/useMacTccAttributionSeveredNotice.test.tsx": 320, + "src/renderer/src/hooks/useMacosTccPromptNotice.test.tsx": 207, + "src/renderer/src/hooks/useMetadataListRequest.test.tsx": 20, + "src/renderer/src/hooks/useModalReturnFocus.test.tsx": 64, + "src/renderer/src/hooks/usePrimarySelectionPaste.terminal-native-paste.test.tsx": 32, + "src/renderer/src/hooks/usePrimarySelectionPaste.test.tsx": 55, + "src/renderer/src/hooks/useResetCountdownClock.test.ts": 23, + "src/renderer/src/hooks/useRetiredWorktreeNames.test.ts": 344, + "src/renderer/src/hooks/useSettingsNavigationMetadata.capability-owner.test.tsx": 85, + "src/renderer/src/hooks/useSettingsNavigationMetadata.language-switch.test.tsx": 235, + "src/renderer/src/hooks/useSettingsNavigationMetadata.test.ts": 184, + "src/renderer/src/hooks/useSidebarResize.test.ts": 9, + "src/renderer/src/hooks/useSkillFreshness.test.tsx": 65, + "src/renderer/src/hooks/useUnreadDockBadge.test.ts": 128, + "src/renderer/src/hooks/useVirtualizedScrollAnchor.listener-deps.test.ts": 30, + "src/renderer/src/hooks/useVirtualizedScrollAnchor.marks-restore-signal.test.ts": 31, + "src/renderer/src/hooks/useVirtualizedScrollAnchor.test.ts": 8, + "src/renderer/src/hooks/useWindowsTerminalCapabilityOwnerKey.test.tsx": 46, + "src/renderer/src/hooks/viewport-size-change-listener.test.ts": 6, + "src/renderer/src/hooks/worktree-change-refresh-queue.test.ts": 14, + "src/renderer/src/hooks/worktree-head-identity-apply.test.ts": 10, + "src/renderer/src/i18n/I18nProvider.test.tsx": 36, + "src/renderer/src/i18n/integration-card-status-localization.test.ts": 7, + "src/renderer/src/i18n/intl-locale.test.ts": 121, + "src/renderer/src/i18n/ja-technical-literal-mistranslations.test.ts": 7, + "src/renderer/src/i18n/ko-ui-semantic-mistranslations.test.ts": 9, + "src/renderer/src/i18n/lazy-locale.test.ts": 109, + "src/renderer/src/i18n/locale-english-regression.test.ts": 22, + "src/renderer/src/i18n/native-chat-locales.test.ts": 10, + "src/renderer/src/i18n/no-top-level-translate.test.ts": 2294, + "src/renderer/src/i18n/plugin-chrome-allowlist.test.ts": 11, + "src/renderer/src/i18n/pseudo-localization.test.ts": 8, + "src/renderer/src/i18n/relative-time-format.test.ts": 27, + "src/renderer/src/i18n/runtime-required-catalog.test.ts": 335, + "src/renderer/src/i18n/settings-status-label-localization.test.ts": 9, + "src/renderer/src/i18n/smart-workspace-jira-locales.test.ts": 8, + "src/renderer/src/i18n/technical-literal-catalog-values.test.ts": 8, + "src/renderer/src/i18n/weekday-names.test.ts": 40, + "src/renderer/src/i18n/worktree-visibility-locales.test.ts": 8, + "src/renderer/src/i18n/zh-technical-literal-mistranslations.test.ts": 45, + "src/renderer/src/lazy-modal-mount-state.test.ts": 5, + "src/renderer/src/lazy-use-ref-ratchet.test.ts": 165, + "src/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.ts": 11, + "src/renderer/src/lib/activate-ai-vault-structured-session.test.ts": 17, + "src/renderer/src/lib/activate-tab-and-focus-pane.test.ts": 11, + "src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts": 385, + "src/renderer/src/lib/active-agent-note-send-focused-session.test.ts": 74, + "src/renderer/src/lib/active-agent-note-send-runtime-error-codes.test.ts": 6, + "src/renderer/src/lib/active-agent-note-send-target-detection.test.ts": 13, + "src/renderer/src/lib/active-view-persist.test.ts": 4, + "src/renderer/src/lib/activity-thread-display.test.ts": 10, + "src/renderer/src/lib/agent-background-session-launch-host.test.ts": 12, + "src/renderer/src/lib/agent-catalog-links.test.ts": 5, + "src/renderer/src/lib/agent-draft-readiness.test.ts": 8, + "src/renderer/src/lib/agent-followup-delivery.test.ts": 18659, + "src/renderer/src/lib/agent-hibernation-confirmation.test.ts": 4, + "src/renderer/src/lib/agent-hibernation-coordinator.test.ts": 163, + "src/renderer/src/lib/agent-hibernation-output-activity.test.ts": 6, + "src/renderer/src/lib/agent-hibernation-pane-age.test.ts": 9, + "src/renderer/src/lib/agent-hibernation-planner.test.ts": 30, + "src/renderer/src/lib/agent-hibernation-runtime-liveness-race.test.ts": 65, + "src/renderer/src/lib/agent-hibernation-visibility.test.ts": 5, + "src/renderer/src/lib/agent-launch-prompt-delivery.test.ts": 14, + "src/renderer/src/lib/agent-launch-route-connection-fallback.test.ts": 6, + "src/renderer/src/lib/agent-launch-route-input.test.ts": 16, + "src/renderer/src/lib/agent-launch-routing-caller-census.test.ts": 552, + "src/renderer/src/lib/agent-launch-routing.test.ts": 11, + "src/renderer/src/lib/agent-paste-draft-readiness-budget.test.ts": 13, + "src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts": 48, + "src/renderer/src/lib/agent-paste-draft.test.ts": 178, + "src/renderer/src/lib/agent-picker-search.test.ts": 28, + "src/renderer/src/lib/agent-ready-wait.test.ts": 7, + "src/renderer/src/lib/agent-resume-launch-target.test.ts": 2472, + "src/renderer/src/lib/agent-row-primary-text.test.ts": 8, + "src/renderer/src/lib/agent-row-tool-preview.test.ts": 9, + "src/renderer/src/lib/agent-send-title-status.test.ts": 14, + "src/renderer/src/lib/agent-session-fork-context.test.ts": 47, + "src/renderer/src/lib/agent-session-launch-plan.test.ts": 10, + "src/renderer/src/lib/agent-skill-cli-prerequisite.test.ts": 68, + "src/renderer/src/lib/agent-skill-nav-install-status.test.ts": 5, + "src/renderer/src/lib/agent-startup-delayed-delivery-perf.test.ts": 85, + "src/renderer/src/lib/agent-status-connection-ownership.test.ts": 7, + "src/renderer/src/lib/agent-status-count.test.ts": 14, + "src/renderer/src/lib/agent-status-epoch-clock.test.ts": 12, + "src/renderer/src/lib/agent-status-evidence-clock.test.ts": 6, + "src/renderer/src/lib/agent-status-terminal-title.test.ts": 11, + "src/renderer/src/lib/agent-status-worktree-attribution.test.ts": 6, + "src/renderer/src/lib/agent-status.test.ts": 31, + "src/renderer/src/lib/agent-tab-shortcuts.test.ts": 7, + "src/renderer/src/lib/agent-trust-preflight.test.ts": 6, + "src/renderer/src/lib/ai-vault-omp-cold-resume.test.ts": 19, + "src/renderer/src/lib/ai-vault-resume-command.drop-repin.test.ts": 6, + "src/renderer/src/lib/ai-vault-resume-command.resumable-agent.test.ts": 9, + "src/renderer/src/lib/ai-vault-resume-command.test.ts": 31, + "src/renderer/src/lib/ai-vault-resume-shell.test.ts": 5, + "src/renderer/src/lib/ai-vault-resume-target.test.ts": 15, + "src/renderer/src/lib/ai-vault-session-drag.test.ts": 15, + "src/renderer/src/lib/ai-vault-session-resume-preparation.test.ts": 16, + "src/renderer/src/lib/ai-vault-tab-title-sync-inputs.test.ts": 38, + "src/renderer/src/lib/ai-vault-tab-title-sync.test.ts": 576, + "src/renderer/src/lib/app-command-dispatch.test.ts": 6, + "src/renderer/src/lib/app-font-family.test.ts": 4, + "src/renderer/src/lib/app-menu-paste.test.ts": 49, + "src/renderer/src/lib/app-menu-selection-actions.test.ts": 4, + "src/renderer/src/lib/automation-session-observer.test.ts": 58, + "src/renderer/src/lib/automation-session-reuse.test.ts": 7, + "src/renderer/src/lib/automation-terminal-ownership.test.ts": 9, + "src/renderer/src/lib/browser-cookie-import-toast.test.ts": 18, + "src/renderer/src/lib/browser-history-match.performance.test.ts": 17, + "src/renderer/src/lib/browser-history-match.test.ts": 34, + "src/renderer/src/lib/browser-page-conversion-history.test.ts": 8, + "src/renderer/src/lib/browser-page-palette-activation.test.ts": 59, + "src/renderer/src/lib/browser-palette-page-entries.test.ts": 145, + "src/renderer/src/lib/browser-palette-search.test.ts": 28, + "src/renderer/src/lib/browser-uuid.test.ts": 5, + "src/renderer/src/lib/browser-workspace-tab-activation.test.ts": 9, + "src/renderer/src/lib/client-creation-action-policy.test.ts": 12, + "src/renderer/src/lib/cmd-j-github-url-lookup.test.ts": 8, + "src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts": 30, + "src/renderer/src/lib/cmd-j-linear-issue-intent.test.ts": 7, + "src/renderer/src/lib/cmd-j-section-leadership.test.ts": 9, + "src/renderer/src/lib/codex-account-display-label.test.ts": 6, + "src/renderer/src/lib/codex-pane-restart-eligibility.test.ts": 8, + "src/renderer/src/lib/codex-pane-selection-lane.test.ts": 21, + "src/renderer/src/lib/codex-session-restart-route-recheck.test.ts": 13, + "src/renderer/src/lib/codex-session-restart-shell-flap.test.ts": 30, + "src/renderer/src/lib/codex-session-restart.test.ts": 93, + "src/renderer/src/lib/codex-stale-pane-account-identity.test.ts": 17, + "src/renderer/src/lib/codex-stale-pane-sweep.test.ts": 44, + "src/renderer/src/lib/comment-body-line-count.test.ts": 40, + "src/renderer/src/lib/comment-body-submit-state.test.ts": 31, + "src/renderer/src/lib/composer-issue-command.test.ts": 7, + "src/renderer/src/lib/composer-submit-cancellation.test.ts": 7, + "src/renderer/src/lib/connection-context.test.ts": 56, + "src/renderer/src/lib/crash-diagnostics.test.ts": 83, + "src/renderer/src/lib/create-untitled-markdown.test.ts": 13, + "src/renderer/src/lib/desktop-window-chrome.test.ts": 5, + "src/renderer/src/lib/diff-comment-compat.test.ts": 5, + "src/renderer/src/lib/diff-comments-format.test.ts": 7, + "src/renderer/src/lib/direct-ssh-reconnect-product-telemetry.test.ts": 11, + "src/renderer/src/lib/direct-ssh-target-scope.test.ts": 12, + "src/renderer/src/lib/doc-preview-grants.test.ts": 118, + "src/renderer/src/lib/document-theme.test.ts": 10, + "src/renderer/src/lib/editable-target.test.ts": 6, + "src/renderer/src/lib/editor-file-operation-owner.test.ts": 29, + "src/renderer/src/lib/editor-font-zoom.test.ts": 5, + "src/renderer/src/lib/ensure-hooks-confirmed.test.ts": 433, + "src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts": 162, + "src/renderer/src/lib/ensure-simulator-tab.test.ts": 5, + "src/renderer/src/lib/ephemeral-vm-failed-create-cleanup.test.ts": 7, + "src/renderer/src/lib/ephemeral-vm-runtime-cleanup.test.ts": 16, + "src/renderer/src/lib/ephemeral-vm-workspace-target.integration.test.ts": 8, + "src/renderer/src/lib/ephemeral-vm-workspace-target.test.ts": 13, + "src/renderer/src/lib/ephemeral-vm-worktree-creation.test.ts": 8, + "src/renderer/src/lib/execute-open-editor-path-move.test.ts": 209, + "src/renderer/src/lib/external-editor-open-capability.test.ts": 5, + "src/renderer/src/lib/feature-education-telemetry.test.ts": 12, + "src/renderer/src/lib/feedback-image-attachments.test.ts": 28, + "src/renderer/src/lib/file-preview-action-visibility.test.tsx": 22, + "src/renderer/src/lib/file-preview.test.ts": 16, + "src/renderer/src/lib/file-search-result-owner.test.ts": 6, + "src/renderer/src/lib/file-search-selection.test.ts": 8, + "src/renderer/src/lib/file-type-icons.test.ts": 9, + "src/renderer/src/lib/find-query-bounds.test.ts": 4, + "src/renderer/src/lib/finished-agent-resume-resurrection.test.ts": 38, + "src/renderer/src/lib/fix-checks-agent-launch.test.ts": 262, + "src/renderer/src/lib/flatten-retained-slice.test.ts": 132, + "src/renderer/src/lib/floating-terminal.test.ts": 7, + "src/renderer/src/lib/floating-workspace-terminal-actions.test.ts": 26, + "src/renderer/src/lib/floating-workspace-tour-interaction-snapshot.test.ts": 9, + "src/renderer/src/lib/focus-terminal-tab-surface.test.ts": 16, + "src/renderer/src/lib/folder-workspace-path-status.test.ts": 8, + "src/renderer/src/lib/foreground-terminal-tabs.test.ts": 14, + "src/renderer/src/lib/github-links.test.ts": 11, + "src/renderer/src/lib/github-pr-start-point.test.ts": 11, + "src/renderer/src/lib/github-source-runtime-context.test.ts": 8, + "src/renderer/src/lib/github-work-item-source-lookup.test.ts": 8, + "src/renderer/src/lib/github-work-item-workspace-attachment.test.ts": 10, + "src/renderer/src/lib/gitlab-links.test.ts": 14, + "src/renderer/src/lib/gitlab-work-item-source-lookup.test.ts": 8, + "src/renderer/src/lib/hook-command-delayed-delivery-perf.test.ts": 121, + "src/renderer/src/lib/hook-command-delayed-delivery.test.ts": 11, + "src/renderer/src/lib/host-mirrored-pane-resume-replay.test.ts": 20, + "src/renderer/src/lib/http-link-destinations.test.ts": 14, + "src/renderer/src/lib/http-link-modifier-routing.test.ts": 12, + "src/renderer/src/lib/http-link-routing.test.ts": 25, + "src/renderer/src/lib/i18n-jsx-spacing-guard.test.ts": 23, + "src/renderer/src/lib/ime-composition-keyboard-event.test.ts": 30, + "src/renderer/src/lib/ios-web-platform.test.ts": 6, + "src/renderer/src/lib/jira-source-host.test.ts": 8, + "src/renderer/src/lib/keyboard-layout/detect-option-as-alt.test.ts": 11, + "src/renderer/src/lib/keyboard-layout/input-source-id.test.ts": 4, + "src/renderer/src/lib/keyboard-layout/layout-base-character.test.ts": 14, + "src/renderer/src/lib/keyboard-layout/option-as-alt-probe.test.ts": 20, + "src/renderer/src/lib/keyboard-layout/option-key-location-state.test.ts": 7, + "src/renderer/src/lib/language-detect.test.ts": 8, + "src/renderer/src/lib/large-text-control-paste.test.ts": 23, + "src/renderer/src/lib/launch-agent-background-session-remote.test.ts": 498, + "src/renderer/src/lib/launch-agent-background-session.test.ts": 360, + "src/renderer/src/lib/launch-agent-in-new-tab-cwd.test.ts": 653, + "src/renderer/src/lib/launch-agent-in-new-tab-host-resolution.test.ts": 553, + "src/renderer/src/lib/launch-agent-in-new-tab-structured.test.ts": 15, + "src/renderer/src/lib/launch-agent-in-new-tab-web-runtime.test.ts": 544, + "src/renderer/src/lib/launch-agent-in-new-tab-windows-quoting.test.ts": 760, + "src/renderer/src/lib/launch-agent-in-new-tab.test.ts": 1687, + "src/renderer/src/lib/launch-agent-session-continuation.test.ts": 27, + "src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts": 945, + "src/renderer/src/lib/launch-ai-vault-session.test.ts": 10, + "src/renderer/src/lib/launch-structured-agent-session.test.ts": 726, + "src/renderer/src/lib/launch-work-item-direct-agent-routing.test.ts": 12, + "src/renderer/src/lib/launch-work-item-direct-agent.test.ts": 15, + "src/renderer/src/lib/launch-work-item-direct-messages.test.ts": 6, + "src/renderer/src/lib/launch-work-item-direct.test.ts": 51, + "src/renderer/src/lib/launch-worktree-background-terminals.test.ts": 305, + "src/renderer/src/lib/lazy-chunk-recovery-reload.test.ts": 61, + "src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts": 10, + "src/renderer/src/lib/lazy-with-retry.right-sidebar-syntax-error.test.ts": 10, + "src/renderer/src/lib/lazy-with-retry.test.ts": 37, + "src/renderer/src/lib/left-sidebar-appearance.test.ts": 13, + "src/renderer/src/lib/linear-agent-skill-update-command.test.ts": 4, + "src/renderer/src/lib/linear-board-drag-payload.test.ts": 10, + "src/renderer/src/lib/linear-issue-context-snapshot.test.ts": 19, + "src/renderer/src/lib/linear-issue-url-lookup.test.ts": 10, + "src/renderer/src/lib/linear-issue-workspace-attachment.test.ts": 10, + "src/renderer/src/lib/linear-issue-workspace-open.test.ts": 10, + "src/renderer/src/lib/linear-linked-work-item.test.ts": 12, + "src/renderer/src/lib/linked-work-item-context.test.ts": 16, + "src/renderer/src/lib/linked-work-item-provider.test.ts": 5, + "src/renderer/src/lib/list-row-interaction.test.ts": 11, + "src/renderer/src/lib/local-path-open-guard.test.ts": 5, + "src/renderer/src/lib/local-preflight-context-cache.test.ts": 31, + "src/renderer/src/lib/local-preflight-context.test.ts": 18, + "src/renderer/src/lib/locale-text-collators.test.ts": 30, + "src/renderer/src/lib/manual-terminal-worktree-parking.test.ts": 7, + "src/renderer/src/lib/markdown-comment-blocks.test.ts": 6, + "src/renderer/src/lib/markdown-document-templates.test.ts": 9, + "src/renderer/src/lib/markdown-review-note-copy.test.ts": 5, + "src/renderer/src/lib/markdown-review-notes.test.ts": 26, + "src/renderer/src/lib/migration-unsupported-agent-entry.test.ts": 4, + "src/renderer/src/lib/mobile-terminal-tab-mount.test.ts": 7, + "src/renderer/src/lib/monaco-delayer-cancellation-guard.test.ts": 25, + "src/renderer/src/lib/monaco-diff-editor-disposal.test.ts": 13, + "src/renderer/src/lib/monaco-languages/monarch-embed-entry-recursion.test.ts": 388, + "src/renderer/src/lib/monaco-languages/monarch-upstream-mdx-recursion.test.ts": 61, + "src/renderer/src/lib/monaco-languages/register-astro.test.ts": 32, + "src/renderer/src/lib/monaco-languages/register-jsonl.test.ts": 27, + "src/renderer/src/lib/monaco-languages/register-nim.test.ts": 22, + "src/renderer/src/lib/monaco-languages/register-svelte.test.ts": 28, + "src/renderer/src/lib/monaco-languages/register-vue.test.ts": 20, + "src/renderer/src/lib/monaco-languages/textmate-language-registration.test.ts": 10, + "src/renderer/src/lib/monaco-languages/textmate-token-provider.test.ts": 123, + "src/renderer/src/lib/monaco-peek-preview-options.test.ts": 12, + "src/renderer/src/lib/native-chat-initial-view-mode.test.ts": 13, + "src/renderer/src/lib/native-chat-launch-draft-mirrorability.test.ts": 12, + "src/renderer/src/lib/native-chat-transcript-readability.test.ts": 4, + "src/renderer/src/lib/nested-repo-selected-paths.test.ts": 8, + "src/renderer/src/lib/new-workspace-composer-repo.test.ts": 7, + "src/renderer/src/lib/new-workspace-create-gates.test.ts": 4, + "src/renderer/src/lib/new-workspace-enter-guard.test.ts": 18, + "src/renderer/src/lib/new-workspace-project-options.test.ts": 21, + "src/renderer/src/lib/new-workspace-ssh-gate.test.ts": 6, + "src/renderer/src/lib/new-workspace.test.ts": 4395, + "src/renderer/src/lib/non-secure-context-crypto.repro.test.ts": 11, + "src/renderer/src/lib/notes-send-agent-targets.test.ts": 30, + "src/renderer/src/lib/onboarding-project-checklist.test.ts": 6, + "src/renderer/src/lib/open-markdown-in-floating-workspace.test.ts": 7, + "src/renderer/src/lib/open-mobile-emulator-tab.test.ts": 17, + "src/renderer/src/lib/open-tab-occupant-agent.test.ts": 16, + "src/renderer/src/lib/orca-hook-trust.test.ts": 9, + "src/renderer/src/lib/orca-yaml-trust-prompt-slot-eviction.test.ts": 278, + "src/renderer/src/lib/orchestration-setup-state.test.ts": 9, + "src/renderer/src/lib/orchestration-skill-coverage.test.ts": 20, + "src/renderer/src/lib/order-empty-query-worktrees.test.ts": 30, + "src/renderer/src/lib/palette-match/cmd-j-ranking-contract.test.ts": 15, + "src/renderer/src/lib/palette-match/match-field-allocation.test.ts": 49, + "src/renderer/src/lib/palette-match/palette-match-core.test.ts": 26, + "src/renderer/src/lib/palette-match/palette-match-performance.test.ts": 9461, + "src/renderer/src/lib/palette-match/palette-ranking.test.ts": 16, + "src/renderer/src/lib/palette-repo-resolution.test.ts": 9, + "src/renderer/src/lib/palette-type-alias-match.test.ts": 9, + "src/renderer/src/lib/pane-agent-evidence.test.ts": 23, + "src/renderer/src/lib/pane-manager/browser-mobile-driver-state.test.ts": 10, + "src/renderer/src/lib/pane-manager/browser-remote-viewer-state.test.ts": 5, + "src/renderer/src/lib/pane-manager/client-hosted-browser-row-ephemerality.test.ts": 22, + "src/renderer/src/lib/pane-manager/client-hosted-browser-row-state.test.ts": 16, + "src/renderer/src/lib/pane-manager/focus-follows-mouse.test.ts": 7, + "src/renderer/src/lib/pane-manager/mobile-driver-state.test.ts": 10, + "src/renderer/src/lib/pane-manager/mobile-fit-overrides-hydration.test.ts": 5, + "src/renderer/src/lib/pane-manager/mobile-fit-overrides.test.ts": 23, + "src/renderer/src/lib/pane-manager/pane-container-listener-lifecycle.test.ts": 10, + "src/renderer/src/lib/pane-manager/pane-cursor-blink-suspension.test.ts": 318, + "src/renderer/src/lib/pane-manager/pane-divider-capture-loss.test.ts": 6, + "src/renderer/src/lib/pane-manager/pane-divider-stray-touch.test.ts": 23, + "src/renderer/src/lib/pane-manager/pane-divider.test.ts": 13, + "src/renderer/src/lib/pane-manager/pane-dom-creation.test.ts": 20, + "src/renderer/src/lib/pane-manager/pane-drag-reorder.test.ts": 17, + "src/renderer/src/lib/pane-manager/pane-drop-no-op.test.ts": 12, + "src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts": 16, + "src/renderer/src/lib/pane-manager/pane-fit.test.ts": 61, + "src/renderer/src/lib/pane-manager/pane-initial-fit-lifecycle.test.ts": 10, + "src/renderer/src/lib/pane-manager/pane-key-resolution.test.ts": 9, + "src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts": 52, + "src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts": 26, + "src/renderer/src/lib/pane-manager/pane-metric-options-deferral.test.ts": 14, + "src/renderer/src/lib/pane-manager/pane-overlay-focus.test.ts": 44, + "src/renderer/src/lib/pane-manager/pane-pointer-focus.test.ts": 5, + "src/renderer/src/lib/pane-manager/pane-pty-resize-hold.test.ts": 8, + "src/renderer/src/lib/pane-manager/pane-reveal-fit.test.ts": 10, + "src/renderer/src/lib/pane-manager/pane-reveal-repaint.test.ts": 53, + "src/renderer/src/lib/pane-manager/pane-scroll.test.ts": 87, + "src/renderer/src/lib/pane-manager/pane-split-close.test.ts": 12, + "src/renderer/src/lib/pane-manager/pane-split-scroll.test.ts": 12, + "src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.test.ts": 8, + "src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.test.ts": 12, + "src/renderer/src/lib/pane-manager/pane-terminal-mouse-wheel.test.ts": 15, + "src/renderer/src/lib/pane-manager/pane-terminal-output-queue-chunks.test.ts": 5, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-ack-credit.test.ts": 69, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-backlog-cap.test.ts": 110, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-disposed-writes.test.ts": 46, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-foreground-refresh.test.ts": 68, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts": 254, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-synchronized-frames.test.ts": 65, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-throughput.bench.test.ts": 256, + "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts": 146, + "src/renderer/src/lib/pane-manager/pane-tree-equalization-parity.test.ts": 1130, + "src/renderer/src/lib/pane-manager/pane-tree-equalization-scaling.test.ts": 65, + "src/renderer/src/lib/pane-manager/pane-tree-ops.test.ts": 26, + "src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts": 30, + "src/renderer/src/lib/pane-manager/pane-webgl-context-recovery.test.ts": 50, + "src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts": 8, + "src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts": 38, + "src/renderer/src/lib/pane-manager/terminal-arabic-shaping-joiner.test.ts": 16, + "src/renderer/src/lib/pane-manager/terminal-canvas-dpr-repair.test.ts": 11, + "src/renderer/src/lib/pane-manager/terminal-complex-script.test.ts": 15, + "src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts": 12, + "src/renderer/src/lib/pane-manager/terminal-foreground-repair-convergence.test.ts": 183, + "src/renderer/src/lib/pane-manager/terminal-ime-anchor.test.ts": 11, + "src/renderer/src/lib/pane-manager/terminal-ime-candidate-anchor.test.ts": 41, + "src/renderer/src/lib/pane-manager/terminal-keyboard-protocol.test.ts": 12, + "src/renderer/src/lib/pane-manager/terminal-ligatures-addon.test.ts": 16, + "src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts": 12, + "src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-mouseleave.test.ts": 14, + "src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-write.test.ts": 13, + "src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset.test.ts": 5, + "src/renderer/src/lib/pane-manager/terminal-render-pause-release-parked-resize.test.ts": 10, + "src/renderer/src/lib/pane-manager/terminal-render-pause-release.test.ts": 12, + "src/renderer/src/lib/pane-manager/terminal-scroll-intent-input-resync.test.ts": 14, + "src/renderer/src/lib/pane-manager/terminal-scroll-intent-key-retention.test.ts": 55, + "src/renderer/src/lib/pane-manager/terminal-scroll-intent-structural-transitions.test.ts": 13, + "src/renderer/src/lib/pane-manager/terminal-scroll-intent.test.ts": 22, + "src/renderer/src/lib/pane-manager/terminal-structural-replay-coordinator.test.ts": 16, + "src/renderer/src/lib/pane-manager/terminal-webgl-auto-policy.test.ts": 13, + "src/renderer/src/lib/pane-manager/terminal-webgl-hidden-retention.test.ts": 19, + "src/renderer/src/lib/pane-manager/terminal-windows-ctrl-alt-chord-classification.test.ts": 16, + "src/renderer/src/lib/pane-manager/terminal-write-pipeline-health.test.ts": 19, + "src/renderer/src/lib/pane-manager/windows-pty-compatibility.test.ts": 20, + "src/renderer/src/lib/pane-manager/xterm-instance-disposed.test.ts": 10, + "src/renderer/src/lib/pane-manager/xterm-user-scrolling-contract.test.ts": 5037, + "src/renderer/src/lib/pane-manager/xterm-write-callback-guard.test.ts": 10, + "src/renderer/src/lib/parked-terminal-host-hydration.test.ts": 12, + "src/renderer/src/lib/passive-macos-app-data-access.test.ts": 6, + "src/renderer/src/lib/paste-payload-metadata.test.ts": 11, + "src/renderer/src/lib/path-head-elision.test.ts": 5, + "src/renderer/src/lib/path.test.ts": 6, + "src/renderer/src/lib/pending-worktree-creation.test.ts": 5, + "src/renderer/src/lib/pi-live-session-no-duplicate-tab.test.ts": 11, + "src/renderer/src/lib/pi-session-resume-wake.test.ts": 12, + "src/renderer/src/lib/plugin-command-execution.test.ts": 7, + "src/renderer/src/lib/plugin-command-keybindings.test.ts": 19, + "src/renderer/src/lib/pr-bot-author-overrides.test.ts": 213, + "src/renderer/src/lib/pr-comment-action-state.test.ts": 12, + "src/renderer/src/lib/pr-comment-reactions.test.ts": 6, + "src/renderer/src/lib/primary-selection-paste.test.ts": 79, + "src/renderer/src/lib/primary-selection.test.ts": 9, + "src/renderer/src/lib/project-clone-url-prefill.test.ts": 15, + "src/renderer/src/lib/project-host-clone-url.test.ts": 8, + "src/renderer/src/lib/project-host-setup-options.test.ts": 20, + "src/renderer/src/lib/project-host-workspace-target.test.ts": 17, + "src/renderer/src/lib/project-skill-runtime.test.ts": 8, + "src/renderer/src/lib/provisioned-root-create-options.test.ts": 8, + "src/renderer/src/lib/quick-workspace-agent-selection.test.ts": 7, + "src/renderer/src/lib/react-commit-cascade-install-order.test.ts": 8, + "src/renderer/src/lib/react-commit-cascade-observer.react185.test.tsx": 44, + "src/renderer/src/lib/react-commit-cascade-observer.test.ts": 150, + "src/renderer/src/lib/react-commit-cascade-store-write-samples.test.ts": 12, + "src/renderer/src/lib/react-commit-cascade-telemetry.test.ts": 108, + "src/renderer/src/lib/react-error-boundary-reporting.test.ts": 15, + "src/renderer/src/lib/react-grab-dev-gate.test.ts": 4, + "src/renderer/src/lib/react-renderer-root.test.ts": 6, + "src/renderer/src/lib/recent-workspace-tab-rows.test.ts": 19, + "src/renderer/src/lib/remap-open-editor-tabs-for-path-change.test.ts": 62, + "src/renderer/src/lib/renderer-agent-status-observation-ingress.test.ts": 36, + "src/renderer/src/lib/renderer-app-platform.test.ts": 34, + "src/renderer/src/lib/renderer-memory-profile.test.ts": 26, + "src/renderer/src/lib/repo-display-labels.test.ts": 6, + "src/renderer/src/lib/repo-runtime-owner.test.ts": 14, + "src/renderer/src/lib/repo-search.test.ts": 10, + "src/renderer/src/lib/repo-slug-cache.test.ts": 8, + "src/renderer/src/lib/repo-slug-index.test.ts": 515, + "src/renderer/src/lib/resolved-worktree-execution-host.test.ts": 7, + "src/renderer/src/lib/resume-sleeping-agent-session-direct-ssh-hydration-gap.test.ts": 22, + "src/renderer/src/lib/resume-sleeping-agent-session-provider-claim.test.ts": 13, + "src/renderer/src/lib/resume-sleeping-agent-session-remote-compat.test.ts": 25, + "src/renderer/src/lib/resume-sleeping-agent-session-replay.test.ts": 65, + "src/renderer/src/lib/resume-sleeping-agent-session-slept-pane-recovery.test.ts": 11, + "src/renderer/src/lib/resume-sleeping-agent-session-suppress-navigation.test.ts": 32, + "src/renderer/src/lib/resume-sleeping-agent-session.test.ts": 126, + "src/renderer/src/lib/resume-stale-structured-agent-session.test.ts": 9, + "src/renderer/src/lib/right-sidebar-visibility.test.ts": 8, + "src/renderer/src/lib/run-quick-command-in-new-tab.test.ts": 10, + "src/renderer/src/lib/running-agent-targets.test.ts": 12, + "src/renderer/src/lib/runtime-pane-title-leaf-id.test.ts": 9, + "src/renderer/src/lib/runtime-session-mirror-targets.test.ts": 6, + "src/renderer/src/lib/runtime-workspace-file-route.test.ts": 14, + "src/renderer/src/lib/screen-submit-shortcut.test.ts": 6, + "src/renderer/src/lib/script-textarea-rows.test.ts": 52, + "src/renderer/src/lib/scroll-cache.test.ts": 9, + "src/renderer/src/lib/serve-desktop-promotion-session-continuity.test.ts": 13, + "src/renderer/src/lib/session-write-subscriber-allocation.test.ts": 21, + "src/renderer/src/lib/session-write-subscriber-deferred-persist.test.ts": 21, + "src/renderer/src/lib/session-write-subscriber.test.ts": 107, + "src/renderer/src/lib/settled-worker-wake-policy.test.ts": 25, + "src/renderer/src/lib/setup-runner.test.ts": 4, + "src/renderer/src/lib/setup-script-prompt.test.ts": 20, + "src/renderer/src/lib/sha256.test.ts": 22, + "src/renderer/src/lib/shutdown-checkpoint-guard.test.ts": 26, + "src/renderer/src/lib/sidebar-worktree-activation.test.ts": 13, + "src/renderer/src/lib/simulator-launch-coordination.test.ts": 112, + "src/renderer/src/lib/simulator-palette-search.test.ts": 29, + "src/renderer/src/lib/simulator-pane-shutdown-scheduler.test.ts": 17, + "src/renderer/src/lib/simulator-tab-palette-activation.test.ts": 32, + "src/renderer/src/lib/skill-freshness-display-status.test.ts": 16, + "src/renderer/src/lib/sleeping-agent-session-launch-windows-quoting.test.ts": 290, + "src/renderer/src/lib/smart-github-submit.test.ts": 19, + "src/renderer/src/lib/source-control-agent-action-plan.test.ts": 15, + "src/renderer/src/lib/source-control-generation-plan.test.ts": 16, + "src/renderer/src/lib/source-control-launch-agent-selection.test.ts": 12, + "src/renderer/src/lib/source-control-launch-platform.test.ts": 10, + "src/renderer/src/lib/source-control-remote-error.test.ts": 27, + "src/renderer/src/lib/sparse-preset-draft.test.ts": 21, + "src/renderer/src/lib/ssh-background-startup-delivery.test.ts": 17, + "src/renderer/src/lib/ssh-mutation-expectation.test.ts": 7, + "src/renderer/src/lib/startup-ui-hydration.test.ts": 10, + "src/renderer/src/lib/state-collection-byte-estimate.test.ts": 46, + "src/renderer/src/lib/structured-agent-launch-settlement-caller-census.test.ts": 361, + "src/renderer/src/lib/structured-agent-launch-settlement.test.ts": 67, + "src/renderer/src/lib/structured-agent-session-launch-join-delivery.test.ts": 8, + "src/renderer/src/lib/structured-agent-session-launch-prompt.test.ts": 12, + "src/renderer/src/lib/structured-agent-session-launch-refusal-fallback.test.ts": 25, + "src/renderer/src/lib/structured-agent-session-launch-resume-identity.test.ts": 16, + "src/renderer/src/lib/structured-agent-session-launch.test.ts": 307, + "src/renderer/src/lib/structured-agent-session-tab-activation.test.ts": 7, + "src/renderer/src/lib/tab-agent-identity-decision-table.test.ts": 438, + "src/renderer/src/lib/tab-agent-status-index.test.ts": 162, + "src/renderer/src/lib/tab-agent.test.ts": 10, + "src/renderer/src/lib/tab-has-live-pty.test.ts": 7, + "src/renderer/src/lib/tab-number-shortcuts.test.ts": 9, + "src/renderer/src/lib/terminal-contrast-correction.test.ts": 13, + "src/renderer/src/lib/terminal-file-uri-link.test.ts": 8, + "src/renderer/src/lib/terminal-input-activity-coalescing.test.ts": 12, + "src/renderer/src/lib/terminal-links-redos.test.ts": 12, + "src/renderer/src/lib/terminal-links.test.ts": 331, + "src/renderer/src/lib/terminal-pane-title-sanitization.test.ts": 7, + "src/renderer/src/lib/terminal-quick-command-project-scope.test.ts": 7, + "src/renderer/src/lib/terminal-quick-command-search.test.ts": 11, + "src/renderer/src/lib/terminal-reveal-identity.test.ts": 10, + "src/renderer/src/lib/terminal-shortcut-capture-notification.test.tsx": 16, + "src/renderer/src/lib/terminal-tab-for-pty-id.test.ts": 10, + "src/renderer/src/lib/terminal-theme.test.ts": 22, + "src/renderer/src/lib/terminal-worktree-route.test.ts": 16, + "src/renderer/src/lib/text-control-paste-ownership.test.ts": 21, + "src/renderer/src/lib/text-control-paste.test.ts": 30, + "src/renderer/src/lib/titlebar-left-chrome.test.ts": 6, + "src/renderer/src/lib/titlebar-worktree-history-controls.test.ts": 4, + "src/renderer/src/lib/tui-agent-startup.test.ts": 18, + "src/renderer/src/lib/typing-latency/diagnostic-lifecycle.test.ts": 14, + "src/renderer/src/lib/typing-latency/diagnostic-summary.test.ts": 13, + "src/renderer/src/lib/typing-latency/echo-instrumentation.test.ts": 28, + "src/renderer/src/lib/typing-latency/input-events.test.ts": 7, + "src/renderer/src/lib/typing-latency/input-source.test.ts": 8, + "src/renderer/src/lib/typing-latency/sample-window.test.ts": 10, + "src/renderer/src/lib/unread-badge-count.test.ts": 4, + "src/renderer/src/lib/update-check-click-options.test.ts": 5, + "src/renderer/src/lib/updater-beforeunload.test.ts": 8, + "src/renderer/src/lib/use-tab-agent-observed-signal-dispatch.test.tsx": 63, + "src/renderer/src/lib/use-tab-agent-opencode-native-title.test.ts": 40, + "src/renderer/src/lib/use-tab-agent-pi-identity.test.ts": 27, + "src/renderer/src/lib/use-tab-agent-process-signals.test.ts": 48, + "src/renderer/src/lib/use-tab-agent-remote-pty-selector.test.ts": 30, + "src/renderer/src/lib/use-tab-agent-retained-identity.test.ts": 36, + "src/renderer/src/lib/use-tab-agent-sleeping-session.test.ts": 37, + "src/renderer/src/lib/use-tab-agent.test.ts": 112, + "src/renderer/src/lib/visible-overlay.test.ts": 15, + "src/renderer/src/lib/wake-sleeping-agents-in-background.test.ts": 19, + "src/renderer/src/lib/wake-sleeping-agents-live-done.test.ts": 10, + "src/renderer/src/lib/web-client-location.test.ts": 6, + "src/renderer/src/lib/window-label-formatter.test.ts": 13, + "src/renderer/src/lib/window-visibility-interval.test.ts": 15, + "src/renderer/src/lib/window-visibility-timeout-poller.test.ts": 10, + "src/renderer/src/lib/windows-terminal-capabilities-race.test.ts": 7, + "src/renderer/src/lib/windows-terminal-capabilities.test.ts": 64, + "src/renderer/src/lib/windows-terminal-capability-reprobe.test.ts": 25, + "src/renderer/src/lib/work-item-link-query-bounds.test.ts": 5, + "src/renderer/src/lib/work-item-lookup-text.test.ts": 8, + "src/renderer/src/lib/worker-terminal-takeover-report.test.ts": 257, + "src/renderer/src/lib/workspace-activation-path-gate.test.ts": 23, + "src/renderer/src/lib/workspace-activation-terminal-focus.test.ts": 270, + "src/renderer/src/lib/workspace-browser-tab-open.test.ts": 37, + "src/renderer/src/lib/workspace-composer-initial-focus.test.ts": 16, + "src/renderer/src/lib/workspace-create-error-format.test.ts": 9, + "src/renderer/src/lib/workspace-doc-address-input.test.ts": 12, + "src/renderer/src/lib/workspace-emoji-shortcodes.lazy.test.ts": 60, + "src/renderer/src/lib/workspace-emoji-shortcodes.test.ts": 29, + "src/renderer/src/lib/workspace-file-drag.test.ts": 7, + "src/renderer/src/lib/workspace-port-groups.test.ts": 5, + "src/renderer/src/lib/workspace-port-host-availability.test.ts": 12, + "src/renderer/src/lib/workspace-port-scan-debounce.test.ts": 12, + "src/renderer/src/lib/workspace-port-scan-publish.test.ts": 24, + "src/renderer/src/lib/workspace-session-browser-history.test.ts": 12, + "src/renderer/src/lib/workspace-session-closed-tab-tombstones.test.ts": 7, + "src/renderer/src/lib/workspace-session-editor-drafts.test.ts": 9, + "src/renderer/src/lib/workspace-session-host-contention.test.ts": 18, + "src/renderer/src/lib/workspace-session-host-persistence.test.ts": 22, + "src/renderer/src/lib/workspace-session-host-split.test.ts": 14, + "src/renderer/src/lib/workspace-session-hydration-keys.test.ts": 8, + "src/renderer/src/lib/workspace-session-liveness.test.ts": 8, + "src/renderer/src/lib/workspace-session-patch.test.ts": 12, + "src/renderer/src/lib/workspace-session-persistence-gate.test.ts": 8, + "src/renderer/src/lib/workspace-session-relevant-fields.test.ts": 6, + "src/renderer/src/lib/workspace-session-staged-browser-tabs.test.ts": 5, + "src/renderer/src/lib/workspace-session.test.ts": 20, + "src/renderer/src/lib/workspace-tab-agent-metadata.test.ts": 11, + "src/renderer/src/lib/workspace-tab-palette-activation.store.test.ts": 10, + "src/renderer/src/lib/workspace-tab-palette-activation.test.ts": 19, + "src/renderer/src/lib/workspace-tab-palette-results.test.ts": 19, + "src/renderer/src/lib/workspace-tab-palette-search.test.ts": 37, + "src/renderer/src/lib/workspace-terminal-host-authority.test.ts": 38, + "src/renderer/src/lib/worktree-activation-agent-startup.test.ts": 42, + "src/renderer/src/lib/worktree-activation-automation-filter.test.ts": 19, + "src/renderer/src/lib/worktree-activation-created-agent.test.ts": 72, + "src/renderer/src/lib/worktree-activation-default-tabs.test.ts": 33, + "src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts": 71, + "src/renderer/src/lib/worktree-activation-empty-remote.test.ts": 63, + "src/renderer/src/lib/worktree-activation-issue-command.test.ts": 21, + "src/renderer/src/lib/worktree-activation-pty-inventory.test.ts": 18, + "src/renderer/src/lib/worktree-activation-reveal.test.ts": 18, + "src/renderer/src/lib/worktree-activation-setup-script.test.ts": 67, + "src/renderer/src/lib/worktree-activation-structured-chat-surface.test.ts": 43, + "src/renderer/src/lib/worktree-activation-surface-caller-wiring.test.ts": 366, + "src/renderer/src/lib/worktree-activation-web-runtime.test.ts": 198, + "src/renderer/src/lib/worktree-activity-state.test.ts": 8, + "src/renderer/src/lib/worktree-agent-activation-gate.test.ts": 36, + "src/renderer/src/lib/worktree-agent-activation-seam.test.ts": 37, + "src/renderer/src/lib/worktree-agent-structured-inventory.test.ts": 8, + "src/renderer/src/lib/worktree-attachment-label.test.ts": 6, + "src/renderer/src/lib/worktree-creation-agent-seeding.test.ts": 11, + "src/renderer/src/lib/worktree-creation-agent-seeds.test.ts": 107, + "src/renderer/src/lib/worktree-creation-chat-setup.test.ts": 23, + "src/renderer/src/lib/worktree-creation-flow-agent-trust-preflight.test.ts": 3, + "src/renderer/src/lib/worktree-creation-flow-dedupe.test.ts": 7, + "src/renderer/src/lib/worktree-creation-flow-stranded-surface.test.ts": 123, + "src/renderer/src/lib/worktree-creation-flow.test.ts": 879, + "src/renderer/src/lib/worktree-creation-structured-session.test.ts": 28, + "src/renderer/src/lib/worktree-creation-structured-unknown-outcome.test.ts": 9, + "src/renderer/src/lib/worktree-creation-surface.test.ts": 4, + "src/renderer/src/lib/worktree-default-display-name.test.ts": 11, + "src/renderer/src/lib/worktree-display-name-order.test.ts": 13, + "src/renderer/src/lib/worktree-draft-startup-view-mode.test.ts": 12, + "src/renderer/src/lib/worktree-git-identity-display.test.ts": 4, + "src/renderer/src/lib/worktree-jump-navigation.test.ts": 21, + "src/renderer/src/lib/worktree-live-terminal-surface-owners.test.ts": 13, + "src/renderer/src/lib/worktree-operation-generation.test.ts": 7, + "src/renderer/src/lib/worktree-operation-route.test.ts": 20, + "src/renderer/src/lib/worktree-palette-comment-snippet.test.ts": 13, + "src/renderer/src/lib/worktree-palette-create-action.test.ts": 7, + "src/renderer/src/lib/worktree-palette-multi-keyword.test.ts": 143, + "src/renderer/src/lib/worktree-palette-review-match.test.ts": 9, + "src/renderer/src/lib/worktree-palette-runtime-owner-identity.test.ts": 14, + "src/renderer/src/lib/worktree-palette-search.test.ts": 62, + "src/renderer/src/lib/worktree-palette-task-url-match.test.ts": 25, + "src/renderer/src/lib/worktree-reactivation-preserved-pane-replacement.test.ts": 53, + "src/renderer/src/lib/worktree-reactivation-runtime-owned-resume-deferral.test.ts": 30, + "src/renderer/src/lib/worktree-reactivation-tab-forkbomb.test.ts": 30, + "src/renderer/src/lib/worktree-runtime-owner-index.detected-perf.test.ts": 411, + "src/renderer/src/lib/worktree-runtime-owner-index.detected.test.ts": 80, + "src/renderer/src/lib/worktree-runtime-owner-index.test.ts": 7, + "src/renderer/src/lib/worktree-runtime-owner.test.ts": 64, + "src/renderer/src/lib/worktree-sort-order-host-split.test.ts": 5, + "src/renderer/src/lib/worktree-sort-order-persistence.test.ts": 29, + "src/renderer/src/lib/worktree-status-spinner-launch-agent.test.ts": 15, + "src/renderer/src/lib/worktree-status-terminal-layout-roots.test.ts": 4, + "src/renderer/src/lib/worktree-status.interrupted.test.ts": 8, + "src/renderer/src/lib/worktree-status.test.ts": 16, + "src/renderer/src/lib/worktree-visit-recency.test.ts": 6, + "src/renderer/src/renderer-node-builtin-boundary.test.ts": 1808, + "src/renderer/src/runtime/agent-resume-host-authority-capability.test.ts": 11, + "src/renderer/src/runtime/agent-session-operation-id.test.ts": 6, + "src/renderer/src/runtime/browser-client-host-identity.test.ts": 7, + "src/renderer/src/runtime/browser-workspace-tab-close-census.test.ts": 221, + "src/renderer/src/runtime/browser-workspace-tab-close-plan.test.ts": 11, + "src/renderer/src/runtime/browser-workspace-tab-close.test.ts": 21, + "src/renderer/src/runtime/client-hosted-browser-close-intent-replay.test.ts": 11, + "src/renderer/src/runtime/client-hosted-browser-close-intents.test.ts": 16, + "src/renderer/src/runtime/client-hosted-browser-row-close.test.ts": 13, + "src/renderer/src/runtime/close-mirrored-editor-tab.test.ts": 61, + "src/renderer/src/runtime/file-explorer-delete-owner-provenance.test.ts": 47, + "src/renderer/src/runtime/focus-runtime-terminal-surface-chat-view.test.ts": 13, + "src/renderer/src/runtime/github-check-details-timeout.test.ts": 14, + "src/renderer/src/runtime/gitlab-job-trace-client.test.ts": 25, + "src/renderer/src/runtime/host-session-mirror-empty-inventory-settle.test.ts": 37, + "src/renderer/src/runtime/host-session-mirror-hydration-frame-ordering.test.tsx": 120, + "src/renderer/src/runtime/host-session-mirror-settle-census.test.ts": 76, + "src/renderer/src/runtime/host-session-mirror-settle-receipt-frames.test.tsx": 165, + "src/renderer/src/runtime/host-session-snapshot-authority-client-hosted.test.ts": 4, + "src/renderer/src/runtime/local-runtime-capabilities.test.ts": 10, + "src/renderer/src/runtime/local-session-tab-close-owner.test.ts": 9, + "src/renderer/src/runtime/local-structured-session-empty-worktree-visibility.test.ts": 17, + "src/renderer/src/runtime/local-structured-session-retired-epoch-repair.test.ts": 21, + "src/renderer/src/runtime/local-structured-session-reveal-visibility.test.ts": 35, + "src/renderer/src/runtime/local-structured-session-tabs-host-isolation.test.ts": 28, + "src/renderer/src/runtime/local-structured-session-tabs-sync.test.ts": 210, + "src/renderer/src/runtime/local-structured-session-tabs-sync/retired-epoch-repair.test.ts": 16, + "src/renderer/src/runtime/mirrored-agent-status-clock-skew.test.ts": 51, + "src/renderer/src/runtime/mobile-markdown-bridge-save-guards.test.ts": 58, + "src/renderer/src/runtime/mobile-markdown-bridge.test.ts": 31, + "src/renderer/src/runtime/native-chat-launch-draft-runtime-resolution.test.ts": 6, + "src/renderer/src/runtime/paired-reconnect-sidebar-agent-count.test.ts": 146, + "src/renderer/src/runtime/remote-agent-row-last-assistant-message.test.ts": 98, + "src/renderer/src/runtime/remote-agent-session-launch.test.ts": 11, + "src/renderer/src/runtime/remote-host-file-delete-repro.test.ts": 30, + "src/renderer/src/runtime/remote-host-file-open-repro.test.ts": 16, + "src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts": 11, + "src/renderer/src/runtime/remote-runtime-snapshot-outcome.test.ts": 28, + "src/renderer/src/runtime/remote-runtime-terminal-end-verdict.test.ts": 49, + "src/renderer/src/runtime/remote-runtime-terminal-frame-drop-resync.test.ts": 47, + "src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts": 296, + "src/renderer/src/runtime/remote-runtime-terminal-snapshot-kitty-flags.test.ts": 17, + "src/renderer/src/runtime/remote-runtime-terminal-stale-stream-frames.test.ts": 9, + "src/renderer/src/runtime/remote-runtime-terminal-stall-recovery.test.ts": 145, + "src/renderer/src/runtime/remote-server-install-failure-probe.test.ts": 9, + "src/renderer/src/runtime/remote-server-parity.test.ts": 28, + "src/renderer/src/runtime/remote-server-restart-wait.test.ts": 7, + "src/renderer/src/runtime/remote-server-update-coordinator.test.ts": 24, + "src/renderer/src/runtime/remote-terminal-layout-resolution.test.ts": 14, + "src/renderer/src/runtime/remote-terminal-stream-watchdog.test.ts": 13, + "src/renderer/src/runtime/restored-client-hosted-browser-host-attach.test.ts": 16, + "src/renderer/src/runtime/restored-client-hosted-browser-host-restart-attach.test.ts": 15, + "src/renderer/src/runtime/runtime-client-events.test.ts": 9, + "src/renderer/src/runtime/runtime-environment-ssh-state.test.ts": 148, + "src/renderer/src/runtime/runtime-file-client-download.test.ts": 21, + "src/renderer/src/runtime/runtime-file-client-external-import.test.ts": 20, + "src/renderer/src/runtime/runtime-file-client-mutation-ownership.test.ts": 17, + "src/renderer/src/runtime/runtime-file-client-search-listing.test.ts": 129, + "src/renderer/src/runtime/runtime-file-client-watch.test.ts": 73, + "src/renderer/src/runtime/runtime-file-client.test.ts": 18, + "src/renderer/src/runtime/runtime-file-import-pairing-revision.test.ts": 22, + "src/renderer/src/runtime/runtime-file-search-bounds.test.ts": 7, + "src/renderer/src/runtime/runtime-git-client-api-contract.test.ts": 4, + "src/renderer/src/runtime/runtime-git-client-branch-line-total.test.ts": 13, + "src/renderer/src/runtime/runtime-git-client-merge.test.ts": 11, + "src/renderer/src/runtime/runtime-git-client.test.ts": 32, + "src/renderer/src/runtime/runtime-hooks-client.test.ts": 9, + "src/renderer/src/runtime/runtime-host-connection-state.test.ts": 7, + "src/renderer/src/runtime/runtime-jira-client.test.ts": 17, + "src/renderer/src/runtime/runtime-jira-payload-stream.test.ts": 10, + "src/renderer/src/runtime/runtime-linear-client.test.ts": 25, + "src/renderer/src/runtime/runtime-provider-accounts-client.test.ts": 37, + "src/renderer/src/runtime/runtime-provider-search-bounds.test.ts": 7, + "src/renderer/src/runtime/runtime-repo-client.test.ts": 7, + "src/renderer/src/runtime/runtime-repo-search-bounds.test.ts": 7, + "src/renderer/src/runtime/runtime-rpc-client-pairing-revision.test.ts": 7, + "src/renderer/src/runtime/runtime-rpc-client.test.ts": 22, + "src/renderer/src/runtime/runtime-rpc-result.test.ts": 9, + "src/renderer/src/runtime/runtime-server-directory-browser.test.ts": 9, + "src/renderer/src/runtime/runtime-skills-client.test.ts": 12, + "src/renderer/src/runtime/runtime-skills-delete-client.test.ts": 15, + "src/renderer/src/runtime/runtime-terminal-inspection.test.ts": 683, + "src/renderer/src/runtime/runtime-terminal-stream.test.ts": 286, + "src/renderer/src/runtime/runtime-worktree-selector.test.ts": 6, + "src/renderer/src/runtime/structured-agent-session-client.test.ts": 14, + "src/renderer/src/runtime/structured-agent-session-close.test.ts": 8, + "src/renderer/src/runtime/structured-agent-session-status-feed-lifecycle.test.ts": 11, + "src/renderer/src/runtime/structured-agent-session-status-feed.test.ts": 18, + "src/renderer/src/runtime/structured-conversation-tab-replacement.test.ts": 20, + "src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts": 51, + "src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts": 20, + "src/renderer/src/runtime/sync-runtime-graph-automation-leaf.test.ts": 16, + "src/renderer/src/runtime/sync-runtime-graph-browser.test.ts": 15, + "src/renderer/src/runtime/sync-runtime-graph-conversion-publish.test.ts": 83, + "src/renderer/src/runtime/sync-runtime-graph-editor-diff-tabs.test.ts": 19, + "src/renderer/src/runtime/sync-runtime-graph-key-reuse.test.ts": 19, + "src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts": 29, + "src/renderer/src/runtime/sync-runtime-graph-payload-partition.test.ts": 32, + "src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts": 128, + "src/renderer/src/runtime/sync-runtime-graph-publication-cost.test.ts": 55, + "src/renderer/src/runtime/sync-runtime-graph-scheduling.test.ts": 24, + "src/renderer/src/runtime/sync-runtime-graph-terminal-layout.test.ts": 696, + "src/renderer/src/runtime/sync-runtime-graph-terminal-registration-ownership.test.ts": 13, + "src/renderer/src/runtime/sync-runtime-graph-terminal-surface-projection.test.ts": 30, + "src/renderer/src/runtime/sync-runtime-graph-workspace-publication.test.ts": 19, + "src/renderer/src/runtime/sync-runtime-graph.test.ts": 20, + "src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.test.ts": 4, + "src/renderer/src/runtime/use-remote-runtime-recovery-triggers.test.ts": 22, + "src/renderer/src/runtime/use-runtime-session-mirror-environment-key.test.ts": 78, + "src/renderer/src/runtime/use-worktree-runtime-target.test.ts": 58, + "src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts": 16, + "src/renderer/src/runtime/web-runtime-browser-materialization.test.ts": 4, + "src/renderer/src/runtime/web-runtime-browser-tab-staging-hosting-intent.test.ts": 9, + "src/renderer/src/runtime/web-runtime-session-browser-client-placement.test.ts": 81, + "src/renderer/src/runtime/web-runtime-session-browser-create-failure.test.ts": 112, + "src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts": 283, + "src/renderer/src/runtime/web-runtime-session-browser-create-split-placement.test.ts": 231, + "src/renderer/src/runtime/web-runtime-session-browser-create-staged-focus.test.ts": 118, + "src/renderer/src/runtime/web-runtime-session-browser-create-staged-hosting-intent.test.ts": 32, + "src/renderer/src/runtime/web-runtime-session-browser-create-staging.test.ts": 320, + "src/renderer/src/runtime/web-runtime-session-browser-placement-staleness.test.ts": 22, + "src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts": 27, + "src/renderer/src/runtime/web-runtime-session-tab-move.test.ts": 11, + "src/renderer/src/runtime/web-runtime-session-tab-props.test.ts": 116, + "src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts": 38, + "src/renderer/src/runtime/web-runtime-session-terminal-legacy-create.test.ts": 35, + "src/renderer/src/runtime/web-runtime-session-terminal-pane-delegation.test.ts": 590, + "src/renderer/src/runtime/web-runtime-session-terminal-workspace-routing.test.ts": 16, + "src/renderer/src/runtime/web-runtime-session.test.ts": 16, + "src/renderer/src/runtime/web-runtime-wake-terminal-respawn.test.ts": 4, + "src/renderer/src/runtime/web-session-browser-placement.test.ts": 8, + "src/renderer/src/runtime/web-session-close-intent.test.ts": 7, + "src/renderer/src/runtime/web-session-existing-tab-index.test.ts": 7, + "src/renderer/src/runtime/web-session-focus-intent-visible-tab.test.ts": 9, + "src/renderer/src/runtime/web-session-intent-owner.test.ts": 7, + "src/renderer/src/runtime/web-session-open-files-batch-equivalence.test.ts": 333, + "src/renderer/src/runtime/web-session-structured-tab-focus.test.ts": 11, + "src/renderer/src/runtime/web-session-tabs-agent-completion-notifications.test.ts": 40, + "src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts": 15, + "src/renderer/src/runtime/web-session-tabs-sync-agent-status.test.ts": 19, + "src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts": 29, + "src/renderer/src/runtime/web-session-tabs-sync-client-owned-page-content.test.ts": 22, + "src/renderer/src/runtime/web-session-tabs-sync-client-owned-placement.test.ts": 36, + "src/renderer/src/runtime/web-session-tabs-sync-diff-focus-steal.test.ts": 19, + "src/renderer/src/runtime/web-session-tabs-sync-editor-tabs.test.ts": 15, + "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts": 26, + "src/renderer/src/runtime/web-session-tabs-sync-host-restart-browser-rows.test.ts": 22, + "src/renderer/src/runtime/web-session-tabs-sync-host-tab-retraction-ghost-rows.test.ts": 138, + "src/renderer/src/runtime/web-session-tabs-sync-html-preview-focus.test.ts": 21, + "src/renderer/src/runtime/web-session-tabs-sync-layout-duplicate-groups.test.ts": 15, + "src/renderer/src/runtime/web-session-tabs-sync-layout-groups.test.ts": 22, + "src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts": 54, + "src/renderer/src/runtime/web-session-tabs-sync-remote-status-title-flap.test.ts": 109, + "src/renderer/src/runtime/web-session-tabs-sync-restored-browser-rows.test.ts": 17, + "src/renderer/src/runtime/web-session-tabs-sync-snapshot-batch.test.ts": 36, + "src/renderer/src/runtime/web-session-tabs-sync-staged-browser-adoption.test.ts": 15, + "src/renderer/src/runtime/web-session-tabs-sync-staged-browser-authority.test.ts": 12, + "src/renderer/src/runtime/web-session-tabs-sync-terminal-bootstrap.test.ts": 5, + "src/renderer/src/runtime/web-session-tabs-sync-terminal-mirroring.test.ts": 30, + "src/renderer/src/runtime/web-session-tabs-sync-tracking-teardown.test.ts": 18, + "src/renderer/src/runtime/web-session-tabs-sync-visibility-collision.test.tsx": 113, + "src/renderer/src/runtime/web-session-tabs-sync-window-visibility.test.tsx": 170, + "src/renderer/src/runtime/web-session-tabs-sync.test.ts": 29, + "src/renderer/src/runtime/web-session-tabs-sync/mirrored-agent-tab-label.test.ts": 7, + "src/renderer/src/runtime/web-session-tabs-sync/mirrored-terminal-recovery-ledger.test.ts": 9, + "src/renderer/src/runtime/web-session-terminal-handle-events.test.ts": 6, + "src/renderer/src/runtime/web-session-terminal-orphan-absence-across-republication.test.ts": 11, + "src/renderer/src/runtime/web-session-terminal-orphan-inventory-retry.test.ts": 43, + "src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts": 49, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts": 179, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery-host-scope-gate.test.ts": 10, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery-prior-removal.test.ts": 15, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts": 295, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery-topology-fence.test.ts": 168, + "src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts": 122, + "src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts": 33, + "src/renderer/src/runtime/web-session-terminal-recovery-snapshot-validation.test.ts": 155, + "src/renderer/src/runtime/web-session-terminal-retirement-proof-ledger.test.ts": 11, + "src/renderer/src/runtime/window-visibility-subscription-parking.test.ts": 26, + "src/renderer/src/runtime/worktree-create-base.test.ts": 4, + "src/renderer/src/ssh/ssh-connect-in-flight.test.ts": 21, + "src/renderer/src/ssh/ssh-connect-ui-timeout.test.ts": 15, + "src/renderer/src/ssh/ssh-connect-verb.test.ts": 7, + "src/renderer/src/ssh/ssh-connection-recoverability.test.ts": 6, + "src/renderer/src/startup/active-workspace-ssh-targets.test.ts": 7, + "src/renderer/src/startup/ssh-startup-reconnect.test.ts": 16, + "src/renderer/src/startup/startup-ssh-connection-restore.test.ts": 13, + "src/renderer/src/store/active-terminal-chrome-selector.test.ts": 17, + "src/renderer/src/store/always-mounted-selector-scan-cost.test.ts": 116, + "src/renderer/src/store/copy-on-write-record.test.ts": 6, + "src/renderer/src/store/folder-workspaces/folder-workspace-catalog.test.ts": 14, + "src/renderer/src/store/pinned-tab-close-guard.test.ts": 13, + "src/renderer/src/store/plugin-language-packs.test.ts": 21, + "src/renderer/src/store/plugin-panels.test.ts": 13, + "src/renderer/src/store/project-host-setup-selector.test.ts": 18, + "src/renderer/src/store/projects/project-catalog-null-field-ingest.test.ts": 12, + "src/renderer/src/store/projects/project-wsl-filesystem-boundary-advisory.test.ts": 13, + "src/renderer/src/store/react-commit-cascade-write-probe.test.ts": 5, + "src/renderer/src/store/repos/safe-auto-fork-sync.test.ts": 8, + "src/renderer/src/store/right-sidebar-route.test.ts": 6, + "src/renderer/src/store/running-terminal-close-confirm.test.ts": 7, + "src/renderer/src/store/selectors.test.ts": 44, + "src/renderer/src/store/slices/active-tab-owner-worktree.test.ts": 12, + "src/renderer/src/store/slices/activity-cleared-at.test.ts": 82, + "src/renderer/src/store/slices/agent-generated-tab-title.test.ts": 141, + "src/renderer/src/store/slices/agent-hibernation-live-anchor-e2e.test.ts": 53, + "src/renderer/src/store/slices/agent-pane-authority.test.ts": 110, + "src/renderer/src/store/slices/agent-status-ack-cleanup.test.ts": 99, + "src/renderer/src/store/slices/agent-status-batch.test.ts": 154, + "src/renderer/src/store/slices/agent-status-drop-ipc.test.ts": 705, + "src/renderer/src/store/slices/agent-status-drop.test.ts": 88, + "src/renderer/src/store/slices/agent-status-freshness-cache.test.ts": 10, + "src/renderer/src/store/slices/agent-status-freshness-scheduler.test.ts": 20, + "src/renderer/src/store/slices/agent-status-live-freshness-request.test.ts": 7, + "src/renderer/src/store/slices/agent-status-live-map-leak.test.ts": 10291, + "src/renderer/src/store/slices/agent-status-manual-sleep-capture.test.ts": 90, + "src/renderer/src/store/slices/agent-status-observation-neutrality.test.ts": 107, + "src/renderer/src/store/slices/agent-status-pane-keyed-records.test.ts": 26, + "src/renderer/src/store/slices/agent-status-pr-refresh-handoff.test.ts": 89, + "src/renderer/src/store/slices/agent-status-provider-session.test.ts": 117, + "src/renderer/src/store/slices/agent-status-quit-capture-resumable-agents.test.ts": 64, + "src/renderer/src/store/slices/agent-status-quit-capture.test.ts": 170, + "src/renderer/src/store/slices/agent-status-reminted-pane-key.test.ts": 41, + "src/renderer/src/store/slices/agent-status-retained-leak.test.ts": 846, + "src/renderer/src/store/slices/agent-status-retention-prefix-sweep.test.ts": 85, + "src/renderer/src/store/slices/agent-status-runtime-orchestration.test.ts": 96, + "src/renderer/src/store/slices/agent-status-session-boundary-done.test.ts": 60, + "src/renderer/src/store/slices/agent-status-ssh-connection-clear.test.ts": 64, + "src/renderer/src/store/slices/agent-status-tool-assistant-fields.test.ts": 128, + "src/renderer/src/store/slices/agent-status-worktree-purge-leak.test.ts": 50, + "src/renderer/src/store/slices/agent-status.test.ts": 54, + "src/renderer/src/store/slices/ambiguous-owner-warning-worktree-removal-leak.test.ts": 587, + "src/renderer/src/store/slices/browser-cleanup-close.test.ts": 151, + "src/renderer/src/store/slices/browser-page-close-intent-recording.test.ts": 191, + "src/renderer/src/store/slices/browser-page-conversion.test.ts": 125, + "src/renderer/src/store/slices/browser-remote-page-lifecycle.test.ts": 221, + "src/renderer/src/store/slices/browser-remote-tab-creation.test.ts": 42, + "src/renderer/src/store/slices/browser-session-host-selection.test.ts": 225, + "src/renderer/src/store/slices/browser-session-profiles.test.ts": 21, + "src/renderer/src/store/slices/browser-webview-cleanup.test.ts": 7, + "src/renderer/src/store/slices/browser-workspace-doc-location.test.ts": 171, + "src/renderer/src/store/slices/browser-workspace-host-ownership.test.ts": 18, + "src/renderer/src/store/slices/browser.test.ts": 43, + "src/renderer/src/store/slices/bulk-worktree-purge-terminal-maps-leak.test.ts": 89, + "src/renderer/src/store/slices/cmd-j-create-actions.test.ts": 90, + "src/renderer/src/store/slices/codex-restart-notice-lifecycle.test.ts": 48, + "src/renderer/src/store/slices/degraded-repo-hydration.test.ts": 81, + "src/renderer/src/store/slices/detected-agents-environment-prune-leak.test.ts": 55, + "src/renderer/src/store/slices/detected-agents.test.ts": 138, + "src/renderer/src/store/slices/detected-worktree-refresh-leases.test.ts": 11, + "src/renderer/src/store/slices/dictation-model-state-stabilisation.test.ts": 9, + "src/renderer/src/store/slices/diffComments.test.ts": 122, + "src/renderer/src/store/slices/direct-ssh-pane-detach-ledger.test.ts": 62, + "src/renderer/src/store/slices/direct-ssh-terminal-recovery.test.ts": 39, + "src/renderer/src/store/slices/direct-ssh-terminal-retry.test.ts": 147, + "src/renderer/src/store/slices/direct-ssh-terminal-workspace-scope.test.ts": 11, + "src/renderer/src/store/slices/editor-branch-diff-snapshots.test.ts": 19, + "src/renderer/src/store/slices/editor-branch-line-total.test.ts": 16, + "src/renderer/src/store/slices/editor-check-details-tabs.test.ts": 27, + "src/renderer/src/store/slices/editor-close-file-cleanup.test.ts": 255, + "src/renderer/src/store/slices/editor-git-status-reconciliation.test.ts": 31, + "src/renderer/src/store/slices/editor-hydration-scaling.test.ts": 85, + "src/renderer/src/store/slices/editor-markdown-link-activation.test.ts": 38, + "src/renderer/src/store/slices/editor-markdown-view-state.test.ts": 37, + "src/renderer/src/store/slices/editor-open-diff.test.ts": 42, + "src/renderer/src/store/slices/editor-read-only-tabs.test.ts": 16, + "src/renderer/src/store/slices/editor-recently-closed-tabs.test.ts": 40, + "src/renderer/src/store/slices/editor-rekey-open-files.test.ts": 75, + "src/renderer/src/store/slices/editor-remote-branch-actions.test.ts": 62, + "src/renderer/src/store/slices/editor-rich-markdown-size-override.test.ts": 14, + "src/renderer/src/store/slices/editor-right-sidebar-state.test.ts": 24, + "src/renderer/src/store/slices/editor-session-duplicate-restore.test.ts": 84, + "src/renderer/src/store/slices/editor-state-worktree-purge-leak.test.ts": 59, + "src/renderer/src/store/slices/editor-tab-placement.test.ts": 30, + "src/renderer/src/store/slices/editor/actions/markdown-preview-actions.test.ts": 9, + "src/renderer/src/store/slices/editor/file-ids/hydrated-editor-file-index.test.ts": 512, + "src/renderer/src/store/slices/editor/file-ids/hydrated-editor-projections.test.ts": 143, + "src/renderer/src/store/slices/editor/git/git-status-reconciliation.perf.test.ts": 36, + "src/renderer/src/store/slices/folder-workspace-activation-and-activity.test.ts": 144, + "src/renderer/src/store/slices/folder-workspace-diff-comments.test.ts": 388, + "src/renderer/src/store/slices/folder-workspace-owner-routed-mutations.test.ts": 188, + "src/renderer/src/store/slices/generation-records-worktree-removal-leak.test.ts": 75, + "src/renderer/src/store/slices/github-branch-mismatched-linked-pr.test.ts": 5, + "src/renderer/src/store/slices/github-cache-eviction-and-bounds.test.ts": 221, + "src/renderer/src/store/slices/github-checks-cache.test.ts": 46, + "src/renderer/src/store/slices/github-checks.test.ts": 9, + "src/renderer/src/store/slices/github-issue-source-indicator-suppression.test.ts": 45, + "src/renderer/src/store/slices/github-issue-state-machine.test.ts": 35, + "src/renderer/src/store/slices/github-pr-branch-coordinator-events.test.ts": 35, + "src/renderer/src/store/slices/github-pr-branch-direct-refresh-scope.test.ts": 20, + "src/renderer/src/store/slices/github-pr-branch-fallback-results.test.ts": 26, + "src/renderer/src/store/slices/github-pr-branch-hosted-review-cache.test.ts": 27, + "src/renderer/src/store/slices/github-pr-branch-linked-pr-divergence.test.ts": 33, + "src/renderer/src/store/slices/github-pr-checks-fetch.test.ts": 121, + "src/renderer/src/store/slices/github-pr-comments.test.ts": 89, + "src/renderer/src/store/slices/github-pr-refresh-host-guard.test.ts": 28, + "src/renderer/src/store/slices/github-pr-refresh-hosted-review-cache-leak.test.ts": 802, + "src/renderer/src/store/slices/github-pr-refresh-owner-routing.test.ts": 218, + "src/renderer/src/store/slices/github-pr-refresh-sequences-leak.test.ts": 16, + "src/renderer/src/store/slices/github-pr-refresh-states-leak.test.ts": 4478, + "src/renderer/src/store/slices/github-project-request-coordination.test.ts": 20, + "src/renderer/src/store/slices/github-project-row-owner.test.ts": 6, + "src/renderer/src/store/slices/github-project-view-tables.test.ts": 60, + "src/renderer/src/store/slices/github-provider-request-concurrency.test.ts": 19, + "src/renderer/src/store/slices/github-refresh-sweep.test.ts": 252, + "src/renderer/src/store/slices/github-repo-lookup-index.test.ts": 4, + "src/renderer/src/store/slices/github-review-thread-actions.test.ts": 24, + "src/renderer/src/store/slices/github-work-item-cache-identity.test.ts": 13, + "src/renderer/src/store/slices/github-work-items-error-envelope.test.ts": 43, + "src/renderer/src/store/slices/github-work-items-pagination.test.ts": 29, + "src/renderer/src/store/slices/github-work-items-query-bounds.test.ts": 10, + "src/renderer/src/store/slices/github-work-items-runtime-routing.test.ts": 218, + "src/renderer/src/store/slices/github-worktree-refresh-if-stale.test.ts": 85, + "src/renderer/src/store/slices/hosted-review-cache-race.test.ts": 15, + "src/renderer/src/store/slices/hosted-review-cache.test.ts": 69, + "src/renderer/src/store/slices/hosted-review.test.ts": 20, + "src/renderer/src/store/slices/jira.test.ts": 41, + "src/renderer/src/store/slices/linear-credential-error-recovery.test.ts": 31, + "src/renderer/src/store/slices/linear-invalidation.test.ts": 23, + "src/renderer/src/store/slices/linear-issue-cache-refresh.test.ts": 17, + "src/renderer/src/store/slices/linear-scoped-collection-cache.test.ts": 40, + "src/renderer/src/store/slices/linear-source-context-cache-scope.test.ts": 20, + "src/renderer/src/store/slices/linear.test.ts": 13, + "src/renderer/src/store/slices/local-detected-agent-state.test.ts": 12, + "src/renderer/src/store/slices/memory.test.ts": 7, + "src/renderer/src/store/slices/native-chat-launch-draft-teardown.test.ts": 84, + "src/renderer/src/store/slices/new-issue-draft.test.ts": 7, + "src/renderer/src/store/slices/new-markdown.test.ts": 23, + "src/renderer/src/store/slices/orca-profiles-auth-actions.test.ts": 80, + "src/renderer/src/store/slices/orca-profiles.test.ts": 89, + "src/renderer/src/store/slices/pane-column-split-drop-no-op.test.ts": 8, + "src/renderer/src/store/slices/pane-foreground-agent.test.ts": 57, + "src/renderer/src/store/slices/persisted-ui-write-baseline.test.ts": 9, + "src/renderer/src/store/slices/pinned-tab-close-confirm.test.ts": 9, + "src/renderer/src/store/slices/preflight.test.ts": 26, + "src/renderer/src/store/slices/project-group-removal-targets.test.ts": 8, + "src/renderer/src/store/slices/purge-stale-runtime-host-ownership.test.ts": 50, + "src/renderer/src/store/slices/purge-stale-runtime-host-state.test.ts": 137, + "src/renderer/src/store/slices/rate-limits.test.ts": 7, + "src/renderer/src/store/slices/readopted-ssh-worktree-rows.test.ts": 6, + "src/renderer/src/store/slices/recently-closed-tabs.test.ts": 211, + "src/renderer/src/store/slices/remote-server-updates.integration.test.ts": 15, + "src/renderer/src/store/slices/repo-identity-reconcile.test.ts": 12, + "src/renderer/src/store/slices/repo-owner-cache-identity.test.ts": 5, + "src/renderer/src/store/slices/repo-reorder-host-split.test.ts": 5, + "src/renderer/src/store/slices/repos-add-races.test.ts": 56, + "src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts": 76, + "src/renderer/src/store/slices/repos-all-hosts-generation.test.ts": 114, + "src/renderer/src/store/slices/repos-all-hosts.test.ts": 148, + "src/renderer/src/store/slices/repos-catalog-merge-identity.test.ts": 88, + "src/renderer/src/store/slices/repos-cross-host-project-collisions.test.ts": 117, + "src/renderer/src/store/slices/repos-cross-host-refresh-identity.test.ts": 91, + "src/renderer/src/store/slices/repos-ephemeral-vm-cleanup-retention.test.ts": 32, + "src/renderer/src/store/slices/repos-host-identity-routing.test.ts": 191, + "src/renderer/src/store/slices/repos-manual-order-hydration.test.ts": 172, + "src/renderer/src/store/slices/repos-module-lifetime-coordinators.test.ts": 71, + "src/renderer/src/store/slices/repos-nested-import-refresh-failures.test.ts": 57, + "src/renderer/src/store/slices/repos-nested-ssh-projection.test.ts": 52, + "src/renderer/src/store/slices/repos-onboarding-folder-startup.test.ts": 349, + "src/renderer/src/store/slices/repos-paired-runtime-add.test.ts": 46, + "src/renderer/src/store/slices/repos-project-group-create-race.test.ts": 101, + "src/renderer/src/store/slices/repos-project-groups-delete.test.ts": 86, + "src/renderer/src/store/slices/repos-project-groups-owner-routing.test.ts": 73, + "src/renderer/src/store/slices/repos-project-groups.test.ts": 200, + "src/renderer/src/store/slices/repos-project-host-capability.test.ts": 93, + "src/renderer/src/store/slices/repos-project-host-lifecycle.test.ts": 50, + "src/renderer/src/store/slices/repos-project-runtime.test.ts": 77, + "src/renderer/src/store/slices/repos-refresh-identity.test.ts": 128, + "src/renderer/src/store/slices/repos-remove-missing-remote-project.test.ts": 57, + "src/renderer/src/store/slices/repos-remove-project-purge-leak.test.ts": 87, + "src/renderer/src/store/slices/repos-runtime-project-groups.test.ts": 56, + "src/renderer/src/store/slices/repos-runtime-visibility-defaults.test.ts": 43, + "src/renderer/src/store/slices/repos-selected-owner-routing.test.ts": 194, + "src/renderer/src/store/slices/repos-setup-script-dismissals.test.ts": 52, + "src/renderer/src/store/slices/repos-shared-project-badge-color.test.ts": 67, + "src/renderer/src/store/slices/repos-ssh-host-reconciliation.test.ts": 80, + "src/renderer/src/store/slices/repos-stale-fetch.test.ts": 93, + "src/renderer/src/store/slices/repos-update-serialization.test.ts": 52, + "src/renderer/src/store/slices/repos.runtime-fallback.test.ts": 73, + "src/renderer/src/store/slices/repos.test.ts": 185, + "src/renderer/src/store/slices/restored-editor-owner-reparent.test.ts": 106, + "src/renderer/src/store/slices/runtime-catalog-merge-removed-env-guard.test.ts": 45, + "src/renderer/src/store/slices/runtime-environment-ssh.test.ts": 93, + "src/renderer/src/store/slices/runtime-host-purge-session-partition-split.test.ts": 50, + "src/renderer/src/store/slices/runtime-pane-title-sort-epoch.test.ts": 189, + "src/renderer/src/store/slices/runtime-status-catalog-identity.test.ts": 94, + "src/renderer/src/store/slices/runtime-status-first-publication.test.ts": 6, + "src/renderer/src/store/slices/runtime-status-refresh-diagnostics.test.ts": 7, + "src/renderer/src/store/slices/runtime-status-restored-browser-host-attach.test.ts": 9, + "src/renderer/src/store/slices/runtime-status-snapshot.test.ts": 8, + "src/renderer/src/store/slices/runtime-status.test.ts": 187, + "src/renderer/src/store/slices/runtime-switch-settings-persistence.test.ts": 37, + "src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts": 13, + "src/renderer/src/store/slices/set-runtime-environments-purge-wiring.test.ts": 46, + "src/renderer/src/store/slices/settings-owner-hydration-write-fence.test.ts": 211, + "src/renderer/src/store/slices/settings-search-state.test.ts": 8, + "src/renderer/src/store/slices/settings.test.ts": 338, + "src/renderer/src/store/slices/sparse-presets-repo-removal-purge-leak.test.ts": 78, + "src/renderer/src/store/slices/sparse-presets.test.ts": 26, + "src/renderer/src/store/slices/ssh.test.ts": 89, + "src/renderer/src/store/slices/stale-runtime-host-rows.test.ts": 15, + "src/renderer/src/store/slices/store-active-worktree-selection.test.ts": 87, + "src/renderer/src/store/slices/store-active-worktree-split-groups.test.ts": 94, + "src/renderer/src/store/slices/store-active-worktree-tab-close.test.ts": 159, + "src/renderer/src/store/slices/store-active-worktree-terminal-creation.test.ts": 88, + "src/renderer/src/store/slices/store-create-tab-id-hint.test.ts": 57, + "src/renderer/src/store/slices/store-session-browser-hydration.test.ts": 117, + "src/renderer/src/store/slices/store-session-cascades.test.ts": 86, + "src/renderer/src/store/slices/store-session-editor-hydration.test.ts": 111, + "src/renderer/src/store/slices/store-session-terminal-activity.test.ts": 169, + "src/renderer/src/store/slices/store-session-terminal-reconnect.test.ts": 78, + "src/renderer/src/store/slices/store-session-workspace-hydration.test.ts": 84, + "src/renderer/src/store/slices/store-sleep-agent-status-retention.test.ts": 122, + "src/renderer/src/store/slices/store-sleep-exact-runtime-stop.test.ts": 147, + "src/renderer/src/store/slices/store-sleep-pane-hibernation.test.ts": 96, + "src/renderer/src/store/slices/store-sleep-runtime-convergence.test.ts": 448, + "src/renderer/src/store/slices/store-sleep-shutdown-order.test.ts": 49, + "src/renderer/src/store/slices/store-worktree-removal-cascade.test.ts": 141, + "src/renderer/src/store/slices/superseded-ssh-repo-rows.test.ts": 7, + "src/renderer/src/store/slices/tab-group-reference-repair.test.ts": 15, + "src/renderer/src/store/slices/tab-group-state.test.ts": 19, + "src/renderer/src/store/slices/tab-view-mode.test.ts": 86, + "src/renderer/src/store/slices/tab-worktree-orphan-map-purge-leak.test.ts": 27, + "src/renderer/src/store/slices/tabs-empty-split-activation.test.ts": 29, + "src/renderer/src/store/slices/tabs-hydration-generated-title.test.ts": 12, + "src/renderer/src/store/slices/tabs-hydration-group-validation.test.ts": 15, + "src/renderer/src/store/slices/tabs-hydration.test.ts": 12, + "src/renderer/src/store/slices/tabs-label-and-pin-state.test.ts": 68, + "src/renderer/src/store/slices/tabs-model-reconciliation.test.ts": 83, + "src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts": 185, + "src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts": 88, + "src/renderer/src/store/slices/tabs-session-hydration.test.ts": 67, + "src/renderer/src/store/slices/tabs-unread-and-focus.test.ts": 71, + "src/renderer/src/store/slices/tabs/hydrated-workspace-reconciliation-batch.test.ts": 423, + "src/renderer/src/store/slices/tabs/tab-selection-contract.test.ts": 12, + "src/renderer/src/store/slices/tabs/tabs-reconciliation-batch-identity.test.ts": 49, + "src/renderer/src/store/slices/task-creation-drafts.test.ts": 6, + "src/renderer/src/store/slices/terminal-helpers.test.ts": 11, + "src/renderer/src/store/slices/terminal-input-activity-store-write.test.ts": 51, + "src/renderer/src/store/slices/terminal-layout-pty-ownership.test.ts": 63, + "src/renderer/src/store/slices/terminal-orphan-helpers.test.ts": 8, + "src/renderer/src/store/slices/terminal-pane-detach-agent-identity.test.ts": 33, + "src/renderer/src/store/slices/terminal-pane-detach-agent-retention.test.ts": 92, + "src/renderer/src/store/slices/terminal-pty-identity-replacement.test.ts": 36, + "src/renderer/src/store/slices/terminal-quick-command-hosts.test.ts": 132, + "src/renderer/src/store/slices/terminal-startup-command-retention.test.ts": 80, + "src/renderer/src/store/slices/terminal-tab-id-hydration.test.ts": 38, + "src/renderer/src/store/slices/terminal-tab-owner-index.test.ts": 6, + "src/renderer/src/store/slices/terminal-tab-recovery-remount.test.ts": 151, + "src/renderer/src/store/slices/terminal-tab-retirement-store.test.ts": 172, + "src/renderer/src/store/slices/terminal-tab-retirement.test.ts": 15, + "src/renderer/src/store/slices/terminal-tab-title-batch.test.ts": 147, + "src/renderer/src/store/slices/terminal-write-identity-bailouts.test.ts": 85, + "src/renderer/src/store/slices/terminals-explicit-empty-hydration.test.ts": 61, + "src/renderer/src/store/slices/terminals-hydration-canonical-pty-overlap.test.ts": 87, + "src/renderer/src/store/slices/terminals-hydration-canonical-rows.test.ts": 76, + "src/renderer/src/store/slices/terminals-hydration.test.ts": 145, + "src/renderer/src/store/slices/ui-acknowledge-agents-clock-skew.test.ts": 17, + "src/renderer/src/store/slices/ui-agent-send-target.test.ts": 58, + "src/renderer/src/store/slices/ui-contextual-tours.test.ts": 51, + "src/renderer/src/store/slices/ui-feature-interactions.test.ts": 31, + "src/renderer/src/store/slices/ui-hydration-view-layout.test.ts": 46, + "src/renderer/src/store/slices/ui-hydration-workspace-cleanup-browse.test.ts": 11, + "src/renderer/src/store/slices/ui-hydration-workspace-preferences.test.ts": 45, + "src/renderer/src/store/slices/ui-modal-slot-dismissal.test.ts": 184, + "src/renderer/src/store/slices/ui-new-workspace-draft.test.ts": 8, + "src/renderer/src/store/slices/ui-notice-dismissals.test.ts": 52, + "src/renderer/src/store/slices/ui-page-navigation.test.ts": 51, + "src/renderer/src/store/slices/usage-snapshot-refresh.benchmark.test.ts": 6, + "src/renderer/src/store/slices/usage-web-client-fallback.test.ts": 12, + "src/renderer/src/store/slices/workspace-cleanup-browse.test.ts": 21, + "src/renderer/src/store/slices/workspace-cleanup-cache-hydration.test.ts": 174, + "src/renderer/src/store/slices/workspace-cleanup-enrichment-performance.test.ts": 285, + "src/renderer/src/store/slices/workspace-cleanup-host-qualified-list-state.test.ts": 71, + "src/renderer/src/store/slices/workspace-cleanup-local-evidence-invariants.test.ts": 7, + "src/renderer/src/store/slices/workspace-cleanup-removal-preflight.test.ts": 62, + "src/renderer/src/store/slices/workspace-cleanup-scan-progress-boundary.test.ts": 5, + "src/renderer/src/store/slices/workspace-cleanup-scan-progress.test.ts": 584, + "src/renderer/src/store/slices/workspace-cleanup-unverified-removal-consent.test.ts": 44, + "src/renderer/src/store/slices/workspace-cleanup-wrong-host-removal-guard.test.ts": 99, + "src/renderer/src/store/slices/workspace-cleanup-wrong-host-removal.test.ts": 143, + "src/renderer/src/store/slices/workspace-document-title-refresh.test.ts": 48, + "src/renderer/src/store/slices/workspace-space.test.ts": 11, + "src/renderer/src/store/slices/worktree-by-id-index.test.ts": 6, + "src/renderer/src/store/slices/worktree-catalog-reconciliation.test.ts": 12, + "src/renderer/src/store/slices/worktree-helpers.test.ts": 6, + "src/renderer/src/store/slices/worktree-listing-branch-switch.test.ts": 11, + "src/renderer/src/store/slices/worktree-meta-update-application.test.ts": 8, + "src/renderer/src/store/slices/worktree-nav-history-view-entries.test.ts": 21, + "src/renderer/src/store/slices/worktree-nav-history.test.ts": 12, + "src/renderer/src/store/slices/worktree-removal-maps-leak.test.ts": 91, + "src/renderer/src/store/slices/worktree-sleep-intent-lifecycle.test.ts": 78, + "src/renderer/src/store/slices/worktree-terminal-removal-teardown.test.ts": 79, + "src/renderer/src/store/slices/worktree-visibility-owner-settings.test.ts": 14, + "src/renderer/src/store/slices/worktree-visibility-settings-write.test.ts": 32, + "src/renderer/src/store/slices/worktrees-activity-persistence.test.ts": 66, + "src/renderer/src/store/slices/worktrees-create-base-status.test.ts": 33, + "src/renderer/src/store/slices/worktrees-create-parent-pick.test.ts": 27, + "src/renderer/src/store/slices/worktrees-fetch-listing-merge.test.ts": 42, + "src/renderer/src/store/slices/worktrees-fetch-owner-routing.test.ts": 173, + "src/renderer/src/store/slices/worktrees-fetch-persisted-metadata-fallback.test.ts": 25, + "src/renderer/src/store/slices/worktrees-fetch-refresh-coalescing.test.ts": 40, + "src/renderer/src/store/slices/worktrees-fetch-remote-lineage.test.ts": 25, + "src/renderer/src/store/slices/worktrees-fetch-removal-purge.test.ts": 87, + "src/renderer/src/store/slices/worktrees-git-identity-branch-title.test.ts": 17, + "src/renderer/src/store/slices/worktrees-git-identity-review-clear-persistence.test.ts": 488, + "src/renderer/src/store/slices/worktrees-hydration-purge.test.ts": 59, + "src/renderer/src/store/slices/worktrees-identity-migration.test.ts": 292, + "src/renderer/src/store/slices/worktrees-lineage-state.test.ts": 41, + "src/renderer/src/store/slices/worktrees-linked-review-push-target.test.ts": 69, + "src/renderer/src/store/slices/worktrees-metadata-persistence.test.ts": 43, + "src/renderer/src/store/slices/worktrees-pending-creation-state.test.ts": 70, + "src/renderer/src/store/slices/worktrees-purge-terminal-state.test.ts": 11, + "src/renderer/src/store/slices/worktrees-remote-runtime-create.test.ts": 35, + "src/renderer/src/store/slices/worktrees-remote-runtime-removal.test.ts": 49, + "src/renderer/src/store/slices/worktrees-removal-state-cleanup.test.ts": 60, + "src/renderer/src/store/slices/worktrees-runtime-connection-generation-fence.test.ts": 81, + "src/renderer/src/store/slices/worktrees-runtime-host-metadata-retirement.test.ts": 25, + "src/renderer/src/store/slices/worktrees-terminal-pr-url-linking.test.ts": 15, + "src/renderer/src/store/slices/worktrees-unread-state.test.ts": 19, + "src/renderer/src/store/slices/worktrees-workspace-selection-state.test.ts": 27, + "src/renderer/src/store/slices/worktrees/listing/detected-worktree-meta.test.ts": 8, + "src/renderer/src/store/slices/worktrees/listing/detected-worktree-unavailable-reason.test.ts": 7, + "src/renderer/src/store/slices/worktrees/metadata/worktree-meta-persist.test.ts": 14, + "src/renderer/src/store/slices/worktrees/teardown/host-qualified-removal-refusal.test.ts": 6, + "src/renderer/src/store/slices/worktrees/teardown/record-key-omission.test.ts": 7, + "src/renderer/src/store/slices/worktrees/teardown/remove-worktree-map-identity.test.ts": 9, + "src/renderer/src/store/slices/worktrees/teardown/worktree-teardown-array-identity.test.ts": 9, + "src/renderer/src/store/store-identity-churn-probe.test.ts": 19, + "src/renderer/src/store/store-listener-census.test.ts": 7, + "src/renderer/src/store/terminals/restored-relay-session-identity.test.ts": 88, + "src/renderer/src/store/terminals/structured-chat-tab-rename.test.ts": 68, + "src/renderer/src/store/terminals/terminal-pane-expansion-write-bailout.test.tsx": 113, + "src/renderer/src/store/terminals/terminal-shutdown-guards-identity.test.ts": 5, + "src/renderer/src/store/terminals/terminal-shutdown-map-identity.test.ts": 7, + "src/renderer/src/store/terminals/terminal-tab-close-map-identity.test.ts": 68, + "src/renderer/src/store/terminals/terminal-tab-close-tombstone.test.ts": 86, + "src/renderer/src/store/terminals/terminal-workspace-routing.scale.test.ts": 9, + "src/renderer/src/store/terminals/workspace-terminal-placeholders.test.ts": 10, + "src/renderer/src/store/worktree-diff-comments-selector.test.ts": 53, + "src/renderer/src/store/worktree-host-collision-index.test.ts": 7, + "src/renderer/src/store/worktree-visibility-defaults-by-host.test.ts": 5, + "src/renderer/src/web/preload-api/web-gitlab-api.test.ts": 23, + "src/renderer/src/web/web-clipboard-copy-fallback.test.ts": 12, + "src/renderer/src/web/web-clipboard-copy-terminal-selection.test.ts": 12, + "src/renderer/src/web/web-file-mutation-methods.test.ts": 14, + "src/renderer/src/web/web-pairing.test.ts": 11, + "src/renderer/src/web/web-preload-api-agent-providers.test.ts": 341, + "src/renderer/src/web/web-preload-api-clipboard.test.ts": 1137, + "src/renderer/src/web/web-preload-api-composition.test.ts": 443, + "src/renderer/src/web/web-preload-api-filesystem.test.ts": 413, + "src/renderer/src/web/web-preload-api-git.test.ts": 391, + "src/renderer/src/web/web-preload-api-github.test.ts": 312, + "src/renderer/src/web/web-preload-api-gitlab.test.ts": 596, + "src/renderer/src/web/web-preload-api-keybindings.test.ts": 444, + "src/renderer/src/web/web-preload-api-runtime-calls.test.ts": 755, + "src/renderer/src/web/web-preload-api-runtime-environment.test.ts": 1195, + "src/renderer/src/web/web-preload-api-settings.test.ts": 1562, + "src/renderer/src/web/web-preload-api-ssh.test.ts": 640, + "src/renderer/src/web/web-preload-api-ui.test.ts": 901, + "src/renderer/src/web/web-preload-api-workspace-catalog.test.ts": 903, + "src/renderer/src/web/web-preload-updater-package-recovery.test.ts": 370, + "src/renderer/src/web/web-runtime-client-export-parity.test.ts": 6, + "src/renderer/src/web/web-runtime-client-file-watch-replay.test.ts": 64, + "src/renderer/src/web/web-runtime-client-heartbeat.test.ts": 42, + "src/renderer/src/web/web-runtime-client-timeout-budget.test.ts": 45, + "src/renderer/src/web/web-runtime-client.test.ts": 351, + "src/renderer/src/web/web-runtime-connection-frame-router.test.ts": 6, + "src/renderer/src/web/web-runtime-connection-heartbeat-unsendable-probe.test.ts": 55, + "src/renderer/src/web/web-runtime-status-owner.test.ts": 651, + "src/renderer/src/web/web-viewport-shell.test.ts": 8, + "src/renderer/src/web/web-workspace-session.test.ts": 8, + "src/shared/add-repo-existing-workspaces-telemetry.test.ts": 9, + "src/shared/agent-cli-flag-detection.test.ts": 7, + "src/shared/agent-cli-install-dir-fallback.test.ts": 26, + "src/shared/agent-decorative-title-signature.test.ts": 11, + "src/shared/agent-detection.test.ts": 57, + "src/shared/agent-feature-install-commands.test.ts": 13, + "src/shared/agent-hook-endpoint-file.test.ts": 5, + "src/shared/agent-hook-endpoint-temp-cleanup.test.ts": 14, + "src/shared/agent-hook-listener-antigravity.test.ts": 17, + "src/shared/agent-hook-listener-claude-compatible-vendors.test.ts": 20, + "src/shared/agent-hook-listener-claude-subagents.test.ts": 21, + "src/shared/agent-hook-listener-claude-turn-state.test.ts": 19, + "src/shared/agent-hook-listener-command-code-transcript.test.ts": 114, + "src/shared/agent-hook-listener-extraction-characterization.test.ts": 23, + "src/shared/agent-hook-listener-grok.test.ts": 32, + "src/shared/agent-hook-listener-hermes-codex-droid.test.ts": 13, + "src/shared/agent-hook-listener-interactive-prompts.test.ts": 17, + "src/shared/agent-hook-listener-pi-compatible.test.ts": 15, + "src/shared/agent-hook-listener-relay-dependency.test.ts": 22, + "src/shared/agent-hook-listener-roster-retention.test.ts": 54, + "src/shared/agent-hook-listener-session-replacement.test.ts": 15, + "src/shared/agent-hook-listener-startless-child-lifecycle.test.ts": 22, + "src/shared/agent-hook-listener-transport.test.ts": 49, + "src/shared/agent-hook-listener/transcript-reader.test.ts": 93, + "src/shared/agent-hook-relay.test.ts": 9, + "src/shared/agent-hook-request-body-memory.test.ts": 53, + "src/shared/agent-hook-spool-read.test.ts": 9, + "src/shared/agent-hook-status-cache.test.ts": 5, + "src/shared/agent-hook-transport-interference.test.ts": 9, + "src/shared/agent-kind.test.ts": 9, + "src/shared/agent-launch-remote.test.ts": 7, + "src/shared/agent-model-probe-spec.test.ts": 9, + "src/shared/agent-notification-id.test.ts": 8, + "src/shared/agent-process-recognition.test.ts": 20, + "src/shared/agent-prompt-injection.test.ts": 7, + "src/shared/agent-resume-argv-drop.test.ts": 11, + "src/shared/agent-resume-launch-command.test.ts": 63, + "src/shared/agent-row-conversation-name.test.ts": 10, + "src/shared/agent-scratch-worktrees.test.ts": 7, + "src/shared/agent-session-conversation-name.test.ts": 9, + "src/shared/agent-session-definitive-refusal.test.ts": 11, + "src/shared/agent-session-journal-schemas.test.ts": 35, + "src/shared/agent-session-lease-adjudication.test.ts": 16, + "src/shared/agent-session-mutation-envelope.test.ts": 10, + "src/shared/agent-session-operation-ledger.test.ts": 9, + "src/shared/agent-session-option-catalog-grok.test.ts": 12, + "src/shared/agent-session-option-catalog.test.ts": 10, + "src/shared/agent-session-provider-handle.test.ts": 21, + "src/shared/agent-session-pty-write-admission.test.ts": 8, + "src/shared/agent-session-question-answer.test.ts": 9, + "src/shared/agent-session-resume.test.ts": 9, + "src/shared/agent-session-turn-record.test.ts": 9, + "src/shared/agent-skill-sharing-contract.test.ts": 9, + "src/shared/agent-skill-sharing-gate.test.ts": 6, + "src/shared/agent-status-observation.test.ts": 13, + "src/shared/agent-status-osc-pending-retention.test.ts": 73, + "src/shared/agent-status-osc-scan-budget.test.ts": 195, + "src/shared/agent-status-osc-split-frame-scan-budget.test.ts": 96, + "src/shared/agent-status-osc.test.ts": 16, + "src/shared/agent-status-types.test.ts": 29, + "src/shared/agent-tab-title.test.ts": 14, + "src/shared/agent-terminal-status-equivalence.test.ts": 4, + "src/shared/agent-title-agy-gemini-collision.test.ts": 23, + "src/shared/agent-title-decoration.test.ts": 9, + "src/shared/agent-title-evidence.test.ts": 43, + "src/shared/agent-title-identity-characterization.test.ts": 11, + "src/shared/agent-tui-input-clear.test.ts": 13, + "src/shared/ai-vault-resume-command.test.ts": 7, + "src/shared/ai-vault-resume-preparation.test.ts": 5, + "src/shared/ai-vault-scan-error-message.test.ts": 14, + "src/shared/ai-vault-search-query-operators.test.ts": 10, + "src/shared/ai-vault-session-depth.test.ts": 17, + "src/shared/ai-vault-session-filters.test.ts": 172, + "src/shared/ai-vault-types.test.ts": 9, + "src/shared/app-version.test.ts": 12, + "src/shared/artifact-sharing-gate.test.ts": 9, + "src/shared/automation-execution-target.test.ts": 11, + "src/shared/automation-host-filter.test.ts": 5, + "src/shared/automation-legacy-list-partition.test.ts": 8, + "src/shared/automation-list-response.test.ts": 10, + "src/shared/automation-list-scope.test.ts": 19, + "src/shared/automation-owner-key.test.ts": 10, + "src/shared/automation-owner-precondition.test.ts": 11, + "src/shared/automation-run-identity.test.ts": 5, + "src/shared/automation-run-retention.test.ts": 19, + "src/shared/automation-schedules.test.ts": 183, + "src/shared/automation-usage-summary.test.ts": 9, + "src/shared/base-ref-search-result.test.ts": 6, + "src/shared/binary-file-extensions.test.ts": 4, + "src/shared/bounded-map.test.ts": 17, + "src/shared/branch-name-from-work.test.ts": 11, + "src/shared/branch-prefix.test.ts": 12, + "src/shared/browser-client-automation-protocol.test.ts": 12, + "src/shared/browser-client-host-id-argument.test.ts": 6, + "src/shared/browser-client-host-protocol.test.ts": 241, + "src/shared/browser-client-host-reconciliation-protocol.test.ts": 22, + "src/shared/browser-client-hosting-eligibility.test.ts": 9, + "src/shared/browser-cookie-import-sources.test.ts": 5, + "src/shared/browser-grab-types.test.ts": 11, + "src/shared/browser-network-capabilities.test.ts": 10, + "src/shared/browser-network-tunnel-protocol.test.ts": 11, + "src/shared/browser-network-tunnel-stream-framing.test.ts": 1908, + "src/shared/browser-screencast-protocol.test.ts": 7, + "src/shared/browser-url.test.ts": 14, + "src/shared/cheap-process-table-snapshot.test.ts": 10, + "src/shared/check-job-log-tail-slice.test.ts": 7, + "src/shared/child-process/child-process-import-boundary.test.ts": 9, + "src/shared/child-process/close-process-registry.test.ts": 8, + "src/shared/child-process/process-tree-termination.test.ts": 16, + "src/shared/child-process/retryable-process-exit-proof.test.ts": 8, + "src/shared/child-process/run-process-termination-failure.test.ts": 12, + "src/shared/child-process/run-process.test.ts": 7159, + "src/shared/child-process/windows-cmd-shim-resolution.test.ts": 15, + "src/shared/child-process/windows-command-line.test.ts": 14, + "src/shared/child-process/windows-console-visibility.test.ts": 7, + "src/shared/claimed-agent-pty-owner.test.ts": 33, + "src/shared/claude-agent-teams-tmux-compat.test.ts": 8, + "src/shared/claude-background-task-status.test.ts": 36, + "src/shared/claude-model-list-probe.test.ts": 14, + "src/shared/claude-statusline-rate-limits.test.ts": 7, + "src/shared/claude-subagent-roster.test.ts": 17, + "src/shared/claude-subagent-row-lifecycle.test.ts": 14, + "src/shared/cli-argument-boundary.test.ts": 5, + "src/shared/cli-runtime-pairing-boundary.test.ts": 182, + "src/shared/cli-workspace-provenance.test.ts": 6, + "src/shared/client-environment-info.test.ts": 10, + "src/shared/clipboard-image.test.ts": 9, + "src/shared/clipboard-text.test.ts": 29, + "src/shared/closed-terminal-tab-tombstones.test.ts": 12, + "src/shared/cloud-service-url.test.ts": 4, + "src/shared/codex-auth-errors.test.ts": 22, + "src/shared/codex-pet-sprite-defaults.test.ts": 12, + "src/shared/codex-reset-credit-attempt-ledger.test.ts": 16, + "src/shared/codex-reset-credit-scope.test.ts": 8, + "src/shared/codex-startup-delivery.test.ts": 8, + "src/shared/codex-subagent-poll-scheduler.test.ts": 14, + "src/shared/codex-subagent-rollout-lifecycle.test.ts": 18, + "src/shared/codex-subagent-roster.test.ts": 6, + "src/shared/codex-subagent-transcript.test.ts": 18, + "src/shared/combined-diff-file-tree-width.test.ts": 6, + "src/shared/command-code-output-status.test.ts": 155, + "src/shared/command-code-turn-boundary.test.ts": 6, + "src/shared/command-token-scanner.test.ts": 11, + "src/shared/commit-message-agent-spec.test.ts": 38, + "src/shared/commit-message-generation.test.ts": 12, + "src/shared/commit-message-plan.test.ts": 21, + "src/shared/commit-message-prompt.test.ts": 28, + "src/shared/computer-awake-mode.test.ts": 7, + "src/shared/computer-use-error-recovery.test.ts": 6, + "src/shared/computer-use-key-spec.test.ts": 7, + "src/shared/constants.test.ts": 15, + "src/shared/contextual-tours.test.ts": 16, + "src/shared/crash-reporting.test.ts": 18, + "src/shared/cross-platform-path-guards.test.ts": 1239, + "src/shared/cross-platform-path.test.ts": 17, + "src/shared/daemon-adoption-telemetry.test.ts": 10, + "src/shared/direct-ssh-reconnect-telemetry-schema.test.ts": 17, + "src/shared/doc-preview-file-access.test.ts": 36, + "src/shared/doc-preview-scheme.test.ts": 8, + "src/shared/draft-paste-ready-scanner-grok-trace-replay.test.ts": 7, + "src/shared/draft-paste-ready-scanner.test.ts": 19, + "src/shared/emoji-shortcode-catalog.lazy.test.ts": 42, + "src/shared/emulator-keyboard-frame.test.ts": 7, + "src/shared/emulator-touch-frame.test.ts": 5, + "src/shared/ephemeral-setup-terminal-worktree-id.test.ts": 6, + "src/shared/ephemeral-vm-recipe-checkout-mode.test.ts": 7, + "src/shared/ephemeral-vm-recipe-doctor.test.ts": 10, + "src/shared/ephemeral-vm-recipe-process.test.ts": 154, + "src/shared/ephemeral-vm-recipe-repo-url.test.ts": 7, + "src/shared/ephemeral-vm-recipes.test.ts": 23, + "src/shared/ephemeral-vm-runtime-feature-sorting.test.ts": 145, + "src/shared/ephemeral-vm-runtime-store-rollback.test.ts": 341, + "src/shared/ephemeral-vm-runtime-store.test.ts": 41, + "src/shared/event-loop-yield.test.ts": 28, + "src/shared/execution-host-registry.test.ts": 15, + "src/shared/execution-host.test.ts": 14, + "src/shared/export-let-function-initializer-ban.test.ts": 2085, + "src/shared/external-automation-jobs-file.test.ts": 48, + "src/shared/external-worktree-inbox.test.ts": 11, + "src/shared/feature-education-telemetry.test.ts": 5, + "src/shared/feature-interactions.test.ts": 1489, + "src/shared/feature-tips.test.ts": 7, + "src/shared/feature-wall-tour-depth.test.ts": 5, + "src/shared/file-link-location.test.ts": 7, + "src/shared/file-name-sort.test.ts": 6, + "src/shared/filesystem-directory-listing-limit.test.ts": 10, + "src/shared/fish-binary-requirement.test.ts": 14, + "src/shared/folder-workspace-execution-host.test.ts": 17, + "src/shared/folder-workspace-worktree.test.ts": 8, + "src/shared/folder-workspaces.test.ts": 6, + "src/shared/foreground-process-ancestry-parity.test.ts": 286, + "src/shared/foreground-process-selection.test.ts": 11, + "src/shared/foreground-wrapper-agent.test.ts": 7, + "src/shared/generated-code-path.test.ts": 10, + "src/shared/git-branch-cleanup.test.ts": 17, + "src/shared/git-branch-compare-head.test.ts": 6, + "src/shared/git-branch-line-total-soft-deadline.test.ts": 3529, + "src/shared/git-branch-line-total.test.ts": 39, + "src/shared/git-capability-cache.test.ts": 10, + "src/shared/git-check-ignore-stdio.test.ts": 8, + "src/shared/git-clone-failure-message.test.ts": 26, + "src/shared/git-config-snapshot-runner.test.ts": 13, + "src/shared/git-configured-branch-target.test.ts": 11, + "src/shared/git-cquoted-path.test.ts": 3, + "src/shared/git-diff-transport-budget.test.ts": 1152, + "src/shared/git-discard-path-safety.test.ts": 22, + "src/shared/git-exec-mutation.test.ts": 10, + "src/shared/git-fetch-head-capability.test.ts": 6, + "src/shared/git-fetch-head-lock-key-derivation.test.ts": 64, + "src/shared/git-fetch-head-lock.test.ts": 108, + "src/shared/git-fork-sync.test.ts": 21, + "src/shared/git-history-graph.test.ts": 10, + "src/shared/git-history-message-allocation.test.ts": 11, + "src/shared/git-history-ref-display.test.ts": 9, + "src/shared/git-history.test.ts": 18, + "src/shared/git-merge-tree-capability.test.ts": 9, + "src/shared/git-metadata-path.test.ts": 13, + "src/shared/git-push-target-validation.test.ts": 6, + "src/shared/git-remote-error.test.ts": 36, + "src/shared/git-remote-identity.test.ts": 10, + "src/shared/git-remote-url-index.test.ts": 11, + "src/shared/git-rev-list-output.test.ts": 8, + "src/shared/git-status-branch-line-total-cache.test.ts": 12, + "src/shared/git-status-conflict-entries.test.ts": 10, + "src/shared/git-status-limit.test.ts": 6, + "src/shared/git-status-line-stat-inputs.test.ts": 6, + "src/shared/git-status-line-stats-cache.test.ts": 10, + "src/shared/git-status-upstream-ref.test.ts": 6, + "src/shared/git-uncommitted-line-stats.test.ts": 54, + "src/shared/git-upstream-status.test.ts": 16, + "src/shared/git-worktree-operation-lock.test.ts": 16, + "src/shared/github/api-availability.test.ts": 6, + "src/shared/github/project-identity.test.ts": 5, + "src/shared/github/project-ref-input.test.ts": 11, + "src/shared/github/project-roadmap-timeline.test.ts": 11, + "src/shared/github/pull-request-auto-merge-availability.test.ts": 6, + "src/shared/github/pull-request-for-branch-outcome.test.ts": 7, + "src/shared/github/pull-request-merge-methods.test.ts": 8, + "src/shared/github/repository-identity-key.test.ts": 5, + "src/shared/github/work-items-query-bounds.test.ts": 9, + "src/shared/gitlab-job-log-excerpt.test.ts": 36, + "src/shared/gitlab-job-trace-check-details.test.ts": 17, + "src/shared/gitlab-pipeline-checks.test.ts": 6, + "src/shared/gitlab-projects.test.ts": 10, + "src/shared/grok-model-list-probe.test.ts": 14, + "src/shared/grok-session-paths.test.ts": 21, + "src/shared/growing-byte-buffer.test.ts": 89, + "src/shared/handled-wire-discriminant.test.ts": 4, + "src/shared/harness-injected-user-turns.test.ts": 8, + "src/shared/hermes-run-ref-retention.test.ts": 6, + "src/shared/hook-command-source-policy.test.ts": 12, + "src/shared/host-balanced-listing-scaling.test.ts": 13, + "src/shared/host-setting-overrides.test.ts": 10, + "src/shared/hosted-review-creation-providers.test.ts": 4, + "src/shared/hosted-review-github.test.ts": 8, + "src/shared/hosted-review-ready-capabilities.test.ts": 5, + "src/shared/hosted-review-refs.test.ts": 6, + "src/shared/image-data-uri.test.ts": 12, + "src/shared/image-paste-following-text.test.ts": 5, + "src/shared/in-flight-promise-dedupe.test.ts": 19, + "src/shared/issue-link-input.test.ts": 9, + "src/shared/jira-issue-url.test.ts": 8, + "src/shared/json-text-structure-limit.test.ts": 9, + "src/shared/keybindings-conflicts.test.ts": 32, + "src/shared/keybindings-default-bindings.test.ts": 32, + "src/shared/keybindings-digit-index.test.ts": 12, + "src/shared/keybindings-double-tap.test.ts": 14, + "src/shared/keybindings-keyboard-layout.test.ts": 8, + "src/shared/keybindings-parse-cache.test.ts": 43, + "src/shared/keybindings-parsing.test.ts": 12, + "src/shared/keybindings-terminal-context.test.ts": 7, + "src/shared/keybindings-unassigned-actions.test.ts": 12, + "src/shared/linear/inline-media.test.ts": 4, + "src/shared/linear/issue-attribute-filter.test.ts": 9, + "src/shared/linear/issue-view-resume-state.test.ts": 15, + "src/shared/linear/links.test.ts": 7, + "src/shared/linear/workspace-types.test.ts": 7, + "src/shared/linux-proc-port-scan-limits.test.ts": 12, + "src/shared/local-account-runtime.test.ts": 7, + "src/shared/local-build-compatibility.test.ts": 5, + "src/shared/localhost-worktree-labels.test.ts": 8, + "src/shared/loose-ref-count.test.ts": 247, + "src/shared/macos-symbolic-hotkeys.test.ts": 14, + "src/shared/managed-agent-command-token.test.ts": 9, + "src/shared/manual-repo-order.test.ts": 12, + "src/shared/map-settled-with-concurrency.test.ts": 20, + "src/shared/map-with-concurrency.test.ts": 58, + "src/shared/markdown-document-listing-limits.test.ts": 5, + "src/shared/markdown-toc-panel-width.test.ts": 7, + "src/shared/mcp-config.test.ts": 29, + "src/shared/mobile-e2ee-v2-contract.test.ts": 15, + "src/shared/mobile-e2ee-v2-framing.test.ts": 13, + "src/shared/mobile-file-directory-limit.test.ts": 11, + "src/shared/mobile-markdown-document.test.ts": 8, + "src/shared/mobile-pairing-connection-mode.test.ts": 9, + "src/shared/mobile-pairing-custom-address.test.ts": 13, + "src/shared/mobile-push-contract.test.ts": 5, + "src/shared/mobile-relay-close-codes.test.ts": 4, + "src/shared/mobile-relay-mint-failure.test.ts": 5, + "src/shared/mobile-relay-pairing-offer.test.ts": 22, + "src/shared/mobile-relay-phone-protocol.test.ts": 12, + "src/shared/model-id-label.test.ts": 4, + "src/shared/modifier-double-tap-detector.test.ts": 10, + "src/shared/native-chat-agent-profiles.test.ts": 17, + "src/shared/native-chat-agent-support.test.ts": 7, + "src/shared/native-chat-ask-fifo.test.ts": 186, + "src/shared/native-chat-ask.test.ts": 12, + "src/shared/native-chat-command-envelope.test.ts": 9, + "src/shared/native-chat-edit-normalize.test.ts": 35, + "src/shared/native-chat-href-routing.test.ts": 11, + "src/shared/native-chat-image-transcript-markers.test.ts": 19, + "src/shared/native-chat-session-option-commands.test.ts": 15, + "src/shared/native-chat-session-option-defaults.test.ts": 15, + "src/shared/native-chat-session-option-snapshot.test.ts": 15, + "src/shared/native-chat-session-option-state.test.ts": 10, + "src/shared/native-chat-slash-commands.test.ts": 31, + "src/shared/native-chat-stream-unsubscribe.test.ts": 3, + "src/shared/native-chat-streaming.test.ts": 9, + "src/shared/native-chat-subagent-summary.test.ts": 11, + "src/shared/native-chat-task-list.test.ts": 11, + "src/shared/native-chat-tool-activity.test.ts": 11, + "src/shared/native-chat-tool-attribution-allocation.test.ts": 20, + "src/shared/native-chat-tool-icon.test.ts": 19, + "src/shared/native-chat-tool-identity.test.ts": 14, + "src/shared/native-chat-tool-pair-limit.test.ts": 74, + "src/shared/native-chat-tool-summary.test.ts": 13, + "src/shared/native-chat-transcript-retention.test.ts": 5, + "src/shared/native-chat-turn-activity.test.ts": 9, + "src/shared/native-chat-turn-status.test.ts": 13, + "src/shared/native-chat-types.test.ts": 4, + "src/shared/native-chat-unverifiable-turn-status.test.ts": 8, + "src/shared/native-file-drop.test.ts": 28, + "src/shared/nested-repo-telemetry-schema.test.ts": 18, + "src/shared/nested-repo-telemetry.test.ts": 15, + "src/shared/nested-worker-depth.test.ts": 8, + "src/shared/network-proxy.test.ts": 9, + "src/shared/network/manual-address.test.ts": 13, + "src/shared/network/server-share-address.test.ts": 24, + "src/shared/new-workspace-dialog-repo.test.ts": 5, + "src/shared/new-workspace/smart-workspace-url-source-results.test.ts": 10, + "src/shared/new-workspace/work-item-lookup-text.test.ts": 8, + "src/shared/new-workspace/workspace-source.test.ts": 8, + "src/shared/new-workspace/worktree-create-retry-policy.test.ts": 7, + "src/shared/node-bounded-file-reader-sync.test.ts": 4, + "src/shared/node-bounded-file-reader.test.ts": 14, + "src/shared/node-bounded-json-stringify.test.ts": 24, + "src/shared/node-file-content-equality.test.ts": 15, + "src/shared/node-markdown-document-discovery.test.ts": 8, + "src/shared/node-pty-spawn-helper.test.ts": 7, + "src/shared/node-readable-text.test.ts": 14, + "src/shared/node-source-copy-content-equality.test.ts": 16, + "src/shared/nul-delimited-fields.test.ts": 5, + "src/shared/nvm-default-alias.test.ts": 38, + "src/shared/omp-pi-semantic-title-preservation.test.ts": 25, + "src/shared/onboarding-tour-telemetry-events.test.ts": 12, + "src/shared/open-in-applications.test.ts": 8, + "src/shared/opencode-permission-status.test.ts": 18, + "src/shared/opencode-terminal-title.test.ts": 4, + "src/shared/orca-yaml-alias-bounds.test.ts": 27, + "src/shared/orca-yaml-bounds.test.ts": 22, + "src/shared/orchestration-ask-timeout.test.ts": 5, + "src/shared/orchestration-check-output.test.ts": 7, + "src/shared/orchestration-compatibility-evidence.test.ts": 8, + "src/shared/orchestration-dispatch-refusal-contract.test.ts": 10, + "src/shared/orchestration-fleet-attention.test.ts": 6, + "src/shared/orchestration-fleet-evidence-clock.test.ts": 8, + "src/shared/orchestration-fleet-projection.test.ts": 12, + "src/shared/orchestration-rpc-contract.test.ts": 12, + "src/shared/orchestration-task-display.test.ts": 9, + "src/shared/orchestration-task-summary.test.ts": 9, + "src/shared/orchestration-timing-budgets.test.ts": 6, + "src/shared/osc-title-scan-tail-retention.test.ts": 34, + "src/shared/osc-title-scan-tail.test.ts": 4, + "src/shared/osc52-clipboard-settings.test.ts": 7, + "src/shared/own-retained-string.test.ts": 77, + "src/shared/pairing-address-auto-selection.test.ts": 5, + "src/shared/pairing-local-ui-fields.test.ts": 6, + "src/shared/pairing.test.ts": 14, + "src/shared/pane-agent-identity-adapter.test.ts": 21, + "src/shared/pane-agent-identity-inventory.test.ts": 782, + "src/shared/pane-agent-identity-resolver.test.ts": 15, + "src/shared/pane-agent-identity-surface-inventory.test.ts": 1226, + "src/shared/pane-agent-identity-title-corpus.test.ts": 17, + "src/shared/pane-agent-owner.test.ts": 8, + "src/shared/pane-key-alias.test.ts": 6, + "src/shared/physical-exit-tracker.test.ts": 11, + "src/shared/pi-agent-kind.test.ts": 4, + "src/shared/pi-overlay-ui-settings.test.ts": 7, + "src/shared/pi-state-title-marker.test.ts": 11, + "src/shared/plugins/plugin-consent-fingerprint.test.ts": 14, + "src/shared/plugins/plugin-consent-request.test.ts": 10, + "src/shared/plugins/plugin-content-pack-contributions.test.ts": 16, + "src/shared/plugins/plugin-demo-fixture.test.ts": 16, + "src/shared/plugins/plugin-hostile-fixture.test.ts": 4, + "src/shared/plugins/plugin-install-lockfile.test.ts": 10, + "src/shared/plugins/plugin-kill-list.test.ts": 47, + "src/shared/plugins/plugin-language-pack-artifact.test.ts": 134, + "src/shared/plugins/plugin-manifest.test.ts": 24, + "src/shared/plugins/plugin-marketplace.test.ts": 64, + "src/shared/plugins/plugin-panel-call-admission.test.ts": 6, + "src/shared/plugins/plugin-panel-message-budget.test.ts": 6, + "src/shared/plugins/plugin-panel-pong-frame.test.ts": 9, + "src/shared/plugins/plugin-panel-shell.test.ts": 7, + "src/shared/plugins/plugin-path-safety.test.ts": 15, + "src/shared/plugins/plugin-vm-recipe-artifact.test.ts": 18, + "src/shared/posix-wait-status.test.ts": 7, + "src/shared/powershell-native-argument.test.ts": 4, + "src/shared/pr-bot-author-overrides.test.ts": 11, + "src/shared/pr-check-severity-order.test.ts": 7, + "src/shared/pr-check-status.test.ts": 5, + "src/shared/pr-comment-audience.test.ts": 5, + "src/shared/pr-comment-groups.test.ts": 8, + "src/shared/pr-comment-time.test.ts": 7, + "src/shared/preferred-git-remote.test.ts": 6, + "src/shared/priority-semaphore.test.ts": 19, + "src/shared/process-output-field-scanner.test.ts": 6, + "src/shared/process-table-snapshot.test.ts": 72, + "src/shared/project-catalog-row-normalization.test.ts": 9, + "src/shared/project-execution-runtime.test.ts": 15, + "src/shared/project-groups.test.ts": 124, + "src/shared/project-host-setup-projection.test.ts": 30, + "src/shared/project-identity-succession.test.ts": 30, + "src/shared/promise-settlement-waiters.test.ts": 337, + "src/shared/protocol-compat.test.ts": 13, + "src/shared/pty-consumer-session.test.ts": 17, + "src/shared/pty-delivery-diagnostics.test.ts": 14, + "src/shared/pty-liveness-verdict.test.ts": 6, + "src/shared/pty-owner-backend.test.ts": 4, + "src/shared/pty-slave-line-discipline-echo.test.ts": 13, + "src/shared/pty-startup-ingress-live-query-reply.test.ts": 23, + "src/shared/pty-startup-ingress.test.ts": 60, + "src/shared/pty-startup-reply-echo-shapes.test.ts": 14, + "src/shared/published-pane-agent-identity.test.ts": 20, + "src/shared/pull-request-generation.test.ts": 11, + "src/shared/quick-open-directory-reader.test.ts": 11, + "src/shared/quick-open-expansion-paths.test.ts": 30, + "src/shared/quick-open-filter.renderer-safety.test.ts": 6, + "src/shared/quick-open-filter.test.ts": 15, + "src/shared/quick-open-git-directory-collapse.test.ts": 51, + "src/shared/quick-open-listing-limits.test.ts": 14, + "src/shared/quick-open-readdir-memory.test.ts": 38, + "src/shared/quick-open-readdir-walk.test.ts": 197, + "src/shared/quick-open-transport-budget.test.ts": 5, + "src/shared/raster-image-base64-preview.test.ts": 322, + "src/shared/raster-image-dimensions.test.ts": 17, + "src/shared/raster-image-preview-limits.test.ts": 7, + "src/shared/rate-limit-reset-format.test.ts": 9, + "src/shared/rate-limit-types.test.ts": 6, + "src/shared/react-update-depth-attribution.test.ts": 7, + "src/shared/reconnect-jitter.test.ts": 5, + "src/shared/relay-frame-buffer.test.ts": 393, + "src/shared/relay-optional-artifacts.test.ts": 5, + "src/shared/relay-version-marker.test.ts": 5, + "src/shared/release-channel.test.ts": 26, + "src/shared/remote-foreground-evidence.test.ts": 9, + "src/shared/remote-pairing-address.test.ts": 18, + "src/shared/remote-pairing-verification.test.ts": 7, + "src/shared/remote-rpc-content-budget.test.ts": 89, + "src/shared/remote-runtime-client-error-classification.test.ts": 8, + "src/shared/remote-runtime-client.test.ts": 4792, + "src/shared/remote-runtime-memory-limits.test.ts": 110, + "src/shared/remote-runtime-outbound-admission.test.ts": 903, + "src/shared/remote-runtime-request-connection-stale.test.ts": 141, + "src/shared/remote-runtime-request-connection.test.ts": 100, + "src/shared/remote-runtime-request-frames.test.ts": 9, + "src/shared/remote-runtime-request-response-router.test.ts": 12, + "src/shared/remote-runtime-request-websocket.test.ts": 32, + "src/shared/remote-runtime-shared-control-boundary.test.ts": 2634, + "src/shared/remote-runtime-shared-control-connection.test.ts": 3301, + "src/shared/remote-runtime-shared-control-keepalive-refresh.test.ts": 14, + "src/shared/remote-runtime-shared-control-reconnect.test.ts": 9, + "src/shared/remote-runtime-shared-control-retired-request-ids.test.ts": 8, + "src/shared/remote-runtime-shared-control-socket-generation.test.ts": 4, + "src/shared/remote-runtime-shared-control-standing-intent.test.ts": 615, + "src/shared/remote-runtime-shared-control-subscription-close.test.ts": 5, + "src/shared/remote-runtime-shared-control-subscriptions.test.ts": 12, + "src/shared/remote-runtime-socket-liveness.test.ts": 9, + "src/shared/remote-runtime-subscription-frame-router.test.ts": 7, + "src/shared/remote-runtime-subscription-request.test.ts": 295, + "src/shared/remote-runtime-tailscale-hint.test.ts": 7, + "src/shared/remote-runtime-transport-error-agreement.test.ts": 9, + "src/shared/remote-workspace-session-projection.test.ts": 9, + "src/shared/renderer-restart-preparation.test.ts": 10, + "src/shared/repo-badge-color.test.ts": 5, + "src/shared/repo-icon.test.ts": 23, + "src/shared/repo-ref-maintenance.test.ts": 316, + "src/shared/repo-search-limits.test.ts": 10, + "src/shared/repro-13889-claude-quarter-circle-busy-title.test.ts": 16, + "src/shared/repro-7732-gitlab-job-id-dropped.test.ts": 5, + "src/shared/repro-8478-opencode-native-title-icon.test.ts": 16, + "src/shared/require-tui-agent-config.test.ts": 4, + "src/shared/resolved-worktree-lineage.test.ts": 10, + "src/shared/retired-pty-incarnations.test.ts": 4, + "src/shared/review-head-tracking-ref.test.ts": 6, + "src/shared/ripgrep-process-availability.test.ts": 11, + "src/shared/runtime-client-export-parity.test.ts": 5, + "src/shared/runtime-environment-store.test.ts": 32, + "src/shared/runtime-host-status-owner.test.ts": 22, + "src/shared/runtime-listing-host-scope.test.ts": 10, + "src/shared/runtime-navigation.test.ts": 5, + "src/shared/runtime-rpc-call-queue.test.ts": 15, + "src/shared/runtime-workspace-file-owner.test.ts": 10, + "src/shared/runtime-workspace-window-availability.test.ts": 5, + "src/shared/search-match-count.test.ts": 7, + "src/shared/search-subprocess-lines.test.ts": 87, + "src/shared/secret-store.test.ts": 8, + "src/shared/secure-file-coarse-ctime.test.ts": 9, + "src/shared/secure-file-fsync-flags.test.ts": 13, + "src/shared/secure-file.test.ts": 602, + "src/shared/secure-path-hardening-cache.test.ts": 9, + "src/shared/secure-path-hardening-retry-budget.test.ts": 39, + "src/shared/serve-option-validation.test.ts": 12, + "src/shared/setup-agent-sequencing.test.ts": 6203, + "src/shared/setup-runner-command.test.ts": 16, + "src/shared/setup-script-imports.test.ts": 26, + "src/shared/setup-script-package-manager-suggestion.test.ts": 14, + "src/shared/setup-script-shebang.test.ts": 7, + "src/shared/setup-script-telemetry-events.test.ts": 10, + "src/shared/setup-script-telemetry.test.ts": 7, + "src/shared/shell-foreground-snapshot.test.ts": 118, + "src/shared/shell-process-readiness.test.ts": 20, + "src/shared/skill-bundle-install-contract.test.ts": 9, + "src/shared/skill-bundle-name.test.ts": 3, + "src/shared/skill-delete-contract.test.ts": 30, + "src/shared/skill-deletion-eligibility.test.ts": 6, + "src/shared/skill-install-contract.test.ts": 13, + "src/shared/skill-install-failure.test.ts": 11, + "src/shared/skill-metadata.test.ts": 6, + "src/shared/skill-package-manifest.test.ts": 78, + "src/shared/skill-path-containment.test.ts": 8, + "src/shared/skills-cli-agent-keys.test.ts": 10, + "src/shared/source-control-ai-action-recipes.test.ts": 11, + "src/shared/source-control-ai-action-variables.test.ts": 12, + "src/shared/source-control-ai-actions.test.ts": 11, + "src/shared/source-control-ai-policy-regression.test.ts": 12, + "src/shared/source-control-ai-recipe-save.test.ts": 15, + "src/shared/source-control-ai.test.ts": 18, + "src/shared/source-control-create-review-intent.test.ts": 12, + "src/shared/source-control-group-order.test.ts": 4, + "src/shared/source-control-primary-action-decision.test.ts": 5, + "src/shared/source-control-push-failure.test.ts": 12, + "src/shared/source-control-recovery-agent-command.test.ts": 7, + "src/shared/source-scan/source-tree-scan.test.ts": 37, + "src/shared/ssh-pending-pty-kill.test.ts": 9, + "src/shared/ssh-pty-id.test.ts": 8, + "src/shared/ssh-relay-pty-ownership-proof.test.ts": 44, + "src/shared/ssh-retained-payload-admission.test.ts": 20, + "src/shared/ssh-target-generation.test.ts": 11, + "src/shared/ssh-types.test.ts": 7, + "src/shared/stable-pane-id.test.ts": 13, + "src/shared/startup-command-submission.test.ts": 6, + "src/shared/status-bar-usage-mode.test.ts": 3, + "src/shared/string-chunk-compaction.test.ts": 19, + "src/shared/structural-value-equality.test.ts": 10, + "src/shared/structured-agent-session-coalescer.test.ts": 7, + "src/shared/structured-agent-session-composer.test.ts": 13, + "src/shared/structured-agent-session-create.test.ts": 16, + "src/shared/structured-agent-session-item-retention.test.ts": 460, + "src/shared/structured-agent-session-live-turn.test.ts": 4, + "src/shared/structured-agent-session-mutation.test.ts": 6, + "src/shared/structured-agent-session-option-picks.test.ts": 8, + "src/shared/structured-agent-session-options.test.ts": 12, + "src/shared/structured-agent-session-projection.test.ts": 14, + "src/shared/structured-agent-session-reducer.test.ts": 43, + "src/shared/structured-agent-session-turn-timing-retention.test.ts": 20, + "src/shared/structured-agent-session-turn-timing.test.ts": 15, + "src/shared/structured-native-chat-launch-route.test.ts": 8, + "src/shared/subprocess-stdin-write.test.ts": 4, + "src/shared/synthetic-agent-title.test.ts": 9, + "src/shared/tab-title-resolution.test.ts": 8, + "src/shared/tailnet-address.test.ts": 5, + "src/shared/task-providers.test.ts": 12, + "src/shared/task-query.test.ts": 24, + "src/shared/task-source-context.test.ts": 12, + "src/shared/telemetry-common-props.test.ts": 9, + "src/shared/telemetry-events-feature-education.test.ts": 12, + "src/shared/telemetry-events.test.ts": 37, + "src/shared/telemetry-feature-wall-events.test.ts": 11, + "src/shared/telemetry-orca-cli-feature-tip.test.ts": 6, + "src/shared/terminal-bell-detector.test.ts": 7, + "src/shared/terminal-color-scheme-protocol.test.ts": 7, + "src/shared/terminal-composer-draft.test.ts": 13, + "src/shared/terminal-custom-themes.test.ts": 10, + "src/shared/terminal-escape-introducer.test.ts": 16, + "src/shared/terminal-exit-cause.test.ts": 8, + "src/shared/terminal-file-url-target.test.ts": 3, + "src/shared/terminal-fonts.test.ts": 7, + "src/shared/terminal-github-pr-link-detector.test.ts": 14, + "src/shared/terminal-input.test.ts": 40, + "src/shared/terminal-kitty-keyboard-mode-tracker.test.ts": 19, + "src/shared/terminal-line-height-settings.test.ts": 4, + "src/shared/terminal-mode-2031-final-state.test.ts": 10, + "src/shared/terminal-mode-reset-profiles.test.ts": 13, + "src/shared/terminal-osc-color-reply.test.ts": 7, + "src/shared/terminal-output-side-effects.test.ts": 14, + "src/shared/terminal-partial-escape-tail-ground-scan.test.ts": 12, + "src/shared/terminal-partial-escape-tail.fuzz.test.ts": 986, + "src/shared/terminal-partial-escape-tail.test.ts": 13, + "src/shared/terminal-query-reply.test.ts": 23, + "src/shared/terminal-quick-commands.test.ts": 10, + "src/shared/terminal-reply-query-scan.test.ts": 5, + "src/shared/terminal-scrollback-policy.test.ts": 11, + "src/shared/terminal-startup-cwd.test.ts": 11, + "src/shared/terminal-stream-protocol.test.ts": 10, + "src/shared/terminal-title-agent-type.test.ts": 27, + "src/shared/terminal-title-classification-corpus.test.ts": 34, + "src/shared/terminal-title-classification-memo.test.ts": 20, + "src/shared/terminal-view-attributes.test.ts": 15, + "src/shared/terminal-wait-blocked-reason-legacy-alias.test.ts": 9, + "src/shared/terminal-zero-dimensions-diagnostic.test.ts": 5, + "src/shared/test-code-path.test.ts": 10, + "src/shared/text-search.test.ts": 53, + "src/shared/timer-delay.test.ts": 9, + "src/shared/tui-agent-config.test.ts": 9, + "src/shared/tui-agent-permissions.test.ts": 10, + "src/shared/tui-agent-selection.test.ts": 10, + "src/shared/tui-agent-startup-copilot-resume.test.ts": 5, + "src/shared/tui-agent-startup-hermes.test.ts": 26, + "src/shared/tui-agent-startup-session-options.test.ts": 13, + "src/shared/tui-agent-startup-shell.test.ts": 23, + "src/shared/tui-agent-startup.test.ts": 29, + "src/shared/ui-language.test.ts": 8, + "src/shared/ui-locale.test.ts": 14, + "src/shared/ui-zoom-level.test.ts": 7, + "src/shared/updated-at-order.test.ts": 82, + "src/shared/updater-windows-signature-check.test.ts": 6, + "src/shared/usage-percentage-display-change-notice.test.ts": 6, + "src/shared/usage-percentage-display.test.ts": 8, + "src/shared/utf8-byte-limits.test.ts": 70, + "src/shared/vscode-remote-ssh-launcher.test.ts": 7, + "src/shared/window-shortcut-policy-agent-dashboard.test.ts": 7, + "src/shared/window-shortcut-policy.test.ts": 25, + "src/shared/windows-cmd-runner-delayed-launch.test.ts": 5, + "src/shared/windows-command-line-budget.test.ts": 1102, + "src/shared/windows-console-input.test.ts": 12, + "src/shared/windows-environment-expansion.test.ts": 48, + "src/shared/windows-interactive-login-spawn.test.ts": 13, + "src/shared/windows-lane-tree-removal-boundary.test.ts": 19, + "src/shared/windows-long-path-git-args.test.ts": 4, + "src/shared/windows-security-descriptor.test.ts": 13, + "src/shared/windows-terminal-shell.test.ts": 9, + "src/shared/windows-transient-lock-removal.test.ts": 1176, + "src/shared/worker-terminal-host-scope.test.ts": 15, + "src/shared/worker-transcript-text-scaling.test.ts": 7, + "src/shared/workspace-cleanup-applied-filters.test.ts": 15, + "src/shared/workspace-cleanup-browse-state-persistence.test.ts": 18, + "src/shared/workspace-cleanup-ui-state.test.ts": 7, + "src/shared/workspace-cleanup.test.ts": 10, + "src/shared/workspace-doc-history.test.ts": 34, + "src/shared/workspace-linked-item-equality.test.ts": 7, + "src/shared/workspace-linked-item-source-context.test.ts": 8, + "src/shared/workspace-name.test.ts": 25, + "src/shared/workspace-session-browser-history.test.ts": 25, + "src/shared/workspace-session-partition-owner.test.ts": 4, + "src/shared/workspace-session-salvage-equivalence.test.ts": 95, + "src/shared/workspace-session-salvage.test.ts": 1522, + "src/shared/workspace-session-schema-field-coverage.test.ts": 15, + "src/shared/workspace-session-schema.sleeping-agent.test.ts": 22, + "src/shared/workspace-session-schema.test.ts": 23, + "src/shared/workspace-session-tab-focus-schema.test.ts": 19, + "src/shared/workspace-session-terminal-buffers.test.ts": 28, + "src/shared/workspace-session-terminal-schema.test.ts": 12, + "src/shared/workspace-session-terminal-tab-close.test.ts": 43, + "src/shared/workspace-session-validation-work.test.ts": 108, + "src/shared/workspace-space-compaction.test.ts": 15, + "src/shared/workspace-space-entry-traversal.test.ts": 1132, + "src/shared/workspace-space-scan-budget.test.ts": 32, + "src/shared/workspace-statuses.test.ts": 24, + "src/shared/worktree-execution-host-resolution.test.ts": 15, + "src/shared/worktree-name-suggestion.test.ts": 29, + "src/shared/worktree/base-ref.test.ts": 11, + "src/shared/worktree/card-properties.test.ts": 6, + "src/shared/worktree/create-preparation.test.ts": 6, + "src/shared/worktree/github-pr-suppression.test.ts": 8, + "src/shared/worktree/host-context-labels.test.ts": 8, + "src/shared/worktree/host-qualified-identity.test.ts": 8, + "src/shared/worktree/id.test.ts": 13, + "src/shared/worktree/identity.test.ts": 7, + "src/shared/worktree/ownership-configured-base-visibility.test.ts": 22, + "src/shared/worktree/ownership-worktree-base-path.test.ts": 8, + "src/shared/worktree/ownership.test.ts": 284, + "src/shared/worktree/removal-fence-error.test.ts": 7, + "src/shared/worktree/removal-force-classification.test.ts": 5, + "src/shared/worktree/retired-name-cache.test.ts": 15, + "src/shared/worktree/retired-name-registry.test.ts": 185, + "src/shared/worktree/sort-order-update.test.ts": 7, + "src/shared/worktree/submodule-removal.test.ts": 7, + "src/shared/worktree/visibility-sources.test.ts": 18, + "src/shared/ws-outbound-backpressure-queue.test.ts": 31, + "src/shared/wsl-exec-mode-separator.test.ts": 2451, + "src/shared/wsl-login-shell-command.test.ts": 66, + "src/shared/wsl-paths.test.ts": 18, + "src/shared/zod-salvage-absence.test.ts": 21, + "tests/e2e/alternate-screen-fixture-script.unit.test.ts": 8, + "tests/e2e/completed-worker-retirement-resume.unit.test.ts": 128, + "tests/e2e/global-teardown.unit.test.ts": 55, + "tests/e2e/helpers/alt-screen-frame.unit.test.ts": 40, + "tests/e2e/helpers/client-hosted-browser-fixture.unit.test.ts": 13, + "tests/e2e/helpers/electron-crashpad-cleanup.unit.test.ts": 11, + "tests/e2e/helpers/electron-home-isolation.unit.test.ts": 7, + "tests/e2e/helpers/electron-launch-args.unit.test.ts": 9, + "tests/e2e/helpers/electron-main-evaluate-retry.unit.test.ts": 1209, + "tests/e2e/helpers/electron-process-shutdown.unit.test.ts": 11, + "tests/e2e/helpers/fake-agent-command-override.unit.test.ts": 3, + "tests/e2e/helpers/fake-agent-paste-end-scanner.unit.test.ts": 16, + "tests/e2e/helpers/git-status-retry-barrier.unit.test.ts": 7, + "tests/e2e/helpers/golden-source-control.unit.test.ts": 11, + "tests/e2e/helpers/headless-paired-runtime-serve-readiness.unit.test.ts": 18, + "tests/e2e/helpers/nested-runtime-proxy-jump-fixture.unit.test.ts": 5, + "tests/e2e/helpers/paired-client-runtime-environment.unit.test.ts": 12, + "tests/e2e/helpers/paired-client-window-reveal.unit.test.ts": 10, + "tests/e2e/helpers/remote-skill-cloud-fixture.unit.test.ts": 138, + "tests/e2e/helpers/remote-terminal-source-range-contract-fixture.unit.test.ts": 7, + "tests/e2e/helpers/ssh-port-forward-snapshot-barrier.unit.test.ts": 57, + "tests/e2e/helpers/streaming-terminal-cleanup.unit.test.ts": 10, + "tests/e2e/host-cold-park-remote-subscriber.unit.test.ts": 518, + "tests/e2e/host-guest-paint-retention-remote-viewer.unit.test.ts": 278, + "tests/e2e/orca-restart-navigation.unit.test.ts": 9, + "tests/e2e/orchestration-run-pagination.unit.test.ts": 9, + "tests/e2e/paired-runtime-rejected-input-remount.unit.test.ts": 935, + "tests/e2e/relay-region-compatibility.unit.test.ts": 56, + "tests/e2e/relay-region-correction.unit.test.ts": 5935, + "tests/e2e/remote-agent-completion-authority.unit.test.ts": 20, + "tests/e2e/remote-terminal-tab-retirement.unit.test.ts": 53, + "tests/e2e/restored-terminal-input-readiness.unit.test.ts": 17, + "tests/e2e/session-tabs-decorative-title-fanout.unit.test.ts": 460, + "tests/e2e/session-tabs-empty-inventory-daemon-oracle.unit.test.ts": 33, + "tests/e2e/session-tabs-rich-status-boundaries.unit.test.ts": 82, + "tests/e2e/ssh-codex-replay-reply-probe.unit.test.ts": 9, + "tests/e2e/structured-native-chat-routing-authority.unit.test.ts": 67, + "tests/e2e/terminal-foreground-confirmation.unit.test.ts": 19, + "tests/e2e/terminal-probe-input-sequence.unit.test.ts": 9, + "tests/e2e/terminal-split-activation-latency-artifact.unit.test.ts": 11, + "tests/e2e/terminal-split-activation-latency-report.unit.test.ts": 13, + "tests/tools/relay-bench/phone-e2ee-desktop-parity.test.mjs": 48, + "tests/tools/relay-bench/region-probe-replay.test.mjs": 26, + "tests/tools/relay-bench/relay-bench-invocation.test.mjs": 16, + "tests/tools/relay-bench/relay-bench-state-file.test.mjs": 13, + "tests/tools/relay-bench/relay-phone-connect-bench.test.mjs": 27, + "tests/tools/win-update-e2e/app-driver.test.mjs": 23, + "tests/tools/windows-pty-native-capability-smoke/packaged-node-pty-capability-oracle.test.mjs": 8, + "tests/tools/windows-pty-native-capability-smoke/packaged-node-pty-capability-probe.test.mjs": 40, + "tests/tools/windows-pty-native-capability-smoke/run.test.mjs": 11 + } + }, + "e2e": { + "runId": "34652504501", + "jobIds": [ + "103437895060", + "103437895072", + "103437895076", + "103437895077", + "103437895093", + "103437895101", + "103437895106", + "103437895107", + "103437895124", + "103437895125", + "103437895133", + "103437895146", + "103437895183", + "103437895216" + ], + "overheadMs": 0, + "timings": { + "tests/e2e/active-view-restart-restore.spec.ts": 22600, + "tests/e2e/activity-agent-pane-isolation.spec.ts": 102400, + "tests/e2e/add-project-default-checkout.spec.ts": 41600, + "tests/e2e/agent-dashboard-status-burst.spec.ts": 16900, + "tests/e2e/agent-descendant-process-kill.spec.ts": 14800, + "tests/e2e/agent-session-live-force-exit-resume.spec.ts": 31800, + "tests/e2e/agent-session-log-tail-stability.spec.ts": 78000, + "tests/e2e/agent-session-quit-resume.spec.ts": 27800, + "tests/e2e/ai-vault-session-delete.spec.ts": 29800, + "tests/e2e/app-menu-paste-ownership.spec.ts": 31900, + "tests/e2e/artificial-opencode-terminal-load.spec.ts": 230300, + "tests/e2e/automation-hidden-terminal-first-mount.spec.ts": 15100, + "tests/e2e/automation-prompt-disclosure.spec.ts": 27000, + "tests/e2e/automation-runs-dashboard.spec.ts": 18900, + "tests/e2e/browser-address-bar-narrow-toolbar.spec.ts": 17100, + "tests/e2e/browser-embedded-owner-routing.spec.ts": 20900, + "tests/e2e/browser-fedcm-fallback.spec.ts": 15100, + "tests/e2e/browser-guest-attachment-validation.spec.ts": 16900, + "tests/e2e/browser-guest-crash-recovery.spec.ts": 123200, + "tests/e2e/browser-loading-surface.spec.ts": 49800, + "tests/e2e/browser-local-https-certificate-trust.spec.ts": 19500, + "tests/e2e/browser-reload-feedback.spec.ts": 21000, + "tests/e2e/browser-split-shortcuts.spec.ts": 75000, + "tests/e2e/browser-tab.spec.ts": 175700, + "tests/e2e/chinese-ime-chat-input-repro.spec.ts": 49200, + "tests/e2e/combined-diff-invalidation-freeze-repro.spec.ts": 101900, + "tests/e2e/combined-diff-scroll-restore.spec.ts": 51900, + "tests/e2e/completed-worker-retirement-resume.spec.ts": 40400, + "tests/e2e/daemon-generation-legacy-close-safety.spec.ts": 5300, + "tests/e2e/daemon-lifecycle-retirement.spec.ts": 222, + "tests/e2e/daemon-live-session-preservation.spec.ts": 20700, + "tests/e2e/daemon-slow-health-check-preservation.spec.ts": 23700, + "tests/e2e/daemon-slow-init-pty-gate.spec.ts": 20700, + "tests/e2e/default-branch-visibility.spec.ts": 14800, + "tests/e2e/dictation-indicator.spec.ts": 14900, + "tests/e2e/diff-note-delete.spec.ts": 18700, + "tests/e2e/diff-note-edit.spec.ts": 20900, + "tests/e2e/diff-note-layout.spec.ts": 24900, + "tests/e2e/droid-notification.spec.ts": 65000, + "tests/e2e/editable-context-paste-ownership.spec.ts": 15700, + "tests/e2e/editor-tab-selection-restore.spec.ts": 28900, + "tests/e2e/electron-home-isolation.spec.ts": 12800, + "tests/e2e/ephemeral-vm-cleanup-retry.spec.ts": 26800, + "tests/e2e/ephemeral-vm-provisioned-root.spec.ts": 60000, + "tests/e2e/feature-wall.spec.ts": 101800, + "tests/e2e/file-explorer-watch-refresh.spec.ts": 17700, + "tests/e2e/file-open.spec.ts": 60000, + "tests/e2e/finished-agent-ghost-resume.spec.ts": 19100, + "tests/e2e/floating-mobile-emulator-tab.spec.ts": 23000, + "tests/e2e/floating-tab-rename.spec.ts": 56200, + "tests/e2e/folder-setup-shallow-priority.spec.ts": 50100, + "tests/e2e/folder-setup.spec.ts": 56200, + "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, + "tests/e2e/golden-file-open-edit-save.spec.ts": 25000, + "tests/e2e/golden-fresh-profile-terminal.spec.ts": 24400, + "tests/e2e/golden-posix-fresh-startup.spec.ts": 4900, + "tests/e2e/golden-posix-profile-index-fsync.spec.ts": 4900, + "tests/e2e/golden-quit-relaunch-session.spec.ts": 29700, + "tests/e2e/golden-shell-after-agent-exit.spec.ts": 28300, + "tests/e2e/golden-shell-command.spec.ts": 15400, + "tests/e2e/golden-source-control-commit.spec.ts": 19100, + "tests/e2e/golden-source-control-open-diff.spec.ts": 20900, + "tests/e2e/golden-tab-bar-agent-launch.spec.ts": 54100, + "tests/e2e/golden-terminal-file-link.spec.ts": 43300, + "tests/e2e/golden-worktree-create-switch.spec.ts": 29100, + "tests/e2e/grok-hook-session-cleanup.spec.ts": 7700, + "tests/e2e/headless-paired-remote-terminal-retention-memory.spec.ts": 58000, + "tests/e2e/headless-serve-cli-terminal-retention-parity.spec.ts": 25300, + "tests/e2e/headless-serve-desktop-activation.spec.ts": 16700, + "tests/e2e/headless-serve-focused-terminal-create.spec.ts": 13200, + "tests/e2e/host-parked-pane-remote-viewer.spec.ts": 144000, + "tests/e2e/issue-12656-terminal-link-tooltip.spec.ts": 21000, + "tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts": 104400, + "tests/e2e/landing-preflight-runtime-routing.spec.ts": 11000, + "tests/e2e/large-diff-freeze-repro.spec.ts": 35800, + "tests/e2e/linear-filter-chip-labels.spec.ts": 29800, + "tests/e2e/linear-issue-view-persistence.spec.ts": 198000, + "tests/e2e/linear-url-workspace-entry.spec.ts": 73500, + "tests/e2e/live-background-terminal-mount-authority.spec.ts": 33300, + "tests/e2e/local-cli-terminal-retention.spec.ts": 18300, + "tests/e2e/local-worktree-visibility-runtime-active.spec.ts": 14700, + "tests/e2e/macos-press-and-hold-startup.spec.ts": 12700, + "tests/e2e/manual-worktree-order-persistence.spec.ts": 36100, + "tests/e2e/markdown-add-review-note-shortcut.spec.ts": 68300, + "tests/e2e/markdown-explorer-find-focus.spec.ts": 16300, + "tests/e2e/markdown-nested-toggle.spec.ts": 50700, + "tests/e2e/markdown-ordered-list-exit.spec.ts": 53200, + "tests/e2e/markdown-prose-reflow.spec.ts": 62000, + "tests/e2e/markdown-table-row-backspace.spec.ts": 24600, + "tests/e2e/mobile-banner.spec.ts": 89300, + "tests/e2e/multi-client-navigation-isolation.spec.ts": 99900, + "tests/e2e/native-chat-ask-user-question-card.spec.ts": 23600, + "tests/e2e/native-chat-first-flush-race.spec.ts": 22700, + "tests/e2e/native-chat-history-prepend-anchor.spec.ts": 29400, + "tests/e2e/new-workspace-create-more.spec.ts": 36300, + "tests/e2e/new-workspace-cross-project-dialog.spec.ts": 17100, + "tests/e2e/new-workspace-linked-item-project-switch.spec.ts": 45200, + "tests/e2e/notification-settings.spec.ts": 23500, + "tests/e2e/onboarding.spec.ts": 276800, + "tests/e2e/orchestration-idle-mail-delivery.spec.ts": 220800, + "tests/e2e/orchestration-idle-mail-restore.spec.ts": 27000, + "tests/e2e/orchestration-legacy-worker-missing-terminal-recovery.spec.ts": 22200, + "tests/e2e/orchestration-legacy-worker-restart-recovery.spec.ts": 50700, + "tests/e2e/orchestration-low-level-dispatch-release.spec.ts": 15000, + "tests/e2e/orchestration-worker-settlement-release-cli.spec.ts": 18100, + "tests/e2e/orchestration-worker-terminal-visibility.spec.ts": 53200, + "tests/e2e/orchestration-worker-transcript-providers.spec.ts": 54700, + "tests/e2e/paired-browser-creation-reconciliation-failure.spec.ts": 21600, + "tests/e2e/paired-cli-terminal-graph-sync-tab-retention.spec.ts": 42600, + "tests/e2e/paired-client-hosted-browser-cookie-survival.spec.ts": 20100, + "tests/e2e/paired-client-hosted-browser-double-restart.spec.ts": 45800, + "tests/e2e/paired-client-hosted-browser-ghost-close.spec.ts": 48600, + "tests/e2e/paired-client-hosted-browser-host-strip.spec.ts": 23300, + "tests/e2e/paired-client-hosted-browser-quit-survival.spec.ts": 145700, + "tests/e2e/paired-client-hosted-browser-restart-survival.spec.ts": 20700, + "tests/e2e/paired-client-hosted-browser-title-hold.spec.ts": 24300, + "tests/e2e/paired-client-hosted-browser.spec.ts": 53000, + "tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts": 53300, + "tests/e2e/paired-external-worktree-discovery.spec.ts": 19300, + "tests/e2e/paired-instant-browser-tab.spec.ts": 191700, + "tests/e2e/paired-preview-address-bar-convergence.spec.ts": 162000, + "tests/e2e/paired-quick-open-large-tree.spec.ts": 26300, + "tests/e2e/paired-remote-browser-ghost-subscriber-rejoin.spec.ts": 22600, + "tests/e2e/paired-remote-browser-link-open-routing.spec.ts": 78000, + "tests/e2e/paired-remote-browser-stream-reconnect.spec.ts": 57200, + "tests/e2e/paired-remote-html-preview-local-render.spec.ts": 262700, + "tests/e2e/paired-remote-pane-layout-retry.spec.ts": 28200, + "tests/e2e/paired-remote-split-pane-host-retired-ghost.spec.ts": 20000, + "tests/e2e/paired-remote-terminal-browser-link.spec.ts": 78000, + "tests/e2e/paired-remote-terminal-host-restart-background-sync.spec.ts": 35600, + "tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts": 27100, + "tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts": 39700, + "tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts": 28300, + "tests/e2e/paired-skill-installation.spec.ts": 26500, + "tests/e2e/paired-split-pane-browser-placement.spec.ts": 85900, + "tests/e2e/paired-startup-exec-readiness.spec.ts": 6000, + "tests/e2e/paired-web-add-project-unavailable-host.spec.ts": 13200, + "tests/e2e/persisted-session-production-upgrade.spec.ts": 26700, + "tests/e2e/pet-status-segment-layout.spec.ts": 25100, + "tests/e2e/pi-ui-prompt-status.spec.ts": 21100, + "tests/e2e/plugin-demo.spec.ts": 26600, + "tests/e2e/plugin-marketplace-content.spec.ts": 39600, + "tests/e2e/plugin-panel-containment.spec.ts": 50300, + "tests/e2e/plugin-startup-budget.spec.ts": 20400, + "tests/e2e/pr-comments-sidebar-cards.spec.ts": 144300, + "tests/e2e/pr11346-selected-runtime-add.spec.ts": 33800, + "tests/e2e/project-group-creation-visibility.spec.ts": 132000, + "tests/e2e/project-group-manual-sort.spec.ts": 100300, + "tests/e2e/pty-snapshot-capability-main-stall.spec.ts": 16100.000000000002, + "tests/e2e/quick-open-file-paths.spec.ts": 22800, + "tests/e2e/remote-session-bulk-open-freeze-repro.spec.ts": 51500, + "tests/e2e/renderer-blocked-navigation-graph-authority.spec.ts": 43600, + "tests/e2e/renderer-crash-recovery-terminal-input.spec.ts": 34500, + "tests/e2e/repo-icon-emoji-picker.spec.ts": 22900, + "tests/e2e/repro-7732-gitlab-checks-job-details.spec.ts": 29900, + "tests/e2e/resource-manager-unbound-session-safety.spec.ts": 18700, + "tests/e2e/resource-usage-warm-reattach.spec.ts": 18700, + "tests/e2e/restart-restore-terminal-input.spec.ts": 77800, + "tests/e2e/rich-markdown-inline-image.spec.ts": 55200, + "tests/e2e/rich-markdown-link-bubble-stacking.spec.ts": 30100, + "tests/e2e/right-sidebar-windows-titlebar.spec.ts": 14600, + "tests/e2e/runtime-host-status-recovery.spec.ts": 156000, + "tests/e2e/settings-agent-awake.spec.ts": 33400, + "tests/e2e/settings-display-name-ime.spec.ts": 18100, + "tests/e2e/settings-search-responsiveness.spec.ts": 15600, + "tests/e2e/settings-search-shortcuts-pane.spec.ts": 15800, + "tests/e2e/settings-skill-detection.spec.ts": 21800, + "tests/e2e/settled-worker-tab-survives-restart.spec.ts": 62400, + "tests/e2e/setup-guide-sidebar.spec.ts": 6800, + "tests/e2e/setup-script-import.spec.ts": 57600, + "tests/e2e/setup-script-prompt-unreadable-orca-yaml.spec.ts": 19100, + "tests/e2e/sidebar-agent-row-identity.spec.ts": 16700, + "tests/e2e/slept-workspace-remount-wake.spec.ts": 31000, + "tests/e2e/source-control-commit-draft-persistence.spec.ts": 16400, + "tests/e2e/source-control-commit-message-ai.spec.ts": 50900, + "tests/e2e/source-control-create-pr-intent-notice-layout.spec.ts": 16900, + "tests/e2e/source-control-create-pr-intent-switch.spec.ts": 41000, + "tests/e2e/source-control-create-pr.spec.ts": 37500, + "tests/e2e/source-control-discard-confirmation.spec.ts": 25900, + "tests/e2e/source-control-large-file-count.spec.ts": 54700, + "tests/e2e/source-control-pr-generation-switch.spec.ts": 146600, + "tests/e2e/source-control-pr-linked-issue-ai.spec.ts": 34200, + "tests/e2e/ssh-config-host-import.spec.ts": 204600, + "tests/e2e/ssh-config-host-picker.spec.ts": 206600, + "tests/e2e/ssh-host-form-modal.spec.ts": 94700, + "tests/e2e/status-bar-caffeinate.spec.ts": 17900, + "tests/e2e/status-bar-session-count-management-kill.spec.ts": 26000, + "tests/e2e/tab-close-navigation.spec.ts": 32100, + "tests/e2e/tab-create-entry-file-paths.spec.ts": 23600, + "tests/e2e/tab-rename-paste-ownership.spec.ts": 15600, + "tests/e2e/tab-rename.spec.ts": 149900, + "tests/e2e/tab-sidebar-closed-overlap.spec.ts": 28600, + "tests/e2e/tabs.spec.ts": 164500, + "tests/e2e/tasks-page.spec.ts": 90400, + "tests/e2e/terminal-attention.spec.ts": 69700, + "tests/e2e/terminal-cjk-ime-committed-text.spec.ts": 122400, + "tests/e2e/terminal-codex-hidden-startup-background.spec.ts": 17500, + "tests/e2e/terminal-codex-home.spec.ts": 14600, + "tests/e2e/terminal-cold-activation-deferral.spec.ts": 22200, + "tests/e2e/terminal-column-desync-repro.spec.ts": 149400, + "tests/e2e/terminal-context-menu-session-id.spec.ts": 20800, + "tests/e2e/terminal-cursor-inactive-style.spec.ts": 15700, + "tests/e2e/terminal-duplicate-pty-renderer-reveal.spec.ts": 23000, + "tests/e2e/terminal-foreground-redraw-freeze.spec.ts": 14800, + "tests/e2e/terminal-hangul-wrap-boundary-bytes.spec.ts": 107100, + "tests/e2e/terminal-hidden-child-tui-kill-mode-reset.spec.ts": 76200, + "tests/e2e/terminal-hidden-tui-visual-restore.spec.ts": 70600, + "tests/e2e/terminal-hidden-view-parking.spec.ts": 199400, + "tests/e2e/terminal-history-size-typing-latency.spec.ts": 21200, + "tests/e2e/terminal-ime-exact-byte.spec.ts": 60400, + "tests/e2e/terminal-inline-tui-reveal-convergence.spec.ts": 309900, + "tests/e2e/terminal-korean-composing-chord-order.spec.ts": 16400, + "tests/e2e/terminal-korean-endofrow-preedit-cell-span.spec.ts": 29800, + "tests/e2e/terminal-korean-midline-preedit-occlusion.spec.ts": 45100, + "tests/e2e/terminal-korean-preedit-visibility.spec.ts": 62200, + "tests/e2e/terminal-large-paste-responsiveness.spec.ts": 15400, + "tests/e2e/terminal-link-click-ownership.spec.ts": 69100, + "tests/e2e/terminal-link-hover-after-worktree-return.spec.ts": 46600, + "tests/e2e/terminal-long-table-scroll-restore.spec.ts": 74600, + "tests/e2e/terminal-macos-system-key-remap.spec.ts": 97000, + "tests/e2e/terminal-opencode-emoji-table-rendering.spec.ts": 21600, + "tests/e2e/terminal-osc-color-queries.spec.ts": 14900, + "tests/e2e/terminal-osc8-cold-park-restore.spec.ts": 19000, + "tests/e2e/terminal-output-scheduler.spec.ts": 54700, + "tests/e2e/terminal-pane-binding-lifecycle.spec.ts": 46800, + "tests/e2e/terminal-pane-close-layout-consistency.spec.ts": 128900, + "tests/e2e/terminal-pane-content-retention.spec.ts": 45100, + "tests/e2e/terminal-pane-layout-resize.spec.ts": 47500, + "tests/e2e/terminal-pane-split-identity.spec.ts": 81000, + "tests/e2e/terminal-pane-title-editing.spec.ts": 189100, + "tests/e2e/terminal-pane-title-focus-handoff.spec.ts": 158000, + "tests/e2e/terminal-pane-title-strip-placement.spec.ts": 62400, + "tests/e2e/terminal-parked-cli-split.spec.ts": 28900, + "tests/e2e/terminal-parked-close-retirement.spec.ts": 16300, + "tests/e2e/terminal-parked-memory.spec.ts": 80100, + "tests/e2e/terminal-paste-ownership.spec.ts": 66100, + "tests/e2e/terminal-pinned-viewport-streaming-switch.spec.ts": 19400, + "tests/e2e/terminal-pinned-viewport-worktree-switch.spec.ts": 16700, + "tests/e2e/terminal-push-delivery-loss-recovery.spec.ts": 16500, + "tests/e2e/terminal-quick-command-pre-bind-recovery.spec.ts": 42100, + "tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts": 35800, + "tests/e2e/terminal-reattach-mouse-mode-leak.spec.ts": 22800, + "tests/e2e/terminal-reattach-tui-mouse-mode.spec.ts": 44200, + "tests/e2e/terminal-restart-persistence.spec.ts": 109600, + "tests/e2e/terminal-reveal-paused-render-repro.spec.ts": 31100, + "tests/e2e/terminal-reveal-stale-pty-resize-repro.spec.ts": 49000, + "tests/e2e/terminal-scroll-intent-follow.spec.ts": 73200, + "tests/e2e/terminal-send-agent-prompt-submit.spec.ts": 51400, + "tests/e2e/terminal-shortcuts.spec.ts": 91700, + "tests/e2e/terminal-sleep-wake-restore.spec.ts": 16800, + "tests/e2e/terminal-split-pane-paste-ownership.spec.ts": 30700, + "tests/e2e/terminal-streaming-refocus-viewport.spec.ts": 15800, + "tests/e2e/terminal-stuck-occlusion-recovery.spec.ts": 15200, + "tests/e2e/terminal-tab-close-restart-persistence.spec.ts": 22800, + "tests/e2e/terminal-tab-close-running-confirm-mouse.spec.ts": 57100, + "tests/e2e/terminal-tab-close-running-confirm.spec.ts": 27500, + "tests/e2e/terminal-tab-switch-sigwinch-restore.spec.ts": 30400, + "tests/e2e/terminal-tab-switch-visual-restore.spec.ts": 90300, + "tests/e2e/terminal-tui-wheel-drain.spec.ts": 528000, + "tests/e2e/terminal-tui-wheel-reports.spec.ts": 34400, + "tests/e2e/terminal-typing-latency.spec.ts": 15200, + "tests/e2e/terminal-wedged-write-pipeline-recovery.spec.ts": 71900, + "tests/e2e/terminal-window-wake-stale-grid-repro.spec.ts": 15900, + "tests/e2e/update-install-renderer-checkpoint-recovery.spec.ts": 14900, + "tests/e2e/usage-overview.spec.ts": 33800, + "tests/e2e/voice-microphone-selection.spec.ts": 34000, + "tests/e2e/windows-terminal-env-icons.spec.ts": 15400, + "tests/e2e/workspace-back-forward-navigation.spec.ts": 108500, + "tests/e2e/workspace-board-lane-virtualization.spec.ts": 107700, + "tests/e2e/workspace-emoji-picker.spec.ts": 32900, + "tests/e2e/workspace-session-corrupt-tab-salvage.spec.ts": 21100, + "tests/e2e/workspace-space-git-status.spec.ts": 16100.000000000002, + "tests/e2e/worktree-active-delete-scroll-position.spec.ts": 59200, + "tests/e2e/worktree-card-downward-drag.spec.ts": 60000, + "tests/e2e/worktree-delete-shortcut.spec.ts": 20600, + "tests/e2e/worktree-jump-palette-filter.spec.ts": 96200, + "tests/e2e/worktree-lifecycle.spec.ts": 44000, + "tests/e2e/worktree-lineage-agent-expansion.spec.ts": 21800, + "tests/e2e/worktree-lineage.spec.ts": 96700, + "tests/e2e/worktree-recent-sort.spec.ts": 28400, + "tests/e2e/worktree-scroll-to-current.spec.ts": 62400, + "tests/e2e/worktree-smart-sort.spec.ts": 14100, + "tests/e2e/worktree-switch-first-paint.spec.ts": 52200, + "tests/e2e/worktree-switch-responsiveness.spec.ts": 15100, + "tests/e2e/worktree.spec.ts": 118100 + } + } +} diff --git a/config/scripts/ci-shard-timings.md b/config/scripts/ci-shard-timings.md new file mode 100644 index 00000000000..f6e846cfd6d --- /dev/null +++ b/config/scripts/ci-shard-timings.md @@ -0,0 +1,93 @@ +# Timing-based CI shards + +The eight unit shards and fourteen general E2E shards use longest-processing-time +assignment of whole files to the currently lightest shard. Ties use file path and +then shard index, independent of filesystem enumeration and locale. Unknown, +zero, or invalid durations use the baseline's positive median (1 second when no +positive evidence exists). Deleted files never enter discovery. Unit weights add +526ms per file for measured transform/setup/import/environment overhead. + +Unit assignment runs inside Vitest's sequencer after discovery and CLI exclusions; +Vitest's default sort, workers and isolation remain intact. It is enabled only by +`ORCA_BALANCE_UNIT_SHARDS=1`; ordinary local runs and explicit file filters retain +their existing behavior. E2E uses Playwright's native `--list` and `--test-list`, +retaining project filters, skipped tests and complete serial groups within files. +The workflow verifies selected test IDs against full discovery before executing. +Dedicated SSH, native IME, WSL and first-paint lanes are unchanged. + +## Evidence and limits + +`ci-shard-timings.json` records run IDs and every contributing job ID: + +- Unit run **34675583768**, Node 24, all eight successful shards: 8,484 completed + file durations. The summed transform/setup/import/environment durations divided + by measured file count give a rounded-up **526ms** per-file overhead allowance. + The original shard weighted loads were **764–849 worker-seconds**, versus + **792–792** after balancing the identical measured files. File counts change + from **1,056–1,065** to **1,060–1,061**. +- General E2E run **34652504501**, all fourteen shard logs: 291 files with completed + headless test durations, including failures. Headful benchmark reruns are not + counted. Original completed test loads were **540–1,727 seconds**, versus + **1,083–1,093** after whole-file balancing on the same measured files. The longest + measured file is **528 seconds**, below the balanced shard load. +- Current checkout discovery at validation contained **8,553 unit files** after the + workflow's exact exclusions and **733 headless E2E tests in 340 files**. New and + unmeasured files remain selected. Projected current loads were about **797 + worker-seconds** per unit shard (1,068–1,070 files) and **1,190–1,200 seconds** per + E2E shard (22–25 files). + +These are scheduling projections, not measured post-change wall-clock gains. +Unit durations overlap across workers and the overhead allowance is an average, +not a per-file import profile. E2E evidence includes failed shards and can omit +unfinished tests; unknowns receive a deterministic estimate. Historical timings +age as specs change. Full CI runs on the existing runner classes are required to +measure elapsed-time and occupancy improvements, including discovery overhead. +No retries, assertions, coverage exclusions, runner classes or shard counts changed. + +## Reproduction and refresh + +Every shard uploads an artifact named with its shard, Node version where relevant, +and run attempt. `assignment.json` contains the checked-out source SHA, run ID, +attempt, baseline SHA-256, algorithm, fallback, all shard files and chosen shard. +E2E also retains both discovery reports and `selected.txt`. Artifacts live for +14 days. A rerun of the same source uses the same checked-in baseline rather than +mutable timing caches; a GitHub job rerun therefore keeps its assignment. + +For E2E reproduction, check out the recorded source and pass the saved list to the +existing command: `pnpm run test:e2e --test-list=/path/to/selected.txt` with the same +CI environment/build inputs. For unit reproduction, use the unchanged workflow +command and exclusions with `ORCA_BALANCE_UNIT_SHARDS=1` and the recorded +`--shard=INDEX/8`. Direct test-file reruns remain supported. + +To refresh the baseline, download `log-JOB_ID.txt` files into one directory from +exactly one eight-shard unit run and one fourteen-shard general E2E run. Use the +job IDs from the Actions jobs API and fetch each with +`gh api repos/stablyai/orca/actions/jobs/JOB_ID/logs`. Do not include dedicated +lanes or multiple attempts. Then run: + +```sh +node config/scripts/ci-shard-timing-import.mjs LOG_DIRECTORY UNIT_RUN_ID E2E_RUN_ID config/scripts/ci-shard-timings.json +``` + +The initial source logs are in `/tmp/orca-ci-shard-logs`; two were reused from +`/tmp/orca-ci-audit`, and the remaining twenty were fetched read-only. Reimporting +those logs reproduced the checked-in JSON byte-for-byte. Review file-count and +load projections before adopting a new baseline; no network access is needed to +plan or run shards. + +## Validation + +- 74 focused tests passed across the two new test files and existing PR + parallelism, E2E gate and release E2E dispatch contracts. +- The pinned Playwright CLI selected the real 733-test suite across all fourteen + saved test lists with exact-once identity coverage and no missing tests. +- A temporary native Playwright fixture checks fourteen shards, serial groups, + skipped cases, headful filtering and mismatch rejection without launching UI. +- Real Vitest discovery with all workflow exclusions yielded 8,553 files; the + sequencer's eight assignments covered each exactly once. An actual opt-in + Vitest shard executed successfully and persisted its manifest. +- Focused TypeScript checking of `config/vitest.config.ts` and imported modules, + oxlint, formatting and baseline reimport checks passed. + +All local tests used `ORCA_BACKGROUND_LAUNCH=1` in background tool sessions. No app +windows or full E2E test bodies were launched. diff --git a/config/scripts/ci-unit-sequencer.mjs b/config/scripts/ci-unit-sequencer.mjs new file mode 100644 index 00000000000..cd55b3343b2 --- /dev/null +++ b/config/scripts/ci-unit-sequencer.mjs @@ -0,0 +1,19 @@ +import { relative } from 'node:path' +import { BaseSequencer } from 'vitest/node' +import { balanceFiles, readTimingBaseline, writeAssignment } from './ci-shard-assignment.mjs' + +export default class TimingSequencer extends BaseSequencer { + async shard(specs) { + const { index, count } = this.ctx.config.shard + const key = (spec) => relative(this.ctx.config.root, spec.moduleId).replaceAll('\\', '/') + const baseline = readTimingBaseline('unit') + const assignment = balanceFiles(specs.map(key), count, baseline.timings, baseline.overheadMs) + writeAssignment(process.env.ORCA_SHARD_MANIFEST ?? 'ci-shards/unit-assignment.json', { + ...assignment, + baselineSha256: baseline.baselineSha256, + selectedShard: index + }) + const selected = new Set(assignment.shards[index - 1].files) + return specs.filter((spec) => selected.has(key(spec))) + } +} diff --git a/config/scripts/computer-e2e-workflow.test.mjs b/config/scripts/computer-e2e-workflow.test.mjs index 97b6adeb694..fc605bac5bd 100644 --- a/config/scripts/computer-e2e-workflow.test.mjs +++ b/config/scripts/computer-e2e-workflow.test.mjs @@ -178,10 +178,15 @@ describe('computer-use e2e workflow', () => { 'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs' ) expect(runs).toContain('pnpm verify:computer-native') + expect( + runs.find((run) => run.includes('config/scripts/build-native-for-platform.test.mjs')) + ).toContain('--config config/vitest.config.ts') expect(runs.join('\n')).not.toContain('test:e2e:computer') expect(workflow.jobs.mac).toBeUndefined() expect(workflow.on.pull_request.paths).toEqual( expect.arrayContaining([ + 'config/scripts/build-native-for-platform.mjs', + 'config/scripts/build-native-for-platform.test.mjs', 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs', 'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs', 'config/scripts/macos-computer-helper-owner-loss-metrics.mjs', diff --git a/config/scripts/electron-builder-config.test.mjs b/config/scripts/electron-builder-config.test.mjs index 3f055ce222d..b7d5f16400b 100644 --- a/config/scripts/electron-builder-config.test.mjs +++ b/config/scripts/electron-builder-config.test.mjs @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs' import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { createRequire } from 'node:module' import { tmpdir } from 'node:os' @@ -433,3 +434,49 @@ describe('electron-builder config', () => { }) }) }) + +describe('arch-aware packaging guard', () => { + // electron-builder Arch enum: ia32=0, x64=1, armv7l=2, arm64=3. + const HOST_ARCH = process.arch === 'arm64' ? 3 : 1 + const OTHER_ARCH = process.arch === 'arm64' ? 1 : 3 + const OTHER_ARCH_NAME = process.arch === 'arm64' ? 'x64' : 'arm64' + const SHERPA_PLATFORM = process.platform === 'win32' ? 'win' : process.platform + const otherSherpa = `sherpa-onnx-${SHERPA_PLATFORM}-${OTHER_ARCH_NAME}` + const packHost = (arch) => + electronBuilderConfig.beforePack({ electronPlatformName: process.platform, arch }) + + it('allows packaging the host platform and architecture', () => { + expect(() => packHost(HOST_ARCH)).not.toThrow() + }) + + it('requires the other architecture natives to be installed', () => { + const otherSherpaInstalled = existsSync( + join(REPO_ROOT, 'node_modules', otherSherpa, 'package.json') + ) + const otherSherpaExpected = Object.hasOwn( + require('../../package.json').optionalDependencies, + otherSherpa + ) + if (otherSherpaExpected && !otherSherpaInstalled) { + expect(() => packHost(OTHER_ARCH)).toThrow(otherSherpa) + expect(() => packHost(OTHER_ARCH)).toThrow('pnpm install:release') + expect(() => packHost(HOST_ARCH)).not.toThrow() + } else { + expect(() => packHost(OTHER_ARCH)).not.toThrow() + } + }) + + it('requires installed Windows addons for Windows packaging', () => { + const windowsAddon = electronBuilderConfig.win.extraResources.some( + (resource) => resource.to === join('node_modules', '@vscode', 'windows-process-tree') + ) + const packWindows = () => + electronBuilderConfig.beforePack({ electronPlatformName: 'win32', arch: 1 }) + if (process.platform === 'win32' || windowsAddon) { + expect(packWindows).not.toThrow() + } else { + expect(packWindows).toThrow('@vscode/windows-process-tree') + expect(packWindows).toThrow('Windows packaging requires a Windows host') + } + }) +}) diff --git a/config/scripts/electron-builder-runtime-resources.test.mjs b/config/scripts/electron-builder-runtime-resources.test.mjs index 45145572b80..6eb34aff896 100644 --- a/config/scripts/electron-builder-runtime-resources.test.mjs +++ b/config/scripts/electron-builder-runtime-resources.test.mjs @@ -1,9 +1,10 @@ -import { readFileSync, readdirSync } from 'node:fs' -import { cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises' +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { cp, mkdir, mkdtemp, readFile, readdir, stat, writeFile } from 'node:fs/promises' import { createRequire } from 'node:module' import { tmpdir } from 'node:os' import { delimiter, dirname, join, relative, resolve } from 'node:path' import { describe, expect, it } from 'vitest' +import { removeTree } from '../../src/shared/windows-transient-lock-removal.ts' const require = createRequire(import.meta.url) const projectRoot = resolve(import.meta.dirname, '..', '..') @@ -21,6 +22,13 @@ const { verifyPackagedMainRuntimeDeps } = require('../packaged-runtime-node-modules.cjs') +// Why this and not process.platform: @vscode/windows-process-tree is the only os: win32 npm +// addon left, so its presence is what decides whether the win32 plan resolves. +// @orca/windows-registry is a workspace link present on every host, so it proves nothing. +const windowsAddonsInstalled = existsSync( + join(projectRoot, 'node_modules', '@vscode', 'windows-process-tree', 'package.json') +) + describe('packaged runtime resources', () => { it('verifies packaged main runtime deps from Windows-style asar entries', async () => { const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-runtime-deps-')) @@ -40,7 +48,7 @@ describe('packaged runtime resources', () => { expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).not.toThrow() } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -75,7 +83,7 @@ describe('packaged runtime resources', () => { }) expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).not.toThrow() } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -93,7 +101,7 @@ describe('packaged runtime resources', () => { /managed-agent-hook-controls\.js was not found/ ) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -127,7 +135,7 @@ describe('packaged runtime resources', () => { await mkdir(join(resourcesDir, 'node_modules', 'jsonc-parser'), { recursive: true }) expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).not.toThrow() } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -147,7 +155,7 @@ describe('packaged runtime resources', () => { expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).toThrow(/jsonc-parser/) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -169,7 +177,7 @@ describe('packaged runtime resources', () => { expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).not.toThrow() } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -207,7 +215,7 @@ describe('packaged runtime resources', () => { 'Unsupported packaged runtime architecture: 4' ) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -243,7 +251,7 @@ describe('packaged runtime resources', () => { `console payload ${arch}` ) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } } }) @@ -264,7 +272,9 @@ describe('packaged runtime resources', () => { }) it('includes the Claude agent SDK in every desktop package plan', () => { - for (const platform of ['darwin', 'linux', 'win32']) { + for (const platform of windowsAddonsInstalled + ? ['darwin', 'linux', 'win32'] + : ['darwin', 'linux']) { const packagedTargets = createPackagedRuntimeNodeModuleResources(platform).map( (resource) => resource.to ) @@ -293,7 +303,7 @@ describe('packaged runtime resources', () => { 'Unsupported packaged runtime architecture: universal' ) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -315,7 +325,7 @@ describe('packaged runtime resources', () => { 'watcher-linux-x64-glibc' ]) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -333,7 +343,7 @@ describe('packaged runtime resources', () => { await expect(readdir(join(packageDir, 'dist'))).resolves.toEqual(['index.cjs']) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -353,7 +363,7 @@ describe('packaged runtime resources', () => { 'sherpa-onnx.node' ]) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -369,7 +379,7 @@ describe('packaged runtime resources', () => { await expect(readdir(packageDir)).resolves.toEqual(['index.cjs']) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) @@ -383,7 +393,7 @@ describe('packaged runtime resources', () => { }) ).rejects.toThrow(/Missing packaged resources directory/) } finally { - await rm(root, { recursive: true, force: true }) + await removeTree(root) } }) @@ -448,7 +458,7 @@ describe('packaged runtime resources', () => { await expect(stat(wrongArchPackage)).rejects.toMatchObject({ code: 'ENOENT' }) } finally { process.env.PATH = previousPath - await rm(root, { recursive: true, force: true }) + await removeTree(root) } } ) @@ -509,7 +519,7 @@ describe('packaged runtime resources', () => { ).resolves.toContain('"version": "9.9.9"') await expect(readFile(join(resourcesDir, 'package-type'), 'utf8')).resolves.toBe('AppImage') } finally { - await rm(root, { recursive: true, force: true }) + await removeTree(root) } } ) @@ -564,11 +574,13 @@ describe('lazily required packages reach Resources/node_modules', () => { const covered = (platform) => destinations[platform].has(`node_modules/${packageName}`) || destinations[platform].has(`node_modules/${specifier}`) - // Windows carries the full closure, so an uncovered specifier is uncovered everywhere. - expect( - covered('win'), - `${source} lazily requires '${specifier}', but nothing copies it to Resources/node_modules` - ).toBe(true) + // The Windows CI lane checks the full closure with its native addons installed. + if (windowsAddonsInstalled) { + expect( + covered('win'), + `${source} lazily requires '${specifier}', but nothing copies it to Resources/node_modules` + ).toBe(true) + } if (covered('mac') && covered('linux')) { continue } @@ -598,7 +610,7 @@ describe('lazily required packages reach Resources/node_modules', () => { const dataset = require(probe)('emojibase-data/en/shortcodes/emojibase.json') expect(Object.keys(dataset).length).toBeGreaterThan(1000) } finally { - await rm(resourcesDir, { recursive: true, force: true }) + await removeTree(resourcesDir) } }) }) diff --git a/config/scripts/electron-vite-output-contract.test.ts b/config/scripts/electron-vite-output-contract.test.ts index a7ac04a1a09..285b58f2ac7 100644 --- a/config/scripts/electron-vite-output-contract.test.ts +++ b/config/scripts/electron-vite-output-contract.test.ts @@ -116,9 +116,9 @@ describe('Electron Vite output contract', () => { expect(external('node:fs', undefined, false)).toBe(true) expect(external('@xterm/headless', undefined, false)).toBe(false) expect(external('@xterm/addon-serialize', undefined, false)).toBe(false) - expect(external('psl', undefined, false)).toBe(false) + expect(external('tldts', undefined, false)).toBe(false) expect(external('zod', undefined, false)).toBe(false) - expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('psl') + expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('tldts') expect(electronViteConfig.main?.build?.externalizeDeps?.exclude).toContain('zod') }) diff --git a/config/scripts/ensure-native-runtime.mjs b/config/scripts/ensure-native-runtime.mjs index 10e8426c2a5..6278a1e0b41 100644 --- a/config/scripts/ensure-native-runtime.mjs +++ b/config/scripts/ensure-native-runtime.mjs @@ -22,7 +22,7 @@ const runtime = readRuntimeArg() const NATIVE_MODULES = [ 'node-pty', ...(process.platform === 'win32' - ? ['windows-native-registry', '@vscode/windows-process-tree'] + ? ['@orca/windows-registry', '@vscode/windows-process-tree'] : []) ] const NODE_PTY_CONPTY_RUNTIME_FILES = ['conpty.dll', 'OpenConsole.exe'] @@ -275,7 +275,7 @@ function loadNativeModule(moduleName) { } return } - if (moduleName === 'windows-native-registry') { + if (moduleName === '@orca/windows-registry') { const registry = require(moduleName) // Why: the package defers loading its .node addon until the first registry call. registry.getRegistryKey(registry.HK.CU, 'Environment') diff --git a/config/scripts/ensure-native-runtime.test.mjs b/config/scripts/ensure-native-runtime.test.mjs index 1e7d888d2e2..47fe435edcc 100644 --- a/config/scripts/ensure-native-runtime.test.mjs +++ b/config/scripts/ensure-native-runtime.test.mjs @@ -87,7 +87,7 @@ describe('ensure-native-runtime', () => { const log = readFileSync(logPath, 'utf8') expect(log.match(/pnpm exec node-gyp rebuild\n/g)).toHaveLength(2) expect(log).toContain(join('node_modules', 'node-pty')) - expect(log).toContain(join('node_modules', 'windows-native-registry')) + expect(log).toContain(join('node_modules', '@orca', 'windows-registry')) } finally { rmSync(projectDir, { recursive: true, force: true }) } @@ -299,11 +299,11 @@ function writeFakeWindowsRegistry(projectDir, { requiresMarker = false } = {}) { if (process.platform !== 'win32') { return } - const registryDir = join(projectDir, 'node_modules', 'windows-native-registry') + const registryDir = join(projectDir, 'node_modules', '@orca', 'windows-registry') mkdirSync(registryDir, { recursive: true }) writeFileSync( join(registryDir, 'package.json'), - '{"name":"windows-native-registry","version":"3.2.2","main":"index.js"}\n' + '{"name":"@orca/windows-registry","version":"1.0.0","main":"index.js"}\n' ) const markerGate = requiresMarker ? `if (!require('node:fs').existsSync(process.env.ORCA_NATIVE_TEST_MARKER)) { throw new Error('registry ABI mismatch sentinel') }` diff --git a/config/scripts/generate-rpc-params-catalog.mjs b/config/scripts/generate-rpc-params-catalog.mjs index e42ea39beb1..9810f952e77 100644 --- a/config/scripts/generate-rpc-params-catalog.mjs +++ b/config/scripts/generate-rpc-params-catalog.mjs @@ -51,7 +51,14 @@ function indexableModules() { const source = readFileSync(path.join(RPC_DIR, file), 'utf8') for (const [, specifier] of source.matchAll(/from\s+'(\.[^']+)'/g)) { const resolved = `${path.resolve(path.dirname(path.join(RPC_DIR, file)), specifier)}.ts` - if (resolved.startsWith(`${SHARED_DIR}${path.sep}`) && existsSync(resolved)) { + // Never re-add the generator's own output: a module under RPC_DIR may import the + // catalog for a type-only contract, and bundling a stale catalog makes regeneration + // crash in exactly the state that requires regenerating. + if ( + resolved !== OUTPUT_PATH && + resolved.startsWith(`${SHARED_DIR}${path.sep}`) && + existsSync(resolved) + ) { modules.add(resolved) } } diff --git a/config/scripts/generate-skill-bundle-manifest.mjs b/config/scripts/generate-skill-bundle-manifest.mjs index 226ea74c4fb..a84923e762d 100644 --- a/config/scripts/generate-skill-bundle-manifest.mjs +++ b/config/scripts/generate-skill-bundle-manifest.mjs @@ -6,8 +6,7 @@ import path from 'node:path' import process from 'node:process' import { isDeepStrictEqual } from 'node:util' -// Why: the three artifacts version independently — bumping one shape must not -// rewrite the others or bypass the registry's schema-gated append-only guard. +// Version artifacts independently to preserve the registry's schema-gated append-only guard. const CURRENT_MANIFEST_SCHEMA_VERSION = 2 const SNAPSHOT_REGISTRY_SCHEMA_VERSION = 1 const RELEASE_MAPPING_SCHEMA_VERSION = 1 @@ -41,18 +40,19 @@ function normalizeText(bytes) { return Buffer.from(text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'), 'utf8') } -function classifyFile(bytes) { +function normalizedTextOrNull(bytes) { if (bytes.includes(0)) { - return 'binary' + return null } try { - normalizeText(bytes) - return 'text' + return normalizeText(bytes) } catch { - return 'binary' + return null } } +const classifyFile = (bytes) => (normalizedTextOrNull(bytes) === null ? 'binary' : 'text') + function assertSafeRelativePath(relativePath) { if ( path.isAbsolute(relativePath) || @@ -64,17 +64,17 @@ function assertSafeRelativePath(relativePath) { } function describeFile(manifestPath, bytes, executable) { - const classification = classifyFile(bytes) + const normalized = normalizedTextOrNull(bytes) const exactSha256 = sha256(bytes) - const textNormalizedSha256 = classification === 'text' ? sha256(normalizeText(bytes)) : null + const textNormalizedSha256 = normalized === null ? null : sha256(normalized) return { path: manifestPath, size: bytes.length, executable, - classification, + classification: normalized === null ? 'binary' : 'text', exactSha256, textNormalizedSha256, - identitySha256: classification === 'text' && !executable ? textNormalizedSha256 : exactSha256, + identitySha256: normalized !== null && !executable ? textNormalizedSha256 : exactSha256, gitBlobSha: gitObjectSha('blob', bytes).toString('hex') } } diff --git a/config/scripts/headless-serve-shutdown-matrix.test.mjs b/config/scripts/headless-serve-shutdown-matrix.test.mjs new file mode 100644 index 00000000000..7231cc21e4f --- /dev/null +++ b/config/scripts/headless-serve-shutdown-matrix.test.mjs @@ -0,0 +1,140 @@ +import { createHash } from 'node:crypto' +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const { spawnSync } = vi.hoisted(() => ({ spawnSync: vi.fn() })) +vi.mock('node:child_process', () => ({ spawnSync })) + +let directory +let artifact +let originalArgv +let originalExitCode +const commands = () => spawnSync.mock.calls.map(([, args]) => args) +const signalRuns = () => commands().filter((args) => ['INT', 'TERM'].includes(args.at(-1))) +const succeeded = { status: 0, stdout: '', stderr: '' } + +async function run(...options) { + process.argv = ['node', 'runner', '--appimage', artifact, ...options] + await import('./run-headless-serve-shutdown-docker.mjs') +} + +beforeEach(() => { + vi.resetModules() + spawnSync.mockReset().mockReturnValue(succeeded) + vi.spyOn(console, 'log').mockImplementation(() => {}) + vi.spyOn(console, 'error').mockImplementation(() => {}) + directory = mkdtempSync(join(tmpdir(), 'orca-shutdown-matrix-')) + artifact = join(directory, 'original.AppImage') + writeFileSync(artifact, 'original package bytes') + originalArgv = process.argv + originalExitCode = process.exitCode +}) + +afterEach(() => { + process.argv = originalArgv + process.exitCode = originalExitCode + rmSync(directory, { recursive: true, force: true }) + vi.restoreAllMocks() +}) + +describe('packaged shutdown matrix', () => { + it('shares extraction but isolates every entrypoint and signal', async () => { + await run('--all-entrypoints') + expect(commands().filter((args) => args[0] === 'build')).toHaveLength(1) + const startup = commands().filter((args) => + args.includes('/usr/local/bin/run-appimage-desktop-startup-case') + ) + const extraction = commands().filter((args) => + args.some((arg) => arg.includes('120s /input/orca.AppImage --appimage-extract')) + ) + expect(startup).toHaveLength(1) + expect(extraction).toHaveLength(1) + expect(commands().indexOf(startup[0])).toBeLessThan(commands().indexOf(extraction[0])) + expect(signalRuns()).toHaveLength(6) + const names = new Set() + for (const [index, args] of signalRuns().entries()) { + const entrypoint = ['app', 'launcher', 'appimage'][Math.floor(index / 2)] + expect(args).toContain(`ORCA_TEST_ENTRYPOINT=${entrypoint}`) + expect(args).toContain( + `ORCA_SIGNAL_TARGET=${entrypoint === 'appimage' ? 'serving-electron' : 'app'}` + ) + expect(args).toContain( + `ORCA_INT_DELIVERY=${entrypoint === 'appimage' ? 'pid' : 'foreground-process-group'}` + ) + expect(args.at(-1)).toBe(index % 2 === 0 ? 'INT' : 'TERM') + expect(args).toContain(`${artifact}:/input/orca.AppImage:ro`) + expect(args.some((arg) => arg.endsWith(':/artifacts:ro'))).toBe(true) + expect(args).toContain('--rm') + names.add(args[args.indexOf('--name') + 1]) + } + expect(names.size).toBe(6) + const evidence = console.log.mock.calls + .map(([line]) => line) + .filter((line) => line.startsWith('{')) + .map(JSON.parse) + expect(evidence).toHaveLength(3) + expect( + evidence.every( + (entry) => + entry.sha256 === createHash('sha256').update('original package bytes').digest('hex') + ) + ).toBe(true) + expect( + commands() + .slice(-2) + .map((args) => args.slice(0, 2)) + ).toEqual([ + ['volume', 'rm'], + ['image', 'rm'] + ]) + }) + + it('attributes failures and still attempts later cases before cleanup', async () => { + spawnSync.mockImplementation((_, args) => + args.at(-1) === 'INT' ? { ...succeeded, status: 7 } : succeeded + ) + await expect(run('--all-entrypoints')).rejects.toThrow( + 'app:INT:7, launcher:INT:7, appimage:INT:7' + ) + expect(signalRuns()).toHaveLength(6) + expect(commands().at(-2).slice(0, 2)).toEqual(['volume', 'rm']) + }) + + it('cleans setup resources without running cases after failed extraction', async () => { + spawnSync.mockImplementation((_, args) => + args.some((arg) => arg.includes('120s /input/orca.AppImage --appimage-extract')) + ? { ...succeeded, status: 9 } + : succeeded + ) + await expect(run('--all-entrypoints')).rejects.toThrow('docker run failed') + expect(signalRuns()).toHaveLength(0) + expect( + commands() + .slice(-2) + .map((args) => args.slice(0, 2)) + ).toEqual([ + ['volume', 'rm'], + ['image', 'rm'] + ]) + }) + + it('preserves individual launcher overlay invocations', async () => { + await run('--entrypoint', 'launcher', '--launcher-exec-overlay') + expect(signalRuns()).toHaveLength(2) + expect(signalRuns().every((args) => args.includes('ORCA_TEST_ENTRYPOINT=launcher'))).toBe(true) + expect( + commands().some((args) => + args.some((arg) => arg.includes("sed -i 's/^ELECTRON_RUN_AS_NODE=1")) + ) + ).toBe(true) + }) + + it('rejects ambiguous matrix overrides before invoking Docker', async () => { + await expect(run('--all-entrypoints', '--entrypoint', 'launcher')).rejects.toThrow( + 'cannot be combined' + ) + expect(spawnSync).not.toHaveBeenCalled() + }) +}) diff --git a/config/scripts/headless-serve-shutdown-workflow.test.mjs b/config/scripts/headless-serve-shutdown-workflow.test.mjs index 90a3f73c77d..3b71a2e774e 100644 --- a/config/scripts/headless-serve-shutdown-workflow.test.mjs +++ b/config/scripts/headless-serve-shutdown-workflow.test.mjs @@ -56,12 +56,6 @@ describe('headless serve shutdown PR gate', () => { const packageStep = steps.find((step) => step.name === 'Package unpacked app') const markerStep = steps.find((step) => step.name === 'Verify root-package marker payloads') const shutdownStep = steps.find((step) => step.name === 'Verify headless serve signal shutdown') - const launcherShutdownStep = steps.find( - (step) => step.name === 'Verify extracted launcher serve signal shutdown' - ) - const appImageShutdownStep = steps.find( - (step) => step.name === 'Verify AppImage CLI registration and serve signal shutdown' - ) expect(workflow.jobs.package['timeout-minutes']).toBe(90) expect(packageStep.run).toContain('--linux AppImage deb rpm --x64 --publish never') @@ -69,19 +63,13 @@ describe('headless serve shutdown PR gate', () => { expect(markerStep.run).toContain('rpm2cpio') expect(steps.indexOf(markerStep)).toBeGreaterThan(steps.indexOf(packageStep)) expect(shutdownStep.run).toBe( - 'node config/scripts/run-headless-serve-shutdown-docker.mjs --appimage dist/orca-linux.AppImage' + 'node config/scripts/run-headless-serve-shutdown-docker.mjs --appimage dist/orca-linux.AppImage --all-entrypoints' ) - expect(launcherShutdownStep.run).toContain( - 'node config/scripts/run-headless-serve-shutdown-docker.mjs' - ) - expect(launcherShutdownStep.run).toContain('--entrypoint launcher') - expect(appImageShutdownStep.run).toContain('--entrypoint appimage') - expect(appImageShutdownStep.run).toContain('--signal-target serving-electron') - expect(appImageShutdownStep.run).toContain('--int-delivery pid') expect(steps.indexOf(shutdownStep)).toBeGreaterThan(steps.indexOf(packageStep)) expect(steps.indexOf(shutdownStep)).toBeGreaterThan(steps.indexOf(markerStep)) - expect(steps.indexOf(launcherShutdownStep)).toBeGreaterThan(steps.indexOf(shutdownStep)) - expect(steps.indexOf(appImageShutdownStep)).toBeGreaterThan(steps.indexOf(launcherShutdownStep)) + expect( + steps.filter((step) => step.run?.includes('run-headless-serve-shutdown-docker.mjs')) + ).toHaveLength(1) }) it('keeps readiness polling finite and leak-free', () => { diff --git a/config/scripts/hermes-run-correlation-benchmark.mjs b/config/scripts/hermes-run-correlation-benchmark.mjs new file mode 100644 index 00000000000..364774577cf --- /dev/null +++ b/config/scripts/hermes-run-correlation-benchmark.mjs @@ -0,0 +1,136 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict' +import { readFile, mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join, basename } from 'node:path' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' + +const root = fileURLToPath(new URL('../..', import.meta.url)) +const fixture = await mkdtemp(join(tmpdir(), 'orca-hermes-correlation-')) +const baselineDirectory = process.argv[2] +const key = (seconds) => + new Date(Date.UTC(2026, 0, 1) + seconds * 1000) + .toISOString() + .replace(/[-:]/g, '') + .replace('T', '_') + .slice(0, 15) +try { + for (const host of ['native', 'relay']) { + const entry = + host === 'native' + ? 'src/main/automations/hermes-cron-run-content.ts' + : 'src/relay/hermes-run-correlation.ts' + const readers = [] + for (const mode of baselineDirectory ? ['baseline', 'current'] : ['current']) { + const bundle = join(fixture, `${host}-${mode}.cjs`) + await build({ + entryPoints: [join(root, entry)], + bundle: true, + platform: 'node', + format: 'cjs', + outfile: bundle, + plugins: + mode === 'baseline' + ? [ + { + name: 'baseline-correlation', + setup(builder) { + builder.onLoad( + { filter: /hermes-(cron-run-content|run-correlation)\.ts$/ }, + async (args) => ({ + contents: await readFile( + join(baselineDirectory, basename(args.path)), + 'utf8' + ), + loader: 'ts' + }) + ) + } + } + ] + : [] + }) + readers.push({ mode, ...createRequire(import.meta.url)(bundle) }) + } + if (readers.length === 2) { + let seed = 92817 + const random = (max) => { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return seed % max + } + const pool = [ + null, + '', + 'invalid', + '20260101_000000', + '20260101_000200', + '20260102_000000', + '20260103_000000', + '20260101_240000' + ] + for (let trial = 0; trial < 200; trial++) { + const sessions = Array.from({ length: random(70) }, (_, i) => ({ + kind: 'session', + id: `session-${i}`, + job_id: 'job', + run_at: null, + run_key: pool[random(pool.length)], + output_content: `session ${i}` + })) + const outputs = Array.from({ length: random(70) }, (_, i) => ({ + kind: 'output', + id: `output-${i}`, + job_id: 'job', + run_at: null, + run_key: pool[random(pool.length)], + output_path: 'unused', + output_content: `output ${i}` + })) + for (const method of [ + 'mergeHermesOutputAndSessionRunRefs', + 'mergeHermesOutputAndSessionRuns' + ]) { + assert.deepEqual( + readers[1][method](outputs, sessions), + readers[0][method](outputs, sessions) + ) + } + } + console.log(JSON.stringify({ host, randomizedParityCases: 400 })) + } + for (const runs of [100, 1000, 5000]) { + const sessions = Array.from({ length: runs }, (_, i) => ({ + kind: 'session', + id: `session-${i}`, + job_id: 'job', + run_at: null, + run_key: key(i * 3600) + })).toReversed() + const outputs = Array.from({ length: runs }, (_, i) => ({ + kind: 'output', + id: `output-${i}`, + job_id: 'job', + run_at: null, + run_key: key(i * 3600 + 120), + output_path: 'unused' + })) + let expected + for (const reader of [...readers, ...readers.toReversed()]) { + const start = performance.now() + const result = reader.mergeHermesOutputAndSessionRunRefs(outputs, sessions) + const durationMs = performance.now() - start + assert.equal(result.length, runs) + result.forEach((row, i) => assert.equal(row.session.id, `session-${i}`)) + if (expected) { + assert.deepEqual(result, expected) + } + expected = result + console.log(JSON.stringify({ host, mode: reader.mode, runs, durationMs })) + } + } + } +} finally { + await rm(fixture, { recursive: true, force: true }) +} diff --git a/config/scripts/journal-replay-retention-benchmark.mjs b/config/scripts/journal-replay-retention-benchmark.mjs new file mode 100644 index 00000000000..3991a886ab5 --- /dev/null +++ b/config/scripts/journal-replay-retention-benchmark.mjs @@ -0,0 +1,122 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict' +import { mkdtemp, readFile, rm, stat } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { basename, dirname, join } from 'node:path' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' + +// Pass a directory containing journal-open.ts and journal-row-table.ts from the base commit. +const baselineDir = process.argv[2] +assert.ok( + baselineDir, + 'Usage: node --expose-gc journal-replay-retention-benchmark.mjs BASELINE_DIR' +) +assert.ok(global.gc, 'Run with --expose-gc to measure live backing memory during replay') +const root = fileURLToPath(new URL('../..', import.meta.url)) +const fixture = await mkdtemp(join(tmpdir(), 'orca-journal-replay-bench-')) +try { + const implementations = {} + for (const arm of ['baseline', 'current']) { + const outfile = join(fixture, `${arm}.cjs`) + await build({ + stdin: { + contents: + "export {openAgentSessionJournal} from './src/main/native-chat/agent-session-journal/journal-store-factory'; export {loadJournal} from './src/main/native-chat/agent-session-journal/journal-open'; export {journalDatabaseFile} from './src/main/native-chat/agent-session-journal/journal-paths';", + resolveDir: root + }, + bundle: true, + platform: 'node', + format: 'cjs', + outfile, + plugins: [ + { + name: 'replay-memory-probe', + setup(plugin) { + plugin.onLoad( + { filter: /journal-(?:open|row-table|reducer)\.ts$/ }, + async ({ path }) => { + const leaf = basename(path) + let source = await readFile( + arm === 'baseline' && leaf !== 'journal-reducer.ts' + ? join(baselineDir, leaf) + : path, + 'utf8' + ) + if (leaf === 'journal-reducer.ts') { + const marker = + 'export function applyJournalRow(state: JournalReducerState, row: JournalRow): void {' + assert.ok(source.includes(marker)) + source = source.replace( + marker, + `${marker}\nglobalThis.__replayMemoryProbe?.(row.seq);` + ) + } + return { contents: source, loader: 'ts', resolveDir: dirname(path) } + } + ) + } + } + ] + }) + implementations[arm] = createRequire(import.meta.url)(outfile) + } + const identity = { + sessionId: 'benchmark', + workspaceId: 'fixture', + hostId: 'local', + agent: 'codex', + providerHandle: { kind: 'codex', threadId: 'thread' } + } + const journalDir = join(fixture, 'session') + const journal = await implementations.current.openAgentSessionJournal({ identity, journalDir }) + const item = { provider: 'codex', threadId: 'thread', turnId: 'turn', ordinal: 0 } + const text = 'x'.repeat(32768) + for (let revision = 0; revision < 2000; revision++) { + await journal.appendItem( + item, + { + kind: 'message', + role: 'assistant', + blocks: [{ type: 'text', text: `${text}${revision}` }] + }, + { fence: 1 } + ) + } + await journal.close() + for (const arm of ['baseline', 'current', 'current', 'baseline']) { + global.gc() + const start = performance.now() + let loaded = implementations[arm].loadJournal(journalDir, identity.sessionId) + const ms = performance.now() - start + assert.equal(loaded.state.items.size, 1) + assert.equal([...loaded.state.items.values()][0].revision, 2000) + loaded = null + global.gc() + const initialHeap = process.memoryUsage().heapUsed + let peakLiveHeap = initialHeap + globalThis.__replayMemoryProbe = (sequence) => { + if (sequence !== 1 && sequence % 256 !== 0) { + return + } + global.gc() + peakLiveHeap = Math.max(peakLiveHeap, process.memoryUsage().heapUsed) + } + loaded = implementations[arm].loadJournal(journalDir, identity.sessionId) + delete globalThis.__replayMemoryProbe + assert.equal(loaded.state.items.size, 1) + loaded = null + console.log( + JSON.stringify({ + arm, + ms, + databaseBytes: (await stat(implementations[arm].journalDatabaseFile(journalDir))).size, + peakLiveHeapDelta: peakLiveHeap - initialHeap + }) + ) + } +} finally { + delete globalThis.__replayMemoryProbe + await rm(fixture, { recursive: true, force: true }) +} diff --git a/config/scripts/legacy-alt-screen-scan-benchmark.mjs b/config/scripts/legacy-alt-screen-scan-benchmark.mjs new file mode 100644 index 00000000000..29c9621fbda --- /dev/null +++ b/config/scripts/legacy-alt-screen-scan-benchmark.mjs @@ -0,0 +1,134 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const modulePath = 'src/main/daemon/terminal-history-legacy-scrollback-restore.ts' +const baselineSource = readFileSync(0, 'utf8') +assert.ok( + baselineSource.includes('function truncateAltScreen'), + 'Pipe the baseline module on stdin' +) +const arms = {} +for (const [name, source] of [ + ['baseline', baselineSource], + ['indexed', readFileSync(modulePath, 'utf8')] +]) { + const result = await build({ + stdin: { + contents: `export { truncateAltScreen } from './${modulePath}'`, + resolveDir: process.cwd(), + loader: 'ts' + }, + bundle: true, + format: 'esm', + platform: 'node', + write: false, + plugins: [ + { + name: 'private-export', + setup(api) { + api.onLoad({ filter: /terminal-history-legacy-scrollback-restore\.ts$/ }, () => ({ + contents: `${source}\nexport { truncateAltScreen }`, + loader: 'ts', + resolveDir: dirname(resolve(modulePath)) + })) + } + } + ] + }) + arms[name] = ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + ).truncateAltScreen +} + +const on = '\x1b[?1049h' +const off = '\x1b[?1049l' +let differentialCases = 0 +function verify(input) { + assert.equal(arms.indexed(input), arms.baseline(input)) + differentialCases++ +} +const tokens = [on, off, '\x1b[?1049', 'h', 'l', 'x'] +function enumerate(prefix, depth) { + verify(prefix) + if (depth === 0) { + return + } + for (const token of tokens) { + enumerate(prefix + token, depth - 1) + } +} +enumerate('', 6) + +let seed = 90211 +function random(max) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return Math.floor((seed / 0x100000000) * max) +} +const fragments = [...tokens, '\r\n', '\x1b[?1047h', '\x1b[0m', 'é中😀', '\ud800', '\x00'] +for (let trial = 0; trial < 5000; trial++) { + verify(Array.from({ length: random(300) }, () => fragments[random(fragments.length)]).join('')) +} +console.log( + JSON.stringify({ + differentialCases, + node: process.version, + platform: process.platform, + arch: process.arch + }) +) + +const workloads = [ + ['empty', ''], + ['plain 4KiB', 'x'.repeat(4096)], + ['plain 16MiB', 'x'.repeat(16 * 1024 * 1024)], + ['8 balanced', `${'x'.repeat(256)}${on}TUI${off}`.repeat(8)], + ['1024 balanced', (on + 'x'.repeat(4096) + off + 'x'.repeat(4096)).repeat(1024)], + ['1024 off', ('x'.repeat(8192) + off).repeat(1024)], + ['1024 nested on', ('x'.repeat(8192) + on).repeat(1024)], + [ + '1024 nested closed', + ('x'.repeat(4096) + on).repeat(1024) + ('x'.repeat(4096) + off).repeat(1024) + ], + ['4096 off near 16MiB limit', ('x'.repeat(4088) + off).repeat(4096)] +] +function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[3] + sorted[4]) / 2 +} +for (const [name, input] of workloads) { + const expected = arms.baseline(input) + assert.equal(arms.indexed(input), expected) + const samples = { baseline: [], indexed: [] } + const repeats = input.length < 8192 ? 10000 : 1 + for (const arm of Object.values(arms)) { + for (let warmup = 0; warmup < Math.min(100, repeats); warmup++) { + assert.equal(arm(input), expected) + } + } + for (const pair of buildCounterbalancedSchedule(8, 'baseline', 'indexed')) { + for (const name of pair) { + const start = performance.now() + let result + for (let repeat = 0; repeat < repeats; repeat++) { + result = arms[name](input) + } + samples[name].push((performance.now() - start) / repeats) + assert.equal(result, expected) + } + } + console.log( + JSON.stringify({ + name, + bytes: Buffer.byteLength(input), + medianMs: Object.fromEntries( + Object.entries(samples).map(([name, values]) => [name, median(values)]) + ) + }) + ) +} diff --git a/config/scripts/locale-brand-prefilter-benchmark.mjs b/config/scripts/locale-brand-prefilter-benchmark.mjs new file mode 100644 index 00000000000..c77e9f52ecd --- /dev/null +++ b/config/scripts/locale-brand-prefilter-benchmark.mjs @@ -0,0 +1,222 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { performance } from 'node:perf_hooks' +import { pathToFileURL } from 'node:url' +import { build } from 'esbuild' + +// Pipe the baseline policy on stdin. Catalog repair mutates only fresh in-memory copies. +const policyPath = path.resolve('config/scripts/locale-translation-policy.mjs') +const verifierPath = path.resolve('config/scripts/verify-localization-catalog.mjs') +const sources = [readFileSync(0, 'utf8'), readFileSync(policyPath, 'utf8')] +assert(sources.every((source) => source.includes('function includesPreservedLatinTerm('))) +const modules = await Promise.all( + sources.map(async (source) => { + const result = await build({ + entryPoints: [verifierPath], + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: [ + { + name: 'actual-locale-policy', + setup(builder) { + builder.onResolve({ filter: /^\.\// }, (args) => { + const resolved = path.resolve(args.resolveDir, args.path) + return resolved === policyPath + ? { path: resolved } + : { path: pathToFileURL(resolved).href, external: true } + }) + builder.onResolve({ filter: /^typescript-api$/ }, () => ({ + path: import.meta.resolve('typescript-api'), + external: true + })) + builder.onLoad({ filter: /locale-translation-policy\.mjs$/ }, () => ({ + contents: `${source}\nexport { includesPreservedLatinTerm };`, + resolveDir: path.dirname(policyPath) + })) + builder.onLoad({ filter: /verify-localization-catalog\.mjs$/ }, () => ({ + contents: `${readFileSync(verifierPath, 'utf8')}\nexport * from './locale-translation-policy.mjs';`, + resolveDir: path.dirname(verifierPath) + })) + } + } + ] + }) + const code = `${result.outputFiles[0].text}\n//# sourceURL=locale-brand-prefilter-bundle.js` + return import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`) + }) +) + +const brands = [ + ...new Set(Object.values(modules[0].BRAND_MISTRANSLATIONS).flatMap(Object.keys)), + '', + '_', + 'a_b', + 'a.b', + '[term]', + '界', + '\ud800' +] +const boundaries = ['', ' ', 'X', '_', '2', '.', '-', '\n', '\0', 'é', '界', '😀', '\ud800'] +let comparisons = 0 +for (const term of brands) { + for (const prefix of boundaries) { + for (const suffix of boundaries) { + for (const value of [ + `${prefix}${term}${suffix}`, + `X${term}X ${prefix}${term}${suffix}`, + `${prefix}${term.toLowerCase()}${suffix}`, + `${prefix}${suffix}` + ]) { + assert.equal( + modules[1].includesPreservedLatinTerm(value, term), + modules[0].includesPreservedLatinTerm(value, term) + ) + comparisons += 1 + } + } + } +} +console.log(`${comparisons} literal/boundary differential cases match`) + +let repairCases = 0 +for (const [locale, translations] of Object.entries(modules[0].BRAND_MISTRANSLATIONS)) { + for (const [brand, wrongForms] of Object.entries(translations)) { + for (const wrong of wrongForms) { + for (const prefix of boundaries) { + for (const key of [ + 'fixture.brand', + 'fixture.search.brand', + 'auto.lib.agent.catalog.test' + ]) { + const input = { + key, + enValue: `${prefix}${brand}${prefix} fixture {{agent}}`, + localeValue: `${wrong} ${prefix}${brand}${prefix} ${wrong} {{agent}}`, + locale + } + assert.equal( + modules[1].repairTranslatedValue(input), + modules[0].repairTranslatedValue(input) + ) + repairCases += 1 + } + } + } + } +} +console.log(`${repairCases} full policy repair cases match`) + +function measured(run) { + const start = performance.now() + const value = run() + return { elapsed: performance.now() - start, value } +} + +function benchmark(name, prepare) { + const expected = prepare(modules[0])() + assert.deepEqual(prepare(modules[1])(), expected) + for (const module of modules) { + const until = performance.now() + 150 + do { + assert.deepEqual(prepare(module)(), expected) + } while (performance.now() < until) + } + /** @type {number[][]} */ + const times = [[], []] + for (let pair = 0; pair < 8; pair++) { + for (const index of pair % 2 ? [1, 0] : [0, 1]) { + const result = measured(prepare(modules[index])) + times[index].push(result.elapsed) + assert.deepEqual(result.value, expected) + } + } + const median = times.map((values) => { + const sorted = values.toSorted((a, b) => a - b) + return (sorted[3] + sorted[4]) / 2 + }) + console.log(JSON.stringify({ name, median, times })) +} + +console.log( + JSON.stringify({ + node: process.version, + platform: process.platform, + arch: process.arch, + unit: 'ms' + }) +) +const localesDir = path.resolve('src/renderer/src/i18n/locales') +const en = JSON.parse(readFileSync(path.join(localesDir, 'en.json'), 'utf8')) +const enEntries = new Map(modules[0].collectStringLeaves(en).map(({ key, value }) => [key, value])) +for (const locale of ['zh', 'ja', 'ko', 'es', 'fr']) { + const catalog = JSON.parse(readFileSync(path.join(localesDir, `${locale}.json`), 'utf8')) + const localeEntries = new Map( + modules[0].collectStringLeaves(catalog).map(({ key, value }) => [key, value]) + ) + const inputs = [...enEntries].flatMap(([key, enValue]) => { + const localeValue = localeEntries.get(key) + return typeof localeValue === 'string' ? [{ key, enValue, localeValue, locale }] : [] + }) + assert.deepEqual( + inputs.map(modules[1].repairTranslatedValue), + inputs.map(modules[0].repairTranslatedValue) + ) + const expressionCounts = modules.map((module) => { + const original = globalThis.RegExp + let count = 0 + globalThis.RegExp = new Proxy(original, { + construct(target, args) { + if (typeof args[0] === 'string' && args[0].startsWith('(^|[^A-Za-z_])')) { + count += 1 + } + return Reflect.construct(target, args) + } + }) + try { + inputs.forEach(module.repairTranslatedValue) + } finally { + globalThis.RegExp = original + } + return count + }) + console.log(JSON.stringify({ locale, leaves: inputs.length, expressionCounts })) + benchmark(`${locale}: repairCatalog`, (module) => { + const copy = structuredClone(catalog) + return () => ({ count: module.repairCatalog(en, copy, locale), catalog: copy }) + }) + benchmark(`${locale}: collectGenericTermRegressions`, (module) => { + return () => module.collectGenericTermRegressions(enEntries, localeEntries, locale) + }) +} + +for (const [name, enValue, localeValue] of [ + ['absent brands', 'Choose an endpoint.', 'Elegir un destino.'], + ['matching brand', 'Use Gemini.', 'Usar Géminis.'], + ['embedded only', 'Use _Gemini_.', 'Usar Géminis.'], + ['all brands', brands.join(' '), brands.join(' ')] +]) { + const input = { key: 'fixture.brand', enValue, localeValue, locale: 'es' } + benchmark(`10k strings: ${name}`, (module) => () => { + let result + for (let i = 0; i < 10000; i++) { + result = module.repairTranslatedValue(input) + } + return result + }) +} + +const cache = new Map([ + ['Use Gemini.', 'Usar Géminis.'], + ['Choose an endpoint.', 'Elegir un destino.'], + ['Use _Gemini_.', 'Usar Géminis.'], + ['Use GitHub Copilot.', 'Usar Copiloto de GitHub.'] +]) +const caches = modules.map((module) => { + const copy = new Map(cache) + return { count: module.repairCacheMap(copy, 'es'), entries: [...copy] } +}) +assert.deepEqual(caches[1], caches[0]) +console.log('Actual catalog outputs, regression reports, repair counts and cache mutation match') diff --git a/config/scripts/locale-brand-prefilter.test.mjs b/config/scripts/locale-brand-prefilter.test.mjs new file mode 100644 index 00000000000..0ea14ffea3b --- /dev/null +++ b/config/scripts/locale-brand-prefilter.test.mjs @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from 'vitest' +import { repairTranslatedValue } from './locale-translation-policy.mjs' + +describe('locale brand matching', () => { + it('does not construct boundary expressions for absent brands', () => { + let boundaryExpressions = 0 + vi.stubGlobal( + 'RegExp', + new Proxy(RegExp, { + construct(target, args) { + if (typeof args[0] === 'string' && args[0].startsWith('(^|[^A-Za-z_])')) { + boundaryExpressions += 1 + } + return Reflect.construct(target, args) + } + }) + ) + try { + for (const locale of ['zh', 'ja', 'ko', 'es']) { + expect( + repairTranslatedValue({ + key: 'fixture.endpoint', + enValue: 'Choose an endpoint.', + localeValue: 'fixture translation', + locale + }) + ).toBe('fixture translation') + } + } finally { + vi.unstubAllGlobals() + } + expect(boundaryExpressions).toBe(0) + }) + + it.each([ + ['Use Gemini.', 'Usar Géminis.', 'Usar Gemini.'], + ['Use GeminiX.', 'Usar Géminis.', 'Usar Géminis.'], + ['Use XGemini.', 'Usar Géminis.', 'Usar Géminis.'], + ['Use _Gemini_.', 'Usar Géminis.', 'Usar Géminis.'], + ['Use Gemini_2.', 'Usar Géminis.', 'Usar Géminis.'], + ['Use 2Gemini3.', 'Usar Géminis.', 'Usar Gemini.'], + ['Use (Gemini).', 'Usar Géminis.', 'Usar Gemini.'], + ['Use éGemini界.', 'Usar Géminis.', 'Usar Gemini.'], + ['Use gemini.', 'Usar Géminis.', 'Usar Géminis.'], + ['Use GeminiX and Gemini.', 'Usar Géminis.', 'Usar Gemini.'], + ['Use Gemini.', 'Gemini y Géminis.', 'Gemini y Géminis.'], + ['Use Gemini.', '_Gemini_ y Géminis.', '_Gemini_ y Gemini.'], + ['Use GitHub Copilot.', 'Usar Copiloto de GitHub.', 'Usar GitHub Copilot.'], + ['Use XGitHub CopilotY.', 'Usar Copiloto de GitHub.', 'Usar GitHub Copilot.'] + ])('preserves literal and boundary matching for %j / %j', (enValue, localeValue, expected) => { + expect( + repairTranslatedValue({ key: 'fixture.brand', enValue, localeValue, locale: 'es' }) + ).toBe(expected) + }) +}) diff --git a/config/scripts/locale-translation-policy.mjs b/config/scripts/locale-translation-policy.mjs index cec2ebf63ad..af88805b8c4 100644 --- a/config/scripts/locale-translation-policy.mjs +++ b/config/scripts/locale-translation-policy.mjs @@ -281,8 +281,11 @@ function escapeRegExp(value) { } function includesPreservedLatinTerm(value, term) { + if (!value.includes(term)) { + return false + } if (!/^[A-Za-z_]+$/.test(term)) { - return value.includes(term) + return true } return new RegExp(`(^|[^A-Za-z_])${escapeRegExp(term)}($|[^A-Za-z_])`).test(value) } diff --git a/config/scripts/markdown-blank-run-scan-benchmark.mjs b/config/scripts/markdown-blank-run-scan-benchmark.mjs new file mode 100644 index 00000000000..2caaef90851 --- /dev/null +++ b/config/scripts/markdown-blank-run-scan-benchmark.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +// Adverse-input audit: compares the previous scanner with the production line-bounded implementation. +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' +import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs' + +const entry = fileURLToPath( + new URL( + '../../src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.ts', + import.meta.url + ) +) +const source = await readFile(entry, 'utf8') +const current = `${String.raw`/\s*(?:`}\`\`\`|~~~)/y` +const replacement = `${String.raw`/[^\S\n]*(?:`}\`\`\`|~~~)/y` +assert.ok( + source.includes(replacement), + 'Production fence regex changed; re-review benchmark candidate' +) +async function load(candidate) { + const result = await build({ + entryPoints: [entry], + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: candidate + ? [ + { + name: 'line-bounded-candidate', + setup(plugin) { + plugin.onLoad({ filter: /monaco-markdown-doc-link-decorations\.ts$/ }, () => ({ + contents: source.replace(replacement, current), + loader: 'ts', + resolveDir: fileURLToPath( + new URL('../../src/renderer/src/components/editor/', import.meta.url) + ) + })) + } + } + ] + : [] + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + ).getMarkdownDocLinkDecorationRanges +} +const baseline = await load(true) +const candidate = await load(false) +const corpus = [ + '', + '\n```\n[[hidden.md]]\n```\n[[shown.md]]', + ' \t\r\n~~~\r\n[[hidden.md]]\r\n~~~\r\n[[shown.md]]', + '\u00a0\u2028```\n[[hidden.md]]\n```\n[[shown.md]]', + '`code` [[shown.md]]' +] +for (const content of corpus) { + assert.deepEqual(candidate(content), baseline(content)) +} +const scenarios = [ + ['ordinary-100k-lines', 'ordinary prose\n'.repeat(100_000)], + ['blank-10k-lines', '\n'.repeat(10_000)], + ['blank-30k-lines', '\n'.repeat(30_000)], + ['blank-100k-lines', '\n'.repeat(100_000)], + ['indented-blank-10k-lines', `${' '.repeat(80)}\n`.repeat(10_000)] +] +for (const [name, content] of scenarios) { + const samples = { baseline: [], candidate: [] } + const scanners = { baseline, candidate } + let expected + for (const arms of buildCounterbalancedSchedule(2, 'baseline', 'candidate')) { + for (const arm of arms) { + const started = performance.now() + const ranges = scanners[arm](content) + samples[arm].push(performance.now() - started) + expected ??= ranges + assert.deepEqual(ranges, expected) + } + } + console.log( + JSON.stringify({ + name, + bytes: Buffer.byteLength(content), + samples, + baseline: summarizeBenchmarkSamples(samples.baseline), + candidate: summarizeBenchmarkSamples(samples.candidate) + }) + ) +} diff --git a/config/scripts/markdown-note-line-scan-benchmark.mjs b/config/scripts/markdown-note-line-scan-benchmark.mjs new file mode 100644 index 00000000000..2b6565278d9 --- /dev/null +++ b/config/scripts/markdown-note-line-scan-benchmark.mjs @@ -0,0 +1,81 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const sourcePath = 'src/renderer/src/lib/markdown-review-notes.ts' +const baseline = process.argv[2] ?? '20ab9950654' +async function load(contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(sourcePath)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false + }) + return import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const before = await load( + execFileSync('git', ['show', `${baseline}:${sourcePath}`], { encoding: 'utf8' }) +) +const after = await load(readFileSync(sourcePath, 'utf8')) +const results = [] +for (const [name, lineCount, width, count, iterations] of [ + ['small', 20, 40, 1, 1000], + ['long-lines', 1000, 1000, 20, 3], + ['many-lines', 20000, 80, 20, 3], + ['early-note', 20000, 80, 1, 1000] +]) { + const content = Array.from({ length: lineCount }, (_, i) => `${i}: ${'x'.repeat(width)}`).join( + '\r\n' + ) + const notes = Array.from({ length: count }, (_, i) => ({ + id: `${i}`, + worktreeId: 'bench', + filePath: 'README.md', + source: 'markdown', + lineNumber: name === 'early-note' ? 2 : lineCount - i, + body: 'Clarify this line', + createdAt: i, + side: 'modified' + })) + assert.equal( + after.formatMarkdownReviewNotes(notes, content), + before.formatMarkdownReviewNotes(notes, content) + ) + const arms = { before, after } + const samples = { before: [], after: [] } + function run(arm) { + const start = performance.now() + for (let i = 0; i < iterations; i++) { + arms[arm].formatMarkdownReviewNotes(notes, content) + } + return (performance.now() - start) / iterations + } + for (let i = 0; i < 6; i++) { + run('before') + run('after') + } + for (const pair of buildCounterbalancedSchedule(12, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + const median = (xs) => xs.sort((a, b) => a - b)[Math.floor(xs.length / 2)] + results.push({ + name, + lineCount, + width, + count, + beforeMs: median(samples.before), + afterMs: median(samples.after) + }) +} +console.log( + JSON.stringify({ node: process.version, platform: process.platform, baseline, results }, null, 2) +) diff --git a/config/scripts/mobile-backspace-benchmark.mjs b/config/scripts/mobile-backspace-benchmark.mjs new file mode 100644 index 00000000000..f1839089776 --- /dev/null +++ b/config/scripts/mobile-backspace-benchmark.mjs @@ -0,0 +1,112 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' +import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs' + +// git show :mobile/src/terminal/terminal-live-text-commit.ts | node config/scripts/mobile-backspace-benchmark.mjs +const target = resolve('mobile/src/terminal/terminal-live-text-commit.ts') +async function load(source) { + const result = await build({ + stdin: { contents: source, loader: 'ts', resolveDir: dirname(target) }, + bundle: true, + write: false, + platform: 'node', + format: 'esm' + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + ).getTerminalLiveAccessoryLocalEditText +} +const baseline = readFileSync(0, 'utf8') +assert.ok( + baseline.includes('function getTerminalLiveAccessoryLocalEditText'), + 'Pipe baseline source into stdin' +) +const implementations = { + before: await load(baseline), + after: await load(readFileSync(target, 'utf8')) +} +const tokens = [ + '', + 'a', + '\u0000', + '\r', + '\n', + '한', + '\u0301', + '\u200d', + '🙂', + '\ud800', + '\udbff', + '\udc00', + '\udfff' +] +let cases = 0 +for (const first of tokens) { + for (const second of tokens) { + for (const third of tokens) { + for (const localEdit of ['backspace', 'delete']) { + const input = { fieldText: first + second + third, localEdit } + assert.equal( + implementations.after(input), + implementations.before(input), + JSON.stringify(input) + ) + cases += 1 + } + } + } +} +const results = [] +for (const inputBytes of [32, 4096, 65_536, 262_144]) { + for (const glyph of ['a', '🙂']) { + const fieldText = glyph.repeat(inputBytes / Buffer.byteLength(glyph)) + const input = { localEdit: 'backspace', fieldText } + const expected = implementations.before(input) + assert.equal(implementations.after(input), expected) + const iterations = Math.max(10, Math.floor(1_000_000 / inputBytes)) + for (let warmup = 0; warmup < 100; warmup += 1) { + implementations.before(input) + implementations.after(input) + } + /** @type {{ before: number[], after: number[] }} */ + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) { + for (const arm of pair) { + let actual + const started = performance.now() + for (let repeat = 0; repeat < iterations; repeat += 1) { + actual = implementations[arm](input) + } + samples[arm].push(performance.now() - started) + assert.equal(actual, expected) + } + } + const means = Object.fromEntries( + Object.entries(samples).map(([arm, values]) => [ + arm, + (values.reduce((sum, ms) => sum + ms, 0) * 1000) / values.length / iterations + ]) + ) + results.push({ + inputBytes, + glyph, + iterations, + meanMicrosecondsPerCall: means, + before: summarizeBenchmarkSamples(samples.before), + after: summarizeBenchmarkSamples(samples.after) + }) + } +} +console.log( + JSON.stringify( + { node: process.version, platform: process.platform, differentialCases: cases, results }, + null, + 2 + ) +) diff --git a/config/scripts/mobile-diagnostics-prefix-benchmark.mjs b/config/scripts/mobile-diagnostics-prefix-benchmark.mjs new file mode 100644 index 00000000000..c7beec3eba1 --- /dev/null +++ b/config/scripts/mobile-diagnostics-prefix-benchmark.mjs @@ -0,0 +1,236 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' + +// Pipe baseline report then submission modules on stdin, in that order. No device/network I/O. +const baseline = readFileSync(0, 'utf8') +const divider = '\nconst CONNECTION_DIAGNOSTICS_ENDPOINT = ' +assert.equal(baseline.split(divider).length, 2) +const split = baseline.indexOf(divider) + 1 +const files = ['report', 'submission'].map((name) => + path.resolve(`mobile/src/diagnostics/connection-diagnostics-${name}.ts`) +) +const sources = [ + [baseline.slice(0, split), baseline.slice(split)], + files.map((file) => readFileSync(file, 'utf8')) +] +const modules = await Promise.all( + sources.map(async (contents) => { + const result = await build({ + stdin: { + contents: files.map((file) => `export * from ${JSON.stringify(file)};`).join('\n'), + resolveDir: process.cwd() + }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: [ + { + name: 'actual-mobile-diagnostics', + setup(builder) { + builder.onLoad( + { filter: /connection-diagnostics-(report|submission)\.ts$/ }, + (args) => ({ + contents: contents[files.indexOf(args.path)], + loader: 'ts', + resolveDir: path.dirname(args.path) + }) + ) + builder.onResolve({ filter: /^@react-native-async-storage\/async-storage$/ }, () => ({ + path: 'forbidden-device-storage', + namespace: 'fixture' + })) + builder.onLoad({ filter: /.*/, namespace: 'fixture' }, () => ({ + contents: `function forbidden() { throw new Error('Device storage is forbidden'); } + export default { getItem: forbidden, setItem: forbidden };` + })) + } + } + ] + }) + const code = `${result.outputFiles[0].text}\n//# sourceURL=mobile-diagnostics-prefix-bundle.js` + return import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`) + }) +) + +const base = { + hostName: 'fixture', + endpoint: 'ws://192.168.1.2:6768', + state: 'reconnecting', + reconnectAttempts: 2, + lastConnectedAt: null, + platform: 'android', + appVersion: 'fixture', + nowMs: 1700000000000 +} +let seed = 0x20d1a6 +function random(max) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return (seed >>> 8) % max +} +const tokens = ['a', 'é', '界', '😀', '\ud800', '\udc00', '\n', '\r\n', '\0', 'e\u0301'] +const limits = [ + Number.NEGATIVE_INFINITY, + -1, + 0, + 1, + 2, + 3, + 4, + 15, + 16, + 17, + 100, + 511, + 2048, + 65536, + Number.POSITIVE_INFINITY, + Number.NaN, + 2.5 +] +for (let trace = 0; trace < 3000; trace++) { + const lines = Array.from({ length: 1 + random(12) }, () => + Array.from({ length: 1 + random(5) }, () => + tokens[random(tokens.length)].repeat(random(100)) + ).join('') + ) + if (trace % 2) { + lines.splice(random(lines.length), 0, 'Recent connection history (fixture):') + } + const report = lines.join('\n') + const limit = limits[random(limits.length)] + assert.equal( + modules[1].boundConnectionDiagnosticsReport(report, limit), + modules[0].boundConnectionDiagnosticsReport(report, limit) + ) +} +console.log('3,000 report-bound differentials match, including nonfinite/fractional limits') + +for (let trace = 0; trace < 600; trace++) { + const entries = Object.freeze( + Array.from({ length: random(12) }, (_, index) => + Object.freeze({ + id: String(index), + ts: base.nowMs + index, + level: ['info', 'error', 'warn'][random(3)], + message: ['Authenticated', 'relay director resolve failed (503)', 'fixture'][random(3)], + detail: `${tokens[random(tokens.length)].repeat(random(4000))} token=fixture-secret`, + code: ['client-session-started', 'liveness-timeout', undefined][random(3)], + path: ['relay', 'lan', 'tailscale'][random(3)] + }) + ) + ) + const args = Object.freeze({ + ...base, + hostName: 'fixture token=host-fixture-secret', + endpoint: trace % 2 ? base.endpoint : 'invalid?token=endpoint-fixture-secret', + desktopAppVersion: trace % 2 ? '1.2.3' : '\ninvalid', + state: ['connected', 'reconnecting', 'connecting'][random(3)], + activePath: ['relay', 'lan', 'tailscale'][random(3)], + pendingPath: trace % 3 ? null : 'relay', + entries + }) + const reports = modules.map((module) => module.buildConnectionDiagnosticsReport(args)) + assert.equal(reports[1], reports[0]) + assert(!reports[1].includes('fixture-secret')) + const limit = limits[random(limits.length)] + assert.equal( + modules[1].boundConnectionDiagnosticsReport(reports[1], limit), + modules[0].boundConnectionDiagnosticsReport(reports[0], limit) + ) +} +console.log('600 frozen report-build + bound journeys preserve redaction, diagnosis and exact text') + +function runSample(run, repeats) { + let value + const start = performance.now() + for (let index = 0; index < repeats; index++) { + value = run() + } + return { value, elapsed: (performance.now() - start) / repeats } +} +function benchmark(name, arms) { + const expected = arms[0]() + assert.equal(arms[1](), expected) + for (const arm of arms) { + const until = performance.now() + 200 + do { + assert.equal(arm(), expected) + } while (performance.now() < until) + } + const repeats = Math.max(3, Math.min(10000, Math.ceil(40 / runSample(arms[0], 1).elapsed))) + /** @type {number[][]} */ + const times = [[], []] + for (let pair = 0; pair < 8; pair++) { + for (const index of pair % 2 ? [1, 0] : [0, 1]) { + const result = runSample(arms[index], repeats) + assert.equal(result.value, expected) + times[index].push(result.elapsed) + } + } + const median = times.map((values) => { + const sorted = values.toSorted((a, b) => a - b) + return (sorted[3] + sorted[4]) / 2 + }) + console.log(JSON.stringify({ name, repeats, median, times })) +} +console.log( + JSON.stringify({ + node: process.version, + platform: process.platform, + arch: process.arch, + unit: 'ms' + }) +) +for (const [events, length, token] of [ + [0, 0, 'a'], + [20, 80, 'a'], + [200, 80, 'a'], + [200, 1000, 'a'], + [200, 4000, 'a'], + [200, 2000, '😀'] +]) { + const args = { + ...base, + entries: Array.from({ length: events }, (_, i) => ({ + id: String(i), + ts: base.nowMs + i, + level: 'error', + message: `fixture-${i} ${token.repeat(length)}` + })) + } + const reports = modules.map((module) => module.buildConnectionDiagnosticsReport(args)) + assert.equal(reports[1], reports[0]) + const label = `${events} events / ${length} ${token}` + benchmark( + `${label}: build`, + modules.map((module) => () => module.buildConnectionDiagnosticsReport(args)) + ) + benchmark( + `${label}: bound`, + modules.map((module) => () => module.boundConnectionDiagnosticsReport(reports[0])) + ) +} + +for (const token of ['a', '😀', '\ud800']) { + const report = token.repeat(100000) + const results = await Promise.all( + modules.map(async (module) => { + let request + const result = await module.submitConnectionDiagnostics( + { report, platform: 'android', appVersion: 'fixture' }, + async (url, options) => { + assert.equal(options.signal.aborted, false) + request = { url, method: options.method, headers: options.headers, body: options.body } + return { ok: true } + } + ) + return { result, request } + }) + ) + assert.deepEqual(results[1], results[0]) +} +console.log('Three fake-fetch submission journeys preserve complete request bytes and results') diff --git a/config/scripts/mobile-file-ranking-benchmark.mjs b/config/scripts/mobile-file-ranking-benchmark.mjs index 68ac5b9d977..27bf99f8ecd 100644 --- a/config/scripts/mobile-file-ranking-benchmark.mjs +++ b/config/scripts/mobile-file-ranking-benchmark.mjs @@ -1,53 +1,141 @@ import assert from 'node:assert/strict' import { execFileSync } from 'node:child_process' import { readFileSync } from 'node:fs' -import { stripTypeScriptTypes } from 'node:module' import { performance } from 'node:perf_hooks' +import { transform } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' +import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs' const baseline = process.argv[2] if (!baseline) { - throw new Error('Usage: node config/scripts/mobile-file-ranking-benchmark.mjs ') + throw new Error( + 'Usage: node config/scripts/mobile-file-ranking-benchmark.mjs ' + ) } +// git show :mobile/src/session/mobile-native-chat-autocomplete.ts | node config/scripts/mobile-file-ranking-benchmark.mjs --autocomplete-stdin +const autocompleteSource = baseline === '--autocomplete-stdin' ? readFileSync(0, 'utf8') : null async function load(source) { - const js = stripTypeScriptTypes(source, { mode: 'transform' }) - return await import(`data:text/javascript;base64,${Buffer.from(js).toString('base64')}`) -} -function measure(fn, paths, query) { - for (let warmup = 0; warmup < 10; warmup++) { - fn(paths, query, 16) - } - const samples = [] - for (let i = 0; i < 9; i++) { - const start = performance.now() - fn(paths, query, 16) - samples.push(performance.now() - start) - } - return samples.sort((a, b) => a - b)[4] + const { code } = await transform(source, { loader: 'ts', format: 'esm' }) + return await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`) } const results = [] +let differentialCases = 0 for (const [file, name] of [ ['src/main/runtime/runtime-mobile-file-path-search.ts', 'rankRuntimeMobileFilePaths'], - ['mobile/src/session/mobile-native-chat-autocomplete.ts', 'rankSuggestions'] + ['mobile/src/session/mobile-native-chat-autocomplete.ts', 'rankSuggestions'], + ['mobile/src/session/mobile-native-chat-autocomplete.ts', 'rankSlashCommandSuggestions'] ]) { + if (autocompleteSource !== null && name === 'rankRuntimeMobileFilePaths') { + continue + } const before = ( - await load(execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' })) + await load( + autocompleteSource ?? + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }) + ) )[name] const after = (await load(readFileSync(file, 'utf8')))[name] - for (const count of [100, 100000]) { - const paths = Array.from( - { length: count }, - (_, i) => `src/components/workspace/group-${i % 100}/file-${i}.tsx` + const slash = name === 'rankSlashCommandSuggestions' + const toCandidates = (names) => + slash ? names.map((name, index) => ({ name, description: `Command ${index}` })) : names + if (name !== 'rankRuntimeMobileFilePaths') { + let seed = 42 + const random = (max) => { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return seed % max + } + const tokens = ['', 'app', 'src/', 'APP', 'zapp', '🙂', '한', '\ud800', '\u0130', ' '] + const limits = [ + undefined, + 0, + -0, + -1, + -0.5, + -Infinity, + Number.NaN, + 0.5, + 1.5, + 2.5, + 8, + 16, + Infinity + ] + for (let index = 0; index < 3000; index += 1) { + const candidates = toCandidates( + Array.from( + { length: random(100) }, + () => tokens[random(tokens.length)] + tokens[random(tokens.length)] + ) + ) + const query = tokens[random(tokens.length)] + const limit = limits[random(limits.length)] + assert.deepEqual(after(candidates, query, limit), before(candidates, query, limit)) + differentialCases += 1 + } + } + for (const count of slash ? [16, 100, 1000] : [16, 100, 10_000, 50_000, 100_000]) { + const names = Array.from({ length: count }, (_, index) => + slash + ? `team-review-${index}` + : `src/components/workspace/group-${index % 100}/file-${index}.tsx` ) - for (const query of ['file-9', 'missing', 'workspace']) { - assert.deepEqual(after(paths, query, 16), before(paths, query, 16)) + const limit = slash ? 12 : 16 + const substringQuery = slash ? 'review' : 'workspace' + const workloads = [ + { name: 'empty-query', names, query: '' }, + { name: 'substring', names, query: substringQuery }, + { name: 'no-match', names, query: 'missing' }, + { name: 'early-prefix', names, query: slash ? 'team' : 'file' }, + { + name: 'late-prefix', + names: [...names, ...Array.from({ length: 4 }, (_, index) => `${substringQuery}-${index}`)], + query: substringQuery + } + ] + for (const workload of workloads) { + const candidates = toCandidates(workload.names) + const expected = before(candidates, workload.query, limit) + assert.deepEqual(after(candidates, workload.query, limit), expected) + const implementations = { before, after } + const iterations = Math.max(10, Math.floor(100_000 / count)) + for (let warmup = 0; warmup < 100; warmup += 1) { + before(candidates, workload.query, limit) + after(candidates, workload.query, limit) + } + /** @type {{ before: number[], after: number[] }} */ + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) { + for (const arm of pair) { + let actual + const start = performance.now() + for (let repeat = 0; repeat < iterations; repeat += 1) { + actual = implementations[arm](candidates, workload.query, limit) + } + samples[arm].push(performance.now() - start) + assert.deepEqual(actual, expected) + } + } results.push({ function: name, - paths: count, - query, - beforeMs: measure(before, paths, query), - afterMs: measure(after, paths, query) + candidates: candidates.length, + workload: workload.name, + iterations, + meanMicrosecondsPerCall: Object.fromEntries( + Object.entries(samples).map(([arm, values]) => [ + arm, + (values.reduce((sum, ms) => sum + ms, 0) * 1000) / values.length / iterations + ]) + ), + before: summarizeBenchmarkSamples(samples.before), + after: summarizeBenchmarkSamples(samples.after) }) } } } -console.log(JSON.stringify({ node: process.version, platform: process.platform, results }, null, 2)) +console.log( + JSON.stringify( + { node: process.version, platform: process.platform, differentialCases, results }, + null, + 2 + ) +) diff --git a/config/scripts/mobile-history-scope-paths-benchmark.mjs b/config/scripts/mobile-history-scope-paths-benchmark.mjs new file mode 100644 index 00000000000..dda318223f2 --- /dev/null +++ b/config/scripts/mobile-history-scope-paths-benchmark.mjs @@ -0,0 +1,91 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error( + 'Usage: node config/scripts/mobile-history-scope-paths-benchmark.mjs ' + ) +} +const file = 'mobile/src/agent-history/agent-history-scope-paths.ts' +async function load(contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {} + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + ).deriveMobileAiVaultScopePaths +} +const arms = { + before: await load(execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' })), + after: await load(readFileSync(file, 'utf8')) +} +const iterations = 200 +const results = [] +for (const [count, unique] of [ + [1, 1], + [16, 16], + [64, 64], + [1000, 32] +]) { + for (const root of ['/home/ada/café/project', 'C:\\Users\\ada\\café\\project']) { + const rows = Array.from({ length: count }, (_, index) => ({ + worktreeId: `w-${index}`, + repoId: 'repo', + path: `${root}/workspace-${index % unique}` + })) + const expected = arms.before('project', rows[0], rows) + assert.deepEqual(arms.after('project', rows[0], rows), expected) + const samples = { before: [], after: [] } + function run(arm) { + let length = 0 + const start = performance.now() + for (let i = 0; i < iterations; i++) { + length += arms[arm]('project', rows[0], rows).length + } + const elapsed = performance.now() - start + assert.equal(length, iterations * expected.length) + return elapsed / iterations + } + for (const arm of ['before', 'after']) { + run(arm) + } + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + count, + unique, + root, + beforeMs: median(samples.before), + afterMs: median(samples.after), + samples + }) + } +} +console.log( + JSON.stringify( + { baseline, node: process.version, platform: process.platform, iterations, results }, + null, + 2 + ) +) diff --git a/config/scripts/mobile-linear-group-sorted-benchmark.mjs b/config/scripts/mobile-linear-group-sorted-benchmark.mjs new file mode 100644 index 00000000000..529515a99ae --- /dev/null +++ b/config/scripts/mobile-linear-group-sorted-benchmark.mjs @@ -0,0 +1,97 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error( + 'Usage: node config/scripts/mobile-linear-group-sorted-benchmark.mjs ' + ) +} +async function load(file, contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {}, + plugins: [ + { + name: 'theme-only', + setup(bundler) { + bundler.onResolve({ filter: /mobile-tasks-dependencies$/ }, () => ({ + path: resolve('mobile/src/theme/mobile-theme.ts') + })) + } + } + ] + }) + return await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const file = 'mobile/src/tasks/mobile-tasks-reviewer-linear.ts' +const original = await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8', windowsHide: true }) +) +const current = await load(file, readFileSync(file, 'utf8')) +const sorterRef = process.argv[3] +const sorter = sorterRef + ? await load( + file, + execFileSync('git', ['show', `${sorterRef}:${file}`], { encoding: 'utf8', windowsHide: true }) + ) + : original +const results = [] +for (const count of [25, 200, 1000]) { + const items = Array.from({ length: count }, (_, i) => ({ + id: `item-${i}`, + identifier: `ENG-${(i * 37) % count}`, + updatedAt: new Date(1700000000000 - i * 100000).toISOString(), + priority: i % 5, + state: { name: `state-${i % 4}`, color: 'red' }, + team: { id: `team-${i % 3}`, name: 'Team' }, + assignee: null + })) + for (const order of ['identifier', 'updated', 'priority']) { + const run = (arm) => { + const sorted = sorter.sortLinearIssues + ? sorter.sortLinearIssues(items, order) + : [...items].sort((a, b) => sorter.compareLinearIssues(a, b, order)) + return arm === 'before' + ? [ + sorter.groupLinearIssues(sorted, 'none', order), + sorter.groupLinearIssues(sorted, 'status', order) + ] + : [ + current.groupSortedLinearIssues(sorted, 'none'), + current.groupSortedLinearIssues(sorted, 'status') + ] + } + assert.deepEqual(run('after'), run('before')) + for (let i = 0; i < 10; i++) { + run('before') + run('after') + } + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) { + for (const arm of pair) { + global.gc?.() + const start = performance.now() + for (let i = 0; i < 10; i++) { + run(arm) + } + samples[arm].push((performance.now() - start) / 10) + } + } + results.push({ count, order, samples }) + } +} +console.log(JSON.stringify({ baseline, sorterRef, node: process.version, results }, null, 2)) diff --git a/config/scripts/mobile-linear-sort-benchmark.mjs b/config/scripts/mobile-linear-sort-benchmark.mjs new file mode 100644 index 00000000000..1a2a2d29ad6 --- /dev/null +++ b/config/scripts/mobile-linear-sort-benchmark.mjs @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error('Usage: node config/scripts/mobile-linear-sort-benchmark.mjs ') +} +async function load(file, contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {}, + plugins: [ + { + name: 'theme-only', + setup(bundler) { + bundler.onResolve({ filter: /mobile-tasks-dependencies$/ }, () => ({ + path: resolve('mobile/src/theme/mobile-theme.ts') + })) + } + } + ] + }) + return await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const file = 'mobile/src/tasks/mobile-tasks-reviewer-linear.ts' +const before = await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }) +) +const after = await load(file, readFileSync(file, 'utf8')) +const results = [] +for (const count of [0, 1, 25, 200, 1000]) { + const items = Array.from({ length: count }, (_, index) => ({ + id: `item-${index}`, + identifier: `ENG-${(index * 37) % Math.max(1, count)}`, + updatedAt: new Date(1700000000000 - index * 100000).toISOString(), + priority: index % 5 + })) + for (const sort of ['updated', 'identifier', 'priority']) { + const arms = { + before: () => [...items].sort((a, b) => before.compareLinearIssues(a, b, sort)), + after: () => after.sortLinearIssues(items, sort) + } + assert.deepEqual(arms.after(), arms.before()) + const iterations = count < 100 ? 100 : 10 + function run(arm) { + const start = performance.now() + for (let i = 0; i < iterations; i++) { + arms[arm]() + } + return (performance.now() - start) / iterations + } + const samples = { before: [], after: [] } + run('before') + run('after') + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + const dateParses = {} + const nativeParse = Date.parse + for (const arm of ['before', 'after']) { + let calls = 0 + Date.parse = (value) => { + calls++ + return nativeParse(value) + } + try { + arms[arm]() + } finally { + Date.parse = nativeParse + } + dateParses[arm] = calls + } + results.push({ + count, + sort, + dateParses, + beforeMs: median(samples.before), + afterMs: median(samples.after), + samples + }) + } +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/mobile-log-revisions-benchmark.mjs b/config/scripts/mobile-log-revisions-benchmark.mjs new file mode 100644 index 00000000000..aa42094fee6 --- /dev/null +++ b/config/scripts/mobile-log-revisions-benchmark.mjs @@ -0,0 +1,85 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] ?? '20ab9950654' +const file = 'mobile/src/transport/connection-log-buffer.ts' +async function load(contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + tsconfigRaw: {} + }) + return import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const before = await load( + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }) +) +const after = await load(readFileSync(file, 'utf8')) +const drain = () => new Promise((resolve) => setImmediate(resolve)) +async function run(module, count, startup) { + let calls = 0 + let bytes = 0 + let stored = '' + const store = module.createConnectionLogStore(200, { + load: async () => [], + save: async (_host, snapshot) => { + stored = JSON.stringify(snapshot) + calls++ + bytes += Buffer.byteLength(stored) + } + }) + if (!startup) { + await store.hydrate('a') + await drain() + calls = 0 + bytes = 0 + } + const start = performance.now() + for (let i = 0; i < count; i++) { + store.append('a', { id: `${i}`, ts: i, level: 'info', message: `connection event ${i}` }) + } + await drain() + return { ms: performance.now() - start, calls, bytes, stored } +} +const results = [] +for (const count of [1, 25, 200, 1000]) { + for (const startup of [false, true]) { + const arms = { before, after } + const initialBefore = await run(before, count, startup) + const initialAfter = await run(after, count, startup) + assert.equal(initialAfter.stored, initialBefore.stored) + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push((await run(arms[arm], count, startup)).ms) + } + } + const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + count, + startup, + before: { + calls: initialBefore.calls, + bytes: initialBefore.bytes, + ms: median(samples.before) + }, + after: { calls: initialAfter.calls, bytes: initialAfter.bytes, ms: median(samples.after) } + }) + } +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/mobile-source-control-collation-benchmark.mjs b/config/scripts/mobile-source-control-collation-benchmark.mjs new file mode 100644 index 00000000000..e48d7dbfb0f --- /dev/null +++ b/config/scripts/mobile-source-control-collation-benchmark.mjs @@ -0,0 +1,110 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error( + 'Usage: node config/scripts/mobile-source-control-collation-benchmark.mjs ' + ) +} +async function load(file, contents, name) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {} + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + )[name] +} +// Match git's path order, including its numeric-looking names, instead of inflating sort work with a shuffle. +const paths = execFileSync('git', ['ls-files', '-z'], { maxBuffer: 16 * 1024 * 1024 }) + .toString() + .split('\0') + .filter(Boolean) +const results = [] +for (const [file, name] of [ + ['mobile/src/source-control/mobile-git-status.ts', 'buildMobileSourceControlSections'], + ['mobile/src/source-control/mobile-branch-compare.ts', 'buildMobileBranchCompareSection'], + ['mobile/src/session/mobile-diff-review-queue.ts', 'buildMobileDiffReviewQueue'] +]) { + const arms = { + before: await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }), + name + ), + after: await load(file, readFileSync(file, 'utf8'), name) + } + for (const count of [0, 1, 17, 63, 1000]) { + const step = Math.max(1, Math.floor(paths.length / Math.max(1, count))) + const entries = Array.from({ length: count }, (_, index) => ({ + path: paths[index * step], + area: 'unstaged', + status: 'modified', + ...(index % 37 === 0 ? { conflictStatus: 'unresolved' } : {}) + })) + const input = + name === 'buildMobileDiffReviewQueue' + ? { + worktreeId: 'workspace', + statusEntries: entries, + branchEntries: [], + comments: [], + reviewState: { version: 1, files: {} } + } + : entries + assert.deepEqual(arms.after(input), arms.before(input)) + const iterations = count < 100 ? 100 : 10 + function run(arm) { + const start = performance.now() + for (let index = 0; index < iterations; index++) { + arms[arm](input) + } + return (performance.now() - start) / iterations + } + const samples = { before: [], after: [] } + run('before') + run('after') + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + name, + count, + beforeMs: median(samples.before), + afterMs: median(samples.after), + samples + }) + } +} +console.log( + JSON.stringify( + { + baseline, + node: process.version, + platform: process.platform, + locale: new Intl.Collator().resolvedOptions().locale, + results + }, + null, + 2 + ) +) diff --git a/config/scripts/mobile-task-sort-benchmark.mjs b/config/scripts/mobile-task-sort-benchmark.mjs new file mode 100644 index 00000000000..b7d6cb469a3 --- /dev/null +++ b/config/scripts/mobile-task-sort-benchmark.mjs @@ -0,0 +1,113 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error('Usage: node config/scripts/mobile-task-sort-benchmark.mjs ') +} +async function load(file, contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {}, + plugins: [ + { + name: 'theme-only', + setup(bundler) { + bundler.onResolve({ filter: /mobile-tasks-dependencies$/ }, () => ({ + path: resolve('mobile/src/theme/mobile-theme.ts') + })) + } + } + ] + }) + return await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const file = 'mobile/src/tasks/mobile-tasks-repository-presentation.ts' +const before = await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }) +) +const after = await load(file, readFileSync(file, 'utf8')) +const repos = new Map() +const results = [] +for (const count of [0, 1, 25, 1000]) { + const items = Array.from({ length: count }, (_, index) => ({ + key: `item-${index}`, + provider: 'github', + title: 'task', + subtitle: '', + status: 'open', + updatedAt: new Date(1700000000000 - index * 100000).toISOString(), + source: { repoId: `repo-${index % 25}`, repoName: `Repository ${index % 25}` } + })) + for (const sort of ['updated', 'repository']) { + const arms = { + before: () => + [...items].sort( + sort === 'repository' + ? (a, b) => before.compareTasksByRepository(a, b, repos) + : before.compareTasksByUpdated + ), + after: () => after.sortMobileTaskItems(items, sort, repos) + } + assert.deepEqual(arms.after(), arms.before()) + const iterations = count < 100 ? 100 : 10 + function run(arm) { + const start = performance.now() + for (let i = 0; i < iterations; i++) { + arms[arm]() + } + return (performance.now() - start) / iterations + } + const samples = { before: [], after: [] } + run('before') + run('after') + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + const dateParses = {} + const nativeParse = Date.parse + for (const arm of ['before', 'after']) { + let calls = 0 + Date.parse = (value) => { + calls++ + return nativeParse(value) + } + try { + arms[arm]() + } finally { + Date.parse = nativeParse + } + dateParses[arm] = calls + } + results.push({ + count, + sort, + dateParses, + beforeMs: median(samples.before), + afterMs: median(samples.after), + samples + }) + } +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/native-provider-line-gate-benchmark.mjs b/config/scripts/native-provider-line-gate-benchmark.mjs new file mode 100644 index 00000000000..75194f2c3ac --- /dev/null +++ b/config/scripts/native-provider-line-gate-benchmark.mjs @@ -0,0 +1,320 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' + +// Pipe the baseline client module on stdin. Native process/filesystem operations are forbidden here. +const entry = path.resolve('src/main/computer/macos-native-provider-client.ts') +const sources = [readFileSync(0, 'utf8'), readFileSync(entry, 'utf8')] +assert(sources.every((source) => source.includes('export class MacOSNativeProviderClient'))) +async function load(source) { + const result = await build({ + entryPoints: [entry], + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: [ + { + name: 'native-client-receive-fixture', + setup(builder) { + builder.onLoad({ filter: /macos-native-provider-client\.ts$/ }, () => ({ + contents: `${source}\nexport { NativeProviderLineBuffer, consumeNativeProviderLines } from './macos-native-provider-transport';`, + loader: 'ts', + resolveDir: path.dirname(entry) + })) + builder.onResolve({ filter: /^node:(fs|child_process)$/ }, (args) => ({ + path: args.path, + namespace: 'forbidden-native-operation' + })) + builder.onLoad({ filter: /.*/, namespace: 'forbidden-native-operation' }, (args) => ({ + contents: `function forbidden() { throw new Error('Native operations are forbidden in this benchmark'); } + export { forbidden as ${ + args.path === 'node:fs' + ? 'chmodSync, forbidden as mkdtempSync, forbidden as rmSync, forbidden as writeFileSync, forbidden as existsSync' + : 'spawn' + } };`, + loader: 'js' + })) + } + } + ] + }) + const bundled = `${result.outputFiles[0].text}\n//# sourceURL=native-provider-line-gate-benchmark-bundle.js` + return import(`data:text/javascript;base64,${Buffer.from(bundled).toString('base64')}`) +} +const modules = await Promise.all(sources.map(load)) + +class FixtureSocket { + destroyed = false + writes = [] + write(line) { + this.writes.push(line) + } + end() { + this.destroyed = true + } + destroy() { + this.destroyed = true + } +} + +function clientFixture(module) { + const client = new module.MacOSNativeProviderClient() + const socket = new FixtureSocket() + client.socket = socket + return { client, socket, stale: new FixtureSocket(), events: [] } +} + +function state(fixture) { + const { client, socket, events } = fixture + return { + buffered: + typeof client.socketBuffer === 'string' ? client.socketBuffer : client.socketBuffer.pending, + pending: [...client.pending.keys()], + active: client.socket === socket, + generation: client.socketStartGeneration, + destroyed: socket.destroyed, + writes: socket.writes, + events + } +} + +function register(fixture, id, throwCallback) { + fixture.client.pending.set(id, { + timer: undefined, + resolve(value) { + fixture.events.push(['resolve', id, value]) + if (throwCallback) { + throw new Error('fixture callback failure') + } + }, + reject(error) { + fixture.events.push(['reject', id, error.code, error.message]) + if (throwCallback) { + throw new Error('fixture callback failure') + } + } + }) +} + +let seed = 0x18c0ffee +function random(max) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return (seed >>> 8) % max +} +for (let trace = 0; trace < 2000; trace++) { + const fixtures = modules.map(clientFixture) + let remainder = '' + for (let step = 0; step < 40; step++) { + const op = random(20) + const id = random(8) + const throwCallback = random(12) === 0 + if (!remainder) { + remainder = [ + `${JSON.stringify({ id, ok: true, result: { text: 'Unicode 界😀', value: step } })}\n`, + `${JSON.stringify({ id, ok: false, error: { code: 'fixture', message: 'failed' } })}\r\n`, + `${JSON.stringify({ id, ok: false })}\n`, + ' \t\r\n', + 'invalid json\n', + 'null\n', + '\ud83d\udc00\n' + ][random(7)] + } + const length = random(remainder.length + 1) + const chunk = remainder.slice(0, length) + if (op > 5) { + remainder = remainder.slice(length) + } + for (const fixture of fixtures) { + const { client, socket } = fixture + try { + if (op === 0) { + client.shutdown() + } else if (op === 1) { + client.handleSocketClose(socket) + } else if (op === 2) { + client.handleTransportError(socket, new Error('fixture transport error')) + } else if (op === 3) { + client.invalidateActiveSocketAfterWriteFailure(socket, new Error('fixture write error')) + } else if (op === 4) { + fixture.stale = socket + fixture.socket = new FixtureSocket() + client.socket = fixture.socket + } else if (op === 5) { + register(fixture, id, throwCallback) + } else { + client.handleSocketData(op === 6 ? fixture.stale : socket, chunk) + } + } catch (error) { + fixture.events.push(['throw', error.name, error.message]) + } + } + assert.deepEqual(state(fixtures[1]), state(fixtures[0])) + } +} +console.log('2,000 actual client receive/lifecycle traces / 80,000 commands match') + +for (let trace = 0; trace < 1000; trace++) { + const fixtures = modules.map(clientFixture) + const failThird = random(2) === 0 + for (const fixture of fixtures) { + for (let id = 1; id <= 4; id++) { + register(fixture, id, id === 3 && failThird) + } + } + const input = [ + JSON.stringify({ id: 1, ok: true, result: { text: `界😀 ${trace}` } }), + JSON.stringify({ id: 2, ok: false, error: { code: 'fixture', message: 'failed' } }), + JSON.stringify({ id: 3, ok: true, result: trace }), + JSON.stringify({ id: 4, ok: true, result: 'final reply' }), + '' + ].join('\n') + let offset = 0 + while (offset < input.length) { + const length = 1 + random(80) + const chunk = input.slice(offset, offset + length) + offset += length + for (const fixture of fixtures) { + try { + fixture.client.handleSocketData(fixture.socket, chunk) + } catch (error) { + fixture.events.push(['throw', error.name, error.message]) + } + } + assert.deepEqual(state(fixtures[1]), state(fixtures[0])) + } + for (const fixture of fixtures) { + fixture.client.handleSocketData(fixture.socket, '') + assert.equal(fixture.client.pending.size, 0) + assert.deepEqual(fixture.events.at(-1), ['resolve', 4, 'final reply']) + } + assert.deepEqual(state(fixtures[1]), state(fixtures[0])) +} +console.log('1,000 fragmented multi-reply client journeys / 4,000 request settlements match') + +class BaselineBuffer { + pending = '' + push(chunk, onLine) { + this.pending += chunk + this.pending = modules[0].consumeNativeProviderLines(this.pending, onLine) + } + clear() { + this.pending = '' + } +} +for (let trace = 0; trace < 3000; trace++) { + const buffers = [new BaselineBuffer(), new modules[1].NativeProviderLineBuffer()] + const events = [[], []] + for (let step = 0; step < 30; step++) { + const clear = random(25) === 0 + const fail = random(10) === 0 + const chunk = ['abc', '\n', '\r\n', '\ud83d', '\udc00', '\n\n', '界', '', 'ok\nfault\npartial'][ + random(9) + ] + buffers.forEach((buffer, index) => { + if (clear) { + buffer.clear() + } + try { + buffer.push(chunk, (line) => { + events[index].push(line) + if (fail) { + throw new Error('fixture callback failure') + } + }) + } catch (error) { + events[index].push({ error: error.message }) + } + }) + assert.deepEqual(events[1], events[0]) + assert.equal(buffers[1].pending, buffers[0].pending) + } +} +console.log('3,000 actual line-buffer traces / 90,000 feeds match') + +function receiveArm(module) { + const fixture = clientFixture(module) + return (chunks) => { + let result + fixture.client.pending.set(1, { + timer: undefined, + resolve: (value) => { + result = value + }, + reject: (error) => { + throw error + } + }) + for (const chunk of chunks) { + fixture.client.handleSocketData(fixture.socket, chunk) + } + return result + } +} +function sample(arm, input, repeats) { + const start = performance.now() + let result + for (let i = 0; i < repeats; i++) { + result = arm(input) + } + return { elapsed: (performance.now() - start) / repeats, result } +} + +console.log( + JSON.stringify({ + node: process.version, + platform: process.platform, + arch: process.arch, + unit: 'ms', + pairs: 8 + }) +) +for (const [size, chunkBytes] of [ + [64, 65536], + [120000, 65536], + [1200000, 65536], + [1200000, 4096], + [4800000, 65536], + [1200000, Number.POSITIVE_INFINITY] +]) { + const expected = { screenshot: { data: 'A'.repeat(size) }, text: 'fixture' } + const input = `${JSON.stringify({ id: 1, ok: true, result: expected })}\n` + const chunks = [] + for (let offset = 0; offset < input.length; offset += chunkBytes) { + chunks.push(input.slice(offset, offset + chunkBytes)) + } + const arms = modules.map(receiveArm) + for (const arm of arms) { + assert.deepEqual(arm(chunks), expected) + const until = performance.now() + 150 + while (performance.now() < until) { + sample(arm, chunks, 1) + } + } + const repeats = Math.max(3, Math.min(100000, Math.ceil(50 / sample(arms[0], chunks, 1).elapsed))) + /** @type {number[][]} */ + const times = [[], []] + for (let pair = 0; pair < 8; pair++) { + for (const index of pair % 2 ? [1, 0] : [0, 1]) { + const result = sample(arms[index], chunks, repeats) + assert.deepEqual(result.result, expected) + times[index].push(result.elapsed) + } + } + const median = times.map((values) => { + values.sort((a, b) => a - b) + return (values[3] + values[4]) / 2 + }) + console.log( + JSON.stringify({ + size, + chunkBytes: Number.isFinite(chunkBytes) ? chunkBytes : 'whole frame', + chunks: chunks.length, + repeats, + median, + times + }) + ) +} diff --git a/config/scripts/package-electron-runtime-contract.test.mjs b/config/scripts/package-electron-runtime-contract.test.mjs index aa34e043268..31d9b7a8d57 100644 --- a/config/scripts/package-electron-runtime-contract.test.mjs +++ b/config/scripts/package-electron-runtime-contract.test.mjs @@ -1,4 +1,4 @@ -import { readFileSync } from 'node:fs' +import { existsSync, readFileSync } from 'node:fs' import { createRequire } from 'node:module' import { join, resolve } from 'node:path' import { describe, expect, it } from 'vitest' @@ -11,47 +11,49 @@ const { createPackagedRuntimeNodeModuleResources } = require('../packaged-runtim const readProject = (file) => readFileSync(join(projectDir, file), 'utf8') const packageJson = JSON.parse(readProject('package.json')) const pnpmWorkspace = parse(readProject('pnpm-workspace.yaml')) +// Why not process.platform: the win32 plan resolves wherever its os-gated npm addon is +// installed; @orca/windows-registry is a workspace link and present everywhere. +const windowsAddonsInstalled = existsSync( + join(projectDir, 'node_modules', '@vscode', 'windows-process-tree', 'package.json') +) describe('Electron runtime package contract', () => { + const packageTargets = { + win32: windowsAddonsInstalled ? createPackagedRuntimeNodeModuleResources('win32') : [], + darwin: createPackagedRuntimeNodeModuleResources('darwin'), + linux: createPackagedRuntimeNodeModuleResources('linux') + } + it('keeps root postinstall as the single Electron binary install owner', () => { expect(packageJson.scripts.postinstall).toBe('node config/scripts/rebuild-native-deps.mjs') expect(pnpmWorkspace.allowBuilds).not.toHaveProperty('electron') }) it('keeps the native Windows registry addon optional and platform-gated', () => { - const rebuildScript = readFileSync( - join(projectDir, 'config/scripts/rebuild-native-deps.mjs'), - 'utf8' - ) - const ensureScript = readFileSync( - join(projectDir, 'config/scripts/ensure-native-runtime.mjs'), - 'utf8' - ) - expect(packageJson.optionalDependencies['windows-native-registry']).toBe('3.2.2') - // Why: pnpm installs optional target architectures on every host; the root - // Windows-only rebuild owns this addon so macOS/Linux never run node-gyp for it. - expect(pnpmWorkspace.allowBuilds['windows-native-registry']).toBe(false) + const rebuildScript = readProject('config/scripts/rebuild-native-deps.mjs') + const ensureScript = readProject('config/scripts/ensure-native-runtime.mjs') + expect(packageJson.optionalDependencies['@orca/windows-registry']).toBe('workspace:*') + // Why: allowBuilds stops pnpm running node-gyp at install time -- the root + // Windows-only rebuild owns this addon so it is built against the right runtime ABI. + expect(pnpmWorkspace.allowBuilds['@orca/windows-registry']).toBe(false) // Why assert the guard and the member separately: the list now carries more // than one addon, so pinning the whole literal only tested its formatting. expect(rebuildScript).toContain("rebuildPlatform === 'win32'") - expect(rebuildScript).toContain("'windows-native-registry'") + expect(rebuildScript).toContain("'@orca/windows-registry'") expect(ensureScript).toContain("process.platform === 'win32'") - expect(ensureScript).toContain("'windows-native-registry'") - const packageTargets = { - win32: createPackagedRuntimeNodeModuleResources('win32'), - darwin: createPackagedRuntimeNodeModuleResources('darwin'), - linux: createPackagedRuntimeNodeModuleResources('linux') + expect(ensureScript).toContain("'@orca/windows-registry'") + if (windowsAddonsInstalled) { + expect(packageTargets.win32).toEqual( + expect.arrayContaining([ + expect.objectContaining({ to: join('node_modules', '@orca', 'windows-registry') }), + expect.objectContaining({ to: join('node_modules', 'node-addon-api') }) + ]) + ) } - expect(packageTargets.win32).toEqual( - expect.arrayContaining([ - expect.objectContaining({ to: join('node_modules', 'windows-native-registry') }), - expect.objectContaining({ to: join('node_modules', 'node-addon-api') }) - ]) - ) for (const platform of ['darwin', 'linux']) { expect(packageTargets[platform]).not.toEqual( expect.arrayContaining([ - expect.objectContaining({ to: join('node_modules', 'windows-native-registry') }) + expect.objectContaining({ to: join('node_modules', '@orca', 'windows-registry') }) ]) ) } @@ -67,8 +69,8 @@ describe('Electron runtime package contract', () => { 'utf8' ) expect(packageJson.optionalDependencies['@vscode/windows-process-tree']).toBe('0.8.0') - // Why: same rule as the registry addon -- pnpm installs optional deps on - // every host, so macOS/Linux must never run node-gyp for a Windows addon. + // Why: same rule as the registry addon -- allowBuilds stops pnpm running node-gyp at + // install time so the Windows-only rebuild owns it with the right runtime ABI. expect(pnpmWorkspace.allowBuilds['@vscode/windows-process-tree']).toBe(false) expect(rebuildScript).toContain("'@vscode/windows-process-tree'") expect(ensureScript).toContain("'@vscode/windows-process-tree'") @@ -79,16 +81,13 @@ describe('Electron runtime package contract', () => { expect(pnpmWorkspace.patchedDependencies['@vscode/windows-process-tree@0.8.0']).toBe( 'config/patches/@vscode__windows-process-tree@0.8.0.patch' ) - const packageTargets = { - win32: createPackagedRuntimeNodeModuleResources('win32'), - darwin: createPackagedRuntimeNodeModuleResources('darwin'), - linux: createPackagedRuntimeNodeModuleResources('linux') + if (windowsAddonsInstalled) { + expect(packageTargets.win32).toEqual( + expect.arrayContaining([ + expect.objectContaining({ to: join('node_modules', '@vscode', 'windows-process-tree') }) + ]) + ) } - expect(packageTargets.win32).toEqual( - expect.arrayContaining([ - expect.objectContaining({ to: join('node_modules', '@vscode', 'windows-process-tree') }) - ]) - ) for (const platform of ['darwin', 'linux']) { expect(packageTargets[platform]).not.toEqual( expect.arrayContaining([ diff --git a/config/scripts/plugin-command-bindings-benchmark.mjs b/config/scripts/plugin-command-bindings-benchmark.mjs new file mode 100644 index 00000000000..58b3dad8cab --- /dev/null +++ b/config/scripts/plugin-command-bindings-benchmark.mjs @@ -0,0 +1,101 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error('Usage: node config/scripts/plugin-command-bindings-benchmark.mjs ') +} +async function load(file, contents, name) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {}, + banner: { + js: "import { createRequire as benchmarkRequire } from 'node:module'; import { resolve as benchmarkPath } from 'node:path'; const require = benchmarkRequire(benchmarkPath('package.json'));" + } + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + )[name] +} +const file = 'src/main/plugins/plugin-command-registry.ts' +const before = await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }), + 'PluginCommandRegistry' +) +const after = await load(file, readFileSync(file, 'utf8'), 'PluginCommandRegistry') +const results = [] +for (const count of [1, 16, 64, 256]) { + const plugins = [ + { + pluginKey: 'sample.commands', + manifest: { + contributes: { + commands: Array.from({ length: count }, (_, index) => ({ + id: `command-${index}`, + title: `Command ${index}`, + action: 'view.tasks' + })), + keybindings: Array.from({ length: Math.min(count, 104) }, (_, index) => ({ + command: `command-${index}`, + key: `Mod+${Math.floor(index / 26) & 1 ? 'Alt+' : ''}${Math.floor(index / 26) & 2 ? 'Shift+' : ''}${String.fromCharCode(65 + (index % 26))}` + })) + } + } + } + ] + const arms = { before: new before(), after: new after() } + for (const platform of ['darwin', 'linux', 'win32']) { + for (const arm of Object.values(arms)) { + arm.reconcile(plugins, () => true, {}, platform) + } + const snapshot = (registry) => ({ + active: registry.list(), + previews: plugins.map((plugin) => registry.preview(plugin.pluginKey)), + errors: plugins.map((plugin) => registry.error(plugin.pluginKey)) + }) + assert.deepEqual(snapshot(arms.after), snapshot(arms.before)) + } + const iterations = 100 + function run(arm) { + const start = performance.now() + for (let i = 0; i < iterations; i++) { + arms[arm].reconcile(plugins, () => true, {}, 'linux') + } + return (performance.now() - start) / iterations + } + const samples = { before: [], after: [] } + run('before') + run('after') + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + commands: count, + bindings: Math.min(count, 104), + beforeMs: median(samples.before), + afterMs: median(samples.after), + samples + }) +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/plugin-message-budget-benchmark.mjs b/config/scripts/plugin-message-budget-benchmark.mjs new file mode 100644 index 00000000000..605eee85ea6 --- /dev/null +++ b/config/scripts/plugin-message-budget-benchmark.mjs @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] ?? '20ab9950654' +const file = 'src/shared/plugins/plugin-panel-message-budget.ts' +async function load(contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false + }) + return import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const before = await load( + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }) +) +const after = await load(readFileSync(file, 'utf8')) +const results = [] +for (const count of [5, 1000, 100000]) { + const entries = Array.from({ length: count }, (_, i) => [`key-${i}`, `value-${i}`]) + for (const [kind, value] of [ + ['array', entries.map(([, value]) => value)], + ['map', new Map(entries)], + ['set', new Set(entries.map(([, value]) => value))], + ['object', Object.fromEntries(entries)] + ]) { + const input = structuredClone(value) + for (const cap of [0, 1, 64, 1024, 65536, Infinity]) { + assert.equal( + after.structuredCloneMessageBytes(input, cap), + before.structuredCloneMessageBytes(input, cap) + ) + } + const arms = { before, after } + const iterations = count < 100 ? 1000 : 10 + const run = (arm) => { + global.gc?.() + const start = performance.now() + const cpuStart = process.cpuUsage() + for (let i = 0; i < iterations; i++) { + arms[arm].structuredCloneMessageBytes(input) + } + const cpu = process.cpuUsage(cpuStart) + return { + ms: (performance.now() - start) / iterations, + cpuMs: (cpu.user + cpu.system) / 1000 / iterations + } + } + for (let i = 0; i < 3; i++) { + run('before') + run('after') + } + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + count, + kind, + beforeMs: median(samples.before.map((sample) => sample.ms)), + beforeCpuMs: median(samples.before.map((sample) => sample.cpuMs)), + afterMs: median(samples.after.map((sample) => sample.ms)), + afterCpuMs: median(samples.after.map((sample) => sample.cpuMs)), + samples + }) + } +} +console.log( + JSON.stringify( + { + baseline, + node: process.version, + platform: process.platform, + forcedGc: Boolean(global.gc), + results + }, + null, + 2 + ) +) diff --git a/config/scripts/plugin-shortcut-conflicts-benchmark.mjs b/config/scripts/plugin-shortcut-conflicts-benchmark.mjs new file mode 100644 index 00000000000..c7dd5d3c60e --- /dev/null +++ b/config/scripts/plugin-shortcut-conflicts-benchmark.mjs @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] +if (!baseline) { + throw new Error( + 'Usage: node config/scripts/plugin-shortcut-conflicts-benchmark.mjs ' + ) +} +async function load(file, contents, name) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + logLevel: 'silent', + tsconfigRaw: {}, + banner: { + js: "import { createRequire as benchmarkRequire } from 'node:module'; import { resolve as benchmarkPath } from 'node:path'; const require = benchmarkRequire(benchmarkPath('package.json'));" + } + }) + return ( + await import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) + )[name] +} +const file = 'src/main/plugins/plugin-command-registry.ts' +const before = await load( + file, + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' }), + 'PluginCommandRegistry' +) +const after = await load(file, readFileSync(file, 'utf8'), 'PluginCommandRegistry') +const results = [] +for (const count of [1, 8, 32, 128]) { + const plugins = Array.from({ length: count }, (_, index) => ({ + pluginKey: `sample.plugin-${index}`, + manifest: { + contributes: { + commands: [ + { + id: 'open', + title: 'Open', + action: 'view.tasks', + context: index % 2 ? 'worktree' : 'global' + } + ], + keybindings: [{ command: 'open', key: 'Mod+Alt+T' }] + } + } + })) + const arms = { before: new before(), after: new after() } + for (const platform of ['darwin', 'linux', 'win32']) { + for (const arm of Object.values(arms)) { + arm.reconcile(plugins, () => true, {}, platform) + } + const snapshot = (registry) => ({ + active: registry.list(), + previews: plugins.map((plugin) => registry.preview(plugin.pluginKey)), + errors: plugins.map((plugin) => registry.error(plugin.pluginKey)) + }) + assert.deepEqual(snapshot(arms.after), snapshot(arms.before)) + } + const iterations = 100 + function run(arm) { + const start = performance.now() + for (let i = 0; i < iterations; i++) { + arms[arm].reconcile(plugins, () => true, {}, 'linux') + } + return (performance.now() - start) / iterations + } + const samples = { before: [], after: [] } + run('before') + run('after') + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + function median(values) { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ count, beforeMs: median(samples.before), afterMs: median(samples.after), samples }) +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/pr-code-change-scope.mjs b/config/scripts/pr-code-change-scope.mjs index 8e88aa16df5..3b39bb34275 100644 --- a/config/scripts/pr-code-change-scope.mjs +++ b/config/scripts/pr-code-change-scope.mjs @@ -215,6 +215,7 @@ const WINDOWS_PACKAGE_TESTS = [ ...LINUX_PACKAGE_TESTS, 'config/scripts/rebuild-native-deps.test.mjs', 'config/scripts/rebuild-native-deps-windows-process-tree.test.mjs', + 'src/main/windows-registry-addon.test.ts', 'src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts', 'src/main/providers/pty-repaint-wide-char-buffer.node-pty.test.ts', 'src/shared/child-process/windows-command-line.win32.test.ts', diff --git a/config/scripts/pr-workflow-parallelism.test.mjs b/config/scripts/pr-workflow-parallelism.test.mjs index 92d4fe4c26b..ed4e1b1f1c8 100644 --- a/config/scripts/pr-workflow-parallelism.test.mjs +++ b/config/scripts/pr-workflow-parallelism.test.mjs @@ -328,10 +328,8 @@ describe('PR workflow parallelism', () => { expect(dependencyInstall.run).toContain('--ignore-scripts') expect(dependencyInstall.run).not.toContain('--os=') expect(dependencyInstall.run).not.toContain('--cpu=') - expect(pnpmWorkspace.supportedArchitectures.os).toEqual( - expect.arrayContaining(['current', 'win32']) - ) - expect(pnpmWorkspace.supportedArchitectures.cpu).toContain('current') + expect(pnpmWorkspace.supportedArchitectures.os).toEqual(['current']) + expect(pnpmWorkspace.supportedArchitectures.cpu).toEqual(['current']) const prepareRuntime = dependencyAction.runs.steps.find( (step) => step.name === 'Prepare native runtime' ) @@ -389,8 +387,8 @@ describe('PR workflow parallelism', () => { expect(cacheStep.with.key).toContain('config/scripts/ensure-native-runtime.mjs') expect(cacheStep.with.key).toContain('config/scripts/rebuild-native-deps.mjs') expect(cacheStep.with.path).toContain('node-pty@*/node_modules/node-pty/build') - expect(cacheStep.with.path).toContain('windows-native-registry@') - expect(cacheStep.with.path).toContain('@vscode+windows-process-tree@') + expect(cacheStep.with.path).toContain('native/windows-registry/build') + expect(cacheStep.with.path).toContain('@vscode+windows-process-tre*') expect(cacheStep.with['restore-keys']).toBeUndefined() } expect(steps[cacheIndex].id).toBe('native-cache-restore') diff --git a/config/scripts/raw-markdown-comment-scan-benchmark.mjs b/config/scripts/raw-markdown-comment-scan-benchmark.mjs new file mode 100644 index 00000000000..cc6b5964e61 --- /dev/null +++ b/config/scripts/raw-markdown-comment-scan-benchmark.mjs @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] ?? '20ab9950654' +const file = 'src/renderer/src/components/editor/raw-markdown-html.ts' +async function load(contents) { + const result = await build({ + stdin: { + contents: `${contents}\nexport { createRichMarkdownEditorCodec } from './rich-markdown-source-transport'`, + loader: 'ts', + resolveDir: dirname(resolve(file)) + }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + banner: { + js: `import { createRequire } from 'node:module'; const require = createRequire(${JSON.stringify(resolve('package.json'))});` + } + }) + return import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const arms = { + before: await load( + execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8', windowsHide: true }) + ), + after: await load(readFileSync(file, 'utf8')) +} +const key = '0123456789abcdef0123456789abcdef' +const codecs = Object.fromEntries( + Object.entries(arms).map(([arm, module]) => [arm, module.createRichMarkdownEditorCodec(key)]) +) +const results = [] +for (const [name, input] of [ + ['plain', '# Heading\nOrdinary prose.'], + ['complete', 'before after text\n'.repeat(100)], + ['unclosed-1000', `prefix ${'${'\n## Review findings\n${'Code sample with details\n'.repeat(100)}` + ], + [ + '10000-lines', + `\n## Review findings\n${'Code sample with details\n'.repeat(10000)}` + ], + ['blank-10000-lines', '# > * - _ `\n'.repeat(10000)] +]) { + const comments = Array.from({ length: 10 }, (_, id) => ({ + id, + author: 'reviewer', + authorAvatarUrl: '', + createdAt: '', + url: '', + body + })) + const arms = { before, after } + assert.equal( + after.buildPRCommentBatchConversationReplyBody(comments), + before.buildPRCommentBatchConversationReplyBody(comments) + ) + const run = (arm) => { + global.gc?.() + const start = performance.now() + const cpuStart = process.cpuUsage() + for (let i = 0; i < 10; i++) { + arms[arm].buildPRCommentBatchConversationReplyBody(comments) + } + const cpu = process.cpuUsage(cpuStart) + return { ms: (performance.now() - start) / 10, cpuMs: (cpu.user + cpu.system) / 10000 } + } + run('before') + run('after') + const samples = { before: [], after: [] } + for (const pair of buildCounterbalancedSchedule(10, 'before', 'after')) { + for (const arm of pair) { + samples[arm].push(run(arm)) + } + } + const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + return (sorted[4] + sorted[5]) / 2 + } + results.push({ + name, + beforeCpuMs: median(samples.before.map((s) => s.cpuMs)), + afterCpuMs: median(samples.after.map((s) => s.cpuMs)), + beforeMs: median(samples.before.map((s) => s.ms)), + afterMs: median(samples.after.map((s) => s.ms)), + samples + }) +} +console.log( + JSON.stringify({ baseline, node: process.version, platform: process.platform, results }, null, 2) +) diff --git a/config/scripts/rich-markdown-blank-run-benchmark.mjs b/config/scripts/rich-markdown-blank-run-benchmark.mjs new file mode 100644 index 00000000000..868a4c2ef6d --- /dev/null +++ b/config/scripts/rich-markdown-blank-run-benchmark.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict' +import { readFile, mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' +import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs' + +const root = fileURLToPath(new URL('../..', import.meta.url)) +const entry = join(root, 'src/renderer/src/components/editor/raw-markdown-html.ts') +const source = await readFile(entry, 'utf8') +const cachedProbe = + /if \(index > fenceProbe\) \{[\s\S]*?fenceMatch = fencePrefix.exec\(normalizedContent\)\n \}/ +assert.match(source, cachedProbe) +const oldProbe = String.raw`fenceMatch = normalizedContent.slice(index).match(/^\s*(\x60{3,}|~{3,})/)` +const temp = await mkdtemp(join(tmpdir(), 'orca-rich-blank-bench-')) +try { + const scanners = {} + for (const arm of ['baseline', 'current']) { + const outfile = join(temp, `${arm}.cjs`) + await build({ + stdin: { + contents: `export { encodeRawMarkdownHtmlForRichEditor as encode } from './src/renderer/src/components/editor/raw-markdown-html'; export { createRichMarkdownEditorCodec as codec } from './src/renderer/src/components/editor/rich-markdown-source-transport';`, + resolveDir: root + }, + bundle: true, + platform: 'node', + format: 'cjs', + outfile, + plugins: + arm === 'baseline' + ? [ + { + name: 'old-probe', + setup(plugin) { + plugin.onLoad({ filter: /raw-markdown-html\.ts$/ }, () => ({ + contents: source.replace(cachedProbe, oldProbe), + loader: 'ts', + resolveDir: join(root, 'src/renderer/src/components/editor') + })) + } + } + ] + : [] + }) + const { encode, codec } = createRequire(import.meta.url)(outfile) + scanners[arm] = (content) => encode(content, codec('0'.repeat(32))) + } + const fragments = [ + '\n', + ' \r\n', + '\u00a0\u2028', + '```\n', + '~~~~\n', + '
\n', + '
\n', + '[[doc.md]]\n', + '`inline`\n', + 'prose\n' + ] + let seed = 42 + for (let sample = 0; sample < 256; sample++) { + let content = '' + for (let i = 0; i < 20; i++) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + content += fragments[seed % fragments.length] + } + assert.equal(scanners.current(content), scanners.baseline(content)) + } + for (const [name, content] of [ + ['ordinary', 'ordinary prose\n'.repeat(10000)], + ['blank-100k', '\n'.repeat(100000)], + ['blank-before-fence', `${'\n'.repeat(30000)}\x60\x60\x60\n
\n\x60\x60\x60\n[[doc.md]]`] + ]) { + const samples = { baseline: [], current: [] } + let expected + for (const arms of buildCounterbalancedSchedule(2, 'baseline', 'current')) { + for (const arm of arms) { + const start = performance.now() + const result = scanners[arm](content) + samples[arm].push(performance.now() - start) + expected ??= result + assert.equal(result, expected) + } + } + console.log( + JSON.stringify({ + name, + bytes: Buffer.byteLength(content), + samples, + baseline: summarizeBenchmarkSamples(samples.baseline), + current: summarizeBenchmarkSamples(samples.current) + }) + ) + } +} finally { + await rm(temp, { recursive: true, force: true }) +} diff --git a/config/scripts/rich-markdown-comment-scan-benchmark.mjs b/config/scripts/rich-markdown-comment-scan-benchmark.mjs new file mode 100644 index 00000000000..29c52e4b761 --- /dev/null +++ b/config/scripts/rich-markdown-comment-scan-benchmark.mjs @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { build } from 'esbuild' +import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs' + +const baseline = process.argv[2] ?? '20ab9950654' +const file = 'src/renderer/src/components/editor/markdown-rich-mode.ts' +async function load(contents) { + const result = await build({ + stdin: { contents, loader: 'ts', resolveDir: dirname(resolve(file)) }, + bundle: true, + platform: 'node', + format: 'esm', + write: false, + plugins: [ + { + name: 'cached-round-trip', + setup(bundler) { + bundler.onResolve({ filter: /markdown-round-trip$|^@\/i18n\/i18n$/ }, (args) => ({ + path: args.path, + namespace: 'bench' + })) + bundler.onLoad({ filter: /.*/, namespace: 'bench' }, (args) => ({ + contents: args.path.endsWith('markdown-round-trip') + ? 'export const getRichMarkdownRoundTripOutput = (content) => content' + : 'export const translate = (_key, fallback) => fallback', + loader: 'js' + })) + } + } + ] + }) + return import( + `data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}` + ) +} +const arms = { + before: await load(execFileSync('git', ['show', `${baseline}:${file}`], { encoding: 'utf8' })), + after: await load(readFileSync(file, 'utf8')) +} +const results = [] +for (const [name, content] of [ + ['plain', '# Heading\nOrdinary prose with .'], + ['complete', 'text'], + ['unclosed-1000', '', null], + ['', 'html-or-jsx'], + ['tail', 'html-or-jsx'], + ['">', 'html-or-jsx'], + ['``', null], + ['```html\n\n```', null], + ['\n[a]: https://example.com', 'reference-links'], + ['\n[^a]: footnote', 'reference-links'] + ] as const)('preserves the decision for %j', (content, expected) => { + vi.mocked(getRichMarkdownRoundTripOutput).mockReturnValue(null) + expect(getMarkdownRichModeUnsupportedReason(content)).toBe(expected) + }) + + it('does not pass unclosed comment openers through a comment regex', () => { + const input = '' + ).length + expect(result).toBeNull() + expect(closerSearches).toBeLessThanOrEqual(1) + vi.mocked(getRichMarkdownRoundTripOutput).mockReturnValue( + input.replace('after', 'after') + ) + expect(getMarkdownRichModeUnsupportedReason(input)).toBe('html-or-jsx') + }) +}) diff --git a/src/renderer/src/components/editor/markdown-rich-line-scan.test.ts b/src/renderer/src/components/editor/markdown-rich-line-scan.test.ts new file mode 100644 index 00000000000..2cadf352b82 --- /dev/null +++ b/src/renderer/src/components/editor/markdown-rich-line-scan.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest' +import { getMarkdownRichModeEligibilityDecision } from './markdown-rich-mode' + +const decide = (content: string) => + getMarkdownRichModeEligibilityDecision({ content, sizeOverridden: false }) + +describe('rich Markdown line scanning', () => { + it('does not inspect every prose character to find newline boundaries', () => { + const content = 'Ordinary prose '.repeat(10_000) + const spy = vi.spyOn(String.prototype, 'charCodeAt') + let decision: ReturnType + let calls: number + try { + decision = decide(content) + calls = spy.mock.calls.length + } finally { + spy.mockRestore() + } + expect(decision).toEqual({ exceedsSizeLimit: false, unsupportedReason: null }) + expect(calls).toBeLessThan(10) + }) + + it.each(['\n', '\r\n'])( + 'preserves fences and reference visibility with %j endings', + (newline) => { + for (const fence of ['```', '~~~']) { + const protectedSource = [`${fence}md`, '[ref]: /hidden', fence].join(newline) + expect(decide(protectedSource).unsupportedReason).toBeNull() + expect(decide(protectedSource + newline).unsupportedReason).toBeNull() + expect(decide(`${protectedSource}${newline}[ref]: /visible`).unsupportedReason).toBe( + 'reference-links' + ) + } + } + ) + + it.each(['', '\n', '\r', '\r\n', '`[ref]: /hidden`', '```\n[ref]: /hidden\r'])( + 'preserves empty, final-CR, inline-code and unclosed-fence input %j', + (content) => expect(decide(content).unsupportedReason).toBeNull() + ) +}) diff --git a/src/renderer/src/components/editor/markdown-rich-mode.ts b/src/renderer/src/components/editor/markdown-rich-mode.ts index 142e0cea560..4849380f8fe 100644 --- a/src/renderer/src/components/editor/markdown-rich-mode.ts +++ b/src/renderer/src/components/editor/markdown-rich-mode.ts @@ -48,7 +48,7 @@ const UNSUPPORTED_PATTERNS: UnsupportedMatch[] = [ // Why: the rich editor preserves common embedded markup via placeholder // tokens before parsing, but any HTML shape that still fails round-trip // must fall back instead of risking silent source corruption. - pattern: /<\/?[A-Za-z][\w.:-]*(?:\s[^<>]*)?\/?>|/ + pattern: /<\/?[A-Za-z][\w.:-]*(?:\s[^<>]*)?\/?>/ }, { reason: 'reference-links', @@ -157,6 +157,11 @@ export function getMarkdownRichModeEligibility(params: { } function hasHtmlOrJsx(content: string, pattern: RegExp): boolean { + // A missing closer after the first opener rules out every later opener. + const commentStart = content.indexOf('', commentStart + 4)) { + return true + } for (const match of content.matchAll(new RegExp(pattern, 'g'))) { if (isHtmlOrJsxFragment(match[0])) { return true @@ -166,7 +171,7 @@ function hasHtmlOrJsx(content: string, pattern: RegExp): boolean { } function isHtmlOrJsxFragment(fragment: string): boolean { - if (fragment.startsWith('') for (let index = 0; index < content.length; index++) { if (content.charCodeAt(index) !== 60) { continue @@ -231,7 +236,7 @@ function forEachEmbeddedHtmlFragment( let fragmentEnd: number | null = null if (content.startsWith('', index + 4) + const commentEnd = index + 4 <= lastCommentClose ? content.indexOf('-->', index + 4) : -1 fragmentEnd = commentEnd === -1 ? null : commentEnd + 3 } else { fragmentEnd = getHtmlTagEnd(content, index) diff --git a/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.offset-scan.test.ts b/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.offset-scan.test.ts index e0e703a23e7..e333f7801a9 100644 --- a/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.offset-scan.test.ts +++ b/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.offset-scan.test.ts @@ -76,6 +76,14 @@ function referenceDecorationRanges(content: string): IRange[] { const CORPUS: { name: string; content: string }[] = [ { name: 'empty', content: '' }, + { + name: 'Unicode whitespace before fences', + content: '\u00a0\u2028```\n[[hidden]]\n```\n[[shown]]' + }, + { + name: 'indented blank run before fences', + content: `${`${' '.repeat(80)}\r\n`.repeat(1000)}\`\`\`\n[[hidden]]\n\`\`\`\n[[shown]]` + }, { name: 'no links', content: '# Title\n\nJust prose.\n' }, { name: 'single link', content: '# Title\n\nSee [[notes.md]] for details.\n' }, { name: 'two links on one line', content: 'See [[a.md]] and [[b.md]].\n' }, diff --git a/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.ts b/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.ts index e81ba97ce1b..457213e0c12 100644 --- a/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.ts +++ b/src/renderer/src/components/editor/monaco-markdown-doc-link-decorations.ts @@ -41,12 +41,11 @@ function isInsideSpan(index: number, spans: number[]): boolean { return false } -const FENCE_PREFIX_RE = /\s*(?:```|~~~)/y +const FENCE_PREFIX_RE = /[^\S\n]*(?:```|~~~)/y function startsCodeFence(content: string, lineStart: number, lineEnd: number): boolean { FENCE_PREFIX_RE.lastIndex = lineStart - // Why: a sticky `\s*` run can cross the newline into the next line, so an - // out-of-line match is rejected to stay identical to the old per-line regex. + // Bound whitespace to this line so blank runs cannot trigger repeated suffix scans. return FENCE_PREFIX_RE.test(content) && FENCE_PREFIX_RE.lastIndex <= lineEnd } diff --git a/src/renderer/src/components/editor/raw-markdown-comment-scan.test.ts b/src/renderer/src/components/editor/raw-markdown-comment-scan.test.ts new file mode 100644 index 00000000000..81e6990f058 --- /dev/null +++ b/src/renderer/src/components/editor/raw-markdown-comment-scan.test.ts @@ -0,0 +1,45 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { encodeRawMarkdownHtmlForRichEditor } from './raw-markdown-html' +import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport' + +const key = '0123456789abcdef0123456789abcdef' +afterEach(() => vi.restoreAllMocks()) + +describe('raw Markdown HTML comment scanning', () => { + it('does not repeatedly match a suffix with no comment closer', () => { + const codec = createRichMarkdownEditorCodec(key) + const input = `prefix ${' after ')} after '])( + 'keeps protected or incomplete text literal: %j', + (input) => { + expect(encodeRawMarkdownHtmlForRichEditor(input, createRichMarkdownEditorCodec(key))).toBe( + input + ) + } + ) + it('retains the existing block-only handling of an overlapping marker', () => { + const codec = createRichMarkdownEditorCodec(key) + expect(encodeRawMarkdownHtmlForRichEditor('', codec)).toBe( + codec.transport.create('block-html', '') + ) + }) +}) diff --git a/src/renderer/src/components/editor/raw-markdown-html.blank-run.test.ts b/src/renderer/src/components/editor/raw-markdown-html.blank-run.test.ts new file mode 100644 index 00000000000..1ee24f73003 --- /dev/null +++ b/src/renderer/src/components/editor/raw-markdown-html.blank-run.test.ts @@ -0,0 +1,40 @@ +import { runInNewContext } from 'node:vm' +import { describe, expect, it } from 'vitest' +import { encodeRawMarkdownHtmlForRichEditor } from './raw-markdown-html' +import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport' + +function encodeWithDeadline(content: string): string { + // Interrupt a quadratic regression without hanging the synchronous test worker. + return runInNewContext( + 'encode(content, codec)', + { + encode: encodeRawMarkdownHtmlForRichEditor, + content, + codec: createRichMarkdownEditorCodec('0'.repeat(32)) + }, + { timeout: 1500 } + ) +} + +describe('rich Markdown blank-run encoding', () => { + it('preserves a long blank document without repeatedly searching its suffix', () => { + const content = '\n'.repeat(100_000) + expect(encodeWithDeadline(content)).toBe(content) + }) + + it('inlines a reference definition after a long blank prefix without rescanning it', () => { + const blankPrefix = '\n'.repeat(100_000) + const content = `${blankPrefix}[Docs]\n\n[docs]: https://example.com/docs\n` + expect(encodeWithDeadline(content)).toBe(`${blankPrefix}[Docs](https://example.com/docs)\n\n`) + }) + + it('preserves code and surrounding HTML after a long blank prefix', () => { + const blankPrefix = '\n'.repeat(100_000) + const content = '```\n
inside
\n```\nafter' + const codec = createRichMarkdownEditorCodec('0'.repeat(32)) + const expected = encodeRawMarkdownHtmlForRichEditor(content, codec) + expect(expected).toContain('
inside
') + expect(expected).not.toContain('after') + expect(encodeWithDeadline(blankPrefix + content)).toBe(blankPrefix + expected) + }) +}) diff --git a/src/renderer/src/components/editor/raw-markdown-html.ts b/src/renderer/src/components/editor/raw-markdown-html.ts index cde5311e80b..1262d5254ab 100644 --- a/src/renderer/src/components/editor/raw-markdown-html.ts +++ b/src/renderer/src/components/editor/raw-markdown-html.ts @@ -59,20 +59,27 @@ export function encodeRawMarkdownHtmlForRichEditor( { htmlSuperscriptLinks = false }: { htmlSuperscriptLinks?: boolean } = {} ): string { const normalizedContent = normalizeMarkdownReferenceLinks(content) + const lastCommentClose = normalizedContent.lastIndexOf('-->') const { transport } = codec let index = 0 let isLineStart = true let activeFence: '`' | '~' | null = null let activeFenceLength = 0 let result = '' + const nonWhitespace = /\S/g + const fencePrefix = /(`{3,}|~{3,})/y + let fenceProbe = -1 + let fenceMatch: RegExpExecArray | null = null while (index < normalizedContent.length) { if (isLineStart) { - // Why: only line starts inspect the rest of the line, so slicing the suffix on every - // character (one throwaway string per char) is pure waste — compute it here. On a large - // doc this drops O(n) suffix allocations from the rich-editor open path (#7056). - const lineRest = normalizedContent.slice(index) - const fenceMatch = lineRest.match(/^\s*(`{3,}|~{3,})/) + // Reuse the lookahead across blank lines, preserving cross-line fence semantics. + if (index > fenceProbe) { + nonWhitespace.lastIndex = index + fenceProbe = nonWhitespace.exec(normalizedContent)?.index ?? normalizedContent.length + fencePrefix.lastIndex = fenceProbe + fenceMatch = fencePrefix.exec(normalizedContent) + } if (fenceMatch) { const fenceChar = fenceMatch[1][0] as '`' | '~' const fenceLength = fenceMatch[1].length @@ -175,7 +182,11 @@ export function encodeRawMarkdownHtmlForRichEditor( continue } } - const inlineHtml = matchInlineHtml(normalizedContent.slice(index)) + // An unterminated comment cannot match; later tags must still be encoded. + const inlineHtml = + normalizedContent.startsWith('/g, ' ') + .split('\n') + .map((line) => + line + .replace(/^[\s>#*\-_`]+/, '') + .replace(/\s+/g, ' ') + .trim() + ) + .find((line) => line.length > 0) + if (!line) { + return 'comment' + } + return `comment — ${line.length > 72 ? `${line.slice(0, 71).trimEnd()}…` : line}` +} +afterEach(() => vi.restoreAllMocks()) + +describe('review acknowledgement summary', () => { + it.each([ + '', + '\n\r\n\t', + '# > ** _ - `\n\nReadable', + '\n## Hello\r\nignored', + 'onetwo\nignored', + 'visible -->', + 'a\rb\nc', + '\u00a0##\u2028Hello\u2029world', + 'a'.repeat(71), + 'a'.repeat(72), + 'a'.repeat(73), + `${'a'.repeat(70)} b`, + `${'a'.repeat(70)}😀tail`, + '\n\n', + 'first\n\nlast' + ])('preserves the existing label for %j', (body) => { + expect(describePRCommentAckTarget(comment(body))).toBe(originalSummary(body)) + }) + + it('skips tail normalization and full-document line splitting', () => { + const input = comment(`## Heading\n${'tail with whitespace\n'.repeat(10_000)}`) + const replace = vi.spyOn(String.prototype, 'replace') + const split = vi.spyOn(String.prototype, 'split') + const actual = describePRCommentAckTarget(input) + const replaces = replace.mock.calls.length + const splits = split.mock.calls.length + expect(actual).toBe('comment — Heading') + expect(replaces).toBe(3) + expect(splits).toBe(0) + }) +}) diff --git a/src/renderer/src/components/right-sidebar/pr-comment-fixing-reply-body.ts b/src/renderer/src/components/right-sidebar/pr-comment-fixing-reply-body.ts index 5122a653ef9..5a12b6bc17a 100644 --- a/src/renderer/src/components/right-sidebar/pr-comment-fixing-reply-body.ts +++ b/src/renderer/src/components/right-sidebar/pr-comment-fixing-reply-body.ts @@ -24,22 +24,26 @@ const ACK_SNIPPET_MAX_LENGTH = 72 /** First readable line of a comment body, minus HTML comments and markdown markers. */ function summarizePRCommentBody(body: string): string { - const line = body - .replace(//g, ' ') - .split('\n') - .map((candidate) => - candidate - .replace(/^[\s>#*\-_`]+/, '') - .replace(/\s+/g, ' ') - .trim() - ) - .find((candidate) => candidate.length > 0) - if (!line) { - return '' + const cleaned = body.replace(//g, ' ') + let start = 0 + while (start <= cleaned.length) { + const newline = cleaned.indexOf('\n', start) + const line = cleaned + .slice(start, newline === -1 ? cleaned.length : newline) + .replace(/^[\s>#*\-_`]+/, '') + .replace(/\s+/g, ' ') + .trim() + if (line) { + return line.length > ACK_SNIPPET_MAX_LENGTH + ? `${line.slice(0, ACK_SNIPPET_MAX_LENGTH - 1).trimEnd()}…` + : line + } + if (newline === -1) { + break + } + start = newline + 1 } - return line.length > ACK_SNIPPET_MAX_LENGTH - ? `${line.slice(0, ACK_SNIPPET_MAX_LENGTH - 1).trimEnd()}…` - : line + return '' } /** Short "what this was" label so the batched reply names each item without quoting it whole. */ diff --git a/src/renderer/src/components/sidebar/smart-sort.ts b/src/renderer/src/components/sidebar/smart-sort.ts index ffebad8cf75..012012eb53b 100644 --- a/src/renderer/src/components/sidebar/smart-sort.ts +++ b/src/renderer/src/components/sidebar/smart-sort.ts @@ -188,9 +188,9 @@ export function sortWorktreesSmart( // Why: `tabHasLivePty` (over `ptyIdsByTabId`) is the source of truth for // liveness — slept terminals retain `tab.ptyId` as a wake hint, so reading // it directly would falsely keep cold-start ordering off after restart. - const hasAnyLivePty = Object.values(tabsByWorktree) - .flat() - .some((tab) => tabHasLivePty(ptyIdsByTabId, tab.id)) + const hasAnyLivePty = Object.values(tabsByWorktree).some((tabs) => + tabs.some((tab) => tabHasLivePty(ptyIdsByTabId, tab.id)) + ) const now = Date.now() const labels = buildWorktreeSortLabels(worktrees) diff --git a/src/renderer/src/components/sidebar/worktree-card-surface.tsx b/src/renderer/src/components/sidebar/worktree-card-surface.tsx index a045a7b2d32..baf68056993 100644 --- a/src/renderer/src/components/sidebar/worktree-card-surface.tsx +++ b/src/renderer/src/components/sidebar/worktree-card-surface.tsx @@ -53,7 +53,7 @@ export function WorktreeCardSurface({ card }: { card: WorktreeCardController }): // Why: the live data attribute updates before React state during navigation, // so it must own the complete active style without stale utility classes. isLineageDropTarget - ? 'border border-accent-foreground/20 bg-accent/80' + ? 'border border-worktree-sidebar-foreground/40 bg-worktree-sidebar-accent text-worktree-sidebar-accent-foreground ring-1 ring-inset ring-worktree-sidebar-ring/60' : isActiveSurface ? 'border border-transparent' : isMultiSelected @@ -72,7 +72,10 @@ export function WorktreeCardSurface({ card }: { card: WorktreeCardController }): isRuntimeDisconnected && !isDeleting && 'opacity-60' )} data-worktree-card-surface="true" - data-worktree-card-active={isActiveSurface ? activeSurfaceVariant : undefined} + data-worktree-card-active={ + isActiveSurface && !isLineageDropTarget ? activeSurfaceVariant : undefined + } + data-worktree-lineage-drop-target={isLineageDropTarget || undefined} onClick={handleClick} onDoubleClick={affiliateListMode ? undefined : handleDoubleClick} draggable={!affiliateListMode && nativeDragEnabled && !isDeleting && !titleRenaming} diff --git a/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.test.ts b/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.test.ts index dc8785f7b84..ba74bec19e1 100644 --- a/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.test.ts +++ b/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.test.ts @@ -14,25 +14,41 @@ describe('isWorktreeLineageDropZoneHit', () => { it('keeps the top and bottom of a card available for reorder drops', () => { const rect = { top: 100, bottom: 200 } as DOMRect - expect(isWorktreeLineageDropZoneHit({ pointerY: 120, rect })).toBe(false) + expect(isWorktreeLineageDropZoneHit({ pointerY: 107, rect })).toBe(false) expect(isWorktreeLineageDropZoneHit({ pointerY: 150, rect })).toBe(true) - expect(isWorktreeLineageDropZoneHit({ pointerY: 180, rect })).toBe(false) + expect(isWorktreeLineageDropZoneHit({ pointerY: 193, rect })).toBe(false) }) - it('caps the parent-drop band on tall cards', () => { + it('makes most of tall cards available for nesting', () => { const rect = { top: 0, bottom: 180 } as DOMRect - expect(isWorktreeLineageDropZoneHit({ pointerY: 67, rect })).toBe(false) + expect(isWorktreeLineageDropZoneHit({ pointerY: 8, rect })).toBe(true) expect(isWorktreeLineageDropZoneHit({ pointerY: 90, rect })).toBe(true) - expect(isWorktreeLineageDropZoneHit({ pointerY: 113, rect })).toBe(false) + expect(isWorktreeLineageDropZoneHit({ pointerY: 172, rect })).toBe(true) + }) + + it('scales down reorder gutters for compact cards', () => { + const rect = { top: 100, bottom: 120 } + + expect(isWorktreeLineageDropZoneHit({ pointerY: 103, rect })).toBe(false) + expect(isWorktreeLineageDropZoneHit({ pointerY: 104, rect })).toBe(true) + expect(isWorktreeLineageDropZoneHit({ pointerY: 116, rect })).toBe(true) + expect(isWorktreeLineageDropZoneHit({ pointerY: 117, rect })).toBe(false) + }) + + it.each([ + { top: 100, bottom: 100 }, + { top: 100, bottom: 90 } + ])('rejects empty and inverted rectangles', (rect) => { + expect(isWorktreeLineageDropZoneHit({ pointerY: 100, rect })).toBe(false) }) }) describe('getWorktreeLineageDropTargetId', () => { - it('returns the row id only when the pointer is in the card content middle band', () => { + it('returns the row id only when the pointer is away from the reorder gutters', () => { const { container, target } = makeTarget({ worktreeId: 'parent', top: 100, bottom: 200 }) - expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 120 })).toBeNull() + expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 107 })).toBeNull() expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 150 })).toBe('parent') }) @@ -60,6 +76,62 @@ describe('getWorktreeLineageDropTargetId', () => { expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 150 })).toBe('parent') } ) + + it('accepts horizontal card padding outside the content element', () => { + const { container } = makeTarget({ worktreeId: 'parent', top: 100, bottom: 200 }) + const row = container.firstElementChild! + const padding = document.createElement('div') + row.append(padding) + + expect(getWorktreeLineageDropTargetId({ container, target: padding, pointerY: 120 })).toBe( + 'parent' + ) + expect(getWorktreeLineageDropTargetId({ container, target: row, pointerY: 180 })).toBe('parent') + expect(getWorktreeLineageDropTargetId({ container, target: padding, pointerY: 201 })).toBeNull() + }) + + it.each([true, false])( + 'keeps descendants out of the ancestor hit zone (inline content: %s)', + (insideParentContent) => { + const { container, target } = makeTarget({ + worktreeId: 'parent', + top: 100, + bottom: insideParentContent ? 300 : 200 + }) + const child = makeTarget({ worktreeId: 'child', top: 220, bottom: 300 }) + const parentContent = target.closest('[data-worktree-card-parent-content]')! + const children = document.createElement('div') + children.append(child.container.firstElementChild!) + const childrenHost = insideParentContent ? parentContent : container.firstElementChild! + childrenHost.append(children) + const childRow = children.firstElementChild as HTMLElement + childRow.getBoundingClientRect = () => ({ top: 220, bottom: 300 }) as DOMRect + + expect( + getWorktreeLineageDropTargetId({ container, target: child.target, pointerY: 250 }) + ).toBe('child') + expect(getWorktreeLineageDropTargetId({ container, target: childRow, pointerY: 250 })).toBe( + 'child' + ) + expect( + getWorktreeLineageDropTargetId({ container, target: children, pointerY: 250 }) + ).toBeNull() + expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 120 })).toBe('parent') + } + ) + + it('does not use a descendant content element for a row without its own content', () => { + const { container, target } = makeTarget({ worktreeId: 'child', top: 100, bottom: 200 }) + const parentRow = document.createElement('div') + parentRow.setAttribute('data-worktree-drag-id', 'parent') + parentRow.append(container.firstElementChild!) + container.append(parentRow) + + expect( + getWorktreeLineageDropTargetId({ container, target: parentRow, pointerY: 150 }) + ).toBeNull() + expect(getWorktreeLineageDropTargetId({ container, target, pointerY: 150 })).toBe('child') + }) }) describe('getReorderedWorktreeIdsToUnnest', () => { diff --git a/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.ts b/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.ts index a67c7ab3e43..9ef4e069dc2 100644 --- a/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.ts +++ b/src/renderer/src/components/sidebar/worktree-lineage-drag-drop.ts @@ -5,8 +5,8 @@ import { getLineageRenderInfo } from './worktree-lineage-projection' const WORKTREE_CARD_CONTENT_TARGET_SELECTOR = '[data-worktree-card-parent-content]' const WORKTREE_DRAG_ROW_SELECTOR = '[data-worktree-drag-id]' -const LINEAGE_DROP_ZONE_RATIO = 0.4 -const LINEAGE_DROP_ZONE_MAX_HEIGHT_PX = 44 +const REORDER_GUTTER_RATIO = 0.2 +const REORDER_GUTTER_MAX_HEIGHT_PX = 8 type VerticalRect = Pick @@ -19,9 +19,9 @@ export function isWorktreeLineageDropZoneHit(args: { return false } - const zoneHeight = Math.min(height * LINEAGE_DROP_ZONE_RATIO, LINEAGE_DROP_ZONE_MAX_HEIGHT_PX) - const zoneTop = args.rect.top + (height - zoneHeight) / 2 - const zoneBottom = args.rect.bottom - (height - zoneHeight) / 2 + const gutterHeight = Math.min(height * REORDER_GUTTER_RATIO, REORDER_GUTTER_MAX_HEIGHT_PX) + const zoneTop = args.rect.top + gutterHeight + const zoneBottom = args.rect.bottom - gutterHeight return args.pointerY >= zoneTop && args.pointerY <= zoneBottom } @@ -30,26 +30,31 @@ export function getWorktreeLineageDropTargetId(args: { target: Element pointerY: number }): string | null { - const contentTarget = args.target.closest(WORKTREE_CARD_CONTENT_TARGET_SELECTOR) - if (!contentTarget || !args.container.contains(contentTarget)) { + const rowTarget = args.target.closest(WORKTREE_DRAG_ROW_SELECTOR) + if (!rowTarget || !args.container.contains(rowTarget)) { return null } - // Why: nesting should be deliberate; the top/bottom of a card stays available - // for reorder drops instead of treating the whole card as a parent target. + const contentTarget = rowTarget.querySelector(WORKTREE_CARD_CONTENT_TARGET_SELECTOR) + if (!contentTarget || contentTarget.closest(WORKTREE_DRAG_ROW_SELECTOR) !== rowTarget) { + return null + } + + const rect = contentTarget.getBoundingClientRect() + // Legacy cards include descendants inside parent content; keep their rows out of its hit zone. + const firstChildRow = contentTarget.querySelector(WORKTREE_DRAG_ROW_SELECTOR) + const bottom = firstChildRow + ? Math.min(rect.bottom, firstChildRow.getBoundingClientRect().top) + : rect.bottom if ( !isWorktreeLineageDropZoneHit({ pointerY: args.pointerY, - rect: contentTarget.getBoundingClientRect() + rect: { top: rect.top, bottom } }) ) { return null } - const rowTarget = contentTarget.closest(WORKTREE_DRAG_ROW_SELECTOR) - if (!rowTarget || !args.container.contains(rowTarget)) { - return null - } return rowTarget.getAttribute('data-worktree-drag-id') } diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.test.ts b/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.test.ts new file mode 100644 index 00000000000..437891d6f69 --- /dev/null +++ b/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.test.ts @@ -0,0 +1,259 @@ +// @vitest-environment happy-dom +import { act, cleanup, renderHook } from '@testing-library/react' +import { useWorktreeDragRuntime } from './use-runtime' +import { useWorktreePointerDragWindowEvents } from './use-pointer-window-events' +import { commitWorktreePointerDrop } from './pointer-commit' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { flushWorktreePointerDragFrame, type WorktreePointerDragFrameArgs } from './pointer-flush' +import { NO_WORKTREE_SIDEBAR_DROP_TARGET, WORKTREE_ROW_DRAG_INITIAL_STATE } from './row-state' + +vi.mock('../../workspace-kanban-sidebar-drop', () => ({ + clearWorkspaceKanbanSidebarDropTargetVisual: vi.fn(), + hasWorkspaceKanbanSidebarDropBoard: () => true, + isWorkspaceKanbanSidebarDropPointInBoard: () => false, + updateWorkspaceKanbanSidebarDropTargetVisual: () => ({ status: null, isPinDrop: false }) +})) + +vi.mock('./pointer-commit', () => ({ commitWorktreePointerDrop: vi.fn() })) + +afterEach(() => { + cleanup() + vi.restoreAllMocks() +}) + +function setup() { + let time = 0 + let nextFrame: FrameRequestCallback | null = null + vi.spyOn(performance, 'now').mockImplementation(() => time) + vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => { + nextFrame = callback + return 1 + }) + let state = WORKTREE_ROW_DRAG_INITIAL_STATE + let target = NO_WORKTREE_SIDEBAR_DROP_TARGET + const offsets = new Map([['parent', 56]]) + const args: WorktreePointerDragFrameArgs = { + drag: { + pointerId: 1, + sourceRow: document.createElement('div'), + startX: 100, + startY: 400, + currentX: 100, + currentY: 300, + worktreeId: 'child', + draggedIds: ['child'], + reorderDraggedIds: ['child'], + reorderUnitDraggedIds: ['child'], + sourceGroupKey: 'repo', + rects: [], + active: true, + preview: document.createElement('div'), + previewOffsetX: 20, + previewOffsetY: 20, + workspaceBoardDragPreviewRequested: false, + frameId: null, + reorderIntent: null, + latestBoardDropTarget: null, + latestStatusDropTarget: null + }, + ctx: { + scrollRef: { current: null }, + workspaceStatuses: [], + worktreeDragGroups: [], + worktreeDragUnitGroups: [], + refreshWorktreeDragSession: () => true, + getEligibleLineageDropTarget: () => target, + computeWorktreeDrop: () => ({ + dropIndex: 1, + dropIndicatorY: 300, + dropAnchorId: 'parent', + previewOffsetsByWorktreeId: offsets + }), + computeWorktreeStatusDrop: () => null, + commitWorktreeLineageParentDrop: () => true, + clearReorderedWorktreeParents: vi.fn(), + clearWorktreeDrag: vi.fn(), + onMoveWorktreesToStatus: vi.fn(), + onMoveWorktreesToStatusAtIndex: vi.fn(), + onReorderWorktrees: vi.fn(), + onPinWorktrees: vi.fn() + }, + workspaceBoardOpen: false, + onWorkspaceBoardDragPreviewStart: vi.fn(), + onWorkspaceBoardDragPreviewCommit: vi.fn(), + shouldShowWorkspaceBoardDropIndicator: () => false, + setDragOverStatus: vi.fn(), + setPinDragOver: vi.fn(), + setWorktreeDragState: (update) => { + state = typeof update === 'function' ? update(state) : update + } + } + return { + args, + offsets, + state: () => state, + nest: (id: string | null) => { + target = { ...NO_WORKTREE_SIDEBAR_DROP_TARGET, lineageParentId: id } + }, + tick: (ms: number) => { + time += ms + const callback = nextFrame + nextFrame = null + callback?.(time) + } + } +} + +describe('combined nesting and animated reordering', () => { + it('lets the pointer cross an edge into nesting without moving the destination', () => { + const t = setup() + flushWorktreePointerDragFrame(t.args) + expect(t.state().previewOffsetsByWorktreeId.size).toBe(0) + t.nest('parent') + t.tick(80) + expect(t.state().lineageDropTargetId).toBe('parent') + expect(t.state().previewOffsetsByWorktreeId.size).toBe(0) + t.tick(200) + expect(t.state().lineageDropTargetId).toBe('parent') + expect(t.state().dropIndicatorY).toBeNull() + }) + + it('opens the reorder gap, holds it during nesting, then restores the edge preview', () => { + const t = setup() + flushWorktreePointerDragFrame(t.args) + t.tick(160) + expect(t.state().previewOffsetsByWorktreeId).toBe(t.offsets) + expect(t.state().dropIndicatorY).toBe(300) + t.nest('parent') + flushWorktreePointerDragFrame(t.args) + expect(t.state().lineageDropTargetId).toBe('parent') + expect(t.state().previewOffsetsByWorktreeId).toBe(t.offsets) + expect(t.state().dropIndicatorY).toBeNull() + t.nest(null) + flushWorktreePointerDragFrame(t.args) + t.tick(160) + expect(t.state().lineageDropTargetId).toBeNull() + expect(t.state().previewOffsetsByWorktreeId).toBe(t.offsets) + expect(t.state().dropIndicatorY).toBe(300) + }) + + it('clears the old line during a new intent and stops scheduling after settling', () => { + const t = setup() + flushWorktreePointerDragFrame(t.args) + t.tick(160) + expect(t.state().dropIndicatorY).toBe(300) + t.args.drag.currentY = 356 + t.args.ctx.computeWorktreeDrop = () => ({ + dropIndex: 2, + dropIndicatorY: 356, + dropAnchorId: null, + previewOffsetsByWorktreeId: t.offsets + }) + flushWorktreePointerDragFrame(t.args) + expect(t.state().dropIndicatorY).toBeNull() + expect(t.state().previewOffsetsByWorktreeId).toBe(t.offsets) + t.tick(160) + expect(t.state().dropIndicatorY).toBe(356) + const frames = vi.mocked(window.requestAnimationFrame).mock.calls.length + t.tick(1000) + expect(vi.mocked(window.requestAnimationFrame).mock.calls).toHaveLength(frames) + }) +}) + +describe('stationary pointer autoscroll', () => { + it.each([1, -1])('keeps the gap tracking slots while scrolling in direction %s', (direction) => { + const t = setup() + const container = document.createElement('div') + t.args.ctx.scrollRef.current = container + let index = 10 + let offsets = new Map([['parent', 56]]) + t.args.ctx.computeWorktreeDrop = () => ({ + dropIndex: index, + dropIndicatorY: 300 - container.scrollTop, + dropAnchorId: null, + previewOffsetsByWorktreeId: offsets + }) + flushWorktreePointerDragFrame(t.args) + for (let frame = 1; frame <= 6; frame++) { + index += direction + container.scrollTop += direction * 56 + offsets = new Map([[`row-${index}`, direction * 56]]) + t.tick(80) + flushWorktreePointerDragFrame(t.args) + if (frame >= 2) { + expect(t.state().previewOffsetsByWorktreeId).toBe(offsets) + expect(t.state().dropIndicatorY).toBe(300 - container.scrollTop) + } + } + expect(t.args.drag.currentY).toBe(300) + }) +}) + +describe('Escape during pointer dragging', () => { + function renderDrag() { + const t = setup() + const cancelBoard = vi.fn() + const { result, unmount } = renderHook(() => { + const runtime = useWorktreeDragRuntime({ + worktreeDragSessionRef: { current: null }, + statusDropAnchorsRef: { current: new Map() }, + onWorkspaceBoardDragPreviewCancel: cancelBoard + }) + useWorktreePointerDragWindowEvents({ + ctx: t.args.ctx, + runtime, + beginWorktreePointerDrag: vi.fn(), + scheduleWorktreePointerDragFrame: vi.fn(), + onWorkspaceBoardDragPreviewCommit: vi.fn(), + onDropWorktreesOnWorkspaceBoard: vi.fn() + }) + return runtime + }) + result.current.worktreePointerDragRef.current = t.args.drag + if (t.args.drag.preview) { + document.body.append(t.args.drag.preview) + } + return { ...t, result, unmount, cancelBoard } + } + + it('removes the preview and cancels frames without committing on pointer release', () => { + const t = renderDrag() + const cancelFrame = vi.spyOn(window, 'cancelAnimationFrame') + t.args.drag.frameId = 12 + t.result.current.pointerAutoscrollFrameIdRef.current = 13 + const escape = new KeyboardEvent('keydown', { key: 'Escape', cancelable: true }) + act(() => window.dispatchEvent(escape)) + expect(escape.defaultPrevented).toBe(true) + expect(t.result.current.worktreePointerDragRef.current).toBeNull() + expect(t.args.drag.preview?.isConnected).toBe(false) + expect(cancelFrame).toHaveBeenCalledWith(12) + expect(cancelFrame).toHaveBeenCalledWith(13) + expect(t.cancelBoard).toHaveBeenCalledOnce() + window.dispatchEvent(new PointerEvent('pointerup', { pointerId: 1 })) + expect(commitWorktreePointerDrop).not.toHaveBeenCalled() + expect(t.result.current.worktreeDragState).toBe(WORKTREE_ROW_DRAG_INITIAL_STATE) + }) + + it('leaves other keys and Escape without a drag available to the app', () => { + const t = renderDrag() + const enter = new KeyboardEvent('keydown', { key: 'Enter', cancelable: true }) + window.dispatchEvent(enter) + expect(enter.defaultPrevented).toBe(false) + expect(t.result.current.worktreePointerDragRef.current).toBe(t.args.drag) + act(() => t.result.current.clearWorktreeDrag()) + const escape = new KeyboardEvent('keydown', { key: 'Escape', cancelable: true }) + window.dispatchEvent(escape) + expect(escape.defaultPrevented).toBe(false) + expect(t.cancelBoard).toHaveBeenCalledOnce() + }) + + it('removes its Escape listener on unmount', () => { + const t = renderDrag() + t.unmount() + const escape = new KeyboardEvent('keydown', { key: 'Escape', cancelable: true }) + window.dispatchEvent(escape) + expect(escape.defaultPrevented).toBe(false) + expect(t.cancelBoard).not.toHaveBeenCalled() + t.args.drag.preview?.remove() + }) +}) diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.ts b/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.ts index 3baf8e81762..0c9017fc0f5 100644 --- a/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.ts +++ b/src/renderer/src/components/sidebar/worktree-list/drag/pointer-flush.ts @@ -11,6 +11,7 @@ import { getPointerDropStatusTarget, shouldPreferSidebarStatusDropTarget } from import type { WorktreeDropCommitContext } from './drop-commit-context' import { applyWorktreeDropPreview, + applyWorktreeLineageDropPreview, clearWorktreeDropPreview, NO_WORKTREE_SIDEBAR_DROP_TARGET, updateLatestWorktreeStatusDropTarget, @@ -19,6 +20,8 @@ import { type WorktreeSidebarLineageDropTarget } from './row-state' +const REORDER_INTENT_DELAY_MS = 160 + export type WorktreePointerDragFrameArgs = { drag: WorktreePointerDrag ctx: WorktreeDropCommitContext @@ -83,6 +86,7 @@ export function flushWorktreePointerDragFrame(args: WorktreePointerDragFrameArgs if (!drag.active || !drag.preview) { return } + delete drag.preview.dataset.worktreeSidebarNesting updateSidebarDragPreviewPosition({ preview: drag.preview, pointerX: drag.currentX, @@ -121,6 +125,7 @@ export function flushWorktreePointerDragFrame(args: WorktreePointerDragFrameArgs args.onWorkspaceBoardDragPreviewCommit() } if (boardTarget.status || boardTarget.isPinDrop) { + drag.reorderIntent = null drag.latestStatusDropTarget = null clearInsertionLine(args) return @@ -137,10 +142,24 @@ export function flushWorktreePointerDragFrame(args: WorktreePointerDragFrameArgs : NO_WORKTREE_SIDEBAR_DROP_TARGET, drag.draggedIds ) - if (preferredStatusTarget.lineageParentId) { + const lineageParentId = preferredStatusTarget.lineageParentId + if (lineageParentId) { + drag.reorderIntent = null updateLatestWorktreeStatusDropTarget(drag, preferredStatusTarget, null) clearWorkspaceKanbanSidebarDropTargetVisual() - clearInsertionLine(args) + drag.preview.dataset.worktreeSidebarNesting = 'true' + updateSidebarDragPreviewPosition({ + preview: drag.preview, + pointerX: drag.currentX, + pointerY: drag.currentY, + offsetX: drag.previewOffsetX, + offsetY: drag.previewOffsetY + }) + args.setDragOverStatus(null) + args.setPinDragOver(false) + args.setWorktreeDragState((prev) => + applyWorktreeLineageDropPreview(prev, lineageParentId, drag.currentY) + ) return } if ( @@ -150,15 +169,42 @@ export function flushWorktreePointerDragFrame(args: WorktreePointerDragFrameArgs workspaceStatuses: ctx.workspaceStatuses }) ) { + drag.reorderIntent = null showStatusHoverWithoutInsertionLine(args, preferredStatusTarget) return } const drop = ctx.computeWorktreeDrop(drag.currentY) if (!drop) { + drag.reorderIntent = null showStatusHoverWithoutInsertionLine(args, preferredStatusTarget) return } + // Let the pointer cross a reorder gutter into the card before moving its target. + let intent = drag.reorderIntent + if (!intent || (intent.dropIndex !== drop.dropIndex && intent.pointerY !== drag.currentY)) { + intent = { + dropIndex: drop.dropIndex, + pointerY: drag.currentY, + startedAt: performance.now() + } + } else { + // Autoscroll changes slots beneath a stationary pointer without renewing intent. + intent.dropIndex = drop.dropIndex + intent.pointerY = drag.currentY + } + drag.reorderIntent = intent + if (performance.now() - intent.startedAt < REORDER_INTENT_DELAY_MS) { + drag.latestStatusDropTarget = null + args.setWorktreeDragState((prev) => + clearWorktreeDropPreview(prev, { + pointerY: drag.currentY, + preserveOffsets: true + }) + ) + drag.frameId = window.requestAnimationFrame(() => flushWorktreePointerDragFrame(args)) + return + } drag.latestStatusDropTarget = null clearWorkspaceKanbanSidebarDropTargetVisual() args.setDragOverStatus(null) diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/row-state.test.ts b/src/renderer/src/components/sidebar/worktree-list/drag/row-state.test.ts new file mode 100644 index 00000000000..f6b21d04667 --- /dev/null +++ b/src/renderer/src/components/sidebar/worktree-list/drag/row-state.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { + applyWorktreeDropPreview, + applyWorktreeLineageDropPreview, + clearWorktreeDropPreview, + WORKTREE_ROW_DRAG_INITIAL_STATE +} from './row-state' + +const reorderPreview = { + dropAnchorId: 'parent', + dropIndex: 2, + dropIndicatorY: 180, + previewOffsetsByWorktreeId: new Map([['parent', -60]]) +} + +function reorderedState() { + return applyWorktreeDropPreview( + { ...WORKTREE_ROW_DRAG_INITIAL_STATE, draggingWorktreeId: 'child' }, + reorderPreview, + { pointerY: 150, matchPointerY: true } + ) +} + +describe('lineage drop preview', () => { + it('keeps the hovered card in place while replacing the reorder line with nesting', () => { + const before = reorderedState() + const nesting = applyWorktreeLineageDropPreview(before, 'parent', 150) + expect(nesting.previewOffsetsByWorktreeId).toBe(before.previewOffsetsByWorktreeId) + expect(nesting.dropIndex).toBeNull() + expect(nesting.dropIndicatorY).toBeNull() + expect(nesting.lineageDropTargetId).toBe('parent') + expect(applyWorktreeLineageDropPreview(nesting, 'parent', 150)).toBe(nesting) + }) + + it('repaints a target change even when the pointer stays at the same height', () => { + const before = applyWorktreeLineageDropPreview(reorderedState(), 'parent', 150) + const after = applyWorktreeLineageDropPreview(before, 'other-parent', 150) + expect(after).not.toBe(before) + expect(after.lineageDropTargetId).toBe('other-parent') + }) + + it('clears nesting feedback when returning to a reorder edge at the same height', () => { + const before = applyWorktreeLineageDropPreview(reorderedState(), 'parent', 150) + const after = applyWorktreeDropPreview(before, reorderPreview, { + pointerY: 150, + matchPointerY: true + }) + expect(after.lineageDropTargetId).toBeNull() + expect(after.dropIndicatorY).toBe(180) + }) + + it('clears nesting when leaving the sidebar even without pointer Y movement or offsets', () => { + const before = applyWorktreeLineageDropPreview(WORKTREE_ROW_DRAG_INITIAL_STATE, 'parent', 150) + const after = clearWorktreeDropPreview(before, { pointerY: 150, matchPointerY: true }) + expect(after).not.toBe(before) + expect(after.lineageDropTargetId).toBeNull() + expect(after.previewOffsetsByWorktreeId.size).toBe(0) + }) +}) diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/row-state.ts b/src/renderer/src/components/sidebar/worktree-list/drag/row-state.ts index 480f78d0a6e..1fd257df808 100644 --- a/src/renderer/src/components/sidebar/worktree-list/drag/row-state.ts +++ b/src/renderer/src/components/sidebar/worktree-list/drag/row-state.ts @@ -13,6 +13,7 @@ export type WorktreeRowDragState = { dropIndicatorY: number | null previewOffsetsByWorktreeId: ReadonlyMap pointerY: number | null + lineageDropTargetId: string | null } export const EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS: ReadonlyMap = new Map() @@ -23,7 +24,8 @@ export const WORKTREE_ROW_DRAG_INITIAL_STATE: WorktreeRowDragState = { dropIndex: null, dropIndicatorY: null, previewOffsetsByWorktreeId: EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, - pointerY: null + pointerY: null, + lineageDropTargetId: null } export type WorktreePointerDrag = { @@ -45,6 +47,7 @@ export type WorktreePointerDrag = { previewOffsetY: number workspaceBoardDragPreviewRequested: boolean frameId: number | null + reorderIntent: { dropIndex: number; pointerY: number; startedAt: number } | null latestBoardDropTarget: WorkspaceKanbanCardTrackedDropTarget | null latestStatusDropTarget: WorktreeSidebarTrackedStatusDropTarget | null } @@ -97,20 +100,24 @@ export function updateLatestWorktreeStatusDropTarget( // updates deliberately keep the previous state identity in that case. export function clearWorktreeDropPreview( previous: WorktreeRowDragState, - args: { pointerY: number | null; matchPointerY?: boolean } + args: { pointerY: number | null; matchPointerY?: boolean; preserveOffsets?: boolean } ): WorktreeRowDragState { const unchanged = + previous.lineageDropTargetId === null && previous.dropIndex === null && previous.dropIndicatorY === null && - previous.previewOffsetsByWorktreeId.size === 0 && + (args.preserveOffsets === true || previous.previewOffsetsByWorktreeId.size === 0) && (args.matchPointerY !== true || previous.pointerY === args.pointerY) return unchanged ? previous : { ...previous, + lineageDropTargetId: null, dropIndex: null, dropIndicatorY: null, - previewOffsetsByWorktreeId: EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, + previewOffsetsByWorktreeId: args.preserveOffsets + ? previous.previewOffsetsByWorktreeId + : EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, pointerY: args.pointerY } } @@ -121,6 +128,7 @@ export function applyWorktreeDropPreview( args: { pointerY: number; matchPointerY?: boolean } ): WorktreeRowDragState { const unchanged = + previous.lineageDropTargetId === null && previous.dropIndex === drop.dropIndex && previous.dropIndicatorY === drop.dropIndicatorY && (args.matchPointerY !== true || previous.pointerY === args.pointerY) && @@ -128,5 +136,30 @@ export function applyWorktreeDropPreview( previous.previewOffsetsByWorktreeId, drop.previewOffsetsByWorktreeId ) - return unchanged ? previous : { ...previous, ...drop, pointerY: args.pointerY } + return unchanged + ? previous + : { ...previous, ...drop, lineageDropTargetId: null, pointerY: args.pointerY } +} + +export function applyWorktreeLineageDropPreview( + previous: WorktreeRowDragState, + lineageDropTargetId: string, + pointerY: number +): WorktreeRowDragState { + if ( + previous.lineageDropTargetId === lineageDropTargetId && + previous.dropIndex === null && + previous.dropIndicatorY === null && + previous.pointerY === pointerY + ) { + return previous + } + // Keep the target under the pointer when switching from a reorder gap to nesting. + return { + ...previous, + lineageDropTargetId, + dropIndex: null, + dropIndicatorY: null, + pointerY + } } diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/use-native-drag.ts b/src/renderer/src/components/sidebar/worktree-list/drag/use-native-drag.ts index b3f7f2f54b7..e83ff97334b 100644 --- a/src/renderer/src/components/sidebar/worktree-list/drag/use-native-drag.ts +++ b/src/renderer/src/components/sidebar/worktree-list/drag/use-native-drag.ts @@ -72,6 +72,7 @@ export function useWorktreeNativeDrag(args: { setWorktreeDragState({ draggingWorktreeId: worktreeId, sourceGroupKey, + lineageDropTargetId: null, dropIndex: null, dropIndicatorY: null, previewOffsetsByWorktreeId: EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-drag.ts b/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-drag.ts index 7db25f797eb..1b751a18e9b 100644 --- a/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-drag.ts +++ b/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-drag.ts @@ -136,6 +136,7 @@ export function useWorktreePointerDrag(args: { setWorktreeDragState({ draggingWorktreeId: drag.worktreeId, sourceGroupKey: drag.sourceGroupKey, + lineageDropTargetId: null, dropIndex: null, dropIndicatorY: null, previewOffsetsByWorktreeId: EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, @@ -207,6 +208,7 @@ export function useWorktreePointerDrag(args: { previewOffsetY: 0, workspaceBoardDragPreviewRequested: false, frameId: null, + reorderIntent: null, latestBoardDropTarget: null, latestStatusDropTarget: null } diff --git a/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-window-events.ts b/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-window-events.ts index b2989fb06ac..fa8be3a21b6 100644 --- a/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-window-events.ts +++ b/src/renderer/src/components/sidebar/worktree-list/drag/use-pointer-window-events.ts @@ -78,10 +78,21 @@ export function useWorktreePointerDragWindowEvents(args: { clearWorktreeDrag() } + const handleKeyDown = (event: KeyboardEvent): void => { + if (event.key !== 'Escape' || !worktreePointerDragRef.current) { + return + } + event.preventDefault() + event.stopPropagation() + clearWorktreeDrag() + } + + window.addEventListener('keydown', handleKeyDown, { capture: true }) window.addEventListener('pointermove', handlePointerMove, { capture: true }) window.addEventListener('pointerup', handlePointerUp, { capture: true }) window.addEventListener('pointercancel', handlePointerCancel, { capture: true }) return () => { + window.removeEventListener('keydown', handleKeyDown, { capture: true }) window.removeEventListener('pointermove', handlePointerMove, { capture: true }) window.removeEventListener('pointerup', handlePointerUp, { capture: true }) window.removeEventListener('pointercancel', handlePointerCancel, { capture: true }) diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx b/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx index 91ebb321726..775ecdaedb6 100644 --- a/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx +++ b/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx @@ -23,7 +23,7 @@ import type { LineageToggleHandler } from '../../worktree-lineage-toggle-handler import { stopNestedWorktreeCardBubble } from './header-event-guards' import type { WorktreeItemRow } from '../listing/renderable-rows' import { getWorktreeOptionId } from './option-dom' -import type { WorktreePointerDrag, WorktreeRowDragState } from '../drag/row-state' +import type { WorktreeRowDragState } from '../drag/row-state' export type WorktreeItemRowContext = { settings: AppState['settings'] @@ -33,7 +33,6 @@ export type WorktreeItemRowContext = { groupIndexByRowKey: ReadonlyMap agentSendTargetWorktreeId: string | null worktreeDragState: WorktreeRowDragState - worktreePointerDragRef: React.MutableRefObject nativeLineageDropTargetId: string | null activeWorktreeId: string | null activeWorkspaceExecutionHostId: ExecutionHostId | null @@ -139,8 +138,7 @@ export function renderWorktreeItemRow( const worktreeIdentity = getWorktreeHostIdentity(itemRow.worktree) const isLineageDropTarget = ctx.worktreeDragState.draggingWorktreeId && - (ctx.worktreePointerDragRef.current?.latestStatusDropTarget?.target.lineageParentId === - itemRow.worktree.id || + (ctx.worktreeDragState.lineageDropTargetId === itemRow.worktree.id || ctx.nativeLineageDropTargetId === itemRow.worktree.id) const isActiveWorktree = ctx.activeWorktreeId === itemRow.worktree.id && diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/virtual-row-dispatch.tsx b/src/renderer/src/components/sidebar/worktree-list/rows/virtual-row-dispatch.tsx index 2981021c76a..2954a2ad861 100644 --- a/src/renderer/src/components/sidebar/worktree-list/rows/virtual-row-dispatch.tsx +++ b/src/renderer/src/components/sidebar/worktree-list/rows/virtual-row-dispatch.tsx @@ -124,6 +124,7 @@ function renderLineageGroupVirtualRow( ref={ctx.measureVirtualRowElement} className={cn( 'absolute left-0 right-0 top-0', + parent?.worktree.id === ctx.worktreeDragState.draggingWorktreeId && 'pointer-events-none', ctx.worktreeDragState.draggingWorktreeId !== null && 'transition-transform duration-150 ease-out will-change-transform' )} @@ -234,6 +235,7 @@ export function renderWorktreeVirtualRow( data-workspace-status={itemWorkspaceStatus ?? undefined} className={cn( 'absolute left-0 right-0 top-0', + row.worktree.id === ctx.worktreeDragState.draggingWorktreeId && 'pointer-events-none', ctx.worktreeDragState.draggingWorktreeId !== null && 'transition-transform duration-150 ease-out will-change-transform' )} diff --git a/src/renderer/src/components/sidebar/worktree-list/viewport/virtual-row-context.ts b/src/renderer/src/components/sidebar/worktree-list/viewport/virtual-row-context.ts index 2787b93a002..ff6572b9e51 100644 --- a/src/renderer/src/components/sidebar/worktree-list/viewport/virtual-row-context.ts +++ b/src/renderer/src/components/sidebar/worktree-list/viewport/virtual-row-context.ts @@ -117,7 +117,6 @@ export function buildWorktreeVirtualRowContext(args: BuildArgs): WorktreeVirtual groupIndexByRowKey: session.groupIndexByRowKey, agentSendTargetWorktreeId: props.agentSendTargetWorktreeId, worktreeDragState: runtime.worktreeDragState, - worktreePointerDragRef: runtime.worktreePointerDragRef, nativeLineageDropTargetId: runtime.nativeLineageDropTargetId, activeWorktreeId: props.activeWorktreeId, activeWorkspaceExecutionHostId: props.activeWorkspaceExecutionHostId, diff --git a/src/renderer/src/components/sidebar/worktree-sidebar-pointer-drag-dom.ts b/src/renderer/src/components/sidebar/worktree-sidebar-pointer-drag-dom.ts index 54f302f872b..1aa2f523311 100644 --- a/src/renderer/src/components/sidebar/worktree-sidebar-pointer-drag-dom.ts +++ b/src/renderer/src/components/sidebar/worktree-sidebar-pointer-drag-dom.ts @@ -59,8 +59,10 @@ export function updateSidebarDragPreviewPosition(args: { offsetX: number offsetY: number }): void { - const x = args.pointerX - args.offsetX - const y = args.pointerY - args.offsetY + const nesting = args.preview.hasAttribute('data-worktree-sidebar-nesting') + // Keep the destination title visible while the pointer rests inside it. + const x = nesting ? args.pointerX + 16 : args.pointerX - args.offsetX + const y = nesting ? args.pointerY + 16 : args.pointerY - args.offsetY args.preview.style.transform = `translate3d(${x}px, ${y}px, 0) scale(1.015)` } diff --git a/src/renderer/src/components/skills/skill-agent-filter.test.ts b/src/renderer/src/components/skills/skill-agent-filter.test.ts new file mode 100644 index 00000000000..da59a1002a6 --- /dev/null +++ b/src/renderer/src/components/skills/skill-agent-filter.test.ts @@ -0,0 +1,203 @@ +import { describe, expect, it } from 'vitest' +import type { DiscoveredSkill } from '../../../../shared/skills' +import { skillAgentOptions, skillMatchesAgent } from './skill-agent-filter' + +function skill(overrides: Partial = {}): DiscoveredSkill { + return { + id: 'id', + name: 'Review', + description: 'Code review', + providers: ['agent-skills'], + sourceKind: 'home', + sourceLabel: 'Agent skills home', + rootPath: '/a', + directoryPath: '/a/review', + skillFilePath: '/a/review/SKILL.md', + installed: true, + updatedAt: null, + ...overrides + } +} + +/** Pre-change implementation, kept verbatim as the parity oracle. */ +function legacySkillMatchesAgent( + target: DiscoveredSkill, + agentId: string, + agentByRootPath: ReadonlyMap +): boolean { + const agents = (): string[] => { + const roots = target.rootPaths?.length ? target.rootPaths : [target.rootPath] + return [...new Set(roots.map((root) => agentByRootPath.get(root)).filter(Boolean))] as string[] + } + return agentId === 'all' || agents().includes(agentId) +} + +/** Counts ownership lookups so the before/after difference is deterministic. */ +class LookupCountingMap extends Map { + lookups = 0 + override get(key: string): string | undefined { + this.lookups += 1 + return super.get(key) + } +} + +const WINDOWS_ROOT = 'C:\\Users\\dev\\.agents\\skills' +const OPAQUE_REMOTE_ROOT = 'orca-ssh://build-box/srv/shared/.agents/skills' + +const OWNERS_WITH_EMPTY: [string, string][] = [ + ['/a', 'claude'], + ['/b', 'codex'], + ['/c', 'codex'], + ['/empty', ''], + [WINDOWS_ROOT, 'shared'], + [OPAQUE_REMOTE_ROOT, 'claude'] +] +const OWNERS_WITHOUT_EMPTY: [string, string][] = OWNERS_WITH_EMPTY.filter( + ([, owner]) => owner !== '' +).map(([root, owner]) => (root === '/a' ? [root, 'shared'] : [root, owner])) + +describe('skillMatchesAgent', () => { + it('matches the legacy agent list across sparse, repeated, and opaque roots', () => { + const rootPathsCases: (string[] | undefined)[] = [ + undefined, + [], + ['/a'], + ['/a', '/a'], + ['/a', '/b'], + ['/b', '/a'], + ['/missing'], + ['/a', '/missing'], + ['/empty'], + ['/a', '/empty'], + [WINDOWS_ROOT], + [OPAQUE_REMOTE_ROOT] + ] + const rootPathCases = ['/a', '/missing', '/empty', WINDOWS_ROOT, OPAQUE_REMOTE_ROOT] + const agentIds = ['all', 'claude', 'codex', 'shared', '', 'unknown'] + const maps = [new Map(OWNERS_WITH_EMPTY), new Map(OWNERS_WITHOUT_EMPTY)] + + let cases = 0 + for (const rootPaths of rootPathsCases) { + for (const rootPath of rootPathCases) { + for (const agentId of agentIds) { + for (const agentByRootPath of maps) { + const row = skill({ rootPath, rootPaths }) + expect({ + rootPaths, + rootPath, + agentId, + matched: skillMatchesAgent(row, agentId, agentByRootPath) + }).toEqual({ + rootPaths, + rootPath, + agentId, + matched: legacySkillMatchesAgent(row, agentId, agentByRootPath) + }) + cases += 1 + } + } + } + } + expect(cases).toBe(720) + }) + + it('treats populated rootPaths as authoritative instead of unioning rootPath', () => { + const agentByRootPath = new Map(OWNERS_WITH_EMPTY) + const row = skill({ rootPath: '/a', rootPaths: ['/b'] }) + expect(skillMatchesAgent(row, 'claude', agentByRootPath)).toBe(false) + expect(skillMatchesAgent(row, 'codex', agentByRootPath)).toBe(true) + }) + + it('falls back to rootPath only when rootPaths is absent or empty', () => { + const agentByRootPath = new Map(OWNERS_WITH_EMPTY) + expect(skillMatchesAgent(skill({ rootPaths: undefined }), 'claude', agentByRootPath)).toBe(true) + expect(skillMatchesAgent(skill({ rootPaths: [] }), 'claude', agentByRootPath)).toBe(true) + }) + + it('never lets an empty owner become a filter', () => { + const agentByRootPath = new Map(OWNERS_WITH_EMPTY) + expect(skillMatchesAgent(skill({ rootPath: '/empty' }), '', agentByRootPath)).toBe(false) + expect(skillMatchesAgent(skill({ rootPaths: ['/empty'] }), '', agentByRootPath)).toBe(false) + expect(skillMatchesAgent(skill({ rootPath: '/a' }), '', agentByRootPath)).toBe(false) + }) + + it('keeps "all" a success before any ownership lookup', () => { + const agentByRootPath = new LookupCountingMap(OWNERS_WITH_EMPTY) + expect( + skillMatchesAgent(skill({ rootPaths: ['/a', '/b', '/c'] }), 'all', agentByRootPath) + ).toBe(true) + expect(agentByRootPath.lookups).toBe(0) + }) + + it('cuts ownership lookups from 30,000 to 10,000 for 10,000 three-root rows', () => { + const rows = Array.from({ length: 10_000 }, (_, index) => + skill({ id: `skill-${index}`, rootPaths: ['/a', '/b', '/c'] }) + ) + const before = new LookupCountingMap(OWNERS_WITH_EMPTY) + const after = new LookupCountingMap(OWNERS_WITH_EMPTY) + + const legacyMatches = rows.map((row) => legacySkillMatchesAgent(row, 'claude', before)) + const matches = rows.map((row) => skillMatchesAgent(row, 'claude', after)) + + expect(matches).toEqual(legacyMatches) + expect(matches.every(Boolean)).toBe(true) + expect(before.lookups).toBe(30_000) + expect(after.lookups).toBe(10_000) + }) + + it('still scans every root when none of them owns the filtered agent', () => { + const agentByRootPath = new LookupCountingMap(OWNERS_WITH_EMPTY) + expect( + skillMatchesAgent(skill({ rootPaths: ['/a', '/b', '/c'] }), 'unknown', agentByRootPath) + ).toBe(false) + expect(agentByRootPath.lookups).toBe(3) + }) +}) + +describe('skillAgentOptions', () => { + it('still counts every owning root once per skill', () => { + const options = skillAgentOptions({ + scannedAt: 1, + sources: [ + { + id: 'a', + label: 'A', + path: '/a', + sourceKind: 'home', + providers: [], + owner: 'claude', + exists: true + }, + { + id: 'b', + label: 'B', + path: '/b', + sourceKind: 'repo', + providers: [], + owner: 'codex', + exists: true + }, + { + id: 'shared', + label: 'S', + path: '/s', + sourceKind: 'repo', + providers: [], + owner: null, + exists: true + } + ], + skills: [ + skill({ id: 'one', rootPaths: ['/a', '/a', '/b'] }), + skill({ id: 'two', rootPaths: ['/a'] }), + skill({ id: 'three', rootPath: '/s', rootPaths: undefined }), + skill({ id: 'four', rootPath: '/missing', rootPaths: undefined }) + ] + }) + expect(options.map((option) => [option.id, option.count])).toEqual([ + ['claude', 2], + ['codex', 1], + ['shared', 1] + ]) + }) +}) diff --git a/src/renderer/src/components/skills/skill-agent-filter.ts b/src/renderer/src/components/skills/skill-agent-filter.ts index 78c35057137..bb497206f13 100644 --- a/src/renderer/src/components/skills/skill-agent-filter.ts +++ b/src/renderer/src/components/skills/skill-agent-filter.ts @@ -35,12 +35,22 @@ function skillAgents( return [...new Set(roots.map((root) => agentByRootPath.get(root)).filter(Boolean))] as string[] } +/** Membership only, so it stops at the first owning root instead of materializing + * the deduplicated agent list `skillAgentOptions` still needs for counting. */ export function skillMatchesAgent( skill: DiscoveredSkill, agentId: string, agentByRootPath: ReadonlyMap ): boolean { - return agentId === 'all' || skillAgents(skill, agentByRootPath).includes(agentId) + if (agentId === 'all') { + return true + } + if (!agentId) { + return false // An empty owner is never a filter; parity with the dropped `.filter(Boolean)`. + } + return skill.rootPaths?.length + ? skill.rootPaths.some((root) => agentByRootPath.get(root) === agentId) + : agentByRootPath.get(skill.rootPath) === agentId } /** Only agents that actually hold a skill; an empty root is not a filter. */ diff --git a/src/renderer/src/components/skills/skill-source-inventory.test.ts b/src/renderer/src/components/skills/skill-source-inventory.test.ts index 67fd41965e8..1d863ea3f6b 100644 --- a/src/renderer/src/components/skills/skill-source-inventory.test.ts +++ b/src/renderer/src/components/skills/skill-source-inventory.test.ts @@ -41,6 +41,61 @@ function result(overrides: Partial = {}): SkillDiscoveryRe } describe('summarizeSkillSources', () => { + it('counts each skill once per root, including a primary root omitted from rootPaths', () => { + const shared = skill({ rootPaths: ['/other', '/other', '/unknown'] }) + const home = source() + const other = source({ path: '/other' }) + const entries = summarizeSkillSources( + result({ + sources: [home, other, other, source({ path: '/OTHER' })], + skills: [shared, shared, skill({ rootPaths: [home.path, home.path] })] + }) + ) + expect(entries.map((entry) => entry.skillCount)).toEqual([3, 2, 2, 0]) + expect(entries[0].source).toBe(home) + expect(entries[1].source).toBe(other) + expect(entries[2].source).toBe(other) + }) + + it('does not scan every skill again for each source', () => { + let rootReads = 0 + const skills = Array.from({ length: 1000 }, () => ({ + ...skill(), + get rootPath() { + rootReads++ + return '/home/dev/.agents/skills' + } + })) + const sources = Array.from({ length: 87 }, (_, index) => source({ id: `${index}` })) + const entries = summarizeSkillSources(result({ skills, sources })) + expect(entries.every((entry) => entry.skillCount === 1000)).toBe(true) + expect(rootReads).toBeLessThanOrEqual(skills.length) + }) + + it('does not inspect skills without sources', () => { + const unused = { + ...skill(), + get rootPath(): string { + throw new Error('No source needs a count') + } + } + expect(summarizeSkillSources(null)).toEqual([]) + expect(summarizeSkillSources(result({ skills: [unused] }))).toEqual([]) + }) + + it('accepts frozen ownership lists and inputs without changing them', () => { + const home = Object.freeze(source()) + const item = skill({ rootPaths: [home.path, '/co-owner', home.path] }) + Object.freeze(item.rootPaths) + Object.freeze(item) + const discovery = result({ sources: [home, source({ path: '/co-owner' })], skills: [item] }) + Object.freeze(discovery.sources) + Object.freeze(discovery.skills) + Object.freeze(discovery) + expect(summarizeSkillSources(discovery).map((entry) => entry.skillCount)).toEqual([1, 1]) + expect(item.rootPaths).toEqual([home.path, '/co-owner', home.path]) + }) + it('counts a symlinked skill under every root that reached it', () => { const shared = source({ id: 'repo', path: '/repo/.agents/skills', sourceKind: 'repo' }) const entries = summarizeSkillSources( diff --git a/src/renderer/src/components/skills/skill-source-inventory.ts b/src/renderer/src/components/skills/skill-source-inventory.ts index 1bedd5fa02b..708334530f1 100644 --- a/src/renderer/src/components/skills/skill-source-inventory.ts +++ b/src/renderer/src/components/skills/skill-source-inventory.ts @@ -13,8 +13,6 @@ export type SkillSourceInventoryEntry = { } function ownsSkill(source: SkillDiscoverySource, skill: DiscoveredSkill): boolean { - // Why: a symlinked skill is deduped to one row but keeps every root that - // reached it, so counting only `rootPath` would zero out the co-owning roots. return skill.rootPath === source.path || (skill.rootPaths?.includes(source.path) ?? false) } @@ -37,12 +35,38 @@ function sourceStatus(source: SkillDiscoverySource): SkillSourceStatus { export function summarizeSkillSources( result: SkillDiscoveryResult | null ): SkillSourceInventoryEntry[] { - if (!result) { + if (!result || result.sources.length === 0) { return [] } + // With no repeated skill traversal, the direct count needs no index. + if (result.sources.length === 1 || result.skills.length === 0) { + return result.sources.map((source) => ({ + source, + skillCount: result.skills.filter((skill) => ownsSkill(source, skill)).length, + status: sourceStatus(source) + })) + } + const counts = new Map( + result.sources.map((source) => [source.path, { count: 0, lastSkillIndex: -1 }]) + ) + const countRoot = (rootPath: string, skillIndex: number): void => { + const count = counts.get(rootPath) + // Symlinked skills can name one owning root more than once. + if (count && count.lastSkillIndex !== skillIndex) { + count.count++ + count.lastSkillIndex = skillIndex + } + } + for (let index = 0; index < result.skills.length; index++) { + const skill = result.skills[index] + countRoot(skill.rootPath, index) + for (const rootPath of skill.rootPaths ?? []) { + countRoot(rootPath, index) + } + } return result.sources.map((source) => ({ source, - skillCount: result.skills.filter((skill) => ownsSkill(source, skill)).length, + skillCount: counts.get(source.path)?.count ?? 0, status: sourceStatus(source) })) } diff --git a/src/renderer/src/components/status-bar/UpdateStatusSegment.test.tsx b/src/renderer/src/components/status-bar/UpdateStatusSegment.test.tsx new file mode 100644 index 00000000000..085795246cf --- /dev/null +++ b/src/renderer/src/components/status-bar/UpdateStatusSegment.test.tsx @@ -0,0 +1,156 @@ +// @vitest-environment happy-dom +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { UpdateStatus } from '../../../../shared/update-status-types' +import { useAppStore } from '../../store' +import { UpdateCard } from '../UpdateCard' +import { TooltipProvider } from '../ui/tooltip' +import { UpdateStatusSegment } from './UpdateStatusSegment' + +const check = vi.fn() +const message = 'Could not reach the update server: net::ERR_CONNECTION_REFUSED' +const error: UpdateStatus = { state: 'error', message } +const actionableErrors: UpdateStatus[] = [ + { ...error, userInitiated: true }, + { ...error, version: '1.4.200' }, + { + ...error, + recovery: { + kind: 'linux-package-install', + packageType: 'deb', + reason: 'manual-install-required', + version: '1.4.200' + } + } +] + +function setStatus(status: UpdateStatus): void { + act(() => useAppStore.getState().setUpdateStatus(status)) +} + +function renderUpdateControls(): void { + render( + + + + + ) +} + +function errorToggle(): HTMLElement { + return screen.getByRole('button', { name: 'Update failed. Click to expand.' }) +} + +beforeEach(() => { + useAppStore.setState(useAppStore.getInitialState(), true) + check.mockReset().mockResolvedValue(undefined) + vi.stubGlobal( + 'matchMedia', + vi.fn().mockReturnValue({ + matches: true, + addEventListener: vi.fn(), + removeEventListener: vi.fn() + }) + ) + Object.defineProperty(window, 'api', { + configurable: true, + value: { updater: { check } } + }) +}) + +afterEach(() => { + cleanup() + useAppStore.setState(useAppStore.getInitialState(), true) + vi.unstubAllGlobals() +}) + +describe('update status disclosure', () => { + it('opens background check failure details on the first click and offers a re-check', () => { + renderUpdateControls() + setStatus({ state: 'checking' }) + setStatus(error) + + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + fireEvent.click(errorToggle()) + + expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy() + expect(errorToggle().getAttribute('aria-expanded')).toBe('true') + fireEvent.click(screen.getByRole('button', { name: 'Show details' })) + expect(screen.getByText(message)).toBeTruthy() + fireEvent.click(screen.getByRole('button', { name: 'Re-check' })) + expect(check).toHaveBeenCalledWith({ includePrerelease: false }) + + fireEvent.click(errorToggle()) + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + expect(errorToggle().getAttribute('aria-expanded')).toBe('false') + }) + + it('announces a quiet automatic check failure as collapsed', () => { + setStatus(error) + renderUpdateControls() + + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + expect(errorToggle().getAttribute('aria-expanded')).toBe('false') + }) + + it('preserves the disclosure choice until the next automatic check', () => { + renderUpdateControls() + setStatus(error) + fireEvent.click(errorToggle()) + setStatus({ ...error, message: 'Still unavailable' }) + expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy() + + fireEvent.click(errorToggle()) + setStatus(error) + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + fireEvent.click(errorToggle()) + + setStatus({ state: 'checking' }) + setStatus(error) + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + expect(errorToggle().getAttribute('aria-expanded')).toBe('false') + }) + + it.each([ + { state: 'checking', userInitiated: true }, + { state: 'available', version: '1.4.200', changelog: null }, + { state: 'downloading', version: '1.4.200', percent: 50 }, + { state: 'downloaded', version: '1.4.200' } + ])('opens an error after $state without requiring a status click', (previousStatus) => { + setStatus(previousStatus) + setStatus(error) + renderUpdateControls() + + expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy() + expect(errorToggle().getAttribute('aria-expanded')).toBe('true') + }) + + it.each(actionableErrors)( + 'opens an explicit actionable error on initial receipt: %j', + (status) => { + setStatus(status) + renderUpdateControls() + + expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy() + expect( + screen.getByRole('button', { name: /Click to expand/ }).getAttribute('aria-expanded') + ).toBe('true') + } + ) + + it.each(actionableErrors)( + 'opens a newly actionable error and preserves dismissal on repeat: %j', + (status) => { + renderUpdateControls() + setStatus(error) + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + + setStatus(status) + expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy() + + fireEvent.click(screen.getByRole('button', { name: /Click to expand/ })) + setStatus({ ...status }) + expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull() + } + ) +}) diff --git a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts index 7e5dd27b6bb..5cd992e2b60 100644 --- a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts +++ b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts @@ -39,13 +39,13 @@ import { buildResourceSessionBindingIndex, type ResourceSessionBindingIndex } from './resource-session-bindings' +import { + resolveResourceFolderWorkspace, + resolveResourceWorkspaceHost +} from './resource-workspace-host' // ─── Helpers ──────────────────────────────────────────────────────── -function deriveRepoIdFromWorktreeId(worktreeId: string): string { - return getRepoIdFromWorktreeId(worktreeId) -} - function deriveWorktreeNameFromWorktreeId(worktreeId: string): string { return getWorktreePathBasenameFromId(worktreeId) ?? worktreeId } @@ -156,24 +156,7 @@ export function mergeSnapshotAndSessions( daemonSessions.map((session) => [session.id, session.agentOwnership]) ) - function isRepoRemote(repoId: string): boolean { - // Why: missing entry === we don't know about this repo (typically the - // unattributed bucket or a session whose repo metadata never made it - // into the renderer). Treat unknown as not-remote so a missing-data - // edge case can never spuriously flip the chip on. The chip should - // only fire when we have positive evidence the repo is SSH-backed. - return ctx.repoConnectionIdById.get(repoId) != null - } - - function isRuntimeScopedRepo(repoId: string): boolean { - return ctx.repoRuntimeScopedById.get(repoId) === true - } - - function ensureRepo( - repoId: string, - repoName: string, - initiallyHasRemoteChildren = false - ): UnifiedProjectGroup { + function ensureRepo(repoId: string, repoName: string): UnifiedProjectGroup { const existing = repos.get(repoId) if (existing) { return existing @@ -183,7 +166,7 @@ export function mergeSnapshotAndSessions( repoName, cpu: null, memory: null, - hasRemoteChildren: initiallyHasRemoteChildren || isRepoRemote(repoId), + hasRemoteChildren: false, worktrees: [] } repos.set(repoId, next) @@ -200,6 +183,7 @@ export function mergeSnapshotAndSessions( function appendWorktreeRow(repo: UnifiedProjectGroup, row: UnifiedWorktreeRow): void { repo.worktrees.push(row) + repo.hasRemoteChildren ||= row.isRemote const rows = worktreeRowsByRepo.get(repo.repoId)! if (!rows.has(row.worktreeId)) { rows.set(row.worktreeId, row) @@ -209,12 +193,16 @@ export function mergeSnapshotAndSessions( // ── Step 1: ingest snapshot worktrees as the local-truth foundation. if (snapshot) { for (const wt of snapshot.worktrees as readonly WorktreeMemory[]) { + const worktree = resolveResourceFolderWorkspace(ctx, wt.worktreeId) + const repoId = worktree?.repoId ?? wt.repoId + const repoName = (worktree && ctx.repoDisplayNameById.get(repoId)) || wt.repoName + const { isRemote, isRuntimeScoped } = resolveResourceWorkspaceHost(ctx, wt.worktreeId, repoId) // Why: local snapshot data must never render under a runtime-hosted repo // row; belt-and-braces with the matching session-ingest guard below. - if (isRuntimeScopedRepo(wt.repoId)) { + if (isRuntimeScoped) { continue } - const repo = ensureRepo(wt.repoId, wt.repoName) + const repo = ensureRepo(repoId, repoName) const sessions: UnifiedSessionRow[] = wt.sessions.map((s) => { seenSessionIds.add(s.sessionId) const tabId = index.ptyIdToTabId.get(s.sessionId) ?? null @@ -233,14 +221,14 @@ export function mergeSnapshotAndSessions( }) appendWorktreeRow(repo, { worktreeId: wt.worktreeId, - worktreeName: wt.worktreeName, - repoId: wt.repoId, - repoName: wt.repoName, + worktreeName: worktree?.displayName?.trim() || wt.worktreeName, + repoId, + repoName, cpu: wt.cpu, memory: wt.memory, history: wt.history, hasLocalSamples: true, - isRemote: isRepoRemote(wt.repoId), + isRemote, sessions, browsers: [] }) @@ -258,35 +246,37 @@ export function mergeSnapshotAndSessions( const tabId = index.ptyIdToTabId.get(session.id) ?? null let worktreeId = tabId ? (index.tabIdToWorktreeId.get(tabId) ?? null) : null - // 2b: @@-parse — recover worktreeId from the minted session id format. + // Prefer daemon metadata; older publishers may only encode the workspace in the session id. if (!worktreeId) { - worktreeId = parsePtySessionId(session.id).worktreeId + worktreeId = session.worktreeId || parsePtySessionId(session.id).worktreeId } // 2c: unattributed bucket. const isUnattributed = !worktreeId const finalWorktreeId = worktreeId ?? `${UNATTRIBUTED_REPO_ID}::${session.id}` + const worktree = resolveResourceFolderWorkspace(ctx, finalWorktreeId) const finalRepoId = isUnattributed ? UNATTRIBUTED_REPO_ID - : deriveRepoIdFromWorktreeId(finalWorktreeId) + : (worktree?.repoId ?? getRepoIdFromWorktreeId(finalWorktreeId)) const finalRepoName = isUnattributed ? UNATTRIBUTED_REPO_NAME : ctx.repoDisplayNameById.get(finalRepoId) || finalRepoId const finalWorktreeName = isUnattributed ? session.title || session.id.slice(0, 12) - : deriveWorktreeNameFromWorktreeId(finalWorktreeId) + : worktree?.displayName?.trim() || deriveWorktreeNameFromWorktreeId(finalWorktreeId) // Why: the current daemon inputs are local/SSH only; this guard prevents a // future local daemon row accidentally exposing kill actions for runtime PTYs. - if (isRuntimeScopedRepo(finalRepoId)) { + const { isRemote, isRuntimeScoped } = resolveResourceWorkspaceHost( + ctx, + finalWorktreeId, + finalRepoId + ) + if (isRuntimeScoped) { continue } - const repoIsRemote = isRepoRemote(finalRepoId) - const repo = ensureRepo(finalRepoId, finalRepoName, repoIsRemote) - if (repoIsRemote) { - repo.hasRemoteChildren = true - } + const repo = ensureRepo(finalRepoId, finalRepoName) let row = findWorktreeRow(repo, finalWorktreeId) if (!row) { @@ -299,7 +289,7 @@ export function mergeSnapshotAndSessions( memory: null, history: [], hasLocalSamples: false, - isRemote: repoIsRemote, + isRemote, sessions: [], browsers: [] } @@ -339,7 +329,7 @@ export function mergeSnapshotAndSessions( memory: null, history: [], hasLocalSamples: false, - isRemote: isRepoRemote(worktree.repoId), + isRemote: resolveResourceWorkspaceHost(ctx, worktreeId, worktree.repoId).isRemote, sessions: [], browsers: [] } @@ -348,10 +338,7 @@ export function mergeSnapshotAndSessions( row.browsers = browsers } - // ── Step 4: per-repo aggregates. Remote children are identified by the - // repo's connectionId, not by missing data — `!hasLocalSamples` would - // mislabel warm-reattached local PTYs. The aggregate still skips rows - // we can't sample (worktree.cpu === null) so the numbers stay honest. + // Only sampled rows contribute to project totals. for (const repo of repos.values()) { let cpuSum = 0 let memSum = 0 diff --git a/src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts b/src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts index fafe3cc155b..ec7c7a62848 100644 --- a/src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts +++ b/src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts @@ -16,7 +16,7 @@ describe('resource session classification parity', () => { const source = readFileSync(DERIVED_MODEL_PATH, 'utf8') const mergeCall = source.slice( source.indexOf('mergeSnapshotAndSessions(resourceSnapshot'), - source.indexOf('worktreeById\n })') + source.indexOf('ambiguousWorktreeIds\n })') ) expect(mergeCall).toContain('...resourceSessionBindings') diff --git a/src/renderer/src/components/status-bar/resource-usage-folder-merge.test.ts b/src/renderer/src/components/status-bar/resource-usage-folder-merge.test.ts new file mode 100644 index 00000000000..6dc264bc975 --- /dev/null +++ b/src/renderer/src/components/status-bar/resource-usage-folder-merge.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, it } from 'vitest' +import type { MemorySnapshot, WorktreeMemory } from '../../../../shared/process-stats-types' +import type { Worktree } from '../../../../shared/worktree/types' +import type { MergeContext } from './resource-usage-merge-types' +import { mergeSnapshotAndSessions } from './mergeSnapshotAndSessions' + +const workspace = { + id: 'folder:notes', + repoId: 'folder-workspace:docs', + displayName: 'Release notes' +} as Worktree +const secondWorkspace = { ...workspace, id: 'folder:research', displayName: 'Research' } +const oldRow: WorktreeMemory = { + worktreeId: workspace.id, + worktreeName: workspace.id, + repoId: workspace.id, + repoName: workspace.id, + cpu: 2, + memory: 2048, + history: [1024, 2048], + sessions: [{ sessionId: 'sampled', paneKey: null, pid: 123, cpu: 2, memory: 2048 }] +} + +function context(overrides: Partial = {}): MergeContext { + return { + tabsByWorktree: {}, + ptyIdsByTabId: {}, + runtimePaneTitlesByTabId: {}, + workspaceSessionReady: true, + repoDisplayNameById: new Map([[workspace.repoId, 'Documentation']]), + repoConnectionIdById: new Map(), + repoRuntimeScopedById: new Map(), + worktreeById: new Map([ + [workspace.id, workspace], + [secondWorkspace.id, secondWorkspace] + ]), + ...overrides + } +} + +describe('folder Resource Manager rows', () => { + it('names daemon-only folders using the host-reported workspace identity', () => { + const groups = mergeSnapshotAndSessions( + null, + [ + { + id: 'folder:notes@@session', + worktreeId: workspace.id, + cwd: '/notes', + title: 'Shell', + agentOwnership: 'unknown' + } + ], + context() + ) + expect(groups).toHaveLength(1) + expect(groups[0]).toMatchObject({ + repoId: workspace.repoId, + repoName: 'Documentation', + cpu: null, + memory: null, + worktrees: [ + { + worktreeId: workspace.id, + worktreeName: 'Release notes', + hasLocalSamples: false, + isRemote: false, + sessions: [{ bound: false, agentOwnership: 'unknown' }] + } + ] + }) + }) + + it('merges old snapshot labels and daemon-only rows into the same named folder without losing metrics', () => { + const groups = mergeSnapshotAndSessions( + { worktrees: [oldRow] } as MemorySnapshot, + [ + { + id: 'extra', + worktreeId: workspace.id, + cwd: '/notes', + title: 'Shell', + agentOwnership: 'present' + }, + { + id: 'second', + worktreeId: secondWorkspace.id, + cwd: '/research', + title: 'Shell', + agentOwnership: 'unknown' + } + ], + context() + ) + expect(groups).toHaveLength(1) + expect(groups[0]).toMatchObject({ repoName: 'Documentation', cpu: 2, memory: 2048 }) + expect(groups[0].worktrees).toHaveLength(2) + expect(groups[0].worktrees[0]).toMatchObject({ + worktreeName: 'Release notes', + cpu: 2, + memory: 2048, + history: [1024, 2048] + }) + expect(groups[0].worktrees[0].sessions.map((s) => s.sessionId)).toEqual(['sampled', 'extra']) + expect(groups[0].worktrees[1].worktreeName).toBe('Research') + }) + + it('keeps old snapshot labels when the folder catalog is unavailable', () => { + const groups = mergeSnapshotAndSessions( + { worktrees: [oldRow] } as MemorySnapshot, + [], + context({ worktreeById: new Map() }) + ) + expect(groups[0].worktrees[0]).toMatchObject({ + worktreeId: workspace.id, + worktreeName: workspace.id, + memory: 2048 + }) + }) + + it('excludes runtime-owned folder sessions from local snapshot and daemon inputs', () => { + expect( + mergeSnapshotAndSessions( + { worktrees: [oldRow] } as MemorySnapshot, + [ + { + id: 'foreign', + worktreeId: workspace.id, + cwd: '/notes', + title: 'Shell', + agentOwnership: 'present' + } + ], + context({ + worktreeById: new Map([[workspace.id, { ...workspace, hostId: 'runtime:paired' }]]) + }) + ) + ).toEqual([]) + }) + + it('keeps an SSH folder marked remote even without a numeric sample', () => { + const groups = mergeSnapshotAndSessions( + null, + [ + { + id: 'ssh-folder', + worktreeId: workspace.id, + cwd: '/notes', + title: 'Shell', + agentOwnership: 'present' + } + ], + context({ + worktreeById: new Map([[workspace.id, { ...workspace, hostId: 'ssh:ssh-target' }]]) + }) + ) + expect(groups[0]).toMatchObject({ hasRemoteChildren: true, memory: null }) + expect(groups[0].worktrees[0]).toMatchObject({ isRemote: true, memory: null }) + }) +}) diff --git a/src/renderer/src/components/status-bar/resource-usage-merge-types.ts b/src/renderer/src/components/status-bar/resource-usage-merge-types.ts index 5943b744dfd..a67f5e0ec66 100644 --- a/src/renderer/src/components/status-bar/resource-usage-merge-types.ts +++ b/src/renderer/src/components/status-bar/resource-usage-merge-types.ts @@ -35,7 +35,7 @@ export type UnifiedWorktreeRow = { memory: Metric history: number[] hasLocalSamples: boolean - /** Why: repo connectionId, not sample presence, drives the remote chip. */ + /** Execution-host metadata drives the remote chip; missing samples do not. */ isRemote: boolean sessions: UnifiedSessionRow[] browsers: BrowserWorkspace[] @@ -46,7 +46,7 @@ export type UnifiedProjectGroup = { repoName: string cpu: Metric memory: Metric - /** Why: kept for callsite stability; this now means SSH-backed repo rows. */ + /** True when any workspace in this project runs over SSH. */ hasRemoteChildren: boolean worktrees: UnifiedWorktreeRow[] } @@ -64,7 +64,7 @@ export type MergeContext = { runtimePaneTitlesByTabId: Record> /** From useAppStore: false until renderer state can distinguish bound/orphan. */ workspaceSessionReady: boolean - /** Repo display names by repo id for daemon-only groups. */ + /** Project display names for sampled and daemon-only groups. */ repoDisplayNameById: Map /** Repo connectionId by repo id (null/missing == local). */ repoConnectionIdById: Map @@ -72,6 +72,8 @@ export type MergeContext = { repoRuntimeScopedById: Map /** Browser inventory is open-only; the Resource Manager never scans it in the background. */ browserTabsByWorktree?: Record - /** Canonical worktrees keep browser-only workspace rows out of synthetic buckets. */ + /** Canonical workspace names and grouping for every resource source. */ worktreeById?: ReadonlyMap + /** Ids present on more than one execution host; their catalog row cannot name a host. */ + ambiguousWorktreeIds?: ReadonlySet } diff --git a/src/renderer/src/components/status-bar/resource-usage-open-slices.test.ts b/src/renderer/src/components/status-bar/resource-usage-open-slices.test.ts index 85663bc2090..21f3aee3e40 100644 --- a/src/renderer/src/components/status-bar/resource-usage-open-slices.test.ts +++ b/src/renderer/src/components/status-bar/resource-usage-open-slices.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest' import { getResourceUsageAllWorktrees, getResourceUsageDeferredSshSessionIdsByTabId, + getResourceUsageFolderWorkspaces, + getResourceUsageProjectGroups, getResourceUsagePtyIdsByTabId, getResourceUsageRepos, getResourceUsageRuntimePaneTitlesByTabId, @@ -42,6 +44,19 @@ const worktree = (): AppState['worktreesByRepo'][string][number] => ({ }) describe('resource usage open slices', () => { + it('subscribes to folder and group catalogs only while open', () => { + const folderWorkspaces: AppState['folderWorkspaces'] = [] + const projectGroups: AppState['projectGroups'] = [] + expect(getResourceUsageFolderWorkspaces({ folderWorkspaces }, true)).toBe(folderWorkspaces) + expect(getResourceUsageProjectGroups({ projectGroups }, true)).toBe(projectGroups) + expect(getResourceUsageFolderWorkspaces({ folderWorkspaces }, false)).toBe( + getResourceUsageFolderWorkspaces({ folderWorkspaces: [] }, false) + ) + expect(getResourceUsageProjectGroups({ projectGroups }, false)).toBe( + getResourceUsageProjectGroups({ projectGroups: [] }, false) + ) + }) + it('returns stable empty slices while the popover is closed', () => { const tabsByWorktree = { 'wt-1': [terminalTab('tab-1')] } const ptyIdsByTabId = { 'tab-1': ['pty-1'] } diff --git a/src/renderer/src/components/status-bar/resource-usage-open-slices.ts b/src/renderer/src/components/status-bar/resource-usage-open-slices.ts index ca50f59b8b8..32bd7237397 100644 --- a/src/renderer/src/components/status-bar/resource-usage-open-slices.ts +++ b/src/renderer/src/components/status-bar/resource-usage-open-slices.ts @@ -8,6 +8,8 @@ const EMPTY_DEFERRED_SSH_SESSION_IDS_BY_TAB_ID: AppState['deferredSshSessionIdsB const EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID: AppState['runtimePaneTitlesByTabId'] = {} const EMPTY_BROWSER_TABS_BY_WORKTREE: AppState['browserTabsByWorktree'] = {} const EMPTY_REPOS: AppState['repos'] = [] +const EMPTY_FOLDER_WORKSPACES: AppState['folderWorkspaces'] = [] +const EMPTY_PROJECT_GROUPS: AppState['projectGroups'] = [] const EMPTY_WORKTREES: ReturnType = [] export function getResourceUsageTabsByWorktree( @@ -65,3 +67,17 @@ export function getResourceUsageAllWorktrees( ): ReturnType { return open ? getAllWorktreesFromState(state) : EMPTY_WORKTREES } + +export function getResourceUsageFolderWorkspaces( + state: Pick, + open: boolean +): AppState['folderWorkspaces'] { + return open ? state.folderWorkspaces : EMPTY_FOLDER_WORKSPACES +} + +export function getResourceUsageProjectGroups( + state: Pick, + open: boolean +): AppState['projectGroups'] { + return open ? state.projectGroups : EMPTY_PROJECT_GROUPS +} diff --git a/src/renderer/src/components/status-bar/resource-workspace-host.ts b/src/renderer/src/components/status-bar/resource-workspace-host.ts new file mode 100644 index 00000000000..5ea2e9df33e --- /dev/null +++ b/src/renderer/src/components/status-bar/resource-workspace-host.ts @@ -0,0 +1,32 @@ +import { parseExecutionHostId } from '../../../../shared/execution-host' +import { parseWorkspaceKey } from '../../../../shared/workspace-scope' +import type { Worktree } from '../../../../shared/worktree/types' +import type { MergeContext } from './resource-usage-merge-types' + +/** Folder catalog row for host/name attribution; a duplicated id cannot choose a host, so it yields nothing. */ +export function resolveResourceFolderWorkspace( + ctx: MergeContext, + worktreeId: string +): Worktree | undefined { + if ( + parseWorkspaceKey(worktreeId)?.type !== 'folder' || + ctx.ambiguousWorktreeIds?.has(worktreeId) + ) { + return undefined + } + return ctx.worktreeById?.get(worktreeId) +} + +export function resolveResourceWorkspaceHost( + ctx: MergeContext, + worktreeId: string, + repoId: string +): { isRemote: boolean; isRuntimeScoped: boolean } { + const folder = resolveResourceFolderWorkspace(ctx, worktreeId) + const host = folder ? parseExecutionHostId(folder.hostId ?? 'local') : null + return { + // Folder siblings may execute on different hosts within the same project group. + isRemote: host ? host.kind === 'ssh' : ctx.repoConnectionIdById.get(repoId) != null, + isRuntimeScoped: host ? host.kind === 'runtime' : ctx.repoRuntimeScopedById.get(repoId) === true + } +} diff --git a/src/renderer/src/components/status-bar/use-resource-usage-actions.test.tsx b/src/renderer/src/components/status-bar/use-resource-usage-actions.test.tsx new file mode 100644 index 00000000000..568afdea1ee --- /dev/null +++ b/src/renderer/src/components/status-bar/use-resource-usage-actions.test.tsx @@ -0,0 +1,77 @@ +// @vitest-environment happy-dom +import { cleanup, renderHook } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { ResourceManagerWorktreeTarget } from './resource-manager-worktree-target' + +const mocks = vi.hoisted(() => ({ + activateAndRevealWorkspace: vi.fn(), + activateAndRevealWorktree: vi.fn(), + worktrees: [] as ResourceManagerWorktreeTarget[] +})) + +vi.mock('@/lib/worktree-activation', () => ({ + activateAndRevealWorkspace: mocks.activateAndRevealWorkspace, + activateAndRevealWorktree: mocks.activateAndRevealWorktree +})) +vi.mock('@/lib/activate-tab-and-focus-pane', () => ({ activateTabAndFocusPane: vi.fn() })) +vi.mock('../../store', () => ({ useAppStore: { getState: () => ({}) } })) +vi.mock('../../store/selectors', () => ({ getAllWorktreesFromState: () => mocks.worktrees })) +vi.mock('../sidebar/delete-worktree-flow', () => ({ runWorktreeDelete: vi.fn() })) + +import { useResourceUsageActions } from './use-resource-usage-actions' + +function renderActions() { + return renderHook(() => + useResourceUsageActions({ + setCollapsedRepos: vi.fn(), + setCollapsedWorktrees: vi.fn(), + tabsByWorktree: {}, + setOpen: vi.fn(), + setActiveView: vi.fn(), + openModal: vi.fn(), + openSpacePage: vi.fn(), + refreshSessions: vi.fn(async () => {}), + removeSession: vi.fn(), + removeSessions: vi.fn(), + sessions: [], + resourceSessionBindings: { + tabsByWorktree: {}, + ptyIdsByTabId: {}, + workspaceSessionReady: true + }, + workspaceSessionReady: true, + killConfirm: null, + setKillConfirm: vi.fn(), + setKilling: vi.fn(), + mountedRef: { current: true }, + cancelPopoverBodyFocusFrame: vi.fn(), + popoverBodyRef: { current: null }, + popoverBodyFocusFrameRef: { current: null } + }) + ).result.current +} + +beforeEach(() => { + mocks.activateAndRevealWorkspace.mockReset() + mocks.activateAndRevealWorktree.mockReset() + mocks.worktrees = [{ id: 'repo::/notes', hostId: 'ssh:box' }] +}) +afterEach(cleanup) + +describe('Resource Manager row navigation', () => { + it('activates a folder workspace row through the workspace dispatcher', () => { + renderActions().navigateToWorktree('folder:notes') + + expect(mocks.activateAndRevealWorkspace).toHaveBeenCalledWith('folder:notes') + expect(mocks.activateAndRevealWorktree).not.toHaveBeenCalled() + }) + + it('still routes worktree rows through the host-resolved activator', () => { + renderActions().navigateToWorktree('repo::/notes') + + expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith('repo::/notes', { + executionHostId: 'ssh:box' + }) + expect(mocks.activateAndRevealWorkspace).not.toHaveBeenCalled() + }) +}) diff --git a/src/renderer/src/components/status-bar/use-resource-usage-actions.ts b/src/renderer/src/components/status-bar/use-resource-usage-actions.ts index 06a2d0ad177..f94237a0bbe 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-actions.ts +++ b/src/renderer/src/components/status-bar/use-resource-usage-actions.ts @@ -1,11 +1,12 @@ import { useCallback, type Dispatch, type MutableRefObject, type SetStateAction } from 'react' -import { activateAndRevealWorktree } from '@/lib/worktree-activation' +import { activateAndRevealWorkspace, activateAndRevealWorktree } from '@/lib/worktree-activation' import { activateTabAndFocusPane } from '@/lib/activate-tab-and-focus-pane' import { useAppStore } from '../../store' import type { AppState } from '../../store/types' import { getAllWorktreesFromState } from '../../store/selectors' import { runWorktreeDelete } from '../sidebar/delete-worktree-flow' import { ORPHAN_WORKTREE_ID } from '../../../../shared/constants' +import { parseWorkspaceKey } from '../../../../shared/workspace-scope' import { UNATTRIBUTED_REPO_ID } from './mergeSnapshotAndSessions' import type { DaemonSession, UnifiedSessionRow } from './resource-usage-merge-types' import type { ResourceSessionBindingInputs } from './resource-session-bindings' @@ -92,6 +93,11 @@ export function useResourceUsageActions({ if (worktreeId === ORPHAN_WORKTREE_ID || worktreeId.startsWith(`${UNATTRIBUTED_REPO_ID}::`)) { return } + // Why: the target resolve below only knows worktrees, so a folder key never matched; the folder activator owns host and path-status gating. + if (parseWorkspaceKey(worktreeId)?.type === 'folder') { + activateAndRevealWorkspace(worktreeId) + return + } const target = resolveResourceManagerWorktreeTarget( worktreeId, getAllWorktreesFromState(useAppStore.getState()) diff --git a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx new file mode 100644 index 00000000000..f5e1bac9dbb --- /dev/null +++ b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx @@ -0,0 +1,196 @@ +// @vitest-environment happy-dom +import { cleanup, renderHook } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' +import type { MemorySnapshot, WorktreeMemory } from '../../../../shared/process-stats-types' +import type { Worktree } from '../../../../shared/worktree/types' +import type { ProjectGroup } from '../../../../shared/project-group-types' +import type { DaemonSession } from './resource-usage-merge-types' +import { useResourceUsageDerivedModel } from './use-resource-usage-derived-model' + +const local = { + id: 'folder:local', + repoId: 'folder-workspace:group', + displayName: 'Local notes', + hostId: 'local' +} as Worktree +const sampled: WorktreeMemory = { + worktreeId: local.id, + worktreeName: 'Published local notes', + repoId: local.repoId, + repoName: 'Local project', + cpu: 2, + memory: 2048, + history: [1024, 2048], + sessions: [{ sessionId: 'sampled', paneKey: null, pid: 123, cpu: 2, memory: 2048 }] +} +const group = { id: 'group', name: 'Local project', executionHostId: 'local' } as ProjectGroup + +function derive( + worktrees: Worktree[], + sessions: DaemonSession[] = [], + row = sampled, + projectGroups = [group], + browserTabsByWorktree: Record = {} +) { + const snapshot = { + worktrees: [row], + host: { totalMemory: 16384 }, + totalMemory: 2048, + totalCpu: 2, + processMemoryMetric: 'rss' + } as MemorySnapshot + return renderHook(() => + useResourceUsageDerivedModel({ + open: true, + resourceSnapshot: snapshot, + sessions, + resourceSessionBindings: { + tabsByWorktree: {}, + ptyIdsByTabId: {}, + workspaceSessionReady: true + }, + runtimePaneTitlesByTabId: {}, + repos: [], + allWorktrees: worktrees, + projectGroups, + browserTabsByWorktree, + workspaceSessionReady: true, + sessionCount: sessions.length, + sessionsError: false, + memorySnapshotError: null, + snapshot, + spaceScanReady: false + }) + ).result.current.unifiedRepos +} + +afterEach(cleanup) + +describe('Resource Manager folder ownership', () => { + it.each(['ssh:box', 'runtime:paired'] as const)( + 'keeps local samples when a sibling folder belongs to %s', + (hostId) => { + const sibling = { ...local, id: 'folder:remote', displayName: 'Remote notes', hostId } + const groups = derive( + [local, sibling], + [ + { + id: 'remote-session', + worktreeId: sibling.id, + cwd: '/notes', + title: 'Shell', + agentOwnership: 'present' + } + ] + ) + + expect(groups).toHaveLength(1) + expect(groups[0]).toMatchObject({ + cpu: 2, + memory: 2048, + hasRemoteChildren: hostId === 'ssh:box' + }) + expect(groups[0].worktrees[0]).toMatchObject({ + worktreeId: local.id, + worktreeName: local.displayName, + isRemote: false, + cpu: 2, + memory: 2048, + sessions: [{ sessionId: 'sampled', memory: 2048 }] + }) + if (hostId === 'ssh:box') { + expect(groups[0].worktrees[1]).toMatchObject({ + worktreeId: sibling.id, + isRemote: true, + cpu: null, + memory: null + }) + } else { + expect(groups[0].worktrees).toHaveLength(1) + } + } + ) + + it.each([false, true])( + 'preserves sampled ownership with duplicate folder ids (reverse=%s)', + (reverse) => { + const remote = { + ...local, + repoId: 'folder-workspace:remote', + displayName: 'Foreign notes', + hostId: 'runtime:paired' + } as Worktree + const groups = derive(reverse ? [remote, local] : [local, remote]) + + expect(groups).toHaveLength(1) + expect(groups[0]).toMatchObject({ + repoId: sampled.repoId, + repoName: sampled.repoName, + memory: 2048 + }) + expect(groups[0].worktrees[0]).toMatchObject({ + worktreeName: sampled.worktreeName, + isRemote: false, + memory: 2048, + history: sampled.history + }) + } + ) + + it.each(['repo::/notes', 'folder:shared'])( + 'keeps browser-only rows when %s exists on two hosts', + (worktreeId) => { + const here = { ...local, id: worktreeId, repoId: 'repo', displayName: 'Notes' } as Worktree + const there = { ...here, hostId: 'ssh:box' } as Worktree + const browser = { id: 'browser-1', worktreeId, title: 'Docs' } as BrowserWorkspace + const groups = derive([here, there], [], sampled, [group], { [worktreeId]: [browser] }) + + expect(groups.find((project) => project.repoId === 'repo')).toMatchObject({ + worktrees: [{ worktreeId, isRemote: false, browsers: [browser] }] + }) + } + ) + + it('does not replace a sampled project name with a same-id group from another host', () => { + const foreignGroup = { ...group, name: 'Foreign project', executionHostId: 'runtime:paired' } + const groups = derive([local], [], sampled, [group, foreignGroup]) + expect(groups[0].repoName).toBe('Local project') + }) + + it('keeps git snapshot identity when the catalog contains a different host', () => { + const row = { ...sampled, worktreeId: 'repo::/notes', repoId: 'repo' } + const foreign = { + ...local, + id: row.worktreeId, + repoId: 'foreign-repo', + displayName: 'Foreign notes', + hostId: 'runtime:paired' + } as Worktree + const groups = derive([foreign], [], row) + + expect(groups[0]).toMatchObject({ repoId: row.repoId, repoName: row.repoName, memory: 2048 }) + expect(groups[0].worktrees[0].worktreeName).toBe(row.worktreeName) + }) + + it('keeps git daemon identity when the catalog contains a different host', () => { + const worktreeId = 'repo::/notes' + const foreign = { + ...local, + id: worktreeId, + repoId: 'foreign-repo', + displayName: 'Foreign notes', + hostId: 'runtime:paired' + } as Worktree + const groups = derive( + [foreign], + [{ id: 'git-session', worktreeId, cwd: '/notes', title: '', agentOwnership: 'absent' }] + ) + + expect(groups.find((project) => project.repoId === 'repo')).toMatchObject({ + repoName: 'repo', + worktrees: [{ worktreeId, worktreeName: 'notes', isRemote: false }] + }) + expect(groups.some((project) => project.repoId === foreign.repoId)).toBe(false) + }) +}) diff --git a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts index 6cf5c780351..cc56fd7f357 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts +++ b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts @@ -17,6 +17,7 @@ import { getResourceMemoryMetricCopy } from './resource-memory-metric-copy' import { formatMemory } from './resource-usage-metrics' +import { findAmbiguousWorktreeIds, findDuplicateIds } from '../../lib/unified-tab-host-ownership' export function useResourceUsageDerivedModel({ open, @@ -26,6 +27,7 @@ export function useResourceUsageDerivedModel({ runtimePaneTitlesByTabId, repos, allWorktrees, + projectGroups, browserTabsByWorktree, workspaceSessionReady, sessionCount, @@ -41,6 +43,7 @@ export function useResourceUsageDerivedModel({ runtimePaneTitlesByTabId: AppState['runtimePaneTitlesByTabId'] repos: AppState['repos'] allWorktrees: Worktree[] + projectGroups: AppState['projectGroups'] browserTabsByWorktree: AppState['browserTabsByWorktree'] workspaceSessionReady: boolean sessionCount: number @@ -57,8 +60,14 @@ export function useResourceUsageDerivedModel({ map.set(repo.id, display) } } + const ambiguousGroupIds = findDuplicateIds(projectGroups) + for (const group of projectGroups) { + if (!ambiguousGroupIds.has(group.id)) { + map.set(`folder-workspace:${group.id}`, group.name) + } + } return map - }, [repos]) + }, [repos, projectGroups]) // Why: non-null connectionId is the only honest "remote" signal (SSH PTYs run remote); build from the store, not a missing memory sample. const repoConnectionIdById = useMemo(() => { @@ -83,6 +92,9 @@ export function useResourceUsageDerivedModel({ () => new Map(allWorktrees.map((worktree) => [worktree.id, worktree])), [allWorktrees] ) + // Why: a bare resource identity cannot choose between the same workspace id on different + // hosts, but the id still exists; keep the map whole and let the merge gate attribution only. + const ambiguousWorktreeIds = useMemo(() => findAmbiguousWorktreeIds(allWorktrees), [allWorktrees]) // Why: skip the merge when closed; the always-mounted segment recomputing on every keystroke-driven store mutation made the app laggy. const unifiedRepos = useMemo( @@ -98,7 +110,8 @@ export function useResourceUsageDerivedModel({ repoConnectionIdById, repoRuntimeScopedById, browserTabsByWorktree, - worktreeById + worktreeById, + ambiguousWorktreeIds }) : [], [ @@ -111,7 +124,8 @@ export function useResourceUsageDerivedModel({ repoConnectionIdById, repoRuntimeScopedById, browserTabsByWorktree, - worktreeById + worktreeById, + ambiguousWorktreeIds ] ) diff --git a/src/renderer/src/components/status-bar/use-resource-usage-status-controller.ts b/src/renderer/src/components/status-bar/use-resource-usage-status-controller.ts index 9dd9c22666d..e565d0cc64b 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-status-controller.ts +++ b/src/renderer/src/components/status-bar/use-resource-usage-status-controller.ts @@ -5,10 +5,13 @@ import { useDaemonActions } from '../shared/useDaemonActions' import type { UnifiedSessionRow } from './resource-usage-merge-types' import type { ResourceSessionBindingInputs } from './resource-session-bindings' import type { SortOption } from './resource-usage-resource-tree' +import { folderWorkspaceToWorktree } from '../../../../shared/folder-workspace-worktree' import { getResourceUsageAllWorktrees, getResourceUsageBrowserTabsByWorktree, getResourceUsageDeferredSshSessionIdsByTabId, + getResourceUsageFolderWorkspaces, + getResourceUsageProjectGroups, getResourceUsagePtyIdsByTabId, getResourceUsageRepos, getResourceUsageRuntimePaneTitlesByTabId, @@ -67,7 +70,13 @@ export function useResourceUsageStatusController() { getResourceUsageRuntimePaneTitlesByTabId(s, open) ) const repos = useAppStore((s) => getResourceUsageRepos(s, open)) - const allWorktrees = useAppStore((s) => getResourceUsageAllWorktrees(s, open)) + const gitWorktrees = useAppStore((s) => getResourceUsageAllWorktrees(s, open)) + const folders = useAppStore((s) => getResourceUsageFolderWorkspaces(s, open)) + const projectGroups = useAppStore((s) => getResourceUsageProjectGroups(s, open)) + const allWorktrees = useMemo( + () => [...gitWorktrees, ...folders.map(folderWorkspaceToWorktree)], + [gitWorktrees, folders] + ) const tabsByWorktree = useAppStore((s) => getResourceUsageTabsByWorktree(s, open)) const browserTabsByWorktree = useAppStore((s) => getResourceUsageBrowserTabsByWorktree(s, open)) // Why: full binding maps stay behind open sentinels so unchanged counts don't rerender the closed segment. @@ -186,6 +195,7 @@ export function useResourceUsageStatusController() { runtimePaneTitlesByTabId, repos, allWorktrees, + projectGroups, browserTabsByWorktree, workspaceSessionReady, sessionCount: sessionInventory.count, diff --git a/src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts b/src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts index 1b34ff4712f..8b96951efc4 100644 --- a/src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts +++ b/src/renderer/src/components/tab-bar/TabBar.context-menu.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { expandNode } from './tab-bar-dropdown-menu-item-probe' import { stubHeadlessReact, stubShallowSelector } from './tab-bar-windows-shell-launch-render-stubs' const appStoreSnapshot: { @@ -175,6 +176,12 @@ vi.mock('@/components/ui/dropdown-menu', () => ({ } })) +vi.mock('@/components/ui/tooltip', () => ({ + Tooltip: 'Tooltip', + TooltipContent: 'TooltipContent', + TooltipTrigger: 'TooltipTrigger' +})) + type ReactElementLike = { type: unknown props: Record @@ -202,6 +209,11 @@ function findChildrenByType(node: unknown, typeName: string): ReactElementLike[] if (matchedName === typeName) { results.push(el) } + if (matchedName === 'TabBarStaticCreateMenu' && typeof el.type === 'function') { + // Expand the deferred pure menu component in this shallow renderer. + visit(el.type(el.props)) + return + } if (el.props && 'children' in el.props) { visit(el.props.children) } @@ -230,22 +242,24 @@ async function renderTabBar(props: Record): Promise { | ((props: Record) => unknown) | { type: (props: Record) => unknown } const TabBar = typeof candidate === 'function' ? candidate : candidate.type - return TabBar({ - activeTabId: null, - worktreeId: 'wt-1', - expandedPaneByTabId: {}, - onActivate: () => {}, - onClose: () => {}, - onCloseOthers: () => {}, - onCloseToRight: () => {}, - onCloseToLeft: () => {}, - onNewTerminalTab: () => {}, - onNewBrowserTab: () => {}, - onSetCustomTitle: () => {}, - onSetTabColor: () => {}, - onTogglePaneExpand: () => {}, - ...props - }) + return expandNode( + TabBar({ + activeTabId: null, + worktreeId: 'wt-1', + expandedPaneByTabId: {}, + onActivate: () => {}, + onClose: () => {}, + onCloseOthers: () => {}, + onCloseToRight: () => {}, + onCloseToLeft: () => {}, + onNewTerminalTab: () => {}, + onNewBrowserTab: () => {}, + onSetCustomTitle: () => {}, + onSetTabColor: () => {}, + onTogglePaneExpand: () => {}, + ...props + }) + ) } const TERMINAL_TAB = { diff --git a/src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx b/src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx index 9a7a19a9186..45c39c2da55 100644 --- a/src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx +++ b/src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx @@ -13,7 +13,7 @@ import { import type { TabBarProps } from './tab-bar-props' import { resolveWindowsShellLaunchTarget } from './windows-shell-launch' -export function renderTabBarStaticCreateMenu({ +export function TabBarStaticCreateMenu({ terminalOnly, mobileEmulatorEnabled, managedBrowserCreationEnabled, diff --git a/src/renderer/src/components/tab-bar/tab-bar-surface.tsx b/src/renderer/src/components/tab-bar/tab-bar-surface.tsx index dcf22611227..0e9540870e3 100644 --- a/src/renderer/src/components/tab-bar/tab-bar-surface.tsx +++ b/src/renderer/src/components/tab-bar/tab-bar-surface.tsx @@ -22,7 +22,7 @@ import type { TabBarCreateMenuController } from './use-tab-bar-create-menu-contr import type { TabBarItemProjection } from './use-tab-bar-item-projection' import type { TabBarItem } from './tab-bar-item-model' import { renderTabBarItems } from './tab-bar-item-surface' -import { renderTabBarStaticCreateMenu } from './tab-bar-static-create-menu' +import { TabBarStaticCreateMenu } from './tab-bar-static-create-menu' import ClientHostedBrowserTabRows from './ClientHostedBrowserTabRows' import type { ClientHostedBrowserRow } from '../../../../shared/client-hosted-browser-rows' @@ -99,24 +99,6 @@ export function renderTabBarSurface({ activeClientHostedBrowserRowId, togglePinned }) - const standardCreateMenuItems = renderTabBarStaticCreateMenu({ - props, - terminalOnly, - mobileEmulatorEnabled, - managedBrowserCreationEnabled, - mobileEmulatorCreationEnabled, - workspaceHasSimulatorTab, - showMobileEmulatorIntroCallout, - windowsShellEntries, - defaultWindowsPowerShellImplementation, - pwshAvailable: windowsTerminalCapabilities.pwshAvailable, - newTerminalShortcut, - newBrowserShortcut, - newSimulatorShortcut, - newFileShortcut, - openMarkdownShortcut, - queueNewActiveTerminalFocusAfterNewTabMenuClose - }) return (
: null} ) : null} - {showStaticCreateMenuItems ? standardCreateMenuItems : null} + {showStaticCreateMenuItems ? ( + + ) : null} {showStaticCreateMenuItems && showAgentLaunchItems ? ( <> diff --git a/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.test.ts b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.test.ts new file mode 100644 index 00000000000..3497cef7cac --- /dev/null +++ b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest' +import { classifyHostUrl } from './tab-create-entry-url-classification' + +// Why this file exists: the suffix check behind `host/path` navigation moved off `psl.isValid`, and +// nothing else in the tree exercises it directly. These cases pin the listed/unlisted split that +// decides whether a typed string navigates or falls through to search. +describe('classifyHostUrl suffix gate', () => { + it('navigates for a listed suffix carrying a path', () => { + expect(classifyHostUrl('example.com/foo')).toEqual({ + kind: 'host-url', + url: 'https://example.com/foo' + }) + expect(classifyHostUrl('example.co.uk/foo')).toEqual({ + kind: 'host-url', + url: 'https://example.co.uk/foo' + }) + }) + + // The PRIVATE section has to stay in: these are navigable hosts, not search terms. + it('navigates for a private-section suffix carrying a path', () => { + expect(classifyHostUrl('foo.github.io/bar')).toEqual({ + kind: 'host-url', + url: 'https://foo.github.io/bar' + }) + expect(classifyHostUrl('foo.vercel.app/bar')).toEqual({ + kind: 'host-url', + url: 'https://foo.vercel.app/bar' + }) + }) + + // Why this is the fix: psl's 2024 snapshot did not know `api.br`, so `isValid` called it a domain + // and a typed `api.br/x` navigated to a bare public suffix instead of searching. + it('refuses a bare public suffix carrying a path', () => { + expect(classifyHostUrl('api.br/foo')).toBeNull() + expect(classifyHostUrl('co.uk/foo')).toBeNull() + expect(classifyHostUrl('github.io/foo')).toBeNull() + }) + + it('keeps localhost and IPv4 on http without consulting the suffix list', () => { + expect(classifyHostUrl('localhost:3000/foo')).toEqual({ + kind: 'host-url', + url: 'http://localhost:3000/foo' + }) + expect(classifyHostUrl('127.0.0.1:8080/foo')).toEqual({ + kind: 'host-url', + url: 'http://127.0.0.1:8080/foo' + }) + }) + + // The gate only applies once an authority ends; a bare host still navigates so unlisted intranet + // names typed on their own are not forced into search. + it('leaves a bare host unfiltered by the suffix list', () => { + expect(classifyHostUrl('api.br')).toEqual({ kind: 'host-url', url: 'https://api.br/' }) + }) +}) diff --git a/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts index a18f59d1ccf..ef99f648b9c 100644 --- a/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts +++ b/src/renderer/src/components/tab-bar/tab-create-entry-url-classification.ts @@ -1,5 +1,5 @@ import { translate } from '@/i18n/i18n' -import { isValid as isListedDomain } from 'psl' +import { parse as parseDomain } from 'tldts' import { classifySchemeLessLocalDevAddress } from '../../../../shared/browser-url' const HOST_FILE_EXTENSIONS = new Set([ @@ -54,6 +54,13 @@ function parseHttpUrl(query: string): ExplicitUrlClassification { } } +// Why: a bare public suffix (`api.br`) is not a navigable host, so the typed string stays a search. +// tldts' PRIVATE section is included so `foo.github.io` still reads as a domain. +function isListedDomain(host: string): boolean { + const parsed = parseDomain(host, { allowPrivateDomains: true }) + return parsed.domain !== null && (parsed.isIcann === true || parsed.isPrivate === true) +} + function splitHostCandidate(query: string): { host: string; port: string | null } | null { if (/[\\\s]/.test(query)) { return null diff --git a/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.test.ts b/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.test.ts new file mode 100644 index 00000000000..55f24f0c3e5 --- /dev/null +++ b/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.test.ts @@ -0,0 +1,114 @@ +// @vitest-environment happy-dom +import type { PointerSensorOptions, SensorProps } from '@dnd-kit/core' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { TabDragPointerSensor } from './tab-drag-pointer-sensor' + +function startSensor(options: PointerSensorOptions = {}) { + const callbacks = { + onAbort: vi.fn(), + onPending: vi.fn(), + onStart: vi.fn(), + onCancel: vi.fn(), + onMove: vi.fn(), + onEnd: vi.fn() + } + new TabDragPointerSensor({ + active: 'tab-1', + event: new PointerEvent('pointerdown', { clientX: 10, clientY: 10 }), + options, + ...callbacks + } as unknown as SensorProps) + return callbacks +} + +function movePointer(): void { + document.dispatchEvent(new PointerEvent('pointermove', { clientX: 100, clientY: 50 })) +} + +beforeEach(() => vi.useFakeTimers()) + +afterEach(() => { + window.dispatchEvent(new Event('resize')) + vi.runOnlyPendingTimers() + vi.restoreAllMocks() + vi.useRealTimers() +}) + +describe('tab drag pointer sensor cancellation', () => { + it('cancels an active gesture on blur and stops subsequent moves and drops', () => { + const callbacks = startSensor() + expect(callbacks.onStart).toHaveBeenCalledOnce() + movePointer() + expect(callbacks.onMove).toHaveBeenCalledOnce() + + window.dispatchEvent(new Event('blur')) + expect(callbacks.onCancel).toHaveBeenCalledOnce() + expect(callbacks.onAbort).not.toHaveBeenCalled() + movePointer() + document.dispatchEvent(new PointerEvent('pointerup')) + + expect(callbacks.onMove).toHaveBeenCalledOnce() + expect(callbacks.onEnd).not.toHaveBeenCalled() + }) + + it.each([ + { distance: 12 }, + { delay: 100, tolerance: 10 } + ])('aborts a pending gesture on blur before activation: %j', (activationConstraint) => { + const callbacks = startSensor({ activationConstraint }) + expect(callbacks.onStart).not.toHaveBeenCalled() + + window.dispatchEvent(new Event('blur')) + expect(callbacks.onAbort).toHaveBeenCalledWith('tab-1') + expect(callbacks.onCancel).toHaveBeenCalledOnce() + vi.advanceTimersByTime(150) + movePointer() + movePointer() + document.dispatchEvent(new PointerEvent('pointerup')) + + expect(callbacks.onStart).not.toHaveBeenCalled() + expect(callbacks.onMove).not.toHaveBeenCalled() + expect(callbacks.onEnd).not.toHaveBeenCalled() + }) + + it('still completes an uninterrupted gesture on pointerup', () => { + const callbacks = startSensor() + movePointer() + document.dispatchEvent(new PointerEvent('pointerup')) + window.dispatchEvent(new Event('blur')) + movePointer() + + expect(callbacks.onEnd).toHaveBeenCalledOnce() + expect(callbacks.onMove).toHaveBeenCalledOnce() + expect(callbacks.onCancel).not.toHaveBeenCalled() + expect(callbacks.onAbort).not.toHaveBeenCalled() + }) + + it('does not restart when a captured activation callback arrives after cancellation', () => { + const schedule = vi.spyOn(window, 'setTimeout') + const callbacks = startSensor({ activationConstraint: { delay: 100, tolerance: 10 } }) + const activate = schedule.mock.calls[0]?.[0] + expect(activate).toBeTypeOf('function') + window.dispatchEvent(new Event('blur')) + ;(activate as () => void)() + + expect(callbacks.onStart).not.toHaveBeenCalled() + expect(callbacks.onCancel).toHaveBeenCalledOnce() + }) + + it('ignores the old Escape listener while a new gesture is active', () => { + const previous = startSensor() + window.dispatchEvent(new Event('blur')) + const current = startSensor() + movePointer() + document.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape' })) + document.dispatchEvent(new PointerEvent('pointerup')) + + expect(previous.onCancel).toHaveBeenCalledOnce() + expect(previous.onMove).not.toHaveBeenCalled() + expect(current.onStart).toHaveBeenCalledOnce() + expect(current.onMove).toHaveBeenCalledOnce() + expect(current.onCancel).toHaveBeenCalledOnce() + expect(current.onEnd).not.toHaveBeenCalled() + }) +}) diff --git a/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.ts b/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.ts index ab96dc5e394..40448c6b197 100644 --- a/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.ts +++ b/src/renderer/src/components/tab-group/tab-drag-pointer-sensor.ts @@ -140,6 +140,7 @@ export class TabDragPointerSensor implements SensorInstance { autoScrollEnabled = true private activated = false + private ended = false private readonly document: Document private readonly initialCoordinates: PointerCoordinates private readonly pointerDownTime = performance.now() @@ -174,6 +175,7 @@ export class TabDragPointerSensor implements SensorInstance { this.windowListeners.add(win, 'dragstart', preventDefault) this.windowListeners.add(win, 'visibilitychange', this.handleCancel) this.windowListeners.add(win, 'contextmenu', preventDefault) + this.windowListeners.add(win, 'blur', this.handleCancel) this.windowListeners.add(win, 'focus', this.handleCancel) this.documentListeners.add(this.document, 'keydown', this.handleKeydown) @@ -200,6 +202,7 @@ export class TabDragPointerSensor implements SensorInstance { } private detach(): void { + this.ended = true this.pointerListeners.removeAll() this.windowListeners.removeAll() window.setTimeout(this.documentListeners.removeAll, 50) @@ -217,7 +220,7 @@ export class TabDragPointerSensor implements SensorInstance { } private handleStart(): void { - if (this.activated) { + if (this.activated || this.ended) { return } this.activated = true @@ -228,6 +231,9 @@ export class TabDragPointerSensor implements SensorInstance { } private handleMove(event: PointerEvent): void { + if (this.ended) { + return + } const coordinates = getPointerCoordinates(event) const { activationConstraint } = this.props.options if (!coordinates) { @@ -277,6 +283,9 @@ export class TabDragPointerSensor implements SensorInstance { } private handleEnd(): void { + if (this.ended) { + return + } this.detach() if (!this.activated) { this.props.onAbort(this.props.active) @@ -285,6 +294,9 @@ export class TabDragPointerSensor implements SensorInstance { } private handleCancel(): void { + if (this.ended) { + return + } this.detach() if (!this.activated) { this.props.onAbort(this.props.active) diff --git a/src/renderer/src/components/tab-group/useTabDragSplit.test.ts b/src/renderer/src/components/tab-group/useTabDragSplit.test.ts index bdd5edd97af..f57380b09b1 100644 --- a/src/renderer/src/components/tab-group/useTabDragSplit.test.ts +++ b/src/renderer/src/components/tab-group/useTabDragSplit.test.ts @@ -16,6 +16,8 @@ import { useTabDragSplit } from './useTabDragSplit' +globalThis.IS_REACT_ACT_ENVIRONMENT = true + vi.mock('../browser-pane/host-guest/webview-registry', () => ({ acquireWebviewsDragPassthrough: vi.fn(() => vi.fn()) })) @@ -94,10 +96,13 @@ function makeDragEvent(activeData: TabDragItemData, pointer: { x: number; y: num } } -function renderDragHook(): ReturnType { +function renderDragHook( + onRender?: (drag: ReturnType) => void +): ReturnType { let result: ReturnType | null = null function Probe(): null { result = useTabDragSplit({ worktreeId: WT }) + onRender?.(result) return null } @@ -267,6 +272,68 @@ describe('canDropTabIntoPaneBody', () => { }) describe('useTabDragSplit', () => { + it.each(['split', 'insertion'])( + 'does not restore a %s preview after blur cleared the drag', + async (preview) => { + if (preview === 'split') { + addPanelGeometry( + 'group-2', + rect({ left: 500, top: 0, width: 400, height: 600 }), + rect({ left: 500, top: 32, width: 400, height: 568 }) + ) + } + const onRender = vi.fn() + const drag = renderDragHook(onRender) + const event = { + ...makeDragEvent(makeDragData('group-1'), { x: 880, y: 300 }), + ...(preview === 'insertion' + ? { + over: { + data: { current: makeDragData('group-2', 'tab-2') }, + rect: rect({ left: 500, top: 0, width: 400, height: 32 }) + } + } + : {}) + } + const previewKey = preview === 'split' ? 'hoveredDropTarget' : 'hoveredTabInsertion' + act(() => drag.onDragStart(event as unknown as Parameters[0])) + act(() => drag.onDragMove(event as unknown as Parameters[0])) + expect(onRender.mock.lastCall?.[0][previewKey]).not.toBeNull() + + await act(async () => { + window.dispatchEvent(new Event('blur')) + await new Promise((resolve) => window.setTimeout(resolve, 0)) + }) + expect(onRender.mock.lastCall?.[0][previewKey]).toBeNull() + act(() => drag.onDragMove(event as unknown as Parameters[0])) + + expect(onRender.mock.lastCall?.[0][previewKey]).toBeNull() + expect(drag.isTabDragActiveRef.current).toBe(false) + } + ) + + it('does not commit a drop after blur cleared the drag', async () => { + addPanelGeometry( + 'group-2', + rect({ left: 500, top: 0, width: 400, height: 600 }), + rect({ left: 500, top: 32, width: 400, height: 568 }) + ) + const dropUnifiedTab = vi.fn(() => true) + const reorderUnifiedTabs = vi.fn() + useAppStore.setState({ dropUnifiedTab, reorderUnifiedTabs }) + const drag = renderDragHook() + const event = makeDragEvent(makeDragData('group-1'), { x: 880, y: 300 }) + act(() => drag.onDragStart(event as unknown as Parameters[0])) + await act(async () => { + window.dispatchEvent(new Event('blur')) + await new Promise((resolve) => window.setTimeout(resolve, 0)) + }) + act(() => drag.onDragEnd(event as unknown as Parameters[0])) + + expect(dropUnifiedTab).not.toHaveBeenCalled() + expect(reorderUnifiedTabs).not.toHaveBeenCalled() + }) + it.each(['pointerup', 'pointercancel', 'blur', 'focus'])( 'clears a stuck active drag when %s arrives without a dnd end event', async (eventName) => { diff --git a/src/renderer/src/components/tab-group/useTabDragSplit.ts b/src/renderer/src/components/tab-group/useTabDragSplit.ts index a3520174aed..ca1724e2bf4 100644 --- a/src/renderer/src/components/tab-group/useTabDragSplit.ts +++ b/src/renderer/src/components/tab-group/useTabDragSplit.ts @@ -209,6 +209,10 @@ export function useTabDragSplit({ const onDragMove = useCallback( (event: DragMoveEvent) => { + // A missed-end cleanup can run before dnd-kit delivers its last move. + if (!tabDragActiveRef.current) { + return + } handleDragUpdate(event) }, [handleDragUpdate] @@ -221,6 +225,10 @@ export function useTabDragSplit({ const onDragEnd = useCallback( (event: DragEndEvent) => { + if (!tabDragActiveRef.current) { + finishDrag(true) + return + } commitTabDragDrop({ event, worktreeId, diff --git a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx index d2b8efd3efa..0531720fe47 100644 --- a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx +++ b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.test.tsx @@ -4,6 +4,11 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' import CloseTerminalDialog from './CloseTerminalDialog' +import { translate } from '@/i18n/i18n' + +vi.mock('@/i18n/i18n', () => ({ + translate: vi.fn((_key: string, fallback: string) => fallback) +})) const mountedRoots: Root[] = [] @@ -49,6 +54,22 @@ describe('CloseTerminalDialog', () => { document.body.innerHTML = '' }) + it('does no dialog-copy work while closed, then builds the opened confirmation', async () => { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + mountedRoots.push(root) + const props = { onCancel: vi.fn(), onConfirm: vi.fn() } + vi.mocked(translate).mockClear() + + await act(async () => root.render()) + expect(translate).not.toHaveBeenCalled() + + await act(async () => root.render()) + expect(document.body.textContent).toContain('Stop running command?') + expect(translate).toHaveBeenCalled() + }) + it('renders running command copy and confirms without skipping by default', async () => { const onConfirm = vi.fn() diff --git a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx index c4da5244457..9edbca8b22e 100644 --- a/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx +++ b/src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx @@ -70,71 +70,104 @@ export default function CloseTerminalDialog({ }} > - - - {isAgent - ? translate( - 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_title', - 'Stop this agent?' - ) - : translate( - 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_title', - 'Stop running command?' - )} - - - {isAgent - ? translate( - 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_description', - "Closing this terminal will stop the agent's current work." - ) - : translate( - 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_description', - 'Closing this terminal will stop the command running inside it.' - )} - - - {trimmedTabLabel ? ( -

- {trimmedTabLabel} -

- ) : null} -
- setDontAskAgain(checked === true)} - /> - -
- - - - +
) } + +// Keep translation and element construction behind the dialog portal's mount boundary. +function CloseTerminalDialogBody({ + isAgent, + trimmedTabLabel, + checkboxId, + dontAskAgain, + setDontAskAgain, + onCancel, + onConfirm +}: { + isAgent: boolean + trimmedTabLabel: string | undefined + checkboxId: string + dontAskAgain: boolean + setDontAskAgain: (value: boolean) => void + onCancel: () => void + onConfirm: (dontAskAgain: boolean) => void +}): React.JSX.Element { + return ( + <> + + + {isAgent + ? translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_title', + 'Stop this agent?' + ) + : translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_title', + 'Stop running command?' + )} + + + {isAgent + ? translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_agent_description', + "Closing this terminal will stop the agent's current work." + ) + : translate( + 'auto.components.terminal.pane.CloseTerminalDialog.stop_command_description', + 'Closing this terminal will stop the command running inside it.' + )} + + + {trimmedTabLabel ? ( +

+ {trimmedTabLabel} +

+ ) : null} +
+ setDontAskAgain(checked === true)} + /> + +
+ + + + + + ) +} diff --git a/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx b/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx index 58a39f28a50..bd25abcce74 100644 --- a/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalContextMenu.test.tsx @@ -2,6 +2,7 @@ import React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import TerminalContextMenu from './TerminalContextMenu' +import { translate } from '@/i18n/i18n' import type { KeybindingOverrides } from '../../../../shared/keybindings' type ItemProps = { onSelect?: () => void; children?: React.ReactNode } @@ -13,9 +14,12 @@ vi.mock('@/components/ui/dropdown-menu', async () => { const React_ = await import('react') const passthrough = ({ children }: { children?: React.ReactNode }) => React_.createElement(React_.Fragment, null, children) + const OpenContext = React_.createContext(false) return { - DropdownMenu: passthrough, - DropdownMenuContent: passthrough, + DropdownMenu: ({ open, children }: { open: boolean; children?: React.ReactNode }) => + React_.createElement(OpenContext.Provider, { value: open }, children), + DropdownMenuContent: ({ children }: { children?: React.ReactNode }) => + React_.useContext(OpenContext) ? passthrough({ children }) : null, DropdownMenuLabel: passthrough, DropdownMenuSeparator: () => null, DropdownMenuShortcut: ({ children }: { children?: React.ReactNode }) => { @@ -36,7 +40,7 @@ vi.mock('@/components/ui/dropdown-menu', async () => { } } }) -vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) +vi.mock('@/i18n/i18n', () => ({ translate: vi.fn((_key: string, fallback: string) => fallback) })) vi.mock('@/lib/agent-catalog', () => ({ AgentIcon: () => null })) vi.mock('./terminal-context-menu-dismiss', () => ({ shouldIgnoreTerminalMenuPointerDownOutside: () => false @@ -104,6 +108,7 @@ function renderMenu(overrides: Record = {}): string { describe('TerminalContextMenu', () => { beforeEach(() => { + vi.mocked(translate).mockClear() items.list = [] shortcuts.list = [] vi.stubGlobal('navigator', { userAgent: 'Linux' }) @@ -113,6 +118,16 @@ describe('TerminalContextMenu', () => { vi.unstubAllGlobals() }) + it('does no menu-copy work while closed, then builds the opened menu', () => { + renderMenu({ open: false }) + expect(translate).not.toHaveBeenCalled() + expect(items.list).toHaveLength(0) + + renderMenu() + expect(translate).toHaveBeenCalled() + expect(items.list.length).toBeGreaterThan(0) + }) + it('renders a "Copy Context" item that triggers onCopyAgentSessionContext (issue #5020)', () => { const onCopyAgentSessionContext = vi.fn() const onForkAgentSession = vi.fn() @@ -167,6 +182,7 @@ describe('TerminalContextMenu', () => { item?.onSelect?.() expect(onCopyAgentSessionId).toHaveBeenCalledTimes(1) + vi.mocked(translate).mockClear() items.list = [] renderMenu({ canCopyAgentSessionId: false }) expect( diff --git a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx index 2cc76cd7164..5236d61d1a6 100644 --- a/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalContextMenu.tsx @@ -78,11 +78,59 @@ type TerminalContextMenuProps = { onCopyAgentSessionId: () => void } -export default function TerminalContextMenu({ - open, +export default function TerminalContextMenu(props: TerminalContextMenuProps): React.JSX.Element { + const { open, onOpenChange, menuPoint, menuOpenedAtRef } = props + return ( + { + if (!nextOpen && Date.now() - menuOpenedAtRef.current < 100) { + return + } + onOpenChange(nextOpen) + }} + modal={false} + > + +