diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index f97c4d7d880..f55737a6c57 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -1074,7 +1074,7 @@ 2 test-no-wall-clock:src/renderer/src/components/dashboard/useLiveDashboardSnapshot.test.ts 2 test-no-wall-clock:src/renderer/src/components/native-chat/NativeChatStructuredSessionStatus.test.tsx 2 test-no-wall-clock:src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx -2 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx +1 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx 2 test-no-wall-clock:src/renderer/src/components/status-bar/status-bar-antigravity-segment.test.tsx 2 test-no-wall-clock:src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts 2 test-no-wall-clock:src/renderer/src/components/terminal-pane/remote-snapshot-alt-screen-host-chain.test.ts diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 886fdc6fa6a..c903fd3d42b 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -3186,13 +3186,13 @@ "providers": ["local", "ssh", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "ssh", "remote-runtime"], - "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic candidate-builder contracts inject local and paired-runtime worktrees with the same bare id into the real aggregated tab buckets, including folder workspaces and a paired runtime transporting an SSH-owned worktree. A mirror contract preserves distinct same-id runtime tabs. A paired Electron journey verifies both visible browser rows and both visible simulator rows, then requires each selection to render its owning active tab and update backing host-qualified state. The test-value audit narrowed simulator-palette-search.test.ts to the recency tie, same-id host isolation, ambiguous legacy host rejection and oversized-input getter tripwire. Supplied timestamp, empty-query context ordering, host projection, alias/range, metadata matching, active-tab score, over-token/whitespace and malformed display-name fallback examples were removed. Their separate alarms are lost; retained candidate and activation contracts do not establish equivalent coverage. Historical run commands and results remain historical. The audit also removed the standalone workspace-tab-palette-search suite: supplied row, title, metadata, alias, range, ranking and ownership examples no longer provide their separate alarms. Other retained contracts are not replacement coverage. The mirror-identity fixture now retains same-id sibling-runtime isolation and the repeated-frame subscriber tripwire only. Supplied field, owner, alias and mapping examples are removed; their separate alarms are lost. The retained sibling-runtime case is not equivalent coverage for them. The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical. Audit1308: src/renderer/src/lib/browser-palette-search.test.ts removed. Browser-palette token and pasted-query size-guard work alarms disappear. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic candidate-builder contracts inject local and paired-runtime worktrees with the same bare id into the real aggregated tab buckets, including folder workspaces and a paired runtime transporting an SSH-owned worktree. A mirror contract preserves distinct same-id runtime tabs. A paired Electron journey verifies both visible browser rows and both visible simulator rows, then requires each selection to render its owning active tab and update backing host-qualified state. The test-value audit narrowed simulator-palette-search.test.ts to the recency tie, same-id host isolation, ambiguous legacy host rejection and oversized-input getter tripwire. Supplied timestamp, empty-query context ordering, host projection, alias/range, metadata matching, active-tab score, over-token/whitespace and malformed display-name fallback examples were removed. Their separate alarms are lost; retained candidate and activation contracts do not establish equivalent coverage. Historical run commands and results remain historical. The audit also removed the standalone workspace-tab-palette-search suite: supplied row, title, metadata, alias, range, ranking and ownership examples no longer provide their separate alarms. Other retained contracts are not replacement coverage. The mirror-identity fixture now retains same-id sibling-runtime isolation and the repeated-frame subscriber tripwire only. Supplied field, owner, alias and mapping examples are removed; their separate alarms are lost. The retained sibling-runtime case is not equivalent coverage for them. The test-value audit removed tabs-open-close-lifecycle host-stamping examples and ordinary tab/group operations from tabs-pane-layout-operations. Local-null, explicit-runtime and SSH-without-active-host stamp assertions are lost; retained mobile/MRU and subscriber/atomic split cases do not replace them. Candidate and live Cmd-J evidence retains its own scope. Historical commands, outcomes and narratives remain historical. Audit1308: src/renderer/src/lib/browser-palette-search.test.ts removed. Browser-palette token and pasted-query size-guard work alarms disappear. No equivalent replacement coverage is claimed. Audit1386: src/renderer/src/lib/open-mobile-emulator-tab.test.ts reduced. Duplicate attach count and null-tab creation retry/guard reset standalone alarms removed; no simulator device used. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4965", "https://github.com/stablyai/orca/pull/15371" ], "invariant": "Candidate identity is the tuple of execution-host identity and worktree identity: same-id sibling hosts retain separate correct browser, simulator, and workspace tabs, each tab appears exactly once, and selection activates its owning host.", - "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Aggregate two hosts' tabs under one intentionally colliding worktree id, stamp each unified tab with its execution host, and require exactly one candidate per owner. Repeat with an SSH physical owner transported by a paired-runtime alias. Independently require each browser and simulator Cmd-J selection to update activeWorkspaceExecutionHostId plus its owning workspace/group and render the selected tab active. Ambiguous legacy tabs without ownership fail closed.", + "oracle": "Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Aggregate two hosts' tabs under one intentionally colliding worktree id, stamp each unified tab with its execution host, and require exactly one candidate per owner. Repeat with an SSH physical owner transported by a paired-runtime alias. Independently require each browser and simulator Cmd-J selection to update activeWorkspaceExecutionHostId plus its owning workspace/group and render the selected tab active. Ambiguous legacy tabs without ownership fail closed.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx", @@ -3449,7 +3449,8 @@ "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-20\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 10.5,\n \"summary\": \"The review-hardened candidate passed 189/189 focused contracts, including direct browser-result host fallback, SSH plus paired-runtime current-row aliases, explicit-owner fail-closed behavior, generic workspace rows, simulator reuse, local creation stamping, activation, and same-id distinct-runtime mirror preservation. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session-schema.test.ts\"\n }", "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-21\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session-schema.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 24.3,\n \"summary\": \"The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\"\n }", "Retired during audit1312 current-main refresh; original complete execution receipt (unchanged): {\"date\":\"2026-08-20\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\"result\":\"passed\",\"durationSeconds\":10.5,\"summary\":\"The review-hardened candidate passed 189/189 focused contracts, including direct browser-result host fallback, SSH plus paired-runtime current-row aliases, explicit-owner fail-closed behavior, generic workspace rows, simulator reuse, local creation stamping, activation, and same-id distinct-runtime mirror preservation. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts\"}", - "Retired during audit1312 current-main refresh; original complete execution receipt (unchanged): {\"date\":\"2026-08-21\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts\",\"result\":\"passed\",\"durationSeconds\":24.3,\"summary\":\"The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\"}" + "Retired during audit1312 current-main refresh; original complete execution receipt (unchanged): {\"date\":\"2026-08-21\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/palette-repo-resolution.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/workspace-tab-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/renderer/src/store/slices/tabs-open-close-lifecycle.test.ts src/renderer/src/store/slices/tabs-pane-layout-operations.test.ts src/shared/workspace-session-schema.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/cmd-j/worktree-checks-review-index.test.ts src/renderer/src/components/worktree-jump/WorktreeJumpPalette.test.tsx src/renderer/src/components/tab-bar/use-open-tab-search.test.ts src/renderer/src/lib/cmd-j-host-qualified-candidate-ownership.test.ts src/renderer/src/lib/browser-palette-page-entries.test.ts src/renderer/src/lib/browser-palette-search.test.ts src/renderer/src/lib/simulator-palette-search.test.ts src/renderer/src/lib/browser-page-palette-activation.test.ts src/renderer/src/lib/simulator-tab-palette-activation.test.ts src/renderer/src/lib/ensure-simulator-tab-behavior.test.ts src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts src/shared/workspace-session/workspace-session-schema.test.ts\",\"result\":\"passed\",\"durationSeconds\":24.3,\"summary\":\"The OpenCode-review-hardened candidate passed 225/225 focused contracts, including unfiltered ownership ambiguity under host filters and scoped open-tab search, review-cache ownership, rendered palette repo metadata, folder workspaces, inactive local simulator creation, paired SSH/runtime display metadata, and explicit-owner fail-closed resolution. Historical command preserved after the test-value audit removed the palette repo resolver suite. currentCommand is an administrative link to a remaining gate command; this historical run has not validated that command or equivalent coverage. Historical command and result are preserved after the test-value audit removed the tab-store assertions this run included. currentCommand is only an administrative link to the remaining gate command; the historical run has not validated that command or equivalent coverage.\"}", + "Audit1386: src/renderer/src/lib/open-mobile-emulator-tab.test.ts reduced. Duplicate attach count and null-tab creation retry/guard reset standalone alarms removed; no simulator device used. No equivalent replacement coverage is claimed." ], "demotionRule": "Cannot promote if a same-id sibling tab duplicates, disappears, activates another host, or if ambiguous unstamped ownership is guessed." }, @@ -5752,8 +5753,8 @@ "platforms": ["macos", "linux", "windows"], "providers": ["remote-runtime"], "coveredPlatforms": ["macos"], - "coveredProviders": ["remote-runtime"], - "coverageNotes": "Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): A macOS desktop host and a separate paired Electron client exercise the Explorer preview action end to end: the document renders inside the client-local orca-preview guest, the client gains exactly one browser workspace and it is the document one, the host's own page registry and tab snapshot gain nothing, the split does not take focus from the source editor, an explicit click activates the preview, and closing it with its X leaves the editor and terminal intact. Both host oracles are read in a run where an ordinary URL browser tab from the same client does arrive in them, because a host that cannot project browser pages at all would otherwise answer the absence question the same way. A second journey quits that client with a document tab open and relaunches it on the same profile, which is the restore the user's original report turned on: the same workspace row comes back blank and named by the document, renders again, and does so over a grant URL that differs from the one that was quit. The same journey pins the guest URL to the orca-preview scheme, so a regression back to file:// or to a host browser page is visible. The rewritten journey caught a live gap the old one could not see: since previews became browser pages they had no chrome to hand focus to their guest, so main refused every reported link click as coming from an unfocused guest and the one route out of a preview was dead. Main's own log answered it — registered true, grant live, focused false — after the embedder reported the anchor really taking six real presses. The pane now hands focus over when it is the reader's surface, and the journey routes. The preview's own components moved out of the editor folder into browser-pane/workspace-doc/ with this change, since a preview is a browser pane and no longer an editor one; their i18n keys keep the historical auto.components.editor.HtmlDocPreview prefix rather than dropping thirty translated strings across four locales for a tidier key path. Seven gate commands named those files by their old paths and were re-issued at the new ones, and the nine dated runs behind them were retired rather than left naming files that are no longer there, on the same principle this gate already applied twice: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. Each retired record's finding is carried into the replacement run recorded below it, including the red half of the entry/subresource pair, which was re-measured by re-applying its mutant at the new path rather than being dropped. The fence over the page-management channels was decided channel by channel before it was built, and that decision record is: of the 23 channels that can name a page, 6 are allowed for a workspace-document page — the tool channels the reader drives, which reach it through the one authority that reads both halves of the registry — 9 refuse (download accept and cancel, devtools, certificate proceed, client page metadata publish, viewport override, WebAuthn account response, registration probe, registration repair), 3 cannot name a page at all because they take a profile or a worktree instead, and the rest are the registration and activation doors, refused on membership. What shipped is not the per-channel guard that classification implies: eleven call sites read the browsing map directly, so a guard per channel is exactly the caller-flag seam that has to be remembered at every future call site. The registry was split in two instead, which makes the classification structural — a document guest is absent from the browsing map, so every refusing channel refuses by absence rather than by a guard, and a channel added later inherits the refusal without being classified at all. Unit contracts cover grant scoping and path containment, per-owner read routing, refusal of a truncated or over-cap read, the old-server binary degradation, scheme admission on will-attach-webview, the guest navigation policy, the plan layer's local/SSH/paired/out-of-worktree routing, and the distinct sentence the reader sees for each failure reason. The paired browser-creation surfaces this gate still owns — capability rejection cleanup, canonical page identity, reconciliation rollback, close convergence — keep their existing headed and headless journeys. Linux and Windows live journeys remain gaps. A refused subresource is a notice on a rendered document rather than a failure panel, since only the entry document's failure leaves the reader with nothing; the entry document is served as text by every owner and cannot be refused for its format at all. Grants die with the renderer document that minted them, at window creation and on every reload, because the renderer holds the only record of which preview owns which grant. The mint and revoke channels answer only the trusted renderer, and a preview tab is never republished to mobile clients, which have no grant to read it with. External URLs leave the preview only via a trusted user click on an anchor while the preview is focused; script-driven navigation and popups reach nothing. The guest's navigation sinks are deny-only and route no URL at all, and the guest carries a preview-only preload, pinned by will-attach-webview and denied to every other attach path, whose capture-phase click handler reports an anchor's URL to main; main answers only a report from a preview guest with a live bound grant that is the focused contents, and only for http(s). Two earlier reader-facing records from 2026-08-26, which measured a shape where a subresource failure was filtered out and the panel still carried an unsupported-asset sentence, were retired with that shape rather than left contradicting the 10/10 runs of the command they share. Two more were retired on the same principle when the external-link design changed: a record showing that removing a recent-input gate let the unattended document open two browser tabs, and a record showing that dropping the webview's allowpopups attribute left a target=_blank press with nothing to route. Both measured machinery that no longer exists — the input gate could not tell a script's navigation from the click it followed, which is the case now recorded below, and the preload intercepts the press before Chromium considers a popup at all, so allowpopups was removed. WebRTC is fenced by the guest's IP-handling policy alone: this Chromium answers a webrtc content-security-policy directive as unrecognized and gathers candidates anyway, so the directive is deliberately absent from the served policy and a unit assertion keeps it out. A standalone Electron probe on the same version gathered 7 candidates including a STUN reflexive one without the policy and 0 with it. Two command paths this gate used to carry no longer exist, and their structured evidence was retired with them rather than left naming files that are no longer there: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. The first is src/renderer/src/lib/file-preview-capability-cleanup.test.ts, deleted in STA-5557 when HTML previews stopped creating paired browser tabs: on 2026-08-12 its five-assertion STA-4063 oracle ran red 5/5 on scan e4c278eb52 and on main bc2e30000b, partially green 2/5 on pre-fix PR #14100, and green 5/5 on candidate 14b5877a75; the surviving half of that oracle is web-runtime-browser-capability-cleanup.test.ts, still commanded above. The second is tests/e2e/paired-remote-html-browser-focus.spec.ts, renamed in STA-5557 to paired-remote-html-preview-local-render.spec.ts because the journey no longer proves anything about browser focus: its 2026-08-12 runs established the owning-runtime routing and no-focus-steal behavior of the then-current browser-tab preview (red on main 346e59c879 and a90a18d43d, green on candidates d05217c40d and b7de9c0961, red again with only the side-preview focus or routing seam disabled), and its 2026-08-26 runs are recorded below under the current path. An adversarial pass after the journey landed found four coverage gaps rather than shipping defects, and all four are pinned here. The page url had a third door: the navigation commit wrote whatever url was reported, unfenced, and while nothing reaches it for a document page today it sat one report away from giving a document page a URL that persistence, the publish boundary and the address bar all read. The group projection was the load-bearing half of the mobile publish boundary that no test had: removing it alone left the whole suite green, because no test in it had tab groups, while the phone payload gained a group whose order and active tab named a document tab it was never sent. The focus handoff was covered only in the narrowing direction, so widening it — dropping the page-activity check, or either half of the reader-surface check — survived both suites. And the handoff itself was offered once and never again, so another app taking the front left the preview's one route out shut until the pane remounted, which is a desktop gap rather than a test-only one. The standalone capability-cleanup and browser-placement ledger suites were removed, including their mixed-version rejection, cleanup transfer, capacity and partition alarms. web-runtime-session.test.ts no longer supplies browser creation, rollback or recovery assertions. The grouped workspace-document exclusion remains; the separate no-group publication example was removed. Existing live/browser-store evidence does not establish equivalent replacement alarms. Earlier descriptions of the removed fixtures and their run results are historical. The client-owned-placement and layout-groups suites are removed. The broad mirror fixture no longer supplies preview placement/reservation/move assertions. Browser rows retain pre-response replay and real local-workspace reuse; focus intent retains response ordering and exact-leaf sibling ordering. Supplied browser-layout and placement/focus projection alarms are lost; retained evidence is narrower and not equivalent. The test-value audit retires renderer grant-release mock ledgers, direct creation/title/session-field copies, notification/onboarding calls and several placement/per-page projections. Canonical-ID rejection, document reopen/restore and preview URL fences remain; they do not prove close-to-registry revocation or replace every lost alarm. Historical execution evidence is unchanged. The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite. Audit1308: src/renderer/src/components/editor/EditorPanelShell.header.test.tsx removed. Header visibility and mocked editor-surface inventory alarms disappear. No equivalent replacement coverage is claimed. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result.", + "coveredProviders": [], + "coverageNotes": "Audit1386 changes referenced sources; no new execution result validates reduced selectors. Retained unchanged receipts apply only to unchanged commands. Historical coverage: Audit1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): A macOS desktop host and a separate paired Electron client exercise the Explorer preview action end to end: the document renders inside the client-local orca-preview guest, the client gains exactly one browser workspace and it is the document one, the host's own page registry and tab snapshot gain nothing, the split does not take focus from the source editor, an explicit click activates the preview, and closing it with its X leaves the editor and terminal intact. Both host oracles are read in a run where an ordinary URL browser tab from the same client does arrive in them, because a host that cannot project browser pages at all would otherwise answer the absence question the same way. A second journey quits that client with a document tab open and relaunches it on the same profile, which is the restore the user's original report turned on: the same workspace row comes back blank and named by the document, renders again, and does so over a grant URL that differs from the one that was quit. The same journey pins the guest URL to the orca-preview scheme, so a regression back to file:// or to a host browser page is visible. The rewritten journey caught a live gap the old one could not see: since previews became browser pages they had no chrome to hand focus to their guest, so main refused every reported link click as coming from an unfocused guest and the one route out of a preview was dead. Main's own log answered it — registered true, grant live, focused false — after the embedder reported the anchor really taking six real presses. The pane now hands focus over when it is the reader's surface, and the journey routes. The preview's own components moved out of the editor folder into browser-pane/workspace-doc/ with this change, since a preview is a browser pane and no longer an editor one; their i18n keys keep the historical auto.components.editor.HtmlDocPreview prefix rather than dropping thirty translated strings across four locales for a tidier key path. Seven gate commands named those files by their old paths and were re-issued at the new ones, and the nine dated runs behind them were retired rather than left naming files that are no longer there, on the same principle this gate already applied twice: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. Each retired record's finding is carried into the replacement run recorded below it, including the red half of the entry/subresource pair, which was re-measured by re-applying its mutant at the new path rather than being dropped. The fence over the page-management channels was decided channel by channel before it was built, and that decision record is: of the 23 channels that can name a page, 6 are allowed for a workspace-document page — the tool channels the reader drives, which reach it through the one authority that reads both halves of the registry — 9 refuse (download accept and cancel, devtools, certificate proceed, client page metadata publish, viewport override, WebAuthn account response, registration probe, registration repair), 3 cannot name a page at all because they take a profile or a worktree instead, and the rest are the registration and activation doors, refused on membership. What shipped is not the per-channel guard that classification implies: eleven call sites read the browsing map directly, so a guard per channel is exactly the caller-flag seam that has to be remembered at every future call site. The registry was split in two instead, which makes the classification structural — a document guest is absent from the browsing map, so every refusing channel refuses by absence rather than by a guard, and a channel added later inherits the refusal without being classified at all. Unit contracts cover grant scoping and path containment, per-owner read routing, refusal of a truncated or over-cap read, the old-server binary degradation, scheme admission on will-attach-webview, the guest navigation policy, the plan layer's local/SSH/paired/out-of-worktree routing, and the distinct sentence the reader sees for each failure reason. The paired browser-creation surfaces this gate still owns — capability rejection cleanup, canonical page identity, reconciliation rollback, close convergence — keep their existing headed and headless journeys. Linux and Windows live journeys remain gaps. A refused subresource is a notice on a rendered document rather than a failure panel, since only the entry document's failure leaves the reader with nothing; the entry document is served as text by every owner and cannot be refused for its format at all. Grants die with the renderer document that minted them, at window creation and on every reload, because the renderer holds the only record of which preview owns which grant. The mint and revoke channels answer only the trusted renderer, and a preview tab is never republished to mobile clients, which have no grant to read it with. External URLs leave the preview only via a trusted user click on an anchor while the preview is focused; script-driven navigation and popups reach nothing. The guest's navigation sinks are deny-only and route no URL at all, and the guest carries a preview-only preload, pinned by will-attach-webview and denied to every other attach path, whose capture-phase click handler reports an anchor's URL to main; main answers only a report from a preview guest with a live bound grant that is the focused contents, and only for http(s). Two earlier reader-facing records from 2026-08-26, which measured a shape where a subresource failure was filtered out and the panel still carried an unsupported-asset sentence, were retired with that shape rather than left contradicting the 10/10 runs of the command they share. Two more were retired on the same principle when the external-link design changed: a record showing that removing a recent-input gate let the unattended document open two browser tabs, and a record showing that dropping the webview's allowpopups attribute left a target=_blank press with nothing to route. Both measured machinery that no longer exists — the input gate could not tell a script's navigation from the click it followed, which is the case now recorded below, and the preload intercepts the press before Chromium considers a popup at all, so allowpopups was removed. WebRTC is fenced by the guest's IP-handling policy alone: this Chromium answers a webrtc content-security-policy directive as unrecognized and gathers candidates anyway, so the directive is deliberately absent from the served policy and a unit assertion keeps it out. A standalone Electron probe on the same version gathered 7 candidates including a STUN reflexive one without the policy and 0 with it. Two command paths this gate used to carry no longer exist, and their structured evidence was retired with them rather than left naming files that are no longer there: vitest treats a missing named file as a filter and exits 0, so such a command reports green while silently running only the half that still exists. The first is src/renderer/src/lib/file-preview-capability-cleanup.test.ts, deleted in STA-5557 when HTML previews stopped creating paired browser tabs: on 2026-08-12 its five-assertion STA-4063 oracle ran red 5/5 on scan e4c278eb52 and on main bc2e30000b, partially green 2/5 on pre-fix PR #14100, and green 5/5 on candidate 14b5877a75; the surviving half of that oracle is web-runtime-browser-capability-cleanup.test.ts, still commanded above. The second is tests/e2e/paired-remote-html-browser-focus.spec.ts, renamed in STA-5557 to paired-remote-html-preview-local-render.spec.ts because the journey no longer proves anything about browser focus: its 2026-08-12 runs established the owning-runtime routing and no-focus-steal behavior of the then-current browser-tab preview (red on main 346e59c879 and a90a18d43d, green on candidates d05217c40d and b7de9c0961, red again with only the side-preview focus or routing seam disabled), and its 2026-08-26 runs are recorded below under the current path. An adversarial pass after the journey landed found four coverage gaps rather than shipping defects, and all four are pinned here. The page url had a third door: the navigation commit wrote whatever url was reported, unfenced, and while nothing reaches it for a document page today it sat one report away from giving a document page a URL that persistence, the publish boundary and the address bar all read. The group projection was the load-bearing half of the mobile publish boundary that no test had: removing it alone left the whole suite green, because no test in it had tab groups, while the phone payload gained a group whose order and active tab named a document tab it was never sent. The focus handoff was covered only in the narrowing direction, so widening it — dropping the page-activity check, or either half of the reader-surface check — survived both suites. And the handoff itself was offered once and never again, so another app taking the front left the preview's one route out shut until the pane remounted, which is a desktop gap rather than a test-only one. The standalone capability-cleanup and browser-placement ledger suites were removed, including their mixed-version rejection, cleanup transfer, capacity and partition alarms. web-runtime-session.test.ts no longer supplies browser creation, rollback or recovery assertions. The grouped workspace-document exclusion remains; the separate no-group publication example was removed. Existing live/browser-store evidence does not establish equivalent replacement alarms. Earlier descriptions of the removed fixtures and their run results are historical. The client-owned-placement and layout-groups suites are removed. The broad mirror fixture no longer supplies preview placement/reservation/move assertions. Browser rows retain pre-response replay and real local-workspace reuse; focus intent retains response ordering and exact-leaf sibling ordering. Supplied browser-layout and placement/focus projection alarms are lost; retained evidence is narrower and not equivalent. The test-value audit retires renderer grant-release mock ledgers, direct creation/title/session-field copies, notification/onboarding calls and several placement/per-page projections. Canonical-ID rejection, document reopen/restore and preview URL fences remain; they do not prove close-to-registry revocation or replace every lost alarm. Historical execution evidence is unchanged. The test-value audit removed the direct legacy editor-preview hydration example from tabs-hydration.test.ts. Dropping legacy preview chrome while preserving the same-document ordinary editor no longer has that alarm. The surviving corrupt-id deduplication case does not cover that migration, and retained preview journeys are not equivalent replacement coverage. Historical run counts and migration mutation findings describe the earlier suite. Audit1308: src/renderer/src/components/editor/EditorPanelShell.header.test.tsx removed. Header visibility and mocked editor-surface inventory alarms disappear. No equivalent replacement coverage is claimed. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1386: src/renderer/src/lib/doc-preview-grants.test.ts reduced. Repeated-mount mint count, ordinary release/remint and failed-mint cache retry standalone alarms removed; no actual grant server/access test. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-5557/remote-html-preview-render-workspace-files-locally-over-the-file", "https://linear.app/stably/issue/STA-3872/unable-to-open-html-files-in-remote-server", @@ -5764,7 +5765,7 @@ "https://github.com/stablyai/orca/pull/13876" ], "invariant": "Opening one HTML preview from a paired client renders the workspace document in exactly one client-local browser tab, located by that document and served over the orca-preview scheme. The client gains exactly that one browser workspace and it is the document one — blank where a URL page carries a URL, named by the document, with the chip naming the file — while the host gains no browser page at all, neither in its own page registry nor in the tab snapshot its clients publish into. The preview occupies its own split without taking focus from the source editor; an explicit click activates it, and closing it removes only the preview. Following a document from a file link is the other half of that switch and does move the reader to it, tab group included, whether the preview is new or already open, because opening a file is a request to look at it. A document tab quit with the client comes back as the same row on a grant the relaunched client mints afresh. A preview is named by the browser page it is open in, not by a namespace of its own, and the page registry has two halves: a workspace-document guest is registered in its own map and is absent from the browsing one entirely. That absence is the fence. Page, session and profile management, agent tab enumeration and command targeting, download routing and certificate attribution all read the browsing map directly, in more places than a per-channel guard could be remembered in, so none of them can name a document page and none of them carries a guard. Browser tools the reader drives (element grab, hover describe, selection capture, the annotation viewport bridge) are the one operation that legitimately spans the halves, and they go through the single authority that reads both, keyed by the page and its hosting renderer. The halves are disjoint in both directions: browsing registration refuses a page the document half already holds, and minting a grant refuses a page the browsing half already holds, so one id can never name a surface in both. The headless backend acts on that refusal by destroying the window it had already opened rather than leaving a policy-less page behind an id nothing can drive, keeping nothing under that id for its own shutdown to hand back. Registration refuses on the same terms when the guest it was asked about is already gone. The exit door is guarded in both its halves: a preview withdraws by revoking its grant and never through the unregister channel, so a page the document half holds arriving there is refused before either the registration teardown or the grab-state disposal beside it, which would otherwise drop the intent an in-flight preview grab compares by identity and leave that grab answering ok without ever arming its guest. A bridge request whose guest does not resolve is refused without tearing down the page it named, so a misaddressed request cannot cancel a healthy page's in-flight downloads and grabs. The annotation viewport bridge resolves its guest when its serialized op actually runs rather than when the request arrived, so a cross-process navigation while it waited cannot leave the bridge installed in a retired guest while the reader looks at a new one. State main keys by a preview's page is disposed when that page's grant is revoked, which is the only signal a preview's surface is gone. A tool asking for a page whose guest has not attached yet waits for that registration and arms when it arrives, rather than answering not-ready at the reader; that wait resolves only the request already naming this page, never the worktree-wide or any-tab waits the CLI and agents use to ask for a browser tab to drive. Handing the previewed document to the reader's own machine routes on the owners its grant was minted against — the file's own connection owner and the worktree's own runtime owner, neither read from the tab's stored fields. Only a document proven to live on this machine reaches the client OS; one with a resolved remote owner is downloaded first; and one whose owner cannot be resolved at all, workspace root included, is refused with a message naming that, because the download route would otherwise read the same absolute path on the client and hand back a same-named local file under the remote document's name. A runtime-owned path that falls outside its worktree root is refused by that route itself and surfaces as a failure toast rather than a download. Nothing the document does writes a file to this machine either: the preview partition denies downloads outright instead of routing them through the browser download flow, which has no page to attribute a preview's bytes to and would otherwise reserve a name in this desktop's Downloads folder and write them there unprompted. That refusal is visible to the reader and invisible to the document: the preview's shell carries a fixed sentence saying downloads are off, published at most once per preview per interval so a document asking in a loop cannot fill Orca's chrome, while the page itself gets back exactly what it got before, which is nothing. The sentence names no file, because the document chooses the name it offers; and a refusal never takes the document away the way an entry document's own failure does, whatever it names. A preview is a browser tab, not an editor tab in a preview mode: it is named the way a browser tab is named — by the document it shows when that document declares a title, and by the file it shows when it does not — while the chip goes on naming the file and the host whatever the document calls itself. A title is refused on the same terms the url is: a document that declares none has Chromium report the grant URL as its title, and that title is stored, mirrored onto the tab and written to disk, so anything carrying the scheme falls back to the file instead. It is created by the preview action as a page located by its document, it carries the workspace-relative path copy the editor's path header owned, and closing it revokes the grant that made the document readable while a URL tab closing beside it revokes nothing. Chrome persisted by builds that made previews editor tabs is dropped on restore rather than coming back naming a surface no restore can produce, and the ordinary editor tab for the same document is left alone. A document tab is held back at the mobile publish boundary — no client holds its grant, and the wire has no tab kind for it — while an ordinary browser tab beside it still publishes. It is held back from the group projection that publishes tab order, recency and group activity as well as from the tab list itself, so no published group names a tab the phone is never sent. A browser page can be located by a workspace document instead of a URL, and the document is the whole of its stored identity. The grant and the orca-preview URL that document is served over are minted when the page mounts and replaced by a hard reload, so neither is ever written to the page's url, mirrored onto its tab, persisted or published: such a page's url is the blank URL from creation through restore, including when a session written elsewhere carries a grant URL in, and what the session carries is the worktree and path a restored page mints afresh against today's owners. Every door onto a page's url holds that line — creation, the title update, and the navigation commit alike — so a report about a document page cannot give it a URL it never had, and the title fallback and the loading affordance follow the url each door actually wrote. The mirror carries the document too, so a tab entry cannot go on naming a document its active page has left. Every guest in the app is policy-attached through one door: a workspace document takes a restricted profile there rather than a separate installer beside it, so the attachment bookkeeping that door owns — what registration refuses, and what teardown frees — covers a preview on the same terms as a browsing page, and a preview takes none of the browsing machinery that door installs. That authority answers from the moment the embedder hands the guest over rather than only after a later navigation: the guest binds to the grant it is already showing, so the tools reach the document the reader opened and not just one they navigated to. A read the host reports as truncated or over-cap is refused rather than served partially, and a document outside the paired worktree is refused with a message naming that boundary instead of a bare read failure. The rendered document reaches nothing off-machine on its own: every served response carries a self-only content security policy, the preview session cancels any request that is not in-document, subframes cannot navigate outside the grant, a guest no document has yet bound to a grant may not navigate at all, the guest gathers no ICE candidates, and an SSH path that canonicalizes outside the grant root is refused before it is read. The one route out is a link the reader presses: a trusted click on an anchor, reported by the preview's own preload from a guest still bound to a live grant, leaves through the configured browser destination and captured source route — and only after the reader confirms the exact destination URL, so a document cannot spend a single stray press exfiltrating what it can read into a link it authored. The preview hands its guest that focus itself whenever it is the surface the reader is in — a browsing page gets it from the chrome around it, and a preview has no chrome to get it from — and it does so only then, so a preview mounted behind a terminal or an editor never takes the keyboard from what the reader is actually in. It offers again when the window itself takes focus back and nothing in the embedder has claimed that focus, because another app coming to the front lands focus on the embedder rather than the guest and the route out would otherwise stay shut until something remounted the pane — while the same window focus also arrives when the reader presses a tab, that being the guest's own blur returning, and taking focus back from there would fight the reader for their own click. Nothing else does. A navigation or popup the document starts by itself is swallowed whatever else is happening, including immediately after a genuine press elsewhere in the document, so a page that can read its grant cannot hand it to a browser tab; a middle click opens nothing; and a fragment link is answered inside the document. A preview attach carries the preview preload and no renderer-supplied one, and no other attach path can acquire it. A subresource the workspace will not send degrades the document to a notice naming that file, never to a failure panel over a page that rendered. A grant outlives neither the tab that owns it nor the renderer document that minted it, and only the trusted renderer can mint or revoke one. For the browser creations this gate still owns, owner-pinned creation returns the canonical host page identity before navigation readiness; delayed navigation cannot turn a created page into an unidentifiable failure or a duplicate retry. Capability rejection before host mutation must preserve the original error, issue no RPC, surface a failure toast, and remove only a caller-declared newly-created empty split. Post-create reconciliation failure requires exact rollback; ambiguous rollback rejects without local fallback.", - "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): In paired Electron, write an HTML fixture that declares its own title on the host, invoke the Explorer preview action on the client, and require the document text to be readable out of the orca-preview guest before judging any absence. With that presence established, require the client to hold exactly one browser workspace more than its baseline and that workspace to be the document one: page and tab url blank, the document path mirrored onto both, the tab named by the document's title, the chip naming the file, no editor row of the retired preview species anywhere, and the guest URL carrying the orca-preview scheme. Ask the host through its own page registry as well as through the tab snapshot, and in the same run open an ordinary URL browser tab from the same client and require that one to arrive in both — the presence precondition without which “the host gained nothing” is satisfied just as well by an oracle that cannot see browser pages at all. Require the preview to sit in a group other than the source editor's while the active group and tab remain the source editor's. Then click away to the terminal, click the preview tab, require it to reactivate and still render, close it with its own X, and require the document tab to be gone while the host still holds only the URL tab and the source group, source editor and terminal survive. Quit the client with a document tab open and relaunch it on the same profile: require the same workspace row to come back, blank and named by the document, rendering the document again over a grant URL that differs from the one that was quit, with no preview-scheme or document-named page anywhere in what the host holds. Prove the halves are live by flipping one product property at a time and requiring the run to fail: publish document workspaces to the host like ordinary ones, and stop mirroring the document onto the workspace row. Have the fixture document attempt its own egress on every load — an unattended window.open and location.href to an off-machine URL, plus an inline ICE gathering probe — and require the same baseline counts and a candidate count of zero, so the document's own attempts are measured rather than assumed. Then, as a separate phase after the close oracle has already run, bring the client window to the front, press the document's heading with a real mouse event, and require the document to report that the same press drove it to attempt a second window.open and location.href while both browser counts stay at that phase's baseline and nothing routes — the case a recent-input gate cannot distinguish from the press's own effect. Only then press the target=_blank link with a real mouse event and require both a recorded routing call that returned success and a browser count above that baseline, with the preview tab still open. Drive the preload's click policy as a unit oracle over a real document: a dispatched click, a trusted press on an external anchor, an anchor reached through what it wraps, an SVG animated href, a sibling preview link, fragment and percent-encoded fragment targets, a bare hash, and a middle click. Create a browser page located by a workspace document, handing creation a live grant URL, and require its stored url, its mirrored tab url and the written session payload all to be blank with no orca-preview string anywhere in what was written, while an ordinary page created the same way keeps the URL it was given and asks for the address bar the document page never does. Parse the written page and tab through the session schema and require the document to survive both halves. Hydrate them back and require the document page to return blank and still named — including when its page row was salvaged away and only the tab's own copy remains, and when a foreign session carried a grant URL into both rows. Drive the mirror across a page switch out of the document and back, and across a repair in which the document is the only mirrored field that differs. Name a document page from its document and require the tab to take that name, name it with an empty title and require the file, name it with a live grant URL and require the file again with no preview scheme anywhere in the written session, and require an ordinary blank browser tab beside it to still be called New Tab. Dispatch a title update out of a rendered preview's own guest and require it to reach the page state while the identity chip still reads the document's workspace-relative path. Attach a browsing guest and a workspace-document guest through the same method in one run and require the browsing one to take clicked-link routing, popup handling and auth-identity detach tracking while the document guest takes none of them, stays inside the grant it is showing, denies every window it asks for, and is dropped from the page-keyed document registry by the same teardown that frees its id for a later attach. Register a browsing guest and attach a workspace-document guest in one run against the real manager, require the one door to answer each page with the guest of its own half, and require the document page to be absent from the browsing map and from its enumeration. Drive both browsing registration entry points with a page the document half already holds and require them to register nothing, and drive the mint channel with a page the browsing half already holds and require it to refuse; and drive the offscreen one with a guest that is missing and with one already destroyed, requiring the same refusal. Arm a grab on a live preview target, drive the unregister channel at that same target in the window before the queued operation runs, and require the grab to reach the guest anyway — then drive the same sequence for an ordinary browser page and require its grab state to be disposed after all. Hold one viewport-bridge op open, queue a second behind it, swap the page's guest while that second op waits, and require the injection to land in the guest the page has then. Revoke a grant after a tool has run against its page and require that page's grab state to be cancelled and disposed. Ask a tool for a document page whose guest has not attached, require the request to park in the registration wait, attach the guest, and require the same request to arm on it; require a page nothing ever renders to answer not-ready once that wait elapses. With a document open, ask a tool for a browsing page id and require it to be answered by the browsing half or not at all, with the same channel reaching the document guest under the page it really renders. Drive open-externally for a document whose per-file owner is remote while the workspace-scoped owner is unresolved, for a runtime-owned worktree whose preview tab carries no runtime id of its own, and for a worktree that resolves no runtime owner while the tab still carries one, requiring the download route in each; and for an owner that cannot be resolved at all, and for an unknown workspace root while nothing names another host, requiring a refusal that neither opens nor downloads. Drive the headless backend with a page the document half already holds and require it to reject, destroy the window, and unregister nothing — then shut the backend down and require it still to have unregistered nothing. Navigate a bound preview guest at a second grant through both latch events and require it to stay on the grant it bound to. Mount a preview while a renderer drag is already in flight and require its guest to be click-through at the moment it is appended, not a turn later. Render the editor panel shell in each remaining tab mode and require the path header exactly where the surface does not already name itself. Drive the preview action and require a browser tab located by the document rather than an editor tab, require a second open of the same document to activate the tab it is already in, and require closing that tab to revoke its grant while a URL tab closed beside it revokes none. Publish a worktree holding a document tab and a URL tab and require only the URL tab to reach the mobile snapshot. Install the shared partition policies for a preview partition and for an ordinary browsing partition in the same run, fire each one's own will-download listener, and require the preview's to cancel while the browsing one still reaches the download router — then require the preview protocol installer to be what asks for that deny. In the same run, require the cancelled download to raise a reader-facing notice and the routed one to raise none. require the guest registry to name the bound grant for a live preview guest and nothing for a contents that is not one, has committed no document, or is gone. Drive the shell with a refusal and require one fixed sentence, still one row after three more refusals, standing beside an asset failure rather than being counted with it, gone behind the failure panel, and ignored when it names another preview's grant. Drive the main-side report gate directly for a sender that is no preview guest, a guest with no bound or a revoked grant, a genuine press Electron's webview focus flag misreports as unfocused, and non-web URLs; drive the reader-facing confirmation for accept, cancel, and a confirmed tab the browser refuses; and drive will-attach-webview in both preload directions. Run the per-owner reader, grant-containment, scheme-admission, guest-policy, and plan-routing contracts as unit oracles, including a host-reported truncation, an over-cap binary, and an out-of-worktree paired path. Drive the reader-facing component with the payloads the reader can actually produce — the entry document fails only as truncated or unreadable, a subresource additionally as a refused format — and require the asset case to leave the guest mounted. Drive the closed-tab cleanup hook, the window installer, and the grant IPC handlers directly, requiring the grant to be released when the preview tab closes, cleared at window creation and on a cross-document main-frame navigation, and refused to any sender that is not the trusted renderer. For the browser creations this gate still owns, run the unchanged contract oracle for direct create and side-preview callers with absent status, unknown capabilities, and a mixed-version host, requiring the original unsupported error or visible toast, zero RPCs, and no retained new split; hold a real navigation response beyond the 15-second client deadline after host creation and require the first RPC to return the exact host inventory page ID, one host page, and no retry; repeat reconciliation faults against headless serve and retain the separate exact-page reconciliation rollback oracle.", + "oracle": "Retained unchanged-selector receipts are the only current execution evidence. Reduced-source assertions await validation. Historical prior oracle: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): In paired Electron, write an HTML fixture that declares its own title on the host, invoke the Explorer preview action on the client, and require the document text to be readable out of the orca-preview guest before judging any absence. With that presence established, require the client to hold exactly one browser workspace more than its baseline and that workspace to be the document one: page and tab url blank, the document path mirrored onto both, the tab named by the document's title, the chip naming the file, no editor row of the retired preview species anywhere, and the guest URL carrying the orca-preview scheme. Ask the host through its own page registry as well as through the tab snapshot, and in the same run open an ordinary URL browser tab from the same client and require that one to arrive in both — the presence precondition without which “the host gained nothing” is satisfied just as well by an oracle that cannot see browser pages at all. Require the preview to sit in a group other than the source editor's while the active group and tab remain the source editor's. Then click away to the terminal, click the preview tab, require it to reactivate and still render, close it with its own X, and require the document tab to be gone while the host still holds only the URL tab and the source group, source editor and terminal survive. Quit the client with a document tab open and relaunch it on the same profile: require the same workspace row to come back, blank and named by the document, rendering the document again over a grant URL that differs from the one that was quit, with no preview-scheme or document-named page anywhere in what the host holds. Prove the halves are live by flipping one product property at a time and requiring the run to fail: publish document workspaces to the host like ordinary ones, and stop mirroring the document onto the workspace row. Have the fixture document attempt its own egress on every load — an unattended window.open and location.href to an off-machine URL, plus an inline ICE gathering probe — and require the same baseline counts and a candidate count of zero, so the document's own attempts are measured rather than assumed. Then, as a separate phase after the close oracle has already run, bring the client window to the front, press the document's heading with a real mouse event, and require the document to report that the same press drove it to attempt a second window.open and location.href while both browser counts stay at that phase's baseline and nothing routes — the case a recent-input gate cannot distinguish from the press's own effect. Only then press the target=_blank link with a real mouse event and require both a recorded routing call that returned success and a browser count above that baseline, with the preview tab still open. Drive the preload's click policy as a unit oracle over a real document: a dispatched click, a trusted press on an external anchor, an anchor reached through what it wraps, an SVG animated href, a sibling preview link, fragment and percent-encoded fragment targets, a bare hash, and a middle click. Create a browser page located by a workspace document, handing creation a live grant URL, and require its stored url, its mirrored tab url and the written session payload all to be blank with no orca-preview string anywhere in what was written, while an ordinary page created the same way keeps the URL it was given and asks for the address bar the document page never does. Parse the written page and tab through the session schema and require the document to survive both halves. Hydrate them back and require the document page to return blank and still named — including when its page row was salvaged away and only the tab's own copy remains, and when a foreign session carried a grant URL into both rows. Drive the mirror across a page switch out of the document and back, and across a repair in which the document is the only mirrored field that differs. Name a document page from its document and require the tab to take that name, name it with an empty title and require the file, name it with a live grant URL and require the file again with no preview scheme anywhere in the written session, and require an ordinary blank browser tab beside it to still be called New Tab. Dispatch a title update out of a rendered preview's own guest and require it to reach the page state while the identity chip still reads the document's workspace-relative path. Attach a browsing guest and a workspace-document guest through the same method in one run and require the browsing one to take clicked-link routing, popup handling and auth-identity detach tracking while the document guest takes none of them, stays inside the grant it is showing, denies every window it asks for, and is dropped from the page-keyed document registry by the same teardown that frees its id for a later attach. Register a browsing guest and attach a workspace-document guest in one run against the real manager, require the one door to answer each page with the guest of its own half, and require the document page to be absent from the browsing map and from its enumeration. Drive both browsing registration entry points with a page the document half already holds and require them to register nothing, and drive the mint channel with a page the browsing half already holds and require it to refuse; and drive the offscreen one with a guest that is missing and with one already destroyed, requiring the same refusal. Arm a grab on a live preview target, drive the unregister channel at that same target in the window before the queued operation runs, and require the grab to reach the guest anyway — then drive the same sequence for an ordinary browser page and require its grab state to be disposed after all. Hold one viewport-bridge op open, queue a second behind it, swap the page's guest while that second op waits, and require the injection to land in the guest the page has then. Revoke a grant after a tool has run against its page and require that page's grab state to be cancelled and disposed. Ask a tool for a document page whose guest has not attached, require the request to park in the registration wait, attach the guest, and require the same request to arm on it; require a page nothing ever renders to answer not-ready once that wait elapses. With a document open, ask a tool for a browsing page id and require it to be answered by the browsing half or not at all, with the same channel reaching the document guest under the page it really renders. Drive open-externally for a document whose per-file owner is remote while the workspace-scoped owner is unresolved, for a runtime-owned worktree whose preview tab carries no runtime id of its own, and for a worktree that resolves no runtime owner while the tab still carries one, requiring the download route in each; and for an owner that cannot be resolved at all, and for an unknown workspace root while nothing names another host, requiring a refusal that neither opens nor downloads. Drive the headless backend with a page the document half already holds and require it to reject, destroy the window, and unregister nothing — then shut the backend down and require it still to have unregistered nothing. Navigate a bound preview guest at a second grant through both latch events and require it to stay on the grant it bound to. Mount a preview while a renderer drag is already in flight and require its guest to be click-through at the moment it is appended, not a turn later. Render the editor panel shell in each remaining tab mode and require the path header exactly where the surface does not already name itself. Drive the preview action and require a browser tab located by the document rather than an editor tab, require a second open of the same document to activate the tab it is already in, and require closing that tab to revoke its grant while a URL tab closed beside it revokes none. Publish a worktree holding a document tab and a URL tab and require only the URL tab to reach the mobile snapshot. Install the shared partition policies for a preview partition and for an ordinary browsing partition in the same run, fire each one's own will-download listener, and require the preview's to cancel while the browsing one still reaches the download router — then require the preview protocol installer to be what asks for that deny. In the same run, require the cancelled download to raise a reader-facing notice and the routed one to raise none. require the guest registry to name the bound grant for a live preview guest and nothing for a contents that is not one, has committed no document, or is gone. Drive the shell with a refusal and require one fixed sentence, still one row after three more refusals, standing beside an asset failure rather than being counted with it, gone behind the failure panel, and ignored when it names another preview's grant. Drive the main-side report gate directly for a sender that is no preview guest, a guest with no bound or a revoked grant, a genuine press Electron's webview focus flag misreports as unfocused, and non-web URLs; drive the reader-facing confirmation for accept, cancel, and a confirmed tab the browser refuses; and drive will-attach-webview in both preload directions. Run the per-owner reader, grant-containment, scheme-admission, guest-policy, and plan-routing contracts as unit oracles, including a host-reported truncation, an over-cap binary, and an out-of-worktree paired path. Drive the reader-facing component with the payloads the reader can actually produce — the entry document fails only as truncated or unreadable, a subresource additionally as a refused format — and require the asset case to leave the guest mounted. Drive the closed-tab cleanup hook, the window installer, and the grant IPC handlers directly, requiring the grant to be released when the preview tab closes, cleared at window creation and on a cross-document main-frame navigation, and refused to any sender that is not the trusted renderer. For the browser creations this gate still owns, run the unchanged contract oracle for direct create and side-preview callers with absent status, unknown capabilities, and a mixed-version host, requiring the original unsupported error or visible toast, zero RPCs, and no retained new split; hold a real navigation response beyond the 15-second client deadline after host creation and require the first RPC to return the exact host inventory page ID, one host page, and no retry; repeat reconciliation faults against headless serve and retain the separate exact-page reconciliation rollback oracle.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime-browser.test.ts src/main/runtime/rpc/methods/browser.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts src/renderer/src/runtime/web-session-tabs-sync-browser-tabs.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/document-preview/doc-preview-grant-registry.test.ts src/main/browser/document-preview/doc-preview-file-reader.test.ts src/main/browser/document-preview/doc-preview-protocol.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/browser/document-preview/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts", @@ -5955,13 +5956,6 @@ "file": "src/shared/doc-preview-scheme.test.ts", "assertions": ["preview URLs round-trip per segment and reject a malformed grant id"] }, - { - "file": "src/renderer/src/lib/doc-preview-grants.test.ts", - "assertions": [ - "grants are deduped per preview tab so a StrictMode double-mount cannot revoke the surviving webview", - "a mint that rejects after its tab was released and reopened leaves the reopened tab's grant revocable" - ] - }, { "file": "src/main/ipc/doc-preview-grant-ipc.test.ts", "assertions": [ @@ -6121,16 +6115,6 @@ "summary": "Candidate with the sole delta that the preview pane does not hand focus to its guest — the tree as it stood before this change — failed at the target=_blank press: six real presses reached the anchor, the embedder had the webview as its active element, and main's own log answered each report with registered true, grant live, focused false. This is the gap the rewrite found: since previews became browser pages they had no chrome to pass focus on, so the one route out of a preview was shut.", "currentCommand": "SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_E2E_FORCE_HEADFUL=1 pnpm exec playwright test tests/e2e/paired-remote-html-preview-local-render.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=line" }, - { - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/renderer/src/lib/file-preview-action-visibility.test.tsx src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts", - "date": "2026-08-27", - "runner": "local", - "platform": "macos", - "result": "passed", - "durationSeconds": 1.63, - "summary": "189/189 over the grant, reader, protocol, guest-policy, scheme-admission, plan-routing, action-visibility, failure-message, external-link and terminal-row contracts. This replaces the 2026-08-27 record of the same command at 174/174, whose thirteenth file was the deleted doc-preview-external-link-bridge.test.ts: the STA-5758 hardening moved that behavior into the reader-facing confirmation (doc-preview-external-link-confirmation.test.ts), which now proves the exact destination is shown and nothing opens on cancel, alongside everything the retired run carried — the stale-mint rejection that must not evict a reopened preview tab, the CSP header and session request filter, the SSH symlink refusal, subframe containment, truncated and over-cap reads, the unservable-asset and empty-file split, the out-of-worktree paired refusal, and the guest deny-only navigation sinks. The count also carries the entry-only rule added on top of the hardening: a grant whose document directory is its own request base — a workspace-root or out-of-workspace document — reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass, because that directory is where secrets live and a DNS-prefetch beacon needs no click. Historical command preserved after removal of the mocked preview test suite in the test-value audit; currentCommand links only the remaining suites. The recorded result, count, duration and mutation narrative describe the historical command. The current command has not been rerun here and does not claim equivalent coverage.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/document-preview/doc-preview-grant-registry.test.ts src/main/browser/document-preview/doc-preview-file-reader.test.ts src/main/browser/document-preview/doc-preview-protocol.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/browser/document-preview/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts" - }, { "date": "2026-08-27", "runner": "local", @@ -6394,19 +6378,19 @@ ], "runtimeBudget": { "p95Seconds": 90, - "scope": "Historical pre-audit selector budget; changed selectors have no new runtime measurement. Unchanged receipts remain scoped to their own commands." + "scope": "Historical prior-selector budget; changed selectors have no new runtime measurement. Unchanged receipts apply only to their own commands." }, "flakeHistory": { "status": "unknown", - "evidence": "Changed selectors have no new soak result after Audit1308; any retained unchanged-source receipts apply only to their own commands. Historical account: The candidate passed isolated local macOS Electron runs before and after the final rebase; longer CI and soak history is not yet available." + "evidence": "Audit1386 changed selectors have no new soak result. Historical account: Changed selectors have no new soak result after Audit1308; any retained unchanged-source receipts apply only to their own commands. Historical account: The candidate passed isolated local macOS Electron runs before and after the final rebase; longer CI and soak history is not yet available." }, "redGreenEvidence": { "status": "missing", - "evidence": "Audit1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The unchanged STA-4063 five-assertion oracle is red 5/5 on scan e4c278eb52 and latest main bc2e30000b, partially green 2/5 on pre-fix PR #14100, green 5/5 on candidate 14b5877a75, and red 3/5 when only the new transaction cleanup boundary is disabled on final candidate a9e6fb7716. The earlier STA-4025 transaction oracle remains red on the post-#13909 base and green on this candidate. The byte-identical STA-4231 headed oracle is red on latest-main b19d99bd00: both 15-second creates return runtime_timeout without a page ID while the host retains two distinct pages after retry. The relevant behavior is unchanged through 0a0ae974d4. The candidate is green with one returned canonical ID, one host/client page, and no retry; disabling only the navigation-independent acknowledgement is red with the same two orphaned pages. The byte-identical 2ec1dcf1 Electron oracle remains red on latest main 346e59c879, green on candidate d05217c40d, and red with only side-preview focus disabled; historical 5806b23f evidence separately proves owning-runtime routing. For the STA-5557 preview behavior the rewritten journey is green 1/1 on the candidate and red when the sole delta makes the same preview action also create a browser tab, failing on clientBrowserWorkspaceCount 1 versus baseline 0. That absence assertion is gated on first reading the rendered document out of the guest, so it cannot pass by the preview never happening. Against pre-STA-5557 builds the journey is red by construction rather than by measurement: those builds have no html-preview tab mode, so the run would fail earlier, at materialization, and it has not been executed against an old build. The reader-facing half is red-green at the unit layer. The 6/6 green and 2/6 red pair recorded earlier on 2026-08-26 measured the superseded shape, where a subresource failure was filtered out entirely; the shipping shape is green 10/10 and red 3/10 when the sole delta makes every failure, asset or entry, set the blocking reason. The containment lines the second review found uncovered are red-green too: installing the scheme handler on the default session instead of the preview session, and widening the scheme privileges with allowServiceWorkers and bypassCSP, each fail the protocol suite as the sole delta, and deleting the html-preview case from the closed-tab cleanup fails the hook suite. The external-link phase added on 2026-08-26 is red-green by measurement: green 3/3 on the trusted-click design, in which the guest's own preload reports a trusted press on an anchor and main answers only a focused preview guest holding a live grant. Its earlier shape depended on the webview carrying allowpopups and went red with zero recorded routing calls when only that attribute was dropped; the preload now intercepts the press before Chromium considers a popup, so the attribute is gone and that measurement retired with it. One further run failed in the paired-client harness before reaching any preview step, when the client window never exposed its store within the launch timeout; that is a harness flake, not a preview signal. The two egress fences added on 2026-08-26 are red-green by measurement in the same journey, whose fixture attempts its own escape on every load and again from inside the handler for the reader's press on the heading: with deny-only navigation sinks the document opens no browser tab and the counts hold at baseline, and with the sole delta that those sinks route again behind a two-second recent-input gate the post-press location.href opens a real tab, clientBrowserWorkspaceCountAllWorktrees 1 against a baseline of 0. An earlier measurement of that same gate, taken when it was the shipping design, showed the unattended on-load egress opening two tabs once the gate was removed entirely; it was retired with the gate, because a gate that cannot tell a script's navigation from the click it followed was never the fence the claim needed. With the guest's IP-handling policy the document reports candidates=0, and with the sole delta that the policy call is removed it reports candidates=6. Both mutants were applied to the shipping module, rebuilt, and run against the live paired journey rather than reasoned about." + "evidence": "Audit1386 changed test sources; no new red/green validation of reduced selectors. Historical account: Audit1308 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The unchanged STA-4063 five-assertion oracle is red 5/5 on scan e4c278eb52 and latest main bc2e30000b, partially green 2/5 on pre-fix PR #14100, green 5/5 on candidate 14b5877a75, and red 3/5 when only the new transaction cleanup boundary is disabled on final candidate a9e6fb7716. The earlier STA-4025 transaction oracle remains red on the post-#13909 base and green on this candidate. The byte-identical STA-4231 headed oracle is red on latest-main b19d99bd00: both 15-second creates return runtime_timeout without a page ID while the host retains two distinct pages after retry. The relevant behavior is unchanged through 0a0ae974d4. The candidate is green with one returned canonical ID, one host/client page, and no retry; disabling only the navigation-independent acknowledgement is red with the same two orphaned pages. The byte-identical 2ec1dcf1 Electron oracle remains red on latest main 346e59c879, green on candidate d05217c40d, and red with only side-preview focus disabled; historical 5806b23f evidence separately proves owning-runtime routing. For the STA-5557 preview behavior the rewritten journey is green 1/1 on the candidate and red when the sole delta makes the same preview action also create a browser tab, failing on clientBrowserWorkspaceCount 1 versus baseline 0. That absence assertion is gated on first reading the rendered document out of the guest, so it cannot pass by the preview never happening. Against pre-STA-5557 builds the journey is red by construction rather than by measurement: those builds have no html-preview tab mode, so the run would fail earlier, at materialization, and it has not been executed against an old build. The reader-facing half is red-green at the unit layer. The 6/6 green and 2/6 red pair recorded earlier on 2026-08-26 measured the superseded shape, where a subresource failure was filtered out entirely; the shipping shape is green 10/10 and red 3/10 when the sole delta makes every failure, asset or entry, set the blocking reason. The containment lines the second review found uncovered are red-green too: installing the scheme handler on the default session instead of the preview session, and widening the scheme privileges with allowServiceWorkers and bypassCSP, each fail the protocol suite as the sole delta, and deleting the html-preview case from the closed-tab cleanup fails the hook suite. The external-link phase added on 2026-08-26 is red-green by measurement: green 3/3 on the trusted-click design, in which the guest's own preload reports a trusted press on an anchor and main answers only a focused preview guest holding a live grant. Its earlier shape depended on the webview carrying allowpopups and went red with zero recorded routing calls when only that attribute was dropped; the preload now intercepts the press before Chromium considers a popup, so the attribute is gone and that measurement retired with it. One further run failed in the paired-client harness before reaching any preview step, when the client window never exposed its store within the launch timeout; that is a harness flake, not a preview signal. The two egress fences added on 2026-08-26 are red-green by measurement in the same journey, whose fixture attempts its own escape on every load and again from inside the handler for the reader's press on the heading: with deny-only navigation sinks the document opens no browser tab and the counts hold at baseline, and with the sole delta that those sinks route again behind a two-second recent-input gate the post-press location.href opens a real tab, clientBrowserWorkspaceCountAllWorktrees 1 against a baseline of 0. An earlier measurement of that same gate, taken when it was the shipping design, showed the unattended on-load egress opening two tabs once the gate was removed entirely; it was retired with the gate, because a gate that cannot tell a script's navigation from the click it followed was never the fence the claim needed. With the guest's IP-handling policy the document reports candidates=0, and with the sole delta that the policy call is removed it reports candidates=6. Both mutants were applied to the shipping module, rebuilt, and run against the live paired journey rather than reasoned about." }, "performanceBudget": { "required": true, - "evidence": "Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. The transaction adds no timer, polling, retry, subprocess, RPC method, stream opcode, or global host publication. Owner-pinned success performs one create RPC followed by one asynchronous goto, retains one page, and performs zero retry or cleanup calls. A lost create acknowledgement performs one exact close by the preallocated canonical ID and at most one confirming snapshot. Headless goto publishes one worktree-scoped session snapshot after navigation. Capability rejection performs zero RPCs and one bounded empty-group close. The E2E-only one-shot faults are build-gated, retain one promise/flag and a bounded page-id set, and reset after each run. Pending client placements remain capped at 128 and clear on adoption, failure, or teardown." + "evidence": "Historical prior-selector budget; changed selectors have no fresh measurement. Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. The transaction adds no timer, polling, retry, subprocess, RPC method, stream opcode, or global host publication. Owner-pinned success performs one create RPC followed by one asynchronous goto, retains one page, and performs zero retry or cleanup calls. A lost create acknowledgement performs one exact close by the preallocated canonical ID and at most one confirming snapshot. Headless goto publishes one worktree-scoped session snapshot after navigation. Capability rejection performs zero RPCs and one bounded empty-group close. The E2E-only one-shot faults are build-gated, retain one promise/flag and a bounded page-id set, and reset after each run. Pending client placements remain capped at 128 and clear on adoption, failure, or teardown." }, "promotionCriteria": [ "Collect Linux and physical Windows paired-server evidence.", @@ -6430,7 +6414,11 @@ "Audit1308: src/renderer/src/components/editor/EditorPanelShell.header.test.tsx removed. Header visibility and mocked editor-surface inventory alarms disappear. No equivalent replacement coverage is claimed.", "Audit1308 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-27\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser-preview-tool-authorization.test.ts src/main/browser/browser-manager-annotation-bridge.test.ts src/main/browser/browser-manager-guest-lifecycle.test.ts src/main/browser/offscreen-browser-backend-lifecycle.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.52,\n \"summary\": \"174/174 at the relocated paths, replacing the 2026-08-27 records of the same command at 162/162 and 158/158. Those established, as sole deltas: removing the unregister-channel refusal goes red because the grab disposal beside it drops the intent an in-flight preview grab compares by identity, while an ordinary browser page driven through the identical sequence still has its grab state disposed; removing registration’s already-gone-guest refusal goes red for both a missing and a destroyed guest; removing the map deletion on the refusal path goes red at shutdown; the bridge’s own unregisterGuest branch, restored, tears down a live page over a misaddressed request; and the download fall-through for an unresolved owner hands back a same-named local file under a remote document’s name.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/browser/browser-preview-tool-authorization.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-annotation-bridge.test.ts src/main/browser/guest-lifecycle/manager/browser-manager-guest-lifecycle.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/editor/EditorPanelShell.header.test.tsx src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx\"\n }", "Retired during audit1312 aggregate-driver import cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-08-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-browser-tab-create.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts src/renderer/src/runtime/web-runtime-session.test.ts\",\"currentCommand\":\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-browser.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts src/renderer/src/hooks/useIpcEvents-close-routing-session-tabs.test.ts src/renderer/src/store/slices/browser.test.ts\",\"result\":\"passed\",\"durationSeconds\":10.83,\"summary\":\"Five post-review host, IPC, store, and client lifecycle files passed 1,356 tests with one pre-existing skip, including both shards that failed before the canonical page/workspace identity and typed missing-page cleanup corrections.\"}", - "Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed." + "Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed.", + "Audit1386: src/renderer/src/lib/doc-preview-grants.test.ts reduced. Repeated-mount mint count, ordinary release/remint and failed-mint cache retry standalone alarms removed; no actual grant server/access test. No equivalent replacement coverage is claimed.", + "Audit1386 retires this complete dated receipt because a changed source or imported registration driver is in either command field. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-grant-registry.test.ts src/main/browser/doc-preview-file-reader.test.ts src/main/browser/doc-preview-protocol.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/main/browser/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/renderer/src/lib/file-preview.test.ts src/renderer/src/lib/file-preview-action-visibility.test.tsx src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts\",\n \"date\": \"2026-08-27\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.63,\n \"summary\": \"189/189 over the grant, reader, protocol, guest-policy, scheme-admission, plan-routing, action-visibility, failure-message, external-link and terminal-row contracts. This replaces the 2026-08-27 record of the same command at 174/174, whose thirteenth file was the deleted doc-preview-external-link-bridge.test.ts: the STA-5758 hardening moved that behavior into the reader-facing confirmation (doc-preview-external-link-confirmation.test.ts), which now proves the exact destination is shown and nothing opens on cancel, alongside everything the retired run carried — the stale-mint rejection that must not evict a reopened preview tab, the CSP header and session request filter, the SSH symlink refusal, subframe containment, truncated and over-cap reads, the unservable-asset and empty-file split, the out-of-worktree paired refusal, and the guest deny-only navigation sinks. The count also carries the entry-only rule added on top of the hardening: a grant whose document directory is its own request base — a workspace-root or out-of-workspace document — reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass, because that directory is where secrets live and a DNS-prefetch beacon needs no click. Historical command preserved after removal of the mocked preview test suite in the test-value audit; currentCommand links only the remaining suites. The recorded result, count, duration and mutation narrative describe the historical command. The current command has not been rerun here and does not claim equivalent coverage.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/document-preview/doc-preview-grant-registry.test.ts src/main/browser/document-preview/doc-preview-file-reader.test.ts src/main/browser/document-preview/doc-preview-protocol.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/main/browser/document-preview/doc-preview-failure-notice.test.ts src/shared/doc-preview-scheme.test.ts src/renderer/src/lib/doc-preview-grants.test.ts src/main/window/main-window-webview-security.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts src/renderer/src/components/terminal-pane/terminal-file-link-actions.test.ts\"\n }", + "Audit1386 historical platform/provider scope before source changes: {\"coveredPlatforms\":[\"macos\"],\"coveredProviders\":[\"remote-runtime\"]}. Static assertion references are not current execution evidence.", + "Audit1386 historical assertion inventory for changed sources:\n[\n {\n \"file\": \"src/renderer/src/lib/doc-preview-grants.test.ts\",\n \"assertions\": [\n \"grants are deduped per preview tab so a StrictMode double-mount cannot revoke the surviving webview\",\n \"a mint that rejects after its tab was released and reopened leaves the reopened tab's grant revocable\"\n ]\n }\n]" ], "demotionRule": "Demote if a paired HTML preview creates a browser workspace on either side, renders from anything other than the orca-preview scheme, serves a read the host reported as truncated or over-cap, serves a document without the self-only content security policy, lets the preview session carry an off-machine request, reads an SSH path that canonicalizes outside its grant root, steals focus from the source editor when it opens, removes unrelated editor or terminal state on close, hides a document that rendered because one subresource was refused, leaves a grant readable after the tab or renderer that owns it is gone, mints one for a sender that is not the trusted renderer, opens a browser tab for a navigation the document started with no one at the keyboard, or lets the guest gather an ICE candidate; or if a paired browser creation this gate still owns produces duplicate client or host identities or loses its requested split." }, diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 5638ab41131..bf349ee8989 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -7101,7 +7101,6 @@ "src/renderer/src/components/dashboard/useDashboardPopoutBridge.test.tsx": 1178, "src/renderer/src/components/dashboard/useLiveDashboardSnapshot.test.ts": 3346, "src/renderer/src/components/dashboard/useRetainedAgents.test.ts": 2028, - "src/renderer/src/components/dashboard/useRetainedAgentsSync.test.ts": 1736, "src/renderer/src/components/dictation/DictationIndicator.test.tsx": 386, "src/renderer/src/components/dictation/dictation-insertion-target.test.ts": 323, "src/renderer/src/components/dictation/dictation-meter-store.test.tsx": 270, @@ -7341,7 +7340,6 @@ "src/renderer/src/components/editor/rich-markdown/clipboard/rich-markdown-paste-fallback.test.ts": 1931, "src/renderer/src/components/editor/rich-markdown/code-blocks/rich-markdown-plain-code.test.ts": 307, "src/renderer/src/components/editor/rich-markdown/review/rich-markdown-range-bounds.test.ts": 70, - "src/renderer/src/components/editor/rich-markdown/review/rich-markdown-review-rail-blocks.test.ts": 2561, "src/renderer/src/components/editor/rich-markdown/review/rich-markdown-review-text-ranges.test.ts": 53, "src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.adversarial.test.ts": 301, "src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.test.ts": 238, @@ -7481,7 +7479,6 @@ "src/renderer/src/components/linear-issue-text-draft-state.test.ts": 49, "src/renderer/src/components/linear-issue-text-save-plan.test.ts": 47, "src/renderer/src/components/linear-issue-view-storage.test.ts": 207, - "src/renderer/src/components/linear-project-view-surfaces.test.tsx": 781, "src/renderer/src/components/linear-scope-selector.test.ts": 281, "src/renderer/src/components/linear-workspace-picker.test.tsx": 693, "src/renderer/src/components/link-actions/LinkActionPopover.subscriptions.test.tsx": 4808, @@ -7501,7 +7498,6 @@ "src/renderer/src/components/native-chat-resume-failure-guidance.test.ts": 240, "src/renderer/src/components/native-chat-resume-finished-run.test.tsx": 4173, "src/renderer/src/components/native-chat-resume-on-restart-grouping.test.ts": 117, - "src/renderer/src/components/native-chat-resume-on-restart-outcome-toast.test.ts": 1930, "src/renderer/src/components/native-chat-resume-result-view.test.tsx": 2723, "src/renderer/src/components/native-chat-resume-run.test.ts": 65, "src/renderer/src/components/native-chat-resume-skipped-progress.test.tsx": 3009, @@ -7941,7 +7937,6 @@ "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-original-pane-index.test.ts": 86, "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-original-pane.test.ts": 64, "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-panel-session-list-request.test.tsx": 3044, - "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-scan-issue-state.test.ts": 222, "src/renderer/src/components/right-sidebar/ai-vault/search/ai-vault-search-session.test.ts": 201, "src/renderer/src/components/right-sidebar/ai-vault/accounts/ai-vault-session-account-render.test.tsx": 2885, "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-conditional-refresh-hook.test.tsx": 2370, @@ -7953,7 +7948,6 @@ "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-refresh.test.ts": 2034, "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat-launch.test.ts": 1260, "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat-workspace.test.ts": 746, - "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat.test.ts": 98, "src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume.test.ts": 1753, "src/renderer/src/components/right-sidebar/ai-vault/session-list/ai-vault-session-worktree-map.test.tsx": 2000, "src/renderer/src/components/right-sidebar/ai-vault/session-list/ai-vault-session-worktree.test.ts": 307, @@ -8081,7 +8075,6 @@ "src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.test.tsx": 1633, "src/renderer/src/components/settings/appearance/AppearancePane.test.tsx": 2066, "src/renderer/src/components/settings/artifacts/ArtifactsSettingsPane.test.tsx": 862, - "src/renderer/src/components/settings/browser/BrowserPane.search-catalog.test.tsx": 774, "src/renderer/src/components/settings/browser/profiles/BrowserProfileRow.google-cookie-clear.test.tsx": 27636, "src/renderer/src/components/settings/browser/BrowserSitePermissionsSetting.test.tsx": 739, "src/renderer/src/components/settings/browser/BrowserUserAgentSetting.test.tsx": 2325, @@ -9195,7 +9188,6 @@ "src/renderer/src/hooks/agent-hook-completion-background-turn-notifications.test.ts": 2279, "src/renderer/src/hooks/agent-hook-completion-grok-notifications.test.ts": 679, "src/renderer/src/hooks/agent-hook-completion-notifications.test.ts": 1131, - "src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts": 52, "src/renderer/src/hooks/automation-dispatch-folder-loss.test.ts": 575, "src/renderer/src/hooks/automation-dispatch-legacy-owner.test.ts": 1914, "src/renderer/src/hooks/automation-dispatch-unverifiable-loss.test.ts": 182, @@ -9334,7 +9326,6 @@ "src/renderer/src/hooks/useVirtualizedScrollAnchor.test.tsx": 311, "src/renderer/src/hooks/virtualized-hidden-row-measurement.test.ts": 199, "src/renderer/src/hooks/worktree-change-refresh-queue.test.ts": 59, - "src/renderer/src/hooks/zoom-routing.test.ts": 842, "src/renderer/src/i18n/I18nProvider.test.tsx": 2101, "src/renderer/src/i18n/language-catalog-timeout.test.tsx": 2025, "src/renderer/src/i18n/language-detection-reconciliation.test.tsx": 2927, diff --git a/src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts b/src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts index 49e91cb93a4..aeea8615dd1 100644 --- a/src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts +++ b/src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts @@ -125,39 +125,10 @@ function runRename(state: AppShortcutState = shortcutState()): boolean | undefin describe('tab.rename shortcut', () => { beforeEach(() => vi.clearAllMocks()) - it('leaves activeTabId on a background terminal while a structured tab is active', () => { - // Prove the stale-terminal premise before testing structured-tab rename. - mocks.store = storeForActiveTab(CHAT_UNIFIED_ID) - expect(mocks.store.activeTabType).toBe('agent-session') - expect(mocks.store.activeTabId).toBe(TERMINAL_ENTITY_ID) - }) - it('renames the structured chat tab, not the stale background terminal', () => { mocks.store = storeForActiveTab(CHAT_UNIFIED_ID) expect(runRename()).toBe(true) expect(mocks.requestTerminalTabRename).toHaveBeenCalledWith(CHAT_UNIFIED_ID) expect(mocks.requestTerminalTabRename).not.toHaveBeenCalledWith(TERMINAL_ENTITY_ID) }) - - it('still renames the terminal tab by its backing terminal id', () => { - mocks.store = storeForActiveTab(TERMINAL_UNIFIED_ID) - expect(mocks.store.activeTabType).toBe('terminal') - expect(runRename()).toBe(true) - expect(mocks.requestTerminalTabRename).toHaveBeenCalledWith(TERMINAL_ENTITY_ID) - }) - - it('does not claim the chord for a tab type that has no inline rename', () => { - mocks.store = { - ...storeForActiveTab(CHAT_UNIFIED_ID), - activeTabType: 'browser' - } as AppState - expect(runRename()).toBe(false) - expect(mocks.requestTerminalTabRename).not.toHaveBeenCalled() - }) - - it('does not claim the chord for a structured tab with no active worktree', () => { - mocks.store = storeForActiveTab(CHAT_UNIFIED_ID) - expect(runRename({ ...shortcutState(), activeWorktreeId: null })).toBe(false) - expect(mocks.requestTerminalTabRename).not.toHaveBeenCalled() - }) }) diff --git a/src/renderer/src/app-shell/startup-window-parity.test.ts b/src/renderer/src/app-shell/startup-window-parity.test.ts index 03a3a6813f9..b74d5552b40 100644 --- a/src/renderer/src/app-shell/startup-window-parity.test.ts +++ b/src/renderer/src/app-shell/startup-window-parity.test.ts @@ -10,15 +10,9 @@ import { WORKSPACE_LAYOUT_PROFILES } from '../../../shared/workspace-layout/work import { saveWorkspaceLayout } from '../../../shared/workspace-layout/workspace-layout-save' import { createStoreSessionMockApi } from '../store/slices/store-session-test-harness' import { createTestStore } from '../store/slices/store-test-helpers' -import { - ACCEPTED_DIFFERENCES, - GIT, - KNOWN_DIFFERENCES, - sweptAtStartup -} from './startup-window-parity-differences.test-fixture' +import { GIT, sweptAtStartup } from './startup-window-parity-differences.test-fixture' import { bootWindowFromPartitions, - diffStartupWindowViews, projectStartupWindowView } from './startup-window-view.test-fixture' @@ -55,48 +49,6 @@ function normalize(hostId: ExecutionHostId, session: WorkspaceSessionState): Wor const PROFILES = [...WORKSPACE_LAYOUT_PROFILES, ...LEGACY_WORKSPACE_LAYOUT_PROFILES] describe('the window shows the same session before and after the shared Loader rewrites it', () => { - it.each(PROFILES.map((profile) => [profile.name, profile] as const))( - '%s', - async (name, { hostId, build }) => { - const stored = build() - const storedView = projectStartupWindowView( - await bootWindowFromPartitions(createTestStore(), [{ hostId, session: stored }]) - ) - const normalizedView = projectStartupWindowView( - await bootWindowFromPartitions(createTestStore(), [ - { hostId, session: normalize(hostId, stored) } - ]) - ) - // The window must show the stored workspaces at all, or an empty view hides differences. - for (const [key, rows] of Object.entries(stored.tabsByWorktree)) { - if (rows.length > 0) { - expect(storedView, key).toHaveProperty([`${key} group 0 tabs`]) - } - } - const differences = diffStartupWindowViews(storedView, normalizedView) - const known = KNOWN_DIFFERENCES.filter((entry) => entry.fixture === name) - const accepted = ACCEPTED_DIFFERENCES.filter((entry) => entry.fixture === name) - expect( - differences.map((difference) => difference.path).sort(), - `window differences (stored vs normalized):\n${JSON.stringify(differences, null, 2)}` - ).toEqual( - [ - ...known.flatMap((entry) => entry.paths), - ...accepted.flatMap((entry) => Object.keys(entry.values)) - ].sort() - ) - // An accepted difference stays accepted only while it shows exactly the recorded values. - const acceptedValues = Object.assign({}, ...accepted.map((entry) => entry.values)) - expect( - Object.fromEntries( - differences - .filter((difference) => difference.path in acceptedValues) - .map(({ path, stored, normalized }) => [path, { stored, normalized }]) - ) - ).toEqual(acceptedValues) - } - ) - it('keeps the tab-bar twin selected when a selected legacy copy is dropped', async () => { const profile = PROFILES.find( (entry) => entry.name === 'legacy row repeating a tab-bar tab’s terminal, selected' @@ -122,11 +74,4 @@ describe('the window shows the same session before and after the shared Loader r expect(view[`${GIT} group 0 tabs`]).toEqual(['tab-a', 'tab-b']) expect(view).not.toHaveProperty([`${GIT} tab /Users/dev/orca/src/app.ts`]) }) - - it('lists differences only for fixtures that exist', () => { - const names = new Set(PROFILES.map((profile) => profile.name)) - expect( - [...KNOWN_DIFFERENCES, ...ACCEPTED_DIFFERENCES].filter((entry) => !names.has(entry.fixture)) - ).toEqual([]) - }) }) diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx index 82007f380ab..56244dbde40 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx @@ -135,31 +135,4 @@ describe('NewWorkspaceComposerCard set location', () => { expect(storeMocks.openModal).not.toHaveBeenCalled() expect(storeMocks.openSettingsPage).not.toHaveBeenCalled() }) - - // Flush independently so this test cannot rely on an earlier test warming the chunk. - it('closes the nested dialog before publishing the ready run target', async () => { - const nestedOpenChanges: boolean[] = [] - const setupChanges: string[] = [] - container = await renderCard({ - onNestedDialogOpenChange: (open) => nestedOpenChanges.push(open), - onProjectHostSetupChange: (setupId) => setupChanges.push(setupId) - }) - - act(() => { - container?.querySelector('div[data-run-target-combobox-root="true"]')?.click() - }) - const setLocation = [...document.body.querySelectorAll('button')].find( - (button) => button.textContent?.includes('Set project location') - ) - act(() => setLocation?.click()) - await act(async () => {}) - const complete = [...document.body.querySelectorAll('button')].find( - (button) => button.textContent === 'Complete location' - ) - act(() => complete?.click()) - - expect(nestedOpenChanges).toEqual([true, false]) - expect(setupChanges).toEqual(['setup-remote']) - expect(document.body.querySelector('[data-testid="set-project-location-dialog"]')).toBeNull() - }) }) diff --git a/src/renderer/src/components/activity/activity-clear-completed-hmr.test.ts b/src/renderer/src/components/activity/activity-clear-completed-hmr.test.ts index 9e6cdda5533..395ef76fb99 100644 --- a/src/renderer/src/components/activity/activity-clear-completed-hmr.test.ts +++ b/src/renderer/src/components/activity/activity-clear-completed-hmr.test.ts @@ -135,20 +135,6 @@ afterEach(() => { vi.clearAllMocks() }) -it('preserves the pending pagehide flush through HMR and removes the listener when drained', () => { - const remove = vi.spyOn(domWindow, 'removeEventListener') - try { - clearCompletedActivity([doneThread]) - disposePendingClearCompletedEvictionListener() - expect(remove).not.toHaveBeenCalledWith('pagehide', flushPendingClearCompletedEvictions) - domWindow.dispatchEvent(new Event('pagehide')) - expect(drop).toHaveBeenCalledOnce() - expect(remove).toHaveBeenCalledWith('pagehide', flushPendingClearCompletedEvictions) - } finally { - remove.mockRestore() - } -}) - it('preserves Undo after HMR without dropping the restored persisted row', () => { clearCompletedActivity([doneThread]) disposePendingClearCompletedEvictionListener() @@ -159,20 +145,3 @@ it('preserves Undo after HMR without dropping the restored persisted row', () => expect(drop).not.toHaveBeenCalled() expect(mockStore.retainedAgentsByPaneKey['t-done:1']).toBeDefined() }) - -it('releases the retired listener when its fallback settles and supports a late stale handler', () => { - const remove = vi.spyOn(domWindow, 'removeEventListener') - try { - clearCompletedActivity([doneThread]) - disposePendingClearCompletedEvictionListener() - vi.advanceTimersByTime(CLEAR_COMPLETED_EVICTION_FALLBACK_MS) - expect(drop).toHaveBeenCalledOnce() - expect(remove).toHaveBeenCalledWith('pagehide', flushPendingClearCompletedEvictions) - mockStore.retainedAgentsByPaneKey = { 't-done:1': makeRetained('t-done:1') } - clearCompletedActivity([doneThread]) - domWindow.dispatchEvent(new Event('pagehide')) - expect(drop).toHaveBeenCalledTimes(2) - } finally { - remove.mockRestore() - } -}) diff --git a/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts b/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts index 67032336f9f..ca62630dae8 100644 --- a/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts +++ b/src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts @@ -3,8 +3,6 @@ import type { AgentStatusIpcPayload } from '../../../../shared/agent-status/cont import type { DashboardCard, DashboardSnapshot } from '../../../../shared/dashboard-snapshot' import { patchDashboardSnapshotFromAgentStatus } from './dashboard-agent-status-patch' -import { dashboardCardDisplayState } from '../../../../shared/dashboard-snapshot' - function card(overrides: Partial = {}): DashboardCard { return { paneKey: 'tab-1:leaf-1', @@ -46,118 +44,6 @@ function snapshot(cards: DashboardCard[] = [card()]): DashboardSnapshot { } describe('patchDashboardSnapshotFromAgentStatus', () => { - it.each(['cancellation', 'failure', 'unconfirmed'] as const)( - 'projects a newer %s verdict and clears it on the next full status', - (outcome) => { - const patched = patchDashboardSnapshotFromAgentStatus( - snapshot(), - event({ - state: 'done', - mainAgent: { state: 'done', outcome, stateStartedAt: 250 } - }) - ).snapshot - expect(dashboardCardDisplayState(patched.cards[0])).toBe( - outcome === 'cancellation' - ? 'interrupted' - : outcome === 'failure' - ? 'failed' - : 'unconfirmed' - ) - expect(patched.cards[0].bucket).toBe('done') - for (const interrupted of [undefined, false]) { - const cleared = patchDashboardSnapshotFromAgentStatus( - patched, - event({ state: 'working', interrupted, receivedAt: 400, stateStartedAt: 350 }) - ).snapshot - expect(dashboardCardDisplayState(cleared.cards[0])).toBe('working') - } - expect( - patchDashboardSnapshotFromAgentStatus( - patched, - event({ receivedAt: 400, programStatusOnly: true }) - ).snapshot - ).toBe(patched) - expect( - patchDashboardSnapshotFromAgentStatus( - patched, - event({ receivedAt: 400, providerSessionOnly: true }) - ).snapshot - ).toBe(patched) - } - ) - - it('patches one known card without rebuilding the dashboard topology', () => { - const original = snapshot([card(), card({ paneKey: 'tab-2:leaf-2' })]) - const result = patchDashboardSnapshotFromAgentStatus(original, event()) - - expect(result.matched).toBe(true) - expect(result.snapshot.cards[0]).toMatchObject({ - bucket: 'attention', - dotState: 'blocked', - task: 'Need a decision', - lastUserMessage: 'Need a decision', - askSummary: '{"question":"Continue?"}', - stateChangedAt: 250, - statusUpdatedAt: 300, - unseen: true - }) - expect(result.snapshot.cards[1]).toBe(original.cards[1]) - }) - - it('preserves cached fields omitted from same-state hook pings', () => { - const original = snapshot([ - card({ - bucket: 'attention', - dotState: 'waiting', - askSummary: 'Pick one', - lastAgentMessage: 'Waiting', - stateChangedAt: 250, - unseen: true - }) - ]) - const result = patchDashboardSnapshotFromAgentStatus( - original, - event({ state: 'waiting', prompt: '', interactivePrompt: undefined, stateStartedAt: 250 }) - ) - - expect(result.snapshot.cards[0]).toMatchObject({ - task: 'old task', - askSummary: 'Pick one', - lastAgentMessage: 'Waiting', - unseen: true - }) - }) - - it('patches same-state working into passive monitoring', () => { - const result = patchDashboardSnapshotFromAgentStatus( - snapshot(), - event({ state: 'working', workingMode: 'monitoring', stateStartedAt: 100 }) - ) - - expect(result.snapshot.cards[0]).toMatchObject({ - bucket: 'working', - dotState: 'working', - workingMode: 'monitoring' - }) - }) - - it('keeps an unverifiable child compatible with the pop-out wire vocabulary', () => { - const result = patchDashboardSnapshotFromAgentStatus( - snapshot(), - event({ - subagents: [{ id: 'child-1', state: 'unverifiable', startedAt: 100 }] - }) - ) - - expect(result.snapshot.cards[0].subagents).toEqual([ - { - id: 'tab-1:leaf-1\u0000subagent:child-1', - name: 'unknown', - dotState: 'idle' - } - ]) - }) - it('ignores stale, wrong-workspace, and session-only events', () => { const original = snapshot() expect( @@ -170,14 +56,4 @@ describe('patchDashboardSnapshotFromAgentStatus', () => { patchDashboardSnapshotFromAgentStatus(original, event({ providerSessionOnly: true })).snapshot ).toBe(original) }) - - it('asks the caller for topology only when the pane is unknown', () => { - const original = snapshot() - const result = patchDashboardSnapshotFromAgentStatus( - original, - event({ paneKey: 'tab-new:leaf-new' }) - ) - - expect(result).toEqual({ matched: false, snapshot: original }) - }) }) diff --git a/src/renderer/src/components/dashboard/useRetainedAgentsSync.test.ts b/src/renderer/src/components/dashboard/useRetainedAgentsSync.test.ts deleted file mode 100644 index 5ac7d37b5b1..00000000000 --- a/src/renderer/src/components/dashboard/useRetainedAgentsSync.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status/contracts/agent-status-freshness' -import type { - AgentStatusEntry, - AgentStatusState -} from '../../../../shared/agent-status/contracts/agent-status-types' -import type { FolderWorkspace } from '../../../../shared/folder-workspaces/folder-workspace-types' -import type { Repo } from '../../../../shared/repo-types' -import type { TerminalTab } from '../../../../shared/workspace-layout/terminal-tab-types' -import type { Worktree } from '../../../../shared/worktree/types' -import { folderWorkspaceKey } from '../../../../shared/workspace-scope' -import { makePaneKey } from '../../../../shared/workspace-layout/stable-pane-id' -import { buildRetainedAgentsSyncSnapshot } from './useRetainedAgents' - -const ACTIVE_PANE_KEY = makePaneKey('tab-active', '22222222-2222-4222-8222-222222222222') -const ARCHIVED_PANE_KEY = makePaneKey('tab-archived', '33333333-3333-4333-8333-333333333333') -const FOLDER_PANE_KEY = makePaneKey('tab-folder', '44444444-4444-4444-8444-444444444444') -const ARCHIVED_FOLDER_PANE_KEY = makePaneKey( - 'tab-folder-archived', - '55555555-5555-4555-8555-555555555555' -) - -function makeFolderWorkspace(overrides?: Partial): FolderWorkspace { - return { - id: 'folder-1', - projectGroupId: 'group-1', - name: 'Folder', - folderPath: '/repo/folder', - linkedTask: null, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 1, - createdAt: 1, - updatedAt: 1, - ...overrides - } -} - -function makeRepo(): Repo { - return { - id: 'repo-1', - path: '/repo', - displayName: 'Repo', - badgeColor: '#000', - addedAt: 1 - } -} - -function makeWorktree(overrides?: Partial): Worktree { - return { - id: 'wt-1', - repoId: 'repo-1', - path: '/repo/wt-1', - head: 'abc123', - branch: 'feature', - isBare: false, - isMainWorktree: false, - displayName: 'feature', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 1, - ...overrides - } -} - -function makeTab(overrides?: Partial): TerminalTab { - return { - id: 'tab-1', - ptyId: null, - worktreeId: 'wt-1', - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1, - ...overrides - } -} - -function makeEntry(args: { - paneKey: string - state: AgentStatusState - updatedAt: number - stateStartedAt?: number - prompt?: string - toolName?: string -}): AgentStatusEntry { - return { - state: args.state, - prompt: args.prompt ?? 'Fix it', - updatedAt: args.updatedAt, - stateStartedAt: args.stateStartedAt ?? args.updatedAt, - paneKey: args.paneKey, - terminalTitle: 'Claude', - stateHistory: [], - agentType: 'claude', - toolName: args.toolName - } -} - -describe('buildRetainedAgentsSyncSnapshot', () => { - it('builds live rows for non-archived worktrees and stale-decays active states', () => { - const repo = makeRepo() - const activeWorktree = makeWorktree({ id: 'wt-active' }) - const archivedWorktree = makeWorktree({ id: 'wt-archived', isArchived: true }) - const activeTab = makeTab({ id: 'tab-active', worktreeId: 'wt-active' }) - const archivedTab = makeTab({ id: 'tab-archived', worktreeId: 'wt-archived' }) - - const snapshot = buildRetainedAgentsSyncSnapshot({ - repos: [repo], - worktreesByRepo: { [repo.id]: [activeWorktree, archivedWorktree] }, - folderWorkspaces: [], - tabsByWorktree: { - [activeWorktree.id]: [activeTab], - [archivedWorktree.id]: [archivedTab] - }, - agentStatusByPaneKey: { - [ACTIVE_PANE_KEY]: makeEntry({ - paneKey: ACTIVE_PANE_KEY, - state: 'working', - updatedAt: 10_000, - stateStartedAt: 10_000 - }), - [ARCHIVED_PANE_KEY]: makeEntry({ - paneKey: ARCHIVED_PANE_KEY, - state: 'done', - updatedAt: 20_000, - stateStartedAt: 20_000 - }) - }, - now: 10_000 + AGENT_STATUS_STALE_AFTER_MS + 1 - }) - - expect([...snapshot.existingWorktreeIds]).toEqual(['wt-active']) - expect(snapshot.currentAgents.get(ACTIVE_PANE_KEY)?.row.state).toBe('idle') - expect(snapshot.currentAgents.get(ARCHIVED_PANE_KEY)).toBeUndefined() - }) - - it('builds live rows for non-archived folder workspaces and skips archived ones', () => { - const folderWorkspace = makeFolderWorkspace() - const archivedFolderWorkspace = makeFolderWorkspace({ id: 'folder-2', isArchived: true }) - const workspaceKey = folderWorkspaceKey(folderWorkspace.id) - const archivedWorkspaceKey = folderWorkspaceKey(archivedFolderWorkspace.id) - const tab = makeTab({ id: 'tab-folder', worktreeId: workspaceKey }) - const archivedTab = makeTab({ id: 'tab-folder-archived', worktreeId: archivedWorkspaceKey }) - - const snapshot = buildRetainedAgentsSyncSnapshot({ - repos: [], - worktreesByRepo: {}, - folderWorkspaces: [folderWorkspace, archivedFolderWorkspace], - tabsByWorktree: { [workspaceKey]: [tab], [archivedWorkspaceKey]: [archivedTab] }, - agentStatusByPaneKey: { - [FOLDER_PANE_KEY]: makeEntry({ - paneKey: FOLDER_PANE_KEY, - state: 'done', - updatedAt: 10_000 - }), - [ARCHIVED_FOLDER_PANE_KEY]: makeEntry({ - paneKey: ARCHIVED_FOLDER_PANE_KEY, - state: 'done', - updatedAt: 10_000 - }) - }, - now: 10_000 - }) - - expect([...snapshot.existingWorktreeIds]).toEqual([workspaceKey]) - expect(snapshot.currentAgents.get(FOLDER_PANE_KEY)?.worktreeId).toBe(workspaceKey) - expect(snapshot.currentAgents.get(ARCHIVED_FOLDER_PANE_KEY)).toBeUndefined() - }) -}) diff --git a/src/renderer/src/components/editor/DiagramViewerModal.test.tsx b/src/renderer/src/components/editor/DiagramViewerModal.test.tsx index 5fd2c8643b7..2e9a2d707b1 100644 --- a/src/renderer/src/components/editor/DiagramViewerModal.test.tsx +++ b/src/renderer/src/components/editor/DiagramViewerModal.test.tsx @@ -62,25 +62,6 @@ afterEach(() => { }) describe('DiagramViewerModal', () => { - it('shows the diagram fitted and centered, and Escape returns focus to the opener', async () => { - const user = userEvent.setup() - render() - const opener = screen.getByRole('button', { name: 'open' }) - await user.click(opener) - - expect(screen.getByRole('dialog', { name: 'Diagram' })).toBeInTheDocument() - sizeCanvas() - const image = screen.getByRole('img', { name: 'Diagram' }) - expect(image.getAttribute('src')).toMatch(/^data:image\/svg\+xml/) - expect(image.style.width).toBe('400px') - expect(image.style.transform).toBe('translate(300px, 200px)') - expect(screen.getByRole('button', { name: 'Fit to screen' })).toBeDisabled() - - await user.keyboard('{Escape}') - await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()) - await waitFor(() => expect(document.activeElement).toBe(opener)) - }) - it('zooms from the toolbar, pans on Alt-drag, and fits again', async () => { const user = userEvent.setup() render() @@ -134,17 +115,6 @@ describe('DiagramViewerModal', () => { await waitFor(() => expect(document.activeElement).toBe(opener)) }) - it('pans by the wheel distance whatever unit the device reports', async () => { - const user = userEvent.setup() - render() - await user.click(screen.getByRole('button', { name: 'open' })) - const canvas = sizeCanvas() - const image = screen.getByRole('img', { name: 'Diagram' }) - - fireEvent.wheel(canvas, { deltaY: 3, deltaMode: WheelEvent.DOM_DELTA_LINE }) - expect(image.style.transform).toBe('translate(300px, 152px)') - }) - it('leaves focus alone when another modal replaced the viewer', async () => { const user = userEvent.setup() render() diff --git a/src/renderer/src/components/editor/rich-markdown/lists/rich-markdown-ordered-task-children.test.ts b/src/renderer/src/components/editor/rich-markdown/lists/rich-markdown-ordered-task-children.test.ts index 99f28240af0..11120de1922 100644 --- a/src/renderer/src/components/editor/rich-markdown/lists/rich-markdown-ordered-task-children.test.ts +++ b/src/renderer/src/components/editor/rich-markdown/lists/rich-markdown-ordered-task-children.test.ts @@ -51,25 +51,6 @@ const expectedTasks = [ ] describe('task children of ordered list items', () => { - it.each([ - ['4. Parent', 3, '-'], - ['10) Parent', 4, '-'] - ] as const)('keeps sibling task states under %s at indent %s', (parent, indent, marker) => { - const source = [ - parent, - `${' '.repeat(indent)}${marker} [ ] First`, - `${' '.repeat(indent)}${marker} [x] Middle`, - `${' '.repeat(indent)}${marker} [X] Last` - ].join('\n') - const editor = openMarkdown(source) - try { - editor.state.doc.check() - expect(taskItems(editor)).toEqual(expectedTasks) - } finally { - editor.destroy() - } - }) - it('preserves checked siblings when Enter splits a task before save and reopen', () => { const source = '4. Parent\n - [ ] First\n - [x] Middle\n - [x] Last' const editor = openMarkdown(source) @@ -119,21 +100,6 @@ describe('task children of ordered list items', () => { } }) - it('keeps a four-space continuation paragraph free of leftover indent', () => { - const editor = openMarkdown('1. Parent\n - [ ] a\n more text') - try { - const paragraphs: string[] = [] - editor.state.doc.descendants((node) => { - if (node.type.name === 'paragraph') { - paragraphs.push(node.textContent) - } - }) - expect(paragraphs).toEqual(['Parent', 'a', 'more text']) - } finally { - editor.destroy() - } - }) - it('keeps an intentionally nested task under its owning sibling', () => { const editor = openMarkdown( '4. Parent\n - [ ] First\n - [x] Child\n - [x] Middle\n - [x] Last' @@ -149,25 +115,4 @@ describe('task children of ordered list items', () => { editor.destroy() } }) - - it('keeps following plain bullets and the next ordered item in their own lists', () => { - const editor = openMarkdown( - '4. Parent\n - [ ] First\n - Plain\n - [x] Last\n5. Following' - ) - try { - editor.state.doc.check() - expect(taskItems(editor)).toEqual([expectedTasks[0], expectedTasks[2]]) - expect(editor.state.doc.firstChild?.childCount).toBe(2) - expect(editor.state.doc.firstChild?.lastChild?.textContent).toBe('Following') - const plainItems: string[] = [] - editor.state.doc.descendants((node) => { - if (node.type.name === 'listItem' && node.firstChild?.textContent === 'Plain') { - plainItems.push(node.type.name) - } - }) - expect(plainItems).toEqual(['listItem']) - } finally { - editor.destroy() - } - }) }) diff --git a/src/renderer/src/components/editor/rich-markdown/review/rich-markdown-review-rail-blocks.test.ts b/src/renderer/src/components/editor/rich-markdown/review/rich-markdown-review-rail-blocks.test.ts deleted file mode 100644 index 959b20eefeb..00000000000 --- a/src/renderer/src/components/editor/rich-markdown/review/rich-markdown-review-rail-blocks.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -// @vitest-environment happy-dom -import { afterEach, describe, expect, it, vi } from 'vitest' -import { Editor, type JSONContent } from '@tiptap/core' -import type { DiffComment } from '../../../../../../shared/diff-comment-types' -import { createRichMarkdownExtensions } from '../schema/rich-markdown-extensions' -import { createRichMarkdownEditorCodec } from '../document/rich-markdown-source-transport' -import './rich-markdown-review-annotations' -import { - buildRichMarkdownCommentBlocks, - getRichMarkdownCommentBlocks as getRichMarkdownReviewRailBlocks -} from './rich-markdown-comment-blocks' - -import { measureRichMarkdownReviewNotePositions } from './rich-markdown-review-note-positioning' - -const editors: Editor[] = [] - -function createEditor( - content: string | JSONContent = '# Heading\n\nFirst paragraph\n\n- One\n- Two' -) { - const editor = new Editor({ - element: document.createElement('div'), - extensions: createRichMarkdownExtensions({ codec: createRichMarkdownEditorCodec() }), - content, - ...(typeof content === 'string' ? { contentType: 'markdown' as const } : {}) - }) - editors.push(editor) - // Settle the trailing-node plugin before measuring selection-only transactions. - editor.view.dispatch(editor.state.tr.setMeta('addToHistory', false)) - return editor -} - -afterEach(() => { - for (const editor of editors.splice(0)) { - editor.destroy() - } - vi.restoreAllMocks() -}) - -describe('review rail source block reuse', () => { - it.each([ - '```ts\nconst value = 1\n\nvalue++\n```\n\nAfter code', - '| First | Second |\n| --- | --- |\n| one | two |\n\nAfter table', - '
Toggle\n\nInside\n\n
\n\nAfter toggle' - ])('preserves multiline block boundaries for %s', (source) => { - const editor = createEditor(source) - expect(getRichMarkdownReviewRailBlocks(editor)).toEqual(buildRichMarkdownCommentBlocks(editor)) - }) - - it('preserves block lines and reuses them after selection-only transactions', () => { - const editor = createEditor() - const expected = buildRichMarkdownCommentBlocks(editor) - const serialize = vi.spyOn(editor.markdown!, 'serialize') - const blocks = getRichMarkdownReviewRailBlocks(editor) - expect(blocks).toEqual(expected) - expect(serialize).toHaveBeenCalledTimes(2 * editor.state.doc.childCount - 1) - serialize.mockClear() - editor.commands.setTextSelection(3) - expect(getRichMarkdownReviewRailBlocks(editor)).toBe(blocks) - expect(serialize).not.toHaveBeenCalled() - }) - - it('rebuilds after edits and undo while keeping editors isolated', () => { - const editor = createEditor() - const original = getRichMarkdownReviewRailBlocks(editor) - editor.commands.insertContentAt(1, 'changed\ntext') - const changed = getRichMarkdownReviewRailBlocks(editor) - expect(changed).not.toBe(original) - expect(changed).toEqual(buildRichMarkdownCommentBlocks(editor)) - editor.commands.undo() - expect(getRichMarkdownReviewRailBlocks(editor)).toEqual(original) - expect(getRichMarkdownReviewRailBlocks(createEditor())).not.toBe(original) - }) - - it('invalidates an in-place serializer replacement and a manager replacement', () => { - const editor = createEditor() - const original = getRichMarkdownReviewRailBlocks(editor) - const serialize = editor.markdown!.serialize.bind(editor.markdown) - vi.spyOn(editor.markdown!, 'serialize').mockImplementation( - (content) => `${serialize(content)}\nextra line` - ) - const changed = getRichMarkdownReviewRailBlocks(editor) - expect(changed).not.toEqual(original) - expect(changed).toEqual(buildRichMarkdownCommentBlocks(editor)) - editor.markdown = createEditor().markdown - expect(getRichMarkdownReviewRailBlocks(editor)).toEqual(original) - }) - - it('does not reuse fallback lines after a missing serializer becomes available', () => { - const editor = createEditor() - const markdown = editor.markdown - editor.markdown = undefined - const fallback = getRichMarkdownReviewRailBlocks(editor) - expect(fallback).toEqual(buildRichMarkdownCommentBlocks(editor)) - editor.markdown = markdown - expect(getRichMarkdownReviewRailBlocks(editor)).not.toEqual(fallback) - expect(getRichMarkdownReviewRailBlocks(editor)).toEqual(buildRichMarkdownCommentBlocks(editor)) - }) - - it('avoids serialization for every comment and repeated scroll while refreshing geometry', () => { - const editor = createEditor() - let sourceTop = 100 - const coords = vi.spyOn(editor.view, 'coordsAtPos').mockImplementation(() => ({ - top: sourceTop, - bottom: sourceTop + 20, - left: 0, - right: 10 - })) - const container = document.createElement('div') - const comment: DiffComment = { - id: 'note', - worktreeId: 'workspace', - filePath: 'notes.md', - source: 'markdown', - lineNumber: 1, - body: 'Review', - createdAt: 1, - side: 'modified' - } - const markdownComments = Array.from({ length: 5 }, (_, index) => ({ - ...comment, - id: `note-${index}`, - selectedText: index === 0 ? 'Heading' : undefined - })) - const serialize = vi.spyOn(editor.markdown!, 'serialize') - const measure = () => - measureRichMarkdownReviewNotePositions({ - editor, - container, - markdownComments, - markdownSourceLineOffset: 0 - }) - expect(measure()[0]?.top).toBe(100) - expect(serialize).toHaveBeenCalledTimes(2 * editor.state.doc.childCount - 1) - serialize.mockClear() - sourceTop = 200 - container.scrollTop = 30 - for (let index = 0; index < 60; index++) { - expect(measure()[0]?.top).toBe(230) - } - expect(serialize).not.toHaveBeenCalled() - expect(coords).toHaveBeenCalledTimes(61 * markdownComments.length) - }) -}) diff --git a/src/renderer/src/components/editor/rich-markdown/schema/rich-markdown-large-html.test.ts b/src/renderer/src/components/editor/rich-markdown/schema/rich-markdown-large-html.test.ts index fe484012231..4394d6d8ff3 100644 --- a/src/renderer/src/components/editor/rich-markdown/schema/rich-markdown-large-html.test.ts +++ b/src/renderer/src/components/editor/rich-markdown/schema/rich-markdown-large-html.test.ts @@ -1,13 +1,12 @@ // @vitest-environment happy-dom import { Editor } from '@tiptap/core' -import { expect, it, vi } from 'vitest' +import { expect, it } from 'vitest' import { encodeRawMarkdownHtmlForRichEditor } from '../raw-source/raw-markdown-html' import { createRichMarkdownEditorCodec } from '../document/rich-markdown-source-transport' import { createRichMarkdownExtensions } from './rich-markdown-extensions' import { getMarkdownRichModeUnsupportedReason } from '../../markdown/render-mode/markdown-rich-mode' import { commitRichMarkdownSerialization } from '../document/rich-markdown-serialization-commit' import { createRichMarkdownHtmlSuperscriptLinkContext } from '../citations/rich-markdown-html-superscript-link-context' -import * as fenceScanner from '../../markdown/source-scanning/markdown-fence-scanner' function createEditor(source: string): Editor { const codec = createRichMarkdownEditorCodec() @@ -81,17 +80,6 @@ it.each([200, 50_000, 50_001])( } ) -it.each([ - ' indented', - '[link](https://example.com "title")', - '[link]()' -])('blocks markup that the parser keeps as transport text: %s', (markup) => { - for (const length of [200, 50_000, 50_001, 120_000]) { - const source = `${'a'.repeat(length)}\n\n${markup}\n` - expect(getMarkdownRichModeUnsupportedReason(source)).toBe('html-or-jsx') - } -}) - it('preserves markup in the edited paragraph through successive saves and reopening', () => { const markup = 'text 1' const source = `${'a'.repeat(50_001)}\n\nBefore ${markup} after\n` @@ -142,14 +130,3 @@ it('preserves markup in the edited paragraph through successive saves and reopen editor.destroy() } }) - -it('scans whole-document fence ranges once for thousands of passthrough details blocks', () => { - const source = '
ToggleBody
\n\n'.repeat(4_000) - const scan = vi.spyOn(fenceScanner, 'getMarkdownFenceRanges') - try { - expect(getMarkdownRichModeUnsupportedReason(source)).toBeNull() - expect(scan.mock.calls.filter(([content]) => content === source)).toHaveLength(1) - } finally { - scan.mockRestore() - } -}) diff --git a/src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.test.ts b/src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.test.ts index d158f4466c9..c1395f9966a 100644 --- a/src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.test.ts +++ b/src/renderer/src/components/editor/rich-markdown/search/rich-markdown-search-focus.test.ts @@ -79,28 +79,6 @@ describe('rich markdown search focus handoff', () => { expect(event.defaultPrevented).toBe(false) }) - it.each(['button', 'input', 'textarea', 'select', 'noneditable', 'task-label'])( - 'leaves embedded %s controls in charge of focus', - (kind) => { - const { editorDom, findInput, focus } = createSurface() - const control = document.createElement( - kind === 'noneditable' ? 'div' : kind === 'task-label' ? 'label' : kind - ) - if (kind === 'noneditable' || kind === 'task-label') { - control.setAttribute('contenteditable', 'false') - } - const child = document.createElement('span') - control.append(child) - editorDom.append(control) - findInput.focus() - - child.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 })) - - expect(document.activeElement).toBe(findInput) - expect(focus).not.toHaveBeenCalled() - } - ) - it('does not steal focus from a different editor find widget or unrelated field', () => { const first = createSurface() const second = createSurface() @@ -116,36 +94,6 @@ describe('rich markdown search focus handoff', () => { expect(first.focus).not.toHaveBeenCalled() }) - it('leaves ordinary editor clicks and blank surface clicks untouched', () => { - const { root, editorDom, paragraph, findInput, focus } = createSurface() - editorDom.focus() - focus.mockClear() - paragraph.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 })) - findInput.focus() - root.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 })) - - expect(focus).not.toHaveBeenCalled() - expect(document.activeElement).toBe(findInput) - }) - - it('leaves canceled and non-primary presses untouched', () => { - const { paragraph, findInput, focus } = createSurface() - findInput.focus() - const canceled = new MouseEvent('mousedown', { bubbles: true, cancelable: true, button: 0 }) - canceled.preventDefault() - - for (const event of [ - canceled, - new MouseEvent('mousedown', { bubbles: true, button: 1 }), - new MouseEvent('mousedown', { bubbles: true, button: 2 }) - ]) { - paragraph.dispatchEvent(event) - } - - expect(focus).not.toHaveBeenCalled() - expect(document.activeElement).toBe(findInput) - }) - it('allows editable content nested inside an unrelated noneditable ancestor', () => { const { root, paragraph, findInput, focus } = createSurface() root.setAttribute('contenteditable', 'false') @@ -155,8 +103,4 @@ describe('rich markdown search focus handoff', () => { expect(focus).toHaveBeenCalledExactlyOnceWith({ preventScroll: true }) }) - - it('does nothing when the editor has not mounted', () => { - focusRichMarkdownEditorFromSearch(new MouseEvent('mousedown', { button: 0 }), null) - }) }) diff --git a/src/renderer/src/components/emulator-pane/emulator-screen-gesture.test.ts b/src/renderer/src/components/emulator-pane/emulator-screen-gesture.test.ts index 22928538744..24c697c7da0 100644 --- a/src/renderer/src/components/emulator-pane/emulator-screen-gesture.test.ts +++ b/src/renderer/src/components/emulator-pane/emulator-screen-gesture.test.ts @@ -1,53 +1,11 @@ import { describe, expect, it } from 'vitest' import { - buildWheelGesturePoints, mapClientPointToSimulatorScreen, - resolveEmulatorWheelDelta, resolveEmulatorPointerAction } from './emulator-screen-gesture' import { resolveVisualStreamGeometry } from './emulator-device-frame-layout' -const rect = { - left: 10, - top: 20, - width: 200, - height: 400 -} - describe('emulator screen gestures', () => { - it.each([ - { orientation: 'portrait', display: { x: 0.2, y: 0.7 } }, - { orientation: 'landscape_left', display: { x: 0.3, y: 0.2 } }, - { orientation: 'landscape_right', display: { x: 0.7, y: 0.8 } }, - { orientation: 'portrait_upside_down', display: { x: 0.8, y: 0.3 } } - ] as const)( - 'round-trips a raw panel point through $orientation display geometry', - ({ orientation, display }) => { - const geometry = resolveVisualStreamGeometry( - { width: 2007, height: 2853 }, - 'portrait', - orientation - ) - const displayRect = { - left: 10, - top: 20, - width: geometry.size?.width ?? 0, - height: geometry.size?.height ?? 0 - } - const point = mapClientPointToSimulatorScreen( - { - clientX: displayRect.left + display.x * displayRect.width, - clientY: displayRect.top + display.y * displayRect.height - }, - displayRect, - geometry.size, - geometry.streamRotation - ) - expect(point?.x).toBeCloseTo(0.2) - expect(point?.y).toBeCloseTo(0.7) - } - ) - it('inverse rotates fallback taps, drag phases and the home edge into raw panel space', () => { const displayRect = { left: 0, top: 0, width: 400, height: 200 } expect( @@ -77,123 +35,6 @@ describe('emulator screen gestures', () => { }) }) - it.each([ - { rotation: 90, x: -0.2, y: 0.1 }, - { rotation: -90, x: 0.2, y: -0.1 }, - { rotation: 180, x: 0.1, y: 0.2 } - ] as const)('inverse rotates wheel vectors for $rotation degrees', ({ rotation, x, y }) => { - const delta = resolveEmulatorWheelDelta( - { clientX: 110, clientY: 220, deltaX: 20, deltaY: 80 }, - rect, - null, - 1, - rotation - ) - expect(delta?.delta.x).toBeCloseTo(x) - expect(delta?.delta.y).toBeCloseTo(y) - }) - - it('maps client coordinates into normalized simulator coordinates', () => { - expect(mapClientPointToSimulatorScreen({ clientX: 110, clientY: 220 }, rect, null)).toEqual({ - x: 0.5, - y: 0.5 - }) - }) - - it('keeps small movement as a tap', () => { - const action = resolveEmulatorPointerAction( - [ - { clientX: 100, clientY: 200 }, - { clientX: 103, clientY: 204 } - ], - rect, - null - ) - - expect(action).toEqual({ - kind: 'tap', - point: { x: 0.465, y: 0.46 } - }) - }) - - it('turns a drag into a begin/move/end gesture sequence', () => { - const action = resolveEmulatorPointerAction( - [ - { clientX: 110, clientY: 320 }, - { clientX: 110, clientY: 260 }, - { clientX: 110, clientY: 180 } - ], - rect, - null - ) - - expect(action).toEqual({ - kind: 'gesture', - points: [ - { type: 'begin', x: 0.5, y: 0.75 }, - { type: 'move', x: 0.5, y: 0.6 }, - { type: 'end', x: 0.5, y: 0.4 } - ] - }) - }) - - it('marks drags that start at the home indicator as bottom-edge gestures', () => { - const action = resolveEmulatorPointerAction( - [ - { clientX: 110, clientY: 396 }, - { clientX: 110, clientY: 260 }, - { clientX: 110, clientY: 120 } - ], - rect, - null - ) - - expect(action).toEqual({ - kind: 'gesture', - points: [ - { type: 'begin', x: 0.5, y: 0.94, edge: 3 }, - { type: 'move', x: 0.5, y: 0.6, edge: 3 }, - { type: 'end', x: 0.5, y: 0.25, edge: 3 } - ] - }) - }) - - it('maps wheel deltas into opposite-direction touch movement', () => { - const delta = resolveEmulatorWheelDelta( - { clientX: 110, clientY: 220, deltaX: 0, deltaY: 80 }, - rect, - null, - 1 - ) - - expect(delta?.start).toEqual({ x: 0.5, y: 0.5 }) - expect(delta?.delta.x).toBeCloseTo(0) - expect(delta?.delta.y).toBeCloseTo(-0.2) - }) - - it('builds a wheel gesture with a move point', () => { - expect(buildWheelGesturePoints({ x: 0.5, y: 0.5 }, { x: 0.5, y: 0.25 })).toEqual([ - { type: 'begin', x: 0.5, y: 0.5 }, - { type: 'move', x: 0.5, y: 0.375 }, - { type: 'end', x: 0.5, y: 0.25 } - ]) - }) - - it('accounts for letterboxed stream content', () => { - expect( - mapClientPointToSimulatorScreen({ clientX: 110, clientY: 220 }, rect, { - width: 100, - height: 100 - }) - ).toEqual({ x: 0.5, y: 0.5 }) - expect( - mapClientPointToSimulatorScreen({ clientX: 110, clientY: 30 }, rect, { - width: 100, - height: 100 - }) - ).toBeNull() - }) - it('maps the full landscape screen while the stream canvas shape settles', () => { const landscapeRect = { left: 0, diff --git a/src/renderer/src/components/github/github-work-item-issue-target.test.ts b/src/renderer/src/components/github/github-work-item-issue-target.test.ts index 5c42cb28ad0..fc59d1f23f1 100644 --- a/src/renderer/src/components/github/github-work-item-issue-target.test.ts +++ b/src/renderer/src/components/github/github-work-item-issue-target.test.ts @@ -4,7 +4,6 @@ import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../shared/task-source-context' import { callRuntimeRpc } from '@/runtime/runtime-rpc-client' import { notifyWorkItemDetailsMutation } from './github-work-item-comment-mutations' -import { runGHEditLabelToggle } from '../github-item-dialog/edit-item-fields/gh-edit-section-mutations' vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: vi.fn() })) vi.mock('@/store', () => ({ useAppStore: { getState: vi.fn(() => ({ repos: [] })) } })) @@ -100,24 +99,6 @@ describe('issue edits retain the displayed repository', () => { }) expectProjectUpdate(sourceContext, { body: 'Project edit' }) }) - - it.each([ - { state: 'closed' as const }, - { addLabels: ['bug'] }, - { addAssignees: ['upstream-assignee'] } - ])('keeps the Project field edit on the same exact actor: %j', async (updates) => { - vi.mocked(window.api.gh.updateIssueBySlug).mockResolvedValue({ ok: true }) - await runIssueUpdate({ - repoPath: '/home/fixture/fork-widgets', - repoId: item.repoId, - sourceContext, - projectOrigin, - issueRepo: UPSTREAM, - number: 12, - updates - }) - expectProjectUpdate(sourceContext, updates) - }) }) it.each([ @@ -143,36 +124,4 @@ describe('issue edits retain the displayed repository', () => { ) expect(window.api.gh.updateIssueBySlug).not.toHaveBeenCalled() }) - - it.each([ - { localLabels: [], updates: { addLabels: ['bug'] } }, - { localLabels: ['bug'], updates: { removeLabels: ['bug'] } } - ])( - 'retains the target for both label toggle directions: %j', - async ({ localLabels, updates }) => { - let mutation: Promise = Promise.resolve() - runGHEditLabelToggle({ - item, - itemNumber: item.number, - itemRepoId: item.repoId, - repoPath: 'C:\\workspace\\widgets', - projectOrigin: undefined, - issueRepo: ORIGIN, - label: 'bug', - localLabels, - run: async (_key, options) => { - mutation = options.mutate() - await mutation - }, - onLabelsChange: vi.fn(), - patchProjectRowIfNeeded: vi.fn(), - onMutated: vi.fn() - }) - await mutation - - expect(window.api.gh.updateIssue).toHaveBeenCalledWith( - expect.objectContaining({ ownerRepo: ORIGIN, updates }) - ) - } - ) }) diff --git a/src/renderer/src/components/linear-project-view-surfaces.test.tsx b/src/renderer/src/components/linear-project-view-surfaces.test.tsx deleted file mode 100644 index 2e7714d6e66..00000000000 --- a/src/renderer/src/components/linear-project-view-surfaces.test.tsx +++ /dev/null @@ -1,172 +0,0 @@ -// @vitest-environment happy-dom - -import { credentialDecryptionMessage } from '../../../shared/integration-credential-errors' -import React, { act } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' - -import { TooltipProvider } from '@/components/ui/tooltip' -import type { - LinearCustomViewSummary, - LinearProjectSummary -} from '../../../shared/linear/project-types' -import { - LinearCollectionNotice, - LinearCustomViewTable, - LinearProjectOverview, - LinearProjectTable -} from './linear-project-view-surfaces' - -Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) - -const roots: Root[] = [] - -afterEach(() => { - roots.splice(0).forEach((root) => act(() => root.unmount())) - document.body.replaceChildren() -}) - -function render(element: React.ReactNode): HTMLElement { - const container = document.createElement('div') - document.body.appendChild(container) - const root = createRoot(container) - roots.push(root) - act(() => root.render({element})) - return container -} - -const project: LinearProjectSummary = { - id: 'project-1', - workspaceId: 'workspace-1', - workspaceName: 'Acme', - name: 'Compiler', - url: 'https://linear.app/project/project-1', - labels: [ - { id: 'label-1', name: 'Alpha' }, - { id: 'label-2', name: 'Beta' }, - { id: 'label-3', name: 'Hidden' } - ], - progress: 0.5, - priority: 2, - targetDate: 'invalid-date' -} - -describe('Linear project view surfaces', () => { - it.each([ - ['Reconnect Linear', 'Unable to load Linear issues. Try again.'], - [ - credentialDecryptionMessage('Linear'), - 'Unable to read saved Linear login. Reconnect it in Settings.' - ] - ])('renders safe collection failure %s and controlled pagination', (message, expected) => { - const onLoadMore = vi.fn() - const container = render( - - ) - expect(container.textContent).toContain(expected) - const button = container.querySelector('button') - act(() => button?.dispatchEvent(new MouseEvent('click', { bubbles: true }))) - expect(onLoadMore).toHaveBeenCalledOnce() - }) - - it('keeps project row keyboard selection and nested actions independent', () => { - const onSelectProject = vi.fn() - const onOpenProject = vi.fn() - const onUseProjectIssues = vi.fn() - const container = render( - - ) - const row = container.querySelector('[role="button"]') as HTMLElement - expect(row.getAttribute('aria-current')).toBe('true') - expect(row.dataset.current).toBe('true') - expect(container.textContent).toContain('Acme') - expect(container.textContent).toContain('Alpha') - expect(container.textContent).toContain('Beta') - expect(container.textContent).not.toContain('Hidden') - act(() => row.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))) - expect(onSelectProject).toHaveBeenCalledWith(project) - - const issuesButton = container.querySelector('button[aria-label="Open Compiler issues"]') - act(() => issuesButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))) - expect(onUseProjectIssues).toHaveBeenCalledWith(project) - expect(onSelectProject).toHaveBeenCalledTimes(1) - - const openButton = container.querySelector('button[aria-label="Open Compiler in Linear"]') - act(() => openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))) - expect(onOpenProject).toHaveBeenCalledWith(project) - expect(onSelectProject).toHaveBeenCalledTimes(1) - }) - - it('preserves custom-view metadata and overview action callbacks', () => { - const view: LinearCustomViewSummary = { - id: 'view-1', - name: 'Triage', - model: 'issue', - shared: true, - owner: { id: 'user-1', displayName: 'Ada' } - } - const onSelectView = vi.fn() - const onOpenView = vi.fn() - const views = render( - - ) - expect(views.textContent).toContain('Shared') - expect(views.textContent).toContain('Ada') - const row = views.querySelector('[role="button"]') as HTMLElement - act(() => row.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }))) - expect(onSelectView).toHaveBeenCalledWith(view) - - const onBack = vi.fn() - const onRefresh = vi.fn() - const onOpenIssues = vi.fn() - const onOpenProject = vi.fn() - const overview = render( - - ) - expect(overview.textContent).toContain('Compiler') - act(() => - overview - .querySelector('button[aria-label="Back to projects"]') - ?.dispatchEvent(new MouseEvent('click', { bubbles: true })) - ) - expect(onBack).toHaveBeenCalledOnce() - const refresh = [...overview.querySelectorAll('button')].find((button) => - button.textContent?.includes('Refresh') - ) - act(() => refresh?.dispatchEvent(new MouseEvent('click', { bubbles: true }))) - expect(onRefresh).toHaveBeenCalledOnce() - }) -}) diff --git a/src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts b/src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts index 46d1c11f6a8..e0ead7e8991 100644 --- a/src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts +++ b/src/renderer/src/components/native-chat-resume-failed-chat-watch.test.ts @@ -86,36 +86,6 @@ afterEach(() => { vi.useRealTimers() }) -it('re-reads once when the user replies in a failed chat, then lets the stream go', async () => { - await listFailure() - expect(mocks.subscribeStatus).toHaveBeenCalledOnce() - const later = Date.now() + 10_000 - hostEmit()({ type: 'status', session: summary('working', 'Carry on please', later) }) - hostEmit()({ type: 'status', session: summary('idle', 'Carry on please', later + 1) }) - expect(offerReads()).toBe(1) - - await vi.advanceTimersByTimeAsync(500) - - expect(offerReads()).toBe(2) - expect(getNativeChatRestartOffer().failed).toEqual([]) - expect(mocks.unsubscribe).toHaveBeenCalledOnce() -}) - -it('does not re-read for an agent streaming in a failed chat or for news older than the list', async () => { - await listFailure({ failed: [failure] }) - const later = Date.now() + 10_000 - // Already reflected in the listing the store holds. - hostEmit()({ type: 'snapshot', sessions: [summary('working', 'Replayed note', 1)] }) - hostEmit()({ type: 'status', session: summary('working', 'Replayed note', later) }) - await vi.advanceTimersByTimeAsync(500) - expect(offerReads()).toBe(1) - - hostEmit()({ type: 'status', session: summary('idle', 'Replayed note', later + 1) }) - await vi.advanceTimersByTimeAsync(500) - expect(offerReads()).toBe(2) - expect(mocks.unsubscribe).not.toHaveBeenCalled() -}) - // Known chat changes are news despite older host timestamps because list and status delivery can race. it('re-reads for a known failed chat that changes even when its stamp predates the list', async () => { await listFailure() diff --git a/src/renderer/src/components/native-chat-resume-on-restart-outcome-toast.test.ts b/src/renderer/src/components/native-chat-resume-on-restart-outcome-toast.test.ts deleted file mode 100644 index 77e3e99a9fb..00000000000 --- a/src/renderer/src/components/native-chat-resume-on-restart-outcome-toast.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { toast } from 'sonner' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import type { ResumeCandidate } from './native-chat-resume-on-restart-grouping' -import { lastToastShow } from './native-chat-resume-toast.test-support' -import { - consumeNativeChatResumeOnRestartDialogRequest, - getNativeChatResumeOnRestartDialogRequest -} from './native-chat-resume-on-restart-dialog' -import { - _resetNativeChatRestartOffer, - continueNativeChatRestartOffer, - getNativeChatRestartOffer, - refreshNativeChatRestartOffer -} from './native-chat-resume-on-restart-store' - -const rpc = vi.hoisted(() => vi.fn()) -vi.mock('@/runtime/structured-agent-session-client', () => ({ - callStructuredAgentSession: rpc, - // A failed row opens the status feed; these cases never drive it. - subscribeStructuredAgentSessionStatus: () => new Promise(() => {}) -})) -vi.mock('sonner', async () => - (await import('./native-chat-resume-sonner-mock.test-support')).resumeSonnerMock() -) - -const offered: ResumeCandidate[] = ['a', 'b'].map((sessionId) => ({ - sessionId, - workspaceId: 'workspace', - agent: 'codex', - trigger: 'quit', - latestPrompt: `Prompt ${sessionId}`, - recordedAt: 1_800_000_000_000 -})) - -beforeEach(() => { - rpc.mockReset() - _resetNativeChatRestartOffer() - consumeNativeChatResumeOnRestartDialogRequest() - vi.mocked(toast).mockClear() - vi.spyOn(console, 'warn').mockImplementation(() => {}) -}) - -afterEach(() => { - vi.mocked(console.warn).mockRestore() - _resetNativeChatRestartOffer() -}) - -/** The host lists both chats, then loses every later call. */ -function hostLostAfterListing(): void { - let reachable = true - rpc.mockImplementation(async (_target, method) => { - if (method === 'agentSession.restartResumable' && reachable) { - return { sessions: offered, failed: [] } - } - reachable = false - throw new Error('host unreachable') - }) -} - -const resumes = { - click: () => continueNativeChatRestartOffer(['a', 'b']), - launch: () => continueNativeChatRestartOffer(offered.map((candidate) => candidate.sessionId)) -} - -// An unavailable host cannot settle delivery; Review still opens the retained action summary. -it.each(['click', 'launch'] as const)( - 'keeps every chat of a lost %s resume unconfirmed when the host cannot be read either', - async (caller) => { - hostLostAfterListing() - await refreshNativeChatRestartOffer() - await resumes[caller]() - expect(vi.mocked(toast).mock.calls).toEqual([ - [ - 'Couldn’t confirm 2 chats were resumed', - expect.objectContaining({ action: expect.objectContaining({ label: 'Review' }) }) - ] - ]) - expect(lastToastShow()).toBeDefined() - lastToastShow()?.() - await new Promise((resolve) => setTimeout(resolve, 0)) - expect(getNativeChatResumeOnRestartDialogRequest()).toBe(true) - } -) - -// A fresh host offer is an observation, so the lost request still needs Review. -it('answers an opted-in launch that loses its resume request with one unconfirmed toast', async () => { - rpc.mockImplementation(async (_target, method) => { - if (method === 'agentSession.restartResumable') { - return { sessions: offered, failed: [] } - } - throw new Error('response lost') - }) - await refreshNativeChatRestartOffer() - await resumes.launch() - expect(getNativeChatRestartOffer().failed.map((entry) => entry.sessionId)).toEqual(['a', 'b']) - expect(vi.mocked(toast).mock.calls.map(([title]) => title)).toEqual([ - 'Couldn’t confirm 2 chats were resumed' - ]) - expect(lastToastShow()).toBeDefined() -}) - -it('answers a mixed opted-in launch with one toast', async () => { - const failure = { ...offered[1]!, failedAt: 1, outcome: 'refused', reason: 'unknown' } - let acted = false - rpc.mockImplementation(async (_target, method, params: { sessionIds: string[] } | undefined) => { - const failed = acted ? [failure] : [] - if (method === 'agentSession.restartResumable') { - return { sessions: acted ? [] : offered, failed } - } - acted = true - return { - continued: (params?.sessionIds ?? []).map((sessionId) => ({ - sessionId, - outcome: sessionId === 'b' ? 'refused' : 'continued' - })), - sessions: [], - failed: [failure] - } - }) - await refreshNativeChatRestartOffer() - await resumes.launch() - expect(vi.mocked(toast).mock.calls).toEqual([ - ['1 chat couldn’t be resumed', expect.objectContaining({ description: 'Resumed 1 chat' })] - ]) - expect(lastToastShow()).toBeDefined() -}) - -it('raises no toast when the host had nothing left for an opted-in launch to resume', async () => { - rpc.mockImplementation(async (_target, method) => - method === 'agentSession.restartResumable' - ? { sessions: offered, failed: [] } - : { continued: [], sessions: [], failed: [] } - ) - await refreshNativeChatRestartOffer() - await resumes.launch() - expect(toast).not.toHaveBeenCalled() -}) - -it('opens the retained summary from Show after the host withdraws a failure', async () => { - let failed = [{ ...offered[0]!, failedAt: 1, outcome: 'unconfirmed', reason: 'unknown' }] - rpc.mockImplementation(async (_target, method) => - method === 'agentSession.restartResumable' - ? { sessions: [], failed } - : { continued: [{ sessionId: 'a', outcome: 'unknown' }], sessions: [], failed } - ) - await refreshNativeChatRestartOffer() - await continueNativeChatRestartOffer(['a']) - expect(vi.mocked(toast).mock.calls.map(([title]) => title)).toEqual([ - 'Couldn’t confirm 1 chat was resumed' - ]) - failed = [] - lastToastShow()?.() - await new Promise((resolve) => setTimeout(resolve, 0)) - expect(rpc.mock.calls.at(-1)?.[1]).toBe('agentSession.restartResumable') - expect(getNativeChatRestartOffer().failed).toEqual([]) - expect(getNativeChatResumeOnRestartDialogRequest()).toBe(true) -}) diff --git a/src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx b/src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx index 67dcdfe1d82..47f798df570 100644 --- a/src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx @@ -3,8 +3,8 @@ import '@testing-library/jest-dom/vitest' import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' -import { loadSyntaxLanguage } from '@/lib/syntax-highlighting/syntax-highlighter' -import { NativeChatCodeBlock, NativeChatPlainCodeBlock } from './NativeChatCodeBlock' + +import { NativeChatCodeBlock } from './NativeChatCodeBlock' vi.mock('@/lib/syntax-highlighting/oniguruma', async () => ({ loadOniguruma: (await import('@/lib/syntax-highlighting/oniguruma-test-harness')) @@ -38,64 +38,6 @@ describe('NativeChatCodeBlock', () => { expect(second.container.querySelector('pre')?.textContent).toBe(code) }) - it('leaves the code of a dimmed row uncolored', async () => { - const code = {'const dimmed = 1\n'} - await loadSyntaxLanguage('typescript') - const colored = render({code}) - expect(colored.container.querySelector('pre span')).not.toBeNull() - - const plain = render( - {code} - ) - - expect(plain.container.querySelector('pre span')).toBeNull() - expect(plain.container.querySelector('pre')?.textContent).toBe('const dimmed = 1\n') - }) - - it('leaves a fence in an unknown language as plain text', async () => { - const { container } = render( - - {'plain words\n'} - - ) - const pre = container.querySelector('pre') - - await waitFor(() => expect(pre?.textContent).toBe('plain words\n')) - expect(pre?.querySelector('span')).toBeNull() - }) - - it.each([ - ['with a language header', 'typescript'], - ['without a language header', undefined] - ])('wraps long lines only after the reader turns wrapping on (%s)', (_name, language) => { - const { container } = render( - - {'const longLine = 1\n'} - - ) - const pre = container.querySelector('pre') - - const wrapOn = screen.getByRole('button', { name: 'Wrap lines' }) - expect(wrapOn).toHaveAttribute('aria-pressed', 'false') - expect(pre).toHaveClass('overflow-x-auto') - expect(pre).not.toHaveClass('whitespace-pre-wrap') - - fireEvent.click(wrapOn) - - const wrapOff = screen.getByRole('button', { name: 'Disable line wrap' }) - expect(wrapOff).toHaveAttribute('aria-pressed', 'true') - expect(pre).toHaveClass('whitespace-pre-wrap', '[overflow-wrap:anywhere]') - expect(pre).not.toHaveClass('overflow-x-auto') - - fireEvent.click(wrapOff) - - expect(screen.getByRole('button', { name: 'Wrap lines' })).toHaveAttribute( - 'aria-pressed', - 'false' - ) - expect(pre).toHaveClass('overflow-x-auto') - }) - it('colors and wraps fenced code, copies its text, and confirms success', async () => { const writeClipboardText = vi.fn().mockResolvedValue(undefined) Object.assign(window, { api: { ui: { writeClipboardText } } }) @@ -120,28 +62,4 @@ describe('NativeChatCodeBlock', () => { await waitFor(() => expect(writeClipboardText).toHaveBeenCalledWith(code)) expect(await screen.findByRole('button', { name: 'Copied' })).toBeInTheDocument() }) - - it('keeps wrapping to the block the reader toggled', () => { - render( - <> - - {'first\n'} - - - {'second\n'} - - - ) - - fireEvent.click(screen.getAllByRole('button', { name: 'Wrap lines' })[0]) - - expect(screen.getByRole('button', { name: 'Disable line wrap' })).toBeInTheDocument() - expect(screen.getAllByRole('button', { name: 'Wrap lines' })).toHaveLength(1) - }) - - it('shows no wrap control for an empty block', () => { - render() - - expect(screen.queryByRole('button', { name: 'Wrap lines' })).toBeNull() - }) }) diff --git a/src/renderer/src/components/native-chat/NativeChatPaneHeader.test.tsx b/src/renderer/src/components/native-chat/NativeChatPaneHeader.test.tsx index b95b8384a64..430a3812d8a 100644 --- a/src/renderer/src/components/native-chat/NativeChatPaneHeader.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatPaneHeader.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment happy-dom -import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' -import { getDefaultSettings } from '../../../../shared/constants' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' + import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { TooltipProvider } from '@/components/ui/tooltip' import { useAppStore } from '@/store' @@ -82,60 +82,3 @@ it('offers chat actions without a duplicate close button and targets Resume at t }) ) }) - -it('changes the saved chat width from the standalone chat toolbar', async () => { - const settings = { - ...getDefaultSettings('/tmp'), - nativeChatAppearance: { fontSize: 16, width: 'wide' as const } - } - const updateSettings = vi.fn(async (updates) => { - useAppStore.setState({ settings: { ...settings, ...updates } }) - }) - useAppStore.setState({ settings, updateSettings }) - renderHeader() - fireEvent.keyDown(screen.getByRole('button', { name: 'Chat settings' }), { key: 'Enter' }) - expect(screen.getByRole('menuitemradio', { name: 'Wide' }).getAttribute('aria-checked')).toBe( - 'true' - ) - fireEvent.click(screen.getByRole('menuitemradio', { name: 'Full' })) - await waitFor(() => - expect(updateSettings).toHaveBeenCalledWith({ - nativeChatAppearance: { fontSize: 16, width: 'full' } - }) - ) -}) - -async function splitTooltipText(): Promise { - const trigger = screen.getByRole('button', { name: 'Split Chat Right' }) - fireEvent.pointerMove(trigger, { pointerType: 'mouse' }) - return (await screen.findByRole('tooltip')).textContent -} - -it('shows the effective split-right shortcut in the Split Chat Right tooltip', async () => { - useAppStore.setState({ keybindings: { 'terminal.splitRight': ['Mod+Alt+K'] } }) - renderHeader() - const shortcut = navigator.userAgent.includes('Mac') ? '⌘⌥K' : 'Ctrl+Alt+K' - expect(await splitTooltipText()).toBe(`Split Chat Right: ${shortcut}`) - expect(screen.getByRole('button', { name: 'Split Chat Right' })).toBeTruthy() -}) - -it.each([ - ['Mac', '⌘D, ⌘K'], - ['Windows', 'Ctrl+D, Ctrl+K'], - ['Linux', 'Ctrl+D, Ctrl+K'] -])('separates alternative split shortcuts on %s', async (userAgent, shortcuts) => { - const userAgentSpy = vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(userAgent) - try { - useAppStore.setState({ keybindings: { 'terminal.splitRight': ['Mod+D', 'Mod+K'] } }) - renderHeader() - expect(await splitTooltipText()).toBe(`Split Chat Right: ${shortcuts}`) - } finally { - userAgentSpy.mockRestore() - } -}) - -it('omits the shortcut from the Split Chat Right tooltip when split-right is unbound', async () => { - useAppStore.setState({ keybindings: { 'terminal.splitRight': [] } }) - renderHeader() - expect(await splitTooltipText()).toBe('Split Chat Right') -}) diff --git a/src/renderer/src/components/native-chat/NativeChatUsageLimitAutoContinue.test.tsx b/src/renderer/src/components/native-chat/NativeChatUsageLimitAutoContinue.test.tsx deleted file mode 100644 index a5b5cbdfbc7..00000000000 --- a/src/renderer/src/components/native-chat/NativeChatUsageLimitAutoContinue.test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -// @vitest-environment happy-dom -import '@testing-library/jest-dom/vitest' -import { useMemo } from 'react' -import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { TooltipProvider } from '@/components/ui/tooltip' -import type { AgentSessionStatusSummary } from '../../../../shared/agent-session/agent-session-wire' -import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' -import { NATIVE_CHAT_USAGE_LIMIT_MAX_OVERDUE_MS } from '../../../../shared/native-chat/native-chat-usage-limit-cut' -import { NativeChatNoticeRow } from './NativeChatNoticeRow' -import { NativeChatOrcaStopContext } from './native-chat-orca-stop-context' -import { useNativeChatUsageLimitAutoContinue } from './use-native-chat-usage-limit-auto-continue' - -const mocks = vi.hoisted(() => { - const state: { - capability: string - summary: Pick | undefined - } = { capability: 'supported', summary: undefined } - return Object.assign(state, { - call: vi.fn(async () => ({ ok: true })), - feed: { activate: () => () => {}, subscribe: () => () => {}, getSnapshot: () => new Map() } - }) -}) -vi.mock('@/runtime/structured-agent-session-host-capability', () => ({ - useStructuredAgentSessionHostCapabilityState: () => mocks.capability -})) -vi.mock('@/runtime/structured-agent-session-client', () => ({ - callStructuredAgentSession: mocks.call -})) -vi.mock('@/runtime/structured-agent-session-status-feed', () => ({ - getStructuredAgentSessionStatusFeed: () => mocks.feed -})) -vi.mock('./NativeChatUsageLimitActions', () => ({ NativeChatUsageLimitActions: () => null })) - -const reset = 1_800_000_000_000 -beforeEach(() => { - vi.useFakeTimers({ toFake: ['Date'] }) - vi.setSystemTime(reset - 60_000) -}) -afterEach(() => { - cleanup() - vi.clearAllMocks() - vi.useRealTimers() -}) -const LOCAL: RuntimeClientTarget = { kind: 'local' } -function Row({ target = LOCAL, known = true }: { target?: RuntimeClientTarget; known?: boolean }) { - const state = useNativeChatUsageLimitAutoContinue(target, 'chat', { - turnItemId: 'cut', - turnEndedAt: reset - 120_000, - ...(known ? { resetsAt: reset } : {}) - }) - const value = useMemo( - () => ({ - hostLabel: null, - remoteHost: target.kind !== 'local', - continueAvailable: false, - offeredTurnItemId: null, - continueNow: () => {}, - usageLimitTurnItemId: 'cut', - continueUsageLimit: () => {}, - usageLimitAutoContinue: state - }), - [state, target.kind] - ) - return ( - - - - - - ) -} -function setState(state: NonNullable['state']) { - mocks.capability = 'supported' - mocks.summary = { usageLimitContinue: { turnItemId: 'cut', resetsAt: reset, state } } - const snapshot = new Map([['chat', mocks.summary]]) - mocks.feed.getSnapshot = () => snapshot -} - -it('shows the setting link, scheduled cancel action, and cancellation in the usage-limit row', async () => { - setState('off') - const view = render() - expect( - screen.getByRole('button', { name: 'Continue automatically when limits reset' }) - ).toBeInTheDocument() - setState('scheduled') - view.rerender() - expect(screen.getByText(/Orca will continue this chat at/)).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: "Don't continue" })) - await waitFor(() => - expect(mocks.call).toHaveBeenCalledWith( - { kind: 'local' }, - 'agentSession.cancelUsageLimitContinue', - { sessionId: 'chat', turnItemId: 'cut', resetsAt: reset } - ) - ) - setState('cancelled') - view.rerender() - expect(screen.getByText("Orca won't continue this chat automatically.")).toBeInTheDocument() - setState('failed') - view.rerender() - expect(screen.getByText("Orca won't continue this chat automatically.")).toBeInTheDocument() -}) - -it('hides new controls on old hosts, missing resets, stale summaries, and remote off states', () => { - setState('scheduled') - mocks.capability = 'unsupported' - const view = render() - expect(screen.queryByText(/Orca will continue/)).toBeNull() - mocks.capability = 'supported' - view.rerender() - expect(screen.queryByText(/Orca will continue/)).toBeNull() - setState('off') - view.rerender() - expect( - screen.queryByRole('button', { name: 'Continue automatically when limits reset' }) - ).toBeNull() - mocks.summary!.usageLimitContinue!.turnItemId = 'old-cut' - view.rerender() - expect( - screen.queryByRole('button', { name: 'Continue automatically when limits reset' }) - ).toBeNull() -}) - -it('shows only the manual actions once a cut is more than a day past its reset, setting on or off', () => { - vi.setSystemTime(reset + NATIVE_CHAT_USAGE_LIMIT_MAX_OVERDUE_MS + 1_000) - // A summary from before the cutoff: 'off' with the setting off, 'scheduled' with it on. - for (const state of ['off', 'scheduled'] as const) { - setState(state) - const view = render() - expect( - screen.queryByRole('button', { name: 'Continue automatically when limits reset' }) - ).toBeNull() - expect(screen.queryByText(/Orca will continue/)).toBeNull() - expect(screen.queryByRole('button', { name: "Don't continue" })).toBeNull() - expect(screen.getByRole('button', { name: 'Continue Now' })).toBeInTheDocument() - view.unmount() - } -}) diff --git a/src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-scan-issue-state.test.ts b/src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-scan-issue-state.test.ts deleted file mode 100644 index ac1cce130e7..00000000000 --- a/src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-scan-issue-state.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { AiVaultListResult } from '../../../../../../shared/ai-vault/contracts/ai-vault-types' -import { - aiVaultScanNoticeIssues, - blockingAiVaultScanIssue, - skippedAiVaultTranscriptCount, - skippedAiVaultTranscriptReasons -} from './ai-vault-scan-issue-state' - -describe('blockingAiVaultScanIssue', () => { - it('surfaces the cause when a scan returns no sessions', () => { - const issue = { - executionHostId: 'ssh:dev-box' as const, - agent: 'codex' as const, - kind: 'host' as const, - path: 'dev-box', - message: 'Remote connection dropped. Reconnect the SSH target.' - } - - expect(blockingAiVaultScanIssue(result([], [issue]))).toEqual(issue) - }) - - it('leaves partial-result issues as a skipped transcript count', () => { - expect( - blockingAiVaultScanIssue( - result( - [{ id: 'session' }], - [{ agent: 'codex', path: '/bad.jsonl', message: 'Malformed transcript' }] - ) - ) - ).toBeNull() - }) - - it('does not block an empty scan for a skipped transcript', () => { - expect( - blockingAiVaultScanIssue( - result([], [{ agent: 'codex', path: '/bad.jsonl', message: 'Malformed transcript' }]) - ) - ).toBeNull() - }) -}) - -describe('aiVaultScanNoticeIssues', () => { - it('surfaces scope truncation without counting it as a skipped transcript', () => { - const scopeIssue = { - agent: 'codex' as const, - kind: 'scope' as const, - path: '/home/ada', - message: 'Only the first 64 project paths were scanned.' - } - const truncated = result([], [scopeIssue]) - - expect(blockingAiVaultScanIssue(truncated)).toBeNull() - expect(aiVaultScanNoticeIssues(truncated)).toEqual([scopeIssue]) - expect(skippedAiVaultTranscriptCount(truncated)).toBe(0) - }) - - it('keeps kinded issues as notices and counts only transcripts as skipped', () => { - const hostIssue = { - executionHostId: 'ssh:dev-box' as const, - agent: 'codex' as const, - kind: 'host' as const, - path: 'dev-box', - message: 'Remote connection dropped.' - } - const scopeIssue = { - agent: 'codex' as const, - kind: 'scope' as const, - path: '/home/ada', - message: 'Only the first 64 project paths were scanned.' - } - const partial = result( - [{ id: 'session' }], - [hostIssue, scopeIssue, { agent: 'codex', path: '/bad.jsonl', message: 'Malformed' }] - ) - - expect(aiVaultScanNoticeIssues(partial)).toEqual([hostIssue, scopeIssue]) - expect(skippedAiVaultTranscriptCount(partial)).toBe(1) - }) - - // An unreadable OpenCode database hides every session; report the source failure, not one skipped transcript (#15036). - it('gives an unreadable OpenCode database its own row instead of a skipped count', () => { - const sourceIssue = { - agent: 'opencode' as const, - kind: 'scope' as const, - path: '\\\\wsl.localhost\\Ubuntu\\home\\ada\\.local\\share\\opencode\\opencode.db', - message: - 'OpenCode is writing to opencode.db right now, so its history was skipped. It is read again on the next refresh.' - } - const empty = result([], [sourceIssue]) - - expect(skippedAiVaultTranscriptCount(empty)).toBe(0) - expect(skippedAiVaultTranscriptReasons(empty)).toEqual([]) - expect(aiVaultScanNoticeIssues(empty)).toEqual([sourceIssue]) - }) - - it('does not repeat the blocking issue as a notice row', () => { - const hostIssue = { - executionHostId: 'ssh:dev-box' as const, - agent: 'codex' as const, - kind: 'host' as const, - path: 'dev-box', - message: 'Remote connection dropped.' - } - - expect(aiVaultScanNoticeIssues(result([], [hostIssue]))).toEqual([]) - }) - - it('reports nothing before the first scan', () => { - expect(aiVaultScanNoticeIssues(null)).toEqual([]) - expect(skippedAiVaultTranscriptCount(null)).toBe(0) - }) -}) - -describe('skippedAiVaultTranscriptReasons', () => { - it('surfaces the file-too-large reason behind a skipped transcript', () => { - expect( - skippedAiVaultTranscriptReasons( - result( - [{ id: 'session' }], - [ - { - agent: 'claude', - path: '/home/dev/.claude/projects/a/huge.jsonl', - message: 'File too large: 12.4MB exceeds 10MB limit' - } - ] - ) - ) - ).toEqual(['File too large: 12.4MB exceeds 10MB limit']) - }) - - it('leaves host and scope notices to their own rows', () => { - expect( - skippedAiVaultTranscriptReasons( - result( - [], - [ - { - agent: 'codex', - kind: 'scope', - path: '/home/dev', - message: 'Only the first 64 project paths were scanned.' - }, - { agent: 'codex', kind: 'host', path: 'dev-box', message: 'Reconnect the SSH target.' } - ] - ) - ) - ).toEqual([]) - }) - - it('dedupes repeats and caps the list so a 500-issue scan stays readable', () => { - const issues = Array.from({ length: 40 }, (_unused, index) => ({ - agent: 'codex' as const, - path: `/transcripts/${index}.jsonl`, - message: `Unreadable transcript ${index % 5}` - })) - - expect(skippedAiVaultTranscriptReasons(result([], issues))).toEqual([ - 'Unreadable transcript 0', - 'Unreadable transcript 1', - 'Unreadable transcript 2' - ]) - }) - - it('reports nothing before the first scan', () => { - expect(skippedAiVaultTranscriptReasons(null)).toEqual([]) - }) -}) - -function result( - sessions: { id: string }[], - issues: AiVaultListResult['issues'] -): AiVaultListResult { - return { - sessions: sessions as AiVaultListResult['sessions'], - issues, - scannedAt: '2026-07-26T00:00:00.000Z' - } -} diff --git a/src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat.test.ts b/src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat.test.ts deleted file mode 100644 index 04688e1475f..00000000000 --- a/src/renderer/src/components/right-sidebar/ai-vault/session-actions/ai-vault-session-resume-in-chat.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { readFileSync } from 'node:fs' -import { describe, expect, it } from 'vitest' -import type { AiVaultSession } from '../../../../../../shared/ai-vault/contracts/ai-vault-types' -import { resolveAiVaultSessionResumeInChatEligibility } from './ai-vault-session-resume-in-chat' - -type ResumeInChatSession = Parameters< - typeof resolveAiVaultSessionResumeInChatEligibility ->[0]['session'] - -const WORKSPACE_PATH = '/repo/orca' - -function session(overrides: Partial = {}): ResumeInChatSession { - return { - agent: 'claude', - cwd: WORKSPACE_PATH, - filePath: '/home/dev/.claude/projects/-repo-orca/session-1.jsonl', - executionHostId: 'local', - messageCount: 12, - previewMessages: [], - ...overrides - } -} - -function eligibility( - overrides: Partial[0]> = {} -) { - return resolveAiVaultSessionResumeInChatEligibility({ - session: session(), - targetWorkspaceId: 'repo-1::/repo/orca', - targetWorkspacePath: WORKSPACE_PATH, - structuredRouteAvailable: true, - ...overrides - }) -} - -describe('resolveAiVaultSessionResumeInChatEligibility', () => { - it('refuses a row already adopted into a chat before any other check', () => { - expect( - eligibility({ - session: { - ...session(), - structuredSession: { sessionId: 'claude_1', workspaceId: 'repo-1::/repo/orca' } - } - }) - ).toEqual({ available: false, reason: 'already-structured' }) - }) - - it.each(['ssh:build-box', 'runtime:unresolved-owner'] as const)( - 'refuses a row recorded on %s', - (executionHostId) => { - expect(eligibility({ session: session({ executionHostId }) })).toEqual({ - available: false, - reason: 'remote' - }) - } - ) - - it.each(['claude', 'codex'])( - 'offers a paired-runtime %s conversation when its route is admitted', - (agent) => { - expect( - eligibility({ session: session({ agent, executionHostId: 'runtime:server-1' }) }) - ).toMatchObject({ available: true }) - } - ) - - it('rejects WSL transcripts on a paired runtime too', () => { - expect( - eligibility({ - session: session({ - executionHostId: 'runtime:server-1', - filePath: '//wsl.localhost/Ubuntu/home/dev/.claude/projects/p/session.jsonl' - }) - }) - ).toEqual({ available: false, reason: 'remote' }) - }) - - it('refuses when the same pair could not take the structured route for a fresh chat', () => { - expect(eligibility({ structuredRouteAvailable: false })).toEqual({ - available: false, - reason: 'workspace' - }) - }) -}) - -describe('workspace matching, which binds the agents that store conversations per folder', () => { - it('accepts host correspondence for cwd from recorded Claude output', () => { - const capture = readFileSync( - new URL( - '../../../../../../main/claude/__fixtures__/claude-adapter-capture-early-steer.jsonl', - import.meta.url - ), - 'utf8' - ) - const cwds = capture - .trim() - .split('\n') - .flatMap((line) => { - const event: unknown = JSON.parse(line) - if (typeof event !== 'object' || !event || !('frame' in event)) { - return [] - } - const frame = event.frame - return typeof frame === 'object' && frame && 'cwd' in frame && typeof frame.cwd === 'string' - ? [frame.cwd] - : [] - }) - expect(cwds).toEqual(['/tmp/scrubbed-cwd']) - const cwd = cwds[0] - expect( - eligibility({ - session: session({ cwd, cwdAliases: [`/private${cwd}`] }), - targetWorkspacePath: `/private${cwd}` - }).available - ).toBe(true) - }) - - it.each(['claude', 'grok', 'omp', 'opencode', 'opencode2', 'pi'])( - 'offers a %s row when its host proves the registered spelling of cwd', - (agent) => { - const row = { - ...session({ agent, cwd: '/private/var/example' }), - cwdAliases: ['/var/example'] - } - expect(eligibility({ session: row, targetWorkspacePath: '/var/example' })).toMatchObject({ - available: true - }) - expect(eligibility({ session: row, targetWorkspacePath: '/var' })).toEqual({ - available: false, - reason: 'workspace' - }) - expect(eligibility({ session: row, structuredRouteAvailable: false })).toEqual({ - available: false, - reason: 'workspace' - }) - } - ) - - it.each(['claude', 'grok', 'omp', 'opencode', 'opencode2', 'pi'])( - 'refuses a %s row whose conversation was recorded in another workspace', - (agent) => { - // These key conversations by launch cwd, so resuming elsewhere finds or loads nothing. - expect( - eligibility({ - session: session({ agent, cwd: '/repo/other' }), - targetWorkspacePath: WORKSPACE_PATH - }) - ).toEqual({ available: false, reason: 'workspace' }) - } - ) - - it('refuses a Claude row that recorded no cwd', () => { - expect(eligibility({ session: session({ cwd: null }) })).toEqual({ - available: false, - reason: 'workspace' - }) - }) - - it('keeps Codex available in a different workspace, and with no recorded cwd', () => { - // Codex is handed the rollout file and a cwd, so it resumes anywhere. - expect(eligibility({ session: session({ agent: 'codex', cwd: '/repo/other' }) })).toMatchObject( - { available: true } - ) - expect(eligibility({ session: session({ agent: 'codex', cwd: null }) })).toMatchObject({ - available: true - }) - }) - - it('treats Windows spellings of one directory as the same workspace', () => { - expect( - eligibility({ - session: session({ cwd: 'C:\\Users\\Dev\\repo\\Orca\\' }), - targetWorkspacePath: 'c:/users/dev/repo/orca' - }) - ).toMatchObject({ available: true }) - }) -}) diff --git a/src/renderer/src/components/right-sidebar/file-explorer/watching/file-explorer-linked-watch.test.ts b/src/renderer/src/components/right-sidebar/file-explorer/watching/file-explorer-linked-watch.test.ts index 6fde541052e..397558d047e 100644 --- a/src/renderer/src/components/right-sidebar/file-explorer/watching/file-explorer-linked-watch.test.ts +++ b/src/renderer/src/components/right-sidebar/file-explorer/watching/file-explorer-linked-watch.test.ts @@ -44,59 +44,6 @@ function fixture(root = '/repo') { afterEach(() => vi.useRealTimers()) describe('linked-directory watcher invalidation', () => { - it.each(['/repo', 'C:\\Repo', '\\\\host\\share\\repo'])( - 'refreshes a collapsed cached alias even when canonical target is not cached (%s)', - (root) => { - const f = fixture(root) - f.process([{ kind: 'create', absolutePath: joinPath(root, 'target/new.ts') }]) - expect(f.refreshTree).toHaveBeenCalledOnce() - expect(f.refreshDir).not.toHaveBeenCalled() - expect(f.cache[f.link].children).toHaveLength(1) - expect(f.selected()).toBe(joinPath(f.link, 'old.ts')) - } - ) - - it.each([ - { kind: 'update' as const, absolutePath: '/repo/target/new.ts', isDirectory: false }, - { kind: 'update' as const, absolutePath: '/repo/target', isDirectory: true }, - { kind: 'update' as const, absolutePath: '/repo/link', isDirectory: false }, - { kind: 'delete' as const, absolutePath: '/repo/target/deleted.ts' }, - { - kind: 'rename' as const, - absolutePath: '/repo/target/new.ts', - oldAbsolutePath: '/repo/target/old.ts' - } - ])('refreshes structural uncertainty including link retarget: %j', (event) => { - const f = fixture() - f.process([event]) - expect(f.refreshTree).toHaveBeenCalledOnce() - }) - - it('keeps known regular content edits cheap and honors node directory/symlink flags', () => { - const f = fixture('C:\\Repo') - const target = 'C:\\Repo\\Target' - f.cache[target] = { children: [node(`${target}\\file.ts`), node(`${target}\\dir`, true)] } - f.process([{ kind: 'update', absolutePath: 'c:/repo/target/FILE.ts', isDirectory: false }]) - expect(f.refreshTree).not.toHaveBeenCalled() - expect(f.refreshDir).not.toHaveBeenCalled() - f.process([{ kind: 'update', absolutePath: 'c:/repo/target/DIR', isDirectory: false }]) - expect(f.refreshTree).toHaveBeenCalledOnce() - }) - - it('keeps cached historical aliases refreshed with the Quick Open preference disabled', () => { - const f = fixture() - const event = { kind: 'create' as const, absolutePath: '/repo/target/new.ts' } - f.process([event], false) - expect(f.refreshTree).toHaveBeenCalledOnce() - f.refreshTree.mockClear() - f.process([{ ...event, absolutePath: '/other/new.ts' }]) - f.process([event], true, '/other') - expect(f.refreshTree).not.toHaveBeenCalled() - delete f.cache[f.link] - f.process([event]) - expect(f.refreshTree).not.toHaveBeenCalled() - }) - it('still clears a lexical deleted selection after a prior event requested full refresh', () => { const f = fixture() f.process([ diff --git a/src/renderer/src/components/settings/browser/BrowserPane.search-catalog.test.tsx b/src/renderer/src/components/settings/browser/BrowserPane.search-catalog.test.tsx deleted file mode 100644 index 152046203a0..00000000000 --- a/src/renderer/src/components/settings/browser/BrowserPane.search-catalog.test.tsx +++ /dev/null @@ -1,143 +0,0 @@ -// @vitest-environment happy-dom -// @vitest-environment happy-dom -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { getDefaultSettings } from '../../../../../shared/constants' -import { BrowserPane } from './BrowserPane' -import { getBrowserPaneSearchEntries } from './browser-search' -import type * as BrowserSearch from './browser-search' - -const fake = vi.hoisted(() => ({ - query: '', - locale: { language: 'en' }, - translate: vi.fn((_key: string, fallback: string) => fallback), - setHost: vi.fn(), - profiles: [], - repos: [], - map: new Map(), - environments: [], - hostOptions: [{ id: 'local', kind: 'local', label: 'Local' }] -})) -vi.mock('@/i18n/i18n', () => ({ i18n: fake.locale, translate: fake.translate })) -vi.mock('./browser-search', async (importOriginal) => { - const original = await importOriginal() - return { ...original, getBrowserPaneSearchEntries: vi.fn(original.getBrowserPaneSearchEntries) } -}) -vi.mock('@/store/index', () => ({ - useAppStore: (selector: (state: Record) => unknown) => - selector({ - settingsSearchQuery: fake.query, - browserDefaultUrl: '', - browserSessionProfiles: fake.profiles, - repos: fake.repos, - sshTargetLabels: fake.map, - sshConnectionStates: fake.map, - runtimeEnvironments: fake.environments, - runtimeStatusByEnvironmentId: fake.map, - browserSessionHostIdOverride: null, - setBrowserSessionHostId: fake.setHost - }) -})) -vi.mock('@/components/terminal-pane/pane-helpers', () => ({ isMacUserAgent: () => false })) -vi.mock('../../sidebar/worktree-list/controls/sidebar-host-options', () => ({ - buildSidebarHostOptions: () => fake.hostOptions -})) -vi.mock('./BrowserHomePageSetting', () => ({ - BrowserHomePageSetting: ({ - value, - onChange - }: { - value: string - onChange: (value: string) => void - }) => ( - onChange(event.target.value)} - /> - ) -})) -vi.mock('./use/BrowserUsePane', () => ({ BrowserUseSetup: () => Browser use row })) -vi.mock('./BrowserDefaultZoomSetting', () => ({ - BrowserDefaultZoomSetting: () => Zoom row -})) -vi.mock('./BrowserSearchEngineSetting', () => ({ - BrowserSearchEngineSetting: () => Search row -})) -vi.mock('./routing/BrowserLinkRoutingSetting', () => ({ - BrowserLinkRoutingSetting: () => Routing row -})) -vi.mock('./routing/BrowserLinkRoutingModifierSetting', () => ({ - BrowserLinkRoutingModifierSetting: () => Modifier row -})) -vi.mock('./routing/BrowserLinkRoutingBackgroundSetting', () => ({ - BrowserLinkRoutingBackgroundSetting: () => Background links row -})) -vi.mock('./routing/BrowserTerminalLinkActionsSetting', () => ({ - BrowserTerminalLinkActionsSetting: () => Terminal links row -})) -vi.mock('./routing/BrowserLocalhostWorktreeLabelsSetting', () => ({ - BrowserLocalhostWorktreeLabelsSetting: () => Localhost row -})) -vi.mock('./BrowserSitePermissionsSetting', () => ({ - BrowserSitePermissionsSetting: () => Site permissions row -})) -vi.mock('./routing/BrowserClientHostedRemoteSetting', () => ({ - BrowserClientHostedRemoteSetting: () => Remote row -})) -vi.mock('./routing/BrowserSshWorkspaceRoutingSetting', () => ({ - BrowserSshWorkspaceRoutingSetting: () => SSH row -})) -vi.mock('./profiles/BrowserProfileScopeSetting', () => ({ - BrowserProfileScopeSetting: () => Profile scope row -})) -vi.mock('./BrowserUserAgentSetting', () => ({ - BrowserUserAgentSetting: () => Identity row -})) -vi.mock('../form-controls/SettingsFormControls', () => ({ SettingsSubsectionHeader: () => null })) -vi.mock('./profiles/BrowserSessionCookiesSection', () => ({ - BrowserSessionCookiesSection: () => Cookies row -})) -vi.mock('./profiles/BrowserNewProfileDialog', () => ({ BrowserNewProfileDialog: () => null })) - -beforeEach(() => { - vi.clearAllMocks() - fake.query = '' - fake.locale.language = 'en' - fake.translate.mockImplementation((_key, fallback) => fallback) -}) -afterEach(cleanup) -it('builds one catalog per render while typing a home page draft', () => { - render() - vi.clearAllMocks() - for (let index = 1; index <= 20; index++) { - fireEvent.change(screen.getByRole('textbox'), { target: { value: 'x'.repeat(index) } }) - } - expect(getBrowserPaneSearchEntries).toHaveBeenCalledTimes(20) -}) -it('keeps targeted search visibility', () => { - fake.query = 'Default Home Page' - render() - expect(screen.getByRole('textbox')).toBeTruthy() - expect(screen.queryByText('Cookies row')).toBeNull() - fake.query = 'google' - cleanup() - render() - expect(screen.getByText('Cookies row')).toBeTruthy() -}) - -it('rebuilds metadata in the next render after the language changes', () => { - fake.query = 'Default Home Page' - const settings = getDefaultSettings('/synthetic') - const view = render() - expect(screen.getByRole('textbox')).toBeTruthy() - fake.locale.language = 'synthetic-locale' - fake.query = 'Translated home' - fake.translate.mockImplementation((_key, fallback) => - fallback === 'Default Home Page' ? 'Translated home' : fallback - ) - vi.mocked(getBrowserPaneSearchEntries).mockClear() - view.rerender() - expect(screen.getByRole('textbox')).toBeTruthy() - expect(screen.queryByText('Cookies row')).toBeNull() -}) diff --git a/src/renderer/src/components/settings/source-control/text-actions/source-control-text-action-repo-discovery.test.tsx b/src/renderer/src/components/settings/source-control/text-actions/source-control-text-action-repo-discovery.test.tsx index 41f68a7fa57..7f74e255db3 100644 --- a/src/renderer/src/components/settings/source-control/text-actions/source-control-text-action-repo-discovery.test.tsx +++ b/src/renderer/src/components/settings/source-control/text-actions/source-control-text-action-repo-discovery.test.tsx @@ -83,18 +83,6 @@ function openRepoCard(repoId: string) { fireEvent.click(screen.getByRole('button', { name: 'Configure Codex' })) } -it('a local repository card lists the models this machine reports when its picker opens', async () => { - useAppStore.setState({ repos: [TEST_REPO] }) - openRepoCard(TEST_REPO.id) - expect(await screen.findByRole('option', { name: 'GPT-6.1 Sol' })).toBeDefined() - expect(discover).toHaveBeenCalledWith( - expect.objectContaining({ target: { kind: 'local' }, worktreeId: null }), - 'codex' - ) - const saved = useAppStore.getState().settings?.sourceControlAi - expect(saved?.discoveredModelsByAgentByHost?.local?.codex).toEqual(SOL) -}) - it('a local repository card in WSL refreshes the local catalog generation reads, as the global card does', async () => { discover.mockImplementation(actualGenerationClient.discoverRuntimeCommitMessageModels) const list = vi.fn(async (args: { worktreePath?: string }) => ({ diff --git a/src/renderer/src/components/sidebar/project-admission/drop/sidebar-project-drop.test.ts b/src/renderer/src/components/sidebar/project-admission/drop/sidebar-project-drop.test.ts index 31bc47bdbb7..d7d7716870d 100644 --- a/src/renderer/src/components/sidebar/project-admission/drop/sidebar-project-drop.test.ts +++ b/src/renderer/src/components/sidebar/project-admission/drop/sidebar-project-drop.test.ts @@ -1,38 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - getFocusedDropRuntimeId, - getSidebarProjectDropAffordance, - isSidebarProjectDropRefusedByFocus, - resolveSidebarProjectDropPath, - routeSidebarProjectDrop -} from './sidebar-project-drop' +import { routeSidebarProjectDrop } from './sidebar-project-drop' import { wslDistroWindowsPathAccess } from '../../../../../../shared/windows-path-access' -describe('resolveSidebarProjectDropPath', () => { - it('accepts exactly one dropped path', () => { - expect(resolveSidebarProjectDropPath(['/Users/alice/repo'])).toEqual({ - status: 'ready', - path: '/Users/alice/repo' - }) - }) - - it('rejects empty and multi-path drops before routing', () => { - expect(resolveSidebarProjectDropPath([])).toEqual({ status: 'empty' }) - expect(resolveSidebarProjectDropPath(['/repo/a', '/repo/b'])).toEqual({ - status: 'multiple', - count: 2 - }) - }) -}) - -describe('getFocusedDropRuntimeId', () => { - it('distinguishes local runtime from active server runtime', () => { - expect(getFocusedDropRuntimeId(null)).toBeNull() - expect(getFocusedDropRuntimeId({ activeRuntimeEnvironmentId: ' ' })).toBeNull() - expect(getFocusedDropRuntimeId({ activeRuntimeEnvironmentId: 'server-1' })).toBe('server-1') - }) -}) - describe('routeSidebarProjectDrop', () => { const wslHosts = [ { environmentId: 'wsl:Ubuntu', access: wslDistroWindowsPathAccess('Ubuntu') }, @@ -67,17 +36,6 @@ describe('routeSidebarProjectDrop', () => { ).toEqual({ kind: 'windows-drive-on-wsl-host', distro: 'Ubuntu', path: 'C:\\src\\app' }) }) - it('with WSL hosts off, routes a drvfs UNC drop exactly as before', () => { - const path = '\\\\wsl.localhost\\Ubuntu\\mnt\\c\\proj' - expect(routeSidebarProjectDrop({ path, focusedRuntimeId: null, wslHosts: [] })).toEqual({ - kind: 'local', - path - }) - expect(routeSidebarProjectDrop({ path, focusedRuntimeId: 'server', wslHosts: [] })).toEqual({ - kind: 'runtime-focused' - }) - }) - it.each([ '\\\\wsl.localhost\\Ubuntu\\mnt\\c\\src\\app', // Any distro's drvfs mount, as the picker treats it. @@ -94,73 +52,4 @@ describe('routeSidebarProjectDrop', () => { path: 'C:\\src\\app' }) }) - - it('keeps refusing other drops while a server is focused', () => { - expect( - routeSidebarProjectDrop({ path: 'C:\\src', focusedRuntimeId: 'paired-server', wslHosts }) - ).toEqual({ kind: 'runtime-focused' }) - expect( - routeSidebarProjectDrop({ - path: '\\\\wsl.localhost\\Arch\\home', - focusedRuntimeId: 'wsl:Ubuntu', - wslHosts - }) - ).toEqual({ kind: 'runtime-focused' }) - expect( - routeSidebarProjectDrop({ path: '/Users/me/app', focusedRuntimeId: 'server', wslHosts: [] }) - ).toEqual({ kind: 'runtime-focused' }) - }) - - it('accepts the drag over a focused server only when a WSL host could own the folder', () => { - expect(isSidebarProjectDropRefusedByFocus(null, [])).toBe(false) - expect(isSidebarProjectDropRefusedByFocus('server', [])).toBe(true) - expect(isSidebarProjectDropRefusedByFocus('server', wslHosts)).toBe(false) - }) -}) - -describe('getSidebarProjectDropAffordance', () => { - it('hides when the sidebar is not in a drop interaction', () => { - expect( - getSidebarProjectDropAffordance({ - isDragOver: false, - isHandlingDrop: false, - remoteRuntimeActive: false - }) - ).toEqual({ visible: false }) - }) - - it('shows ready, busy, and blocked states', () => { - expect( - getSidebarProjectDropAffordance({ - isDragOver: true, - isHandlingDrop: false, - remoteRuntimeActive: false - }) - ).toMatchObject({ visible: true, tone: 'ready' }) - - expect( - getSidebarProjectDropAffordance({ - isDragOver: false, - isHandlingDrop: true, - remoteRuntimeActive: false - }) - ).toMatchObject({ visible: true, tone: 'busy' }) - - expect( - getSidebarProjectDropAffordance({ - isDragOver: true, - isHandlingDrop: false, - remoteRuntimeActive: true - }) - ).toMatchObject({ visible: true, tone: 'blocked' }) - - expect( - getSidebarProjectDropAffordance({ - isDragOver: true, - isHandlingDrop: false, - remoteRuntimeActive: false, - wslDropsOnly: true - }) - ).toMatchObject({ visible: true, tone: 'ready', label: 'Drop a WSL folder to add project' }) - }) }) diff --git a/src/renderer/src/components/sidebar/workspace-actions/context-menu/workspace-lineage-menu-actions.test.ts b/src/renderer/src/components/sidebar/workspace-actions/context-menu/workspace-lineage-menu-actions.test.ts index 5fa37a28dd1..d8690626e15 100644 --- a/src/renderer/src/components/sidebar/workspace-actions/context-menu/workspace-lineage-menu-actions.test.ts +++ b/src/renderer/src/components/sidebar/workspace-actions/context-menu/workspace-lineage-menu-actions.test.ts @@ -1,12 +1,9 @@ import { describe, expect, it } from 'vitest' -import type { Tab } from '../../../../../../shared/tab-types' + import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types' import type { Worktree } from '../../../../../../shared/worktree/types' -import { structuredAgentSessionPaneKey } from '../../../../../../shared/agent-session/view/structured-agent-session-projection' -import { - getWorkspaceLineageMenuActions, - hasSleepableWorkspaceActivity -} from './workspace-lineage-menu-actions' + +import { getWorkspaceLineageMenuActions } from './workspace-lineage-menu-actions' function makeWorktree(id: string): Worktree { return { @@ -44,42 +41,6 @@ function makeLineage(child: Worktree, parent: Worktree): WorktreeLineage { } describe('workspace lineage menu actions', () => { - it('collects recursive descendants and only targets workspaces with active panels for sleep', () => { - const parent = makeWorktree('parent') - const child = makeWorktree('child') - const grandchild = makeWorktree('grandchild') - const actions = getWorkspaceLineageMenuActions({ - parent, - worktrees: [parent, child, grandchild], - lineageById: { - [child.id]: makeLineage(child, parent), - [grandchild.id]: makeLineage(grandchild, child) - }, - activity: { - tabsByWorktree: { - [parent.id]: [{ id: 'parent-tab' }], - [child.id]: [{ id: 'child-tab' }], - [grandchild.id]: [{ id: 'grandchild-tab' }] - }, - ptyIdsByTabId: { - 'parent-tab': [], - 'child-tab': ['child-pty'], - 'grandchild-tab': [] - }, - browserTabsByWorktree: { - [grandchild.id]: [{ id: 'grandchild-browser' }] - }, - unifiedTabsByWorktree: {}, - agentStatusByPaneKey: {}, - structuredChatSleptAtByPaneKey: {} - } - }) - - expect(actions.descendants.map((target) => target.id)).toEqual(['child', 'grandchild']) - expect(actions.targets.map((target) => target.id)).toEqual(['parent', 'child', 'grandchild']) - expect(actions.sleepableTargets.map((target) => target.id)).toEqual(['child', 'grandchild']) - }) - it('does not expose stale descendants through the recursive action scope', () => { const parent = makeWorktree('parent') const child = makeWorktree('child') @@ -107,74 +68,3 @@ describe('workspace lineage menu actions', () => { expect(actions.sleepableTargets).toEqual([]) }) }) - -function hostHeld(tab: Tab): Record { - return { [structuredAgentSessionPaneKey(tab.id, tab.entityId)]: { structuredHostOwned: true } } -} - -describe('hasSleepableWorkspaceActivity', () => { - it('treats preserved empty PTY arrays as slept, not live', () => { - expect( - hasSleepableWorkspaceActivity('wt-1', { - tabsByWorktree: { 'wt-1': [{ id: 'tab-1' }] }, - ptyIdsByTabId: { 'tab-1': [] }, - browserTabsByWorktree: {}, - unifiedTabsByWorktree: {}, - agentStatusByPaneKey: {}, - structuredChatSleptAtByPaneKey: {} - }) - ).toBe(false) - }) - - it('detects live terminal and browser activity', () => { - expect( - hasSleepableWorkspaceActivity('wt-1', { - tabsByWorktree: { 'wt-1': [{ id: 'tab-1' }] }, - ptyIdsByTabId: { 'tab-1': ['pty-1'] }, - browserTabsByWorktree: {}, - unifiedTabsByWorktree: {}, - agentStatusByPaneKey: {}, - structuredChatSleptAtByPaneKey: {} - }) - ).toBe(true) - expect( - hasSleepableWorkspaceActivity('wt-1', { - tabsByWorktree: {}, - ptyIdsByTabId: {}, - browserTabsByWorktree: { 'wt-1': [{ id: 'browser-1' }] }, - unifiedTabsByWorktree: {}, - agentStatusByPaneKey: {}, - structuredChatSleptAtByPaneKey: {} - }) - ).toBe(true) - }) - - it('offers Sleep for a chat-only workspace while its host holds the chat', () => { - const chat: Tab = { - id: 'chat-1', - entityId: 'session-1', - groupId: 'group-1', - worktreeId: 'wt-1', - contentType: 'agent-session', - agentSessionAgent: 'claude', - label: 'Claude', - customLabel: null, - color: null, - sortOrder: 0, - createdAt: 0 - } - const activity = (agentStatusByPaneKey: Record) => - hasSleepableWorkspaceActivity('wt-1', { - tabsByWorktree: {}, - ptyIdsByTabId: {}, - browserTabsByWorktree: {}, - unifiedTabsByWorktree: { 'wt-1': [chat] }, - agentStatusByPaneKey, - structuredChatSleptAtByPaneKey: { - [structuredAgentSessionPaneKey(chat.id, chat.entityId)]: null - } - }) - expect(activity(hostHeld(chat))).toBe(true) - expect(activity({})).toBe(true) - }) -}) diff --git a/src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx b/src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx index 9d7d43a8bb8..4c25c519426 100644 --- a/src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx +++ b/src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx @@ -1,5 +1,5 @@ // @vitest-environment happy-dom -import { act, cleanup, fireEvent, render } from '@testing-library/react' +import { cleanup, fireEvent, render } from '@testing-library/react' import React, { createRef } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../../../shared/repo-types' @@ -134,16 +134,6 @@ function mountedStatusIds(container: HTMLElement): string[] { ) } -function flushNextAnimationFrame(): void { - const next = animationFrames.entries().next().value as [number, FrameRequestCallback] | undefined - expect(next).toBeDefined() - if (!next) { - return - } - animationFrames.delete(next[0]) - act(() => next[1](performance.now())) -} - beforeEach(() => { animationFrames.clear() nextAnimationFrameId = 1 @@ -181,16 +171,4 @@ describe('WorkspaceKanbanLaneGrid', () => { 'state-21' ]) }) - - it('hydrates at most one mounted lane per animation frame', () => { - const { container } = renderGrid() - const renderedLaneCount = (): number => - container.querySelectorAll('[data-render-cards="true"]').length - - expect(renderedLaneCount()).toBe(0) - flushNextAnimationFrame() - expect(renderedLaneCount()).toBe(1) - flushNextAnimationFrame() - expect(renderedLaneCount()).toBe(2) - }) }) diff --git a/src/renderer/src/components/sidebar/workspace-card/review/workspace-reference-refresh-targets.test.ts b/src/renderer/src/components/sidebar/workspace-card/review/workspace-reference-refresh-targets.test.ts index d4a1aeb864e..34a8d7f2741 100644 --- a/src/renderer/src/components/sidebar/workspace-card/review/workspace-reference-refresh-targets.test.ts +++ b/src/renderer/src/components/sidebar/workspace-card/review/workspace-reference-refresh-targets.test.ts @@ -2,14 +2,11 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { useAppStore } from '@/store' -import { createVisibleHostedReviewRefreshScheduler } from '@/store/github/visible-hosted-review-refresh-scheduler' -import { getHostedReviewCacheKey } from '@/store/slices/hosted-review-cache-identity' import { getWorkspaceReferenceRefreshTargets } from './workspace-reference-refresh-targets' import { referenceAttachment, referenceRepo, - referenceWorkspace, - referenceReview + referenceWorkspace } from './workspace-reference-fixtures.test-support' vi.mock('@/store', async () => { const browserWindow = globalThis.window @@ -63,87 +60,6 @@ beforeEach(() => { afterEach(() => { vi.useRealTimers() }) -it('leaves the detached primary review to its visible target while retaining sibling reviews', () => { - useAppStore.setState({ - worktreesByRepo: { - repo: [ - { - ...workspace, - branch: '', - linkedPR: 7, - linkedItems: [referenceAttachment(7), referenceAttachment(8)] - } - ] - } - }) - const targets = update() - expect(targets).toHaveLength(1) - expect(targets[0]?.key).toContain('8') -}) - -it('pauses disconnected SSH owners and only admits the owning reconnect', () => { - useAppStore.setState({ repos: [{ ...referenceRepo, connectionId: 'ssh-a' }] }) - expect(update()).toEqual([]) - for (const [targetId, status, count] of [ - ['other', 'connected', 0], - ['ssh-a', 'connected', 1], - ['ssh-a', 'connecting', 0] - ] as const) { - useAppStore - .getState() - .sshConnectionStates.set(targetId, { targetId, status, error: null, reconnectAttempt: 0 }) - expect(update()).toHaveLength(count) - } -}) -it('shares one scheduler timer and removes queued work when the workspace leaves the viewport', async () => { - useAppStore.setState({ - worktreesByRepo: { - repo: [ - { - ...workspace, - linkedItems: Array.from({ length: 12 }, (_, i) => referenceAttachment(i + 1)) - } - ] - } - }) - const scheduler = createVisibleHostedReviewRefreshScheduler() - try { - scheduler.update(update()) - scheduler.setVisible(true) - await vi.advanceTimersByTimeAsync(0) - expect(read).toHaveBeenCalledTimes(12) - expect(vi.getTimerCount()).toBe(1) - useAppStore.setState({ visibleReviewWorktreeIds: [] }) - scheduler.update(update()) - expect(vi.getTimerCount()).toBe(0) - await vi.advanceTimersByTimeAsync(600_000) - expect(read).toHaveBeenCalledTimes(12) - } finally { - scheduler.dispose() - } -}) -it('uses the known branch provider for sparse enterprise references', () => { - const repo = { - ...referenceRepo, - gitRemoteIdentity: { - canonicalKey: 'enterprise.test/acme/orca', - remoteName: 'origin', - remoteUrl: 'https://enterprise.test/acme/orca.git' - } - } - const key = getHostedReviewCacheKey(repo.path, workspace.branch, null, repo.id, null, null, true) - useAppStore.setState({ - repos: [repo], - worktreesByRepo: { - repo: [{ ...workspace, linkedItems: [{ provider: 'github', type: 'pr', number: 42 }] }] - } - }) - expect(update()).toHaveLength(0) - useAppStore.setState({ - hostedReviewCache: { [key]: { data: referenceReview(), fetchedAt: Date.now() } } - }) - expect(update()).toHaveLength(1) -}) it('retains both explicit owners when equal visible workspace IDs exist on two hosts', () => { const remoteRepo = { ...referenceRepo, diff --git a/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx b/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx index df5b12d83dd..4bd92ed3b87 100644 --- a/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx +++ b/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx @@ -10,7 +10,7 @@ import { referenceRepo, referenceLinearIssue } from '../review/workspace-reference-fixtures.test-support' -import { getWorkspaceReferenceRequest } from '../review/workspace-reference-details' + import { normalizeTaskSourceContext } from '../../../../../../shared/task-source-context' import { getLinearReadScope, scopedLinearCacheKey } from '@/store/slices/linear/linear-slice-scope' @@ -43,62 +43,6 @@ vi.mock('../../workspace-actions/deletion/workspace-delete-quick-action', () => afterEach(cleanup) describe('single linked reference rich hover details', () => { - it.each([ - { - data: { - provider: 'gitlab' as const, - number: 8, - title: 'Fix detached review', - state: 'open' as const, - status: 'success' as const, - updatedAt: '', - mergeable: 'MERGEABLE' as const, - url: 'https://gitlab.example.com/team/repo/-/merge_requests/8' - }, - title: 'Fix detached review' - }, - { data: null, title: 'MR details unavailable' } - ])( - 'reads detached MR details from the same exact cache as Linked work: $title', - ({ data, title }) => { - const repo = { - ...referenceRepo, - gitRemoteIdentity: { - canonicalKey: 'gitlab.example.com/team/repo', - remoteName: 'origin', - remoteUrl: 'https://gitlab.example.com/team/repo.git' - } - } - const item = { - provider: 'gitlab' as const, - type: 'mr' as const, - number: 8, - url: 'https://gitlab.example.com/team/repo/-/merge_requests/8' - } - const workspace = { - ...referenceWorkspace, - branch: '', - linkedPR: null, - linkedGitLabMR: 8, - linkedItems: [item] - } - const key = getWorkspaceReferenceRequest(item, workspace, repo).key - cache.hosted = { [key]: { data, fetchedAt: Date.now(), linkedReviewHintKey: 'gitlab:8' } } - const { result } = renderHook(() => - useWorktreeCardReviewDetails({ - worktree: workspace, - repo, - settings: null, - projectGroups: [], - cardProps: ['pr'], - newCardStyle: false - }) - ) - expect(result.current.hostedReviewCacheKey).toBe(key) - expect(result.current.prDisplay?.title).toBe(title) - } - ) - it('reuses source-bound Linear details when an exact workspace read is outside the legacy all-workspaces cache slot', () => { const source = normalizeTaskSourceContext({ provider: 'linear', diff --git a/src/renderer/src/components/sidebar/workspace-card/template/card-template-compiler.test.tsx b/src/renderer/src/components/sidebar/workspace-card/template/card-template-compiler.test.tsx index dda610c3bd7..a22cea16643 100644 --- a/src/renderer/src/components/sidebar/workspace-card/template/card-template-compiler.test.tsx +++ b/src/renderer/src/components/sidebar/workspace-card/template/card-template-compiler.test.tsx @@ -1,4 +1,3 @@ -import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it } from 'vitest' import { CARD_TEMPLATE_LIMITS, @@ -23,66 +22,7 @@ function template(root: CardTemplate['root'], extra: Partial = {}) return { version: 1, facts: 1, root, ...extra } } -function draw(t: CardTemplate, facts: Record = {}): string { - const { content } = compileCardTemplate(t, registry).render(facts, '') - return renderToStaticMarkup(<>{content}) -} - describe('card template compiler', () => { - it('draws layout as host elements and evaluates props, vars and when', () => { - const t = template( - { - el: 'Row', - props: { gap: 'sm', align: '=vars.wide ? "center" : "start"' }, - children: [ - { el: 'Echo', props: { text: '=ws.name' } }, - { el: 'Echo', when: 'ws.hidden', props: { text: 'never' } } - ] - }, - { vars: { wide: 'len(ws.name) > 3' } } - ) - expect(draw(t, { ws: { name: 'card plugins', hidden: false } })).toBe( - '
card plugins
' - ) - expect(draw(t, { ws: { name: 'abc' } })).toContain('items-start') - }) - - it('expands defs and reports frame answers', () => { - const t = template( - { el: 'Stack', children: [{ el: 'Label' }, { el: 'Label', when: 'false' }] }, - { - defs: { Label: { el: 'Echo', props: { text: 'hi' } } }, - frame: { singleLine: '=true', statusLane: false } - } - ) - const compiled = compileCardTemplate(t, registry) - const { frame, content } = compiled.render({}, '!') - expect(frame).toEqual({ singleLine: true, statusLane: false }) - expect(renderToStaticMarkup(<>{content})).toBe( - '
hi!
' - ) - }) - - it('lets the frame add attributes to the root', () => { - const { content } = compileCardTemplate(template({ el: 'Row' }), registry).render( - {}, - '', - () => ({ - style: { marginLeft: '-4px' } - }) - ) - expect(renderToStaticMarkup(<>{content})).toContain('margin-left:-4px') - }) - - it('passes frame attributes through a def used as the root, and honours its when', () => { - const t = template({ el: 'Body', when: 'ws.show' }, { defs: { Body: { el: 'Row' } } }) - const compiled = compileCardTemplate(t, registry) - const attrs = (): Record => ({ style: { marginLeft: '-4px' } }) - const shown = compiled.render({ ws: { show: true } }, '', attrs).content - expect(renderToStaticMarkup(<>{shown})).toContain('margin-left:-4px') - expect(compiled.render({ ws: { show: false } }, '', attrs).content).toBeNull() - }) - it('bounds the tree defs expand to', () => { // Each def holds `width` copies of the next: tiny as written, width^levels once inlined. const fanOut = (levels: number, width: number): CardTemplate => { diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/default-branch-visible-under-hide-sleeping.test.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/default-branch-visible-under-hide-sleeping.test.ts index 24b2760ceda..14c6f681e42 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/default-branch-visible-under-hide-sleeping.test.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/default-branch-visible-under-hide-sleeping.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { computeVisibleWorktreeIds } from '../listing/compute-visible-worktrees' -import { isDefaultBranchWorkspace } from '../../../../../../shared/worktree/workspace-kinds' + import type { Repo } from '../../../../../../shared/repo-types' import type { Worktree } from '../../../../../../shared/worktree/types' import { LOCAL_EXECUTION_HOST_ID } from '../../../../../../shared/execution-host' @@ -59,25 +59,6 @@ function visibleOptions(overrides: Partial = {}): VisibleOptions } } -describe('#8873 default-branch workspace under "Hide sleeping"', () => { - it('is genuinely the default-branch row the "Hide default branch" toggle targets', () => { - expect(isDefaultBranchWorkspace(makeDefaultBranchWorktree(), repoMap.get('repo1'))).toBe(true) - }) - - it('stays in the sidebar when it is sleeping and "Hide sleeping" is on', () => { - const worktree = makeDefaultBranchWorktree() - - const visible = computeVisibleWorktreeIds({ repo1: [worktree] }, [worktree.id], { - ...visibleOptions({ - showSleepingWorkspaces: false, - hideDefaultBranchWorkspace: false - }) - }) - - expect(visible).toEqual([worktree.id]) - }) -}) - describe('the "Hide sleeping" exemption for project entry-point rows', () => { function visible(worktrees: Worktree[], overrides: Partial): string[] { return computeVisibleWorktreeIds( @@ -93,34 +74,6 @@ describe('the "Hide sleeping" exemption for project entry-point rows', () => { expect(visible([worktree], { alwaysShowDefaultBranchWorkspace: false })).toEqual([]) }) - it('keeps the sleeping default branch when the option is explicitly on', () => { - const worktree = makeDefaultBranchWorktree() - - expect(visible([worktree], { alwaysShowDefaultBranchWorkspace: true })).toEqual([worktree.id]) - }) - - it('lets an explicit "Hide default branch" still win over the exemption', () => { - const worktree = makeDefaultBranchWorktree() - - expect( - visible([worktree], { - hideDefaultBranchWorkspace: true, - alwaysShowDefaultBranchWorkspace: true - }) - ).toEqual([]) - }) - - it('still sweeps sleeping non-main workspaces', () => { - const feature: Worktree = { - ...makeDefaultBranchWorktree(), - id: 'wt-feature', - branch: 'refs/heads/feature', - isMainWorktree: false - } - - expect(visible([feature], { alwaysShowDefaultBranchWorkspace: true })).toEqual([]) - }) - it('keeps a sleeping folder workspace, which has no sibling row to fall back to', () => { // Folder roots have no head/branch; sweeping the sole root hides the entire project. const folder: Worktree = { @@ -132,14 +85,6 @@ describe('the "Hide sleeping" exemption for project entry-point rows', () => { expect(visible([folder], { alwaysShowDefaultBranchWorkspace: true })).toEqual([folder.id]) }) - it('keeps a sleeping detached-HEAD main worktree', () => { - const detachedMain: Worktree = { ...makeDefaultBranchWorktree(), id: 'wt-detached', branch: '' } - - expect(visible([detachedMain], { alwaysShowDefaultBranchWorkspace: true })).toEqual([ - detachedMain.id - ]) - }) - it('lets "Hide detached HEAD" still win over the exemption', () => { const detachedMain: Worktree = { ...makeDefaultBranchWorktree(), id: 'wt-detached', branch: '' } @@ -150,20 +95,4 @@ describe('the "Hide sleeping" exemption for project entry-point rows', () => { }) ).toEqual([]) }) - - it('keeps every project’s entry point, not just the first', () => { - const mainA = makeDefaultBranchWorktree() - const mainB: Worktree = { ...makeDefaultBranchWorktree(), id: 'wt-main-b' } - - expect(visible([mainA, mainB], {})).toEqual([mainA.id, mainB.id]) - }) - - it('does not flicker out while an SSH host is offline', () => { - // Offline SSH synthesis preserves main-worktree identity but empties head/branch and loses live PTYs. - const awake = makeDefaultBranchWorktree() - const offline: Worktree = { ...awake, head: '', branch: '' } - - expect(visible([offline], {})).toEqual([offline.id]) - expect(visible([awake], {})).toEqual([awake.id]) - }) }) diff --git a/src/renderer/src/components/task-page/jira/Content.board-owner.test.tsx b/src/renderer/src/components/task-page/jira/Content.board-owner.test.tsx index bf0bcfcc205..ce669b98e2f 100644 --- a/src/renderer/src/components/task-page/jira/Content.board-owner.test.tsx +++ b/src/renderer/src/components/task-page/jira/Content.board-owner.test.tsx @@ -121,21 +121,3 @@ it.each([ ) } ) - -it('disables sprint filtering in linked mode while retaining its checked preference', () => { - const current = model(source) - current.jiraHasWorkspace = true - const { rerender } = render() - expect(screen.getByRole('switch', { name: 'Current sprint' }).hasAttribute('disabled')).toBe(true) - expect(screen.getByRole('switch', { name: 'Current sprint' }).getAttribute('aria-checked')).toBe( - 'true' - ) - current.jiraHasWorkspace = false - rerender() - expect(screen.getByRole('switch', { name: 'Current sprint' }).hasAttribute('disabled')).toBe( - false - ) - expect(screen.getByRole('switch', { name: 'Current sprint' }).getAttribute('aria-checked')).toBe( - 'true' - ) -}) diff --git a/src/renderer/src/components/terminal-pane/remote-runtime-recovery-input-hold.test.ts b/src/renderer/src/components/terminal-pane/remote-runtime-recovery-input-hold.test.ts index f86e2d3ee2d..520f448e381 100644 --- a/src/renderer/src/components/terminal-pane/remote-runtime-recovery-input-hold.test.ts +++ b/src/renderer/src/components/terminal-pane/remote-runtime-recovery-input-hold.test.ts @@ -120,28 +120,6 @@ describe('remote runtime recovery input hold', () => { expect(sent).toEqual([]) }) - it('accepts new input right after a release drained the previous hold', async () => { - const hold = createRemoteRuntimeRecoveryInputHold() - const { sent, writer } = createWriter() - hold.enqueue(endpoint, 'a', 'driving') - hold.release(endpoint, writer) - await vi.waitFor(() => expect(sent).toEqual(['a'])) - - expect(hold.enqueue(endpoint, 'b', 'driving')).toBe(true) - hold.release(endpoint, writer) - await vi.waitFor(() => expect(sent).toEqual(['a', 'b'])) - }) - - it('resolves held acknowledged input as undelivered on discard', async () => { - const hold = createRemoteRuntimeRecoveryInputHold() - const accepted = hold.enqueueAccepted(endpoint, 'x', 'driving') - - hold.discard() - - await expect(accepted).resolves.toBe(false) - expect(hold.isHolding()).toBe(false) - }) - it('lets only the latest connect settle typing held before the pane bound a terminal', async () => { const hold = createRemoteRuntimeRecoveryInputHold({ holdBeforeFirstConnect: true }) const { sent, writer } = createWriter() diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-surface-ownership.test.ts b/src/renderer/src/components/terminal-pane/terminal-pane-surface-ownership.test.ts index e0666f0cc5d..49043ee05b3 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-surface-ownership.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-surface-ownership.test.ts @@ -130,44 +130,4 @@ describe('terminal surface ownership', () => { ).resolves.toBe(false) expect(mocks.remountTerminalTabForRecovery).not.toHaveBeenCalled() }) - - // Round-trip through disk/Zod: undeclared fields vanish only at load, invisible to same-process assertions. - it('still refuses a chat-owned tab after a real persist/parse round trip', async () => { - const loaded = parseWorkspaceSession({ - activeRepoId: null, - activeWorktreeId: WORKTREE_ID, - activeTabId: 'tab-1', - tabsByWorktree: { - [WORKTREE_ID]: [ - { - id: 'tab-1', - ptyId: null, - worktreeId: WORKTREE_ID, - title: 'Terminal 1', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 0, - viewMode: 'chat' - } - ] - }, - terminalLayoutsByTabId: {} - }) - expect(loaded.ok).toBe(true) - setTerminalTabs( - (loaded.ok ? loaded.value.tabsByWorktree[WORKTREE_ID] : []) as StoredTerminalTab[] - ) - // Blind on purpose: only the reloaded row can refuse this. - mocks.getTab.mockReturnValue(null) - - await expect( - requestTerminalPaneRecovery({ - tabId: 'tab-1', - ptyId: 'pty-1', - reason: 'write-stalled' - }) - ).resolves.toBe(false) - expect(mocks.remountTerminalTabForRecovery).not.toHaveBeenCalled() - }) }) diff --git a/src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts b/src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts deleted file mode 100644 index 846f6dce594..00000000000 --- a/src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - _measureAgentHookCompletionStoreSyncForTest, - shouldSyncAgentHookCompletionForStoreUpdate, - type AgentHookCompletionStoreSnapshot -} from './agent-hook-completion-store-sync' - -function createState( - overrides: Partial = {} -): AgentHookCompletionStoreSnapshot { - return { - settings: { - experimentalTerminalAttention: false, - notifications: { enabled: true, agentTaskComplete: true } - }, - tabsByWorktree: { - 'wt-1': [{ id: 'tab-1', ptyId: 'pty-1', title: 'Terminal 1' }] - }, - ptyIdsByTabId: { 'tab-1': ['pty-1'] }, - terminalLayoutsByTabId: {}, - suppressedPtyExitIds: {}, - ...overrides - } -} - -describe('agent hook completion store sync', () => { - it('ignores unrelated and title-only writes at accumulated-workspace scale', () => { - const tabCount = 100 - const coordinatorCount = 100 - const updateCount = 600 - const tabsByWorktree = Object.fromEntries( - Array.from({ length: tabCount }, (_, index) => [ - `wt-${index}`, - [{ id: `tab-${index}`, ptyId: `pty-${index}`, title: `Terminal ${index}` }] - ]) - ) - let previous = createState({ tabsByWorktree }) - let syncPasses = 0 - let tabVisits = 0 - - for (let index = 0; index < updateCount; index += 1) { - const current = { ...previous } - const measurement = _measureAgentHookCompletionStoreSyncForTest(current, previous) - syncPasses += Number(measurement.shouldSync) - tabVisits += measurement.tabVisits - previous = current - } - - for (let index = 0; index < updateCount; index += 1) { - const targetTabs = previous.tabsByWorktree['wt-99'] - if (!targetTabs) { - throw new Error('Expected title-update fixture tab') - } - const current = createState({ - ...previous, - tabsByWorktree: { - ...previous.tabsByWorktree, - 'wt-99': targetTabs.map((tab) => ({ ...tab, title: `Agent frame ${index}` })) - } - }) - const measurement = _measureAgentHookCompletionStoreSyncForTest(current, previous) - syncPasses += Number(measurement.shouldSync) - tabVisits += measurement.tabVisits - previous = current - } - - const previousFullPassCost = { - tabVisits: updateCount * 2 * tabCount, - coordinatorVisits: updateCount * 2 * coordinatorCount - } - const gatedCost = { - tabVisits, - coordinatorVisits: syncPasses * coordinatorCount - } - expect(previousFullPassCost).toEqual({ tabVisits: 120_000, coordinatorVisits: 120_000 }) - expect(gatedCost).toEqual({ tabVisits: 600, coordinatorVisits: 0 }) - }) - - it('keeps every coordinator-liveness input reactive', () => { - const previous = createState() - const titleOnly = createState({ - ...previous, - tabsByWorktree: { - 'wt-1': [{ id: 'tab-1', ptyId: 'pty-1', title: 'Codex working' }] - } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(titleOnly, previous)).toBe(false) - - const tabCreated = createState({ - ...previous, - tabsByWorktree: { - ...previous.tabsByWorktree, - 'wt-2': [{ id: 'tab-2', ptyId: null }] - } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(tabCreated, previous)).toBe(true) - - const tabRemoved = createState({ ...previous, tabsByWorktree: {} }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(tabRemoved, previous)).toBe(true) - - const tabPtyChanged = createState({ - ...previous, - tabsByWorktree: { 'wt-1': [{ id: 'tab-1', ptyId: 'pty-2' }] } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(tabPtyChanged, previous)).toBe(true) - - expect( - shouldSyncAgentHookCompletionForStoreUpdate( - createState({ ...previous, ptyIdsByTabId: { 'tab-1': ['pty-2'] } }), - previous - ) - ).toBe(true) - expect( - shouldSyncAgentHookCompletionForStoreUpdate( - createState({ ...previous, terminalLayoutsByTabId: { 'tab-1': { root: null } } }), - previous - ) - ).toBe(true) - expect( - shouldSyncAgentHookCompletionForStoreUpdate( - createState({ ...previous, suppressedPtyExitIds: { 'pty-1': true } }), - previous - ) - ).toBe(true) - - const trackingDisabled = createState({ - ...previous, - settings: { - experimentalTerminalAttention: false, - notifications: { enabled: false, agentTaskComplete: false } - } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(trackingDisabled, previous)).toBe(false) - expect( - shouldSyncAgentHookCompletionForStoreUpdate( - createState({ ...previous, settings: null }), - previous - ) - ).toBe(true) - }) - - it('treats tab order and duplicate-id worktree precedence as liveness inputs', () => { - const previous = createState({ - tabsByWorktree: { - 'wt-1': [{ id: 'tab-shared', ptyId: 'pty-first' }], - 'wt-2': [{ id: 'tab-shared', ptyId: 'pty-second' }] - } - }) - const reorderedWorktrees = createState({ - ...previous, - tabsByWorktree: { - 'wt-2': previous.tabsByWorktree['wt-2'] ?? [], - 'wt-1': previous.tabsByWorktree['wt-1'] ?? [] - } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(reorderedWorktrees, previous)).toBe(true) - - const twoTabPrevious = createState({ - tabsByWorktree: { - 'wt-1': [ - { id: 'tab-1', ptyId: 'pty-1' }, - { id: 'tab-2', ptyId: 'pty-2' } - ] - } - }) - const reorderedTabs = createState({ - ...twoTabPrevious, - tabsByWorktree: { - 'wt-1': [ - { id: 'tab-2', ptyId: 'pty-2' }, - { id: 'tab-1', ptyId: 'pty-1' } - ] - } - }) - expect(shouldSyncAgentHookCompletionForStoreUpdate(reorderedTabs, twoTabPrevious)).toBe(true) - }) -}) diff --git a/src/renderer/src/hooks/useReadOnScreenAttention.mixed-workspace.test.ts b/src/renderer/src/hooks/useReadOnScreenAttention.mixed-workspace.test.ts index ea104361867..18e287dadf0 100644 --- a/src/renderer/src/hooks/useReadOnScreenAttention.mixed-workspace.test.ts +++ b/src/renderer/src/hooks/useReadOnScreenAttention.mixed-workspace.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getDefaultSettings } from '../../../shared/constants' + import { resolveAutoAckTabTargets } from './agent-auto-ack-targets' import { acknowledgeViewedAutoAckTarget } from './agent-auto-ack-surfaces' import { @@ -128,34 +128,3 @@ describe('the on-screen read in a workspace holding both a terminal and a struct expect(workspaceStillHeld(store)).toBe(true) }) }) - -function markChatGroupUnread(store: TestStore, groupId: string): void { - // Why: tab markers for completions sit behind the experimental attention setting. - store.setState({ - settings: { ...getDefaultSettings('/home/test'), experimentalTerminalAttention: true } - }) - store.getState().markAgentAttentionUnread({ - workspaceId: WORKSPACE, - subjectKey: CHAT_SUBJECT, - groupId, - reason: 'agent-completion' - }) -} - -describe('tab marker container addressing', () => { - it('accepts a structured chat addressed by its unified tab id', () => { - const { store } = seedMixedWorkspace({ visible: 'chat', unreadSubjectKeys: [] }) - - markChatGroupUnread(store, CHAT_TAB) - - expect(store.getState().unreadTerminalTabs[CHAT_TAB]).toBe('agent-completion') - }) - - it('still refuses an id no open tab owns', () => { - const { store } = seedMixedWorkspace({ visible: 'chat', unreadSubjectKeys: [] }) - - markChatGroupUnread(store, 'tab-that-never-existed') - - expect(store.getState().unreadTerminalTabs['tab-that-never-existed']).toBeUndefined() - }) -}) diff --git a/src/renderer/src/hooks/zoom-routing.test.ts b/src/renderer/src/hooks/zoom-routing.test.ts deleted file mode 100644 index e0fa4a27fda..00000000000 --- a/src/renderer/src/hooks/zoom-routing.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { NativeChatAppearanceSettings } from '../../../shared/native-chat/native-chat-appearance-settings' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -function makeTarget(args: { - hasXtermClass?: boolean - editorClosest?: boolean - chatClosest?: boolean - browserPageId?: string - /** The page's sits in a viewport shell outside its pane. */ - viewportPageId?: string -}): { - classList: { contains: (token: string) => boolean } - closest: (selector: string) => object | null -} { - const { - hasXtermClass = false, - editorClosest = false, - chatClosest = false, - browserPageId, - viewportPageId - } = args - return { - classList: { - contains: (token: string) => hasXtermClass && token === 'xterm-helper-textarea' - }, - closest: (selector) => { - if (selector === '[data-browser-page-pane-id]') { - return browserPageId ? { getAttribute: () => browserPageId } : null - } - if (selector === '[data-browser-page-viewport-id]') { - return viewportPageId ? { getAttribute: () => viewportPageId } : null - } - return (selector === '[data-native-chat-root]' ? chatClosest : editorClosest) ? {} : null - } - } -} - -describe('registerZoomIpcBridge', () => { - beforeEach(() => { - vi.resetModules() - vi.unstubAllGlobals() - }) - - // Test the zoom bridge directly: useIpcEvents adds an unrelated import graph that timed out under parallel load. - async function mountZoomBridge( - args: { - activeView?: string - activeTabType?: string - activeElement?: ReturnType - uiZoomLevel?: number - nativeChatAppearance?: NativeChatAppearanceSettings - editorFontZoomLevel?: number - viewerTakesZoom?: boolean - } = {} - ) { - const { - activeView = 'terminal', - activeTabType = 'browser', - activeElement = makeTarget({ editorClosest: true, hasXtermClass: true }), - uiZoomLevel = 0, - editorFontZoomLevel = 0, - nativeChatAppearance, - viewerTakesZoom = false - } = args - - const applyUIZoom = vi.fn() - const dispatchZoomLevelChanged = vi.fn() - const setEditorFontZoomLevel = vi.fn() - const setUI = vi.fn() - const settings = { terminalFontSize: 13, nativeChatAppearance } - const updateSettings = vi.fn(async (updates: Partial) => { - Object.assign(settings, updates) - }) - - const requestViewerZoom = vi.fn(() => viewerTakesZoom) - const dispatchBrowserPageZoom = vi.fn() - vi.doMock('@/components/browser-pane/host-guest/browser-page-zoom', () => ({ - dispatchBrowserPageZoom - })) - vi.doMock('@/components/editor/viewer-zoom-request', () => ({ - requestViewerZoom, - requestFrontDialogViewerZoom: () => false - })) - vi.doMock('@/lib/ui-zoom', () => ({ applyUIZoom })) - vi.doMock('@/lib/zoom-events', () => ({ dispatchZoomLevelChanged })) - vi.doMock('../store', () => ({ - useAppStore: { - getState: () => ({ - activeView, - activeTabType, - editorFontZoomLevel, - setEditorFontZoomLevel, - settings, - updateSettings - }) - } - })) - - const listenerRef: { current: ((direction: 'in' | 'out' | 'reset') => void) | null } = { - current: null - } - vi.stubGlobal('document', { activeElement }) - vi.stubGlobal('window', { - api: { - ui: { - onTerminalZoom: (listener: (direction: 'in' | 'out' | 'reset') => void) => { - listenerRef.current = listener - return () => {} - }, - getZoomLevel: () => uiZoomLevel, - set: setUI - } - } - }) - - const { registerZoomIpcBridge } = await import('./ipc-events/zoom-ipc-bridge') - const unsubs: (() => void)[] = [] - registerZoomIpcBridge(unsubs) - - expect(unsubs).toHaveLength(1) - const fire = listenerRef.current - if (!fire) { - throw new Error('Expected the terminal-zoom listener to be registered') - } - return { - fire, - dispatchBrowserPageZoom, - requestViewerZoom, - applyUIZoom, - dispatchZoomLevelChanged, - setEditorFontZoomLevel, - setUI, - settings, - updateSettings - } - } - - it('routes keyboard and menu zoom directions to persisted chat size, never app or editor zoom', async () => { - const zoom = await mountZoomBridge({ - activeTabType: 'terminal', - activeElement: makeTarget({ chatClosest: true }), - nativeChatAppearance: { fontSize: 19, codeFontSize: 16, width: 'wide' } - }) - zoom.fire('in') - zoom.fire('in') - zoom.fire('out') - await vi.waitFor(() => expect(zoom.updateSettings).toHaveBeenCalledTimes(2)) - expect(zoom.settings.nativeChatAppearance).toEqual({ - fontSize: 19, - codeFontSize: 16, - width: 'wide' - }) - zoom.fire('reset') - await vi.waitFor(() => - expect(zoom.settings.nativeChatAppearance).toEqual({ codeFontSize: 16, width: 'wide' }) - ) - expect(zoom.applyUIZoom).not.toHaveBeenCalled() - expect(zoom.setUI).not.toHaveBeenCalled() - expect(zoom.setEditorFontZoomLevel).not.toHaveBeenCalled() - }) - - it('consumes menu chat zoom while matching without changing saved size or app zoom', async () => { - const zoom = await mountZoomBridge({ - activeTabType: 'terminal', - activeElement: makeTarget({ chatClosest: true }), - nativeChatAppearance: { fontSize: 18, matchTerminalInterface: true } - }) - zoom.fire('in') - zoom.fire('out') - zoom.fire('reset') - await vi.waitFor(() => expect(zoom.settings.nativeChatAppearance?.fontSize).toBe(18)) - expect(zoom.updateSettings).not.toHaveBeenCalled() - expect(zoom.applyUIZoom).not.toHaveBeenCalled() - expect(zoom.setEditorFontZoomLevel).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.ts b/src/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.ts index 952a8f0503b..55cbfd4561a 100644 --- a/src/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.ts +++ b/src/renderer/src/lib/activate-ai-vault-structured-session-reveal.test.ts @@ -23,7 +23,6 @@ import { revealStructuredSession } from './activate-ai-vault-structured-session' import { STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY } from '../../../shared/agent-session/structured-agent-session-surface-capabilities' -import { RUNTIME_COMPAT_BLOCK_CODE } from '@/runtime/runtime-protocol-compat' const target = { worktreeId: 'workspace-1', sessionId: 'session-1' } @@ -40,37 +39,6 @@ beforeEach(() => { }) describe('revealStructuredSession', () => { - it('asks a local host without a capability round trip', async () => { - // The local host shares this build; probing it would only add a round trip. - await expect(revealStructuredSession(target)).resolves.toEqual({ ...target, agent: 'codex' }) - - expect(mocks.supports).not.toHaveBeenCalled() - expect(mocks.call).toHaveBeenCalledWith( - { kind: 'local' }, - 'agentSession.reveal', - { sessionId: 'session-1' }, - expect.objectContaining({ timeoutMs: expect.any(Number) }) - ) - }) - - it('negotiates against the host that owns the workspace, not the local one', async () => { - mocks.environmentIdFor.mockReturnValue('env-1') - - await expect(revealStructuredSession(target)).resolves.toEqual({ ...target, agent: 'codex' }) - - expect(mocks.supports).toHaveBeenCalledWith( - 'env-1', - STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY, - expect.any(Number) - ) - expect(mocks.call).toHaveBeenCalledWith( - { kind: 'environment', environmentId: 'env-1' }, - 'agentSession.reveal', - { sessionId: 'session-1' }, - expect.objectContaining({ timeoutMs: expect.any(Number) }) - ) - }) - it('keeps a known recipient target when the active workspace would resolve locally', async () => { const host = { kind: 'environment', environmentId: 'recipient-host' } as const await expect(revealStructuredSession({ ...target, target: host })).resolves.toEqual({ @@ -99,17 +67,6 @@ describe('revealStructuredSession', () => { expect(mocks.call).not.toHaveBeenCalled() }) - - it('reads a version block as a host too old, not as a lost connection', async () => { - mocks.environmentIdFor.mockReturnValue('env-1') - const blocked = Object.assign(new Error('Update the Orca server'), { - code: RUNTIME_COMPAT_BLOCK_CODE - }) - mocks.supports.mockRejectedValue(blocked) - - await expect(revealStructuredSession(target)).resolves.toBe('host-cannot-open') - expect(mocks.call).not.toHaveBeenCalled() - }) }) it('returns the actual revealed identity/workspace/provider for reconciliation', async () => { diff --git a/src/renderer/src/lib/doc-preview-grants.test.ts b/src/renderer/src/lib/doc-preview-grants.test.ts index 3f61eb4953c..b5733b2701b 100644 --- a/src/renderer/src/lib/doc-preview-grants.test.ts +++ b/src/renderer/src/lib/doc-preview-grants.test.ts @@ -34,25 +34,6 @@ beforeEach(() => { }) describe('doc preview grant lifetime', () => { - it('mints once for repeated mounts of the same preview tab', async () => { - const request = { - owner: { kind: 'ssh' as const, connectionId: 'ssh-1' }, - requestBase: '/d', - root: '/d', - entryRelativePath: 'a.html' - } - - const [first, second] = await Promise.all([ - ensureDocPreviewGrant('preview-1', request), - ensureDocPreviewGrant('preview-1', request) - ]) - - // StrictMode effect replay must neither mint twice nor revoke the surviving webview’s grant. - expect(mocks.mintGrant).toHaveBeenCalledOnce() - expect(first).toBe(second) - expect(mocks.revokeGrant).not.toHaveBeenCalled() - }) - // Why: Share reads and keys from the handle's location, so a later host change must not move it. it('keeps the minted location on the handle until the grant is released', async () => { const minted = { @@ -85,22 +66,6 @@ describe('doc preview grant lifetime', () => { }) }) - it('revokes on release and mints fresh afterwards', async () => { - const request = { - owner: { kind: 'ssh' as const, connectionId: 'ssh-1' }, - requestBase: '/d', - root: '/d', - entryRelativePath: 'a.html' - } - await ensureDocPreviewGrant('preview-2', request) - - releaseDocPreviewGrant('preview-2') - await vi.waitFor(() => expect(mocks.revokeGrant).toHaveBeenCalledWith('grant-1')) - - await ensureDocPreviewGrant('preview-2', request) - expect(mocks.mintGrant).toHaveBeenCalledTimes(2) - }) - // Late rejection from a released tab must not evict its reopened tab’s new, otherwise unrevokable grant. it('leaves the entry of a later mint alone when an earlier one rejects', async () => { const request = { @@ -129,21 +94,6 @@ describe('doc preview grant lifetime', () => { releaseDocPreviewGrant('preview-4') await vi.waitFor(() => expect(mocks.revokeGrant).toHaveBeenCalledWith('grant-2')) }) - - it('does not cache a failed mint', async () => { - mocks.mintGrant.mockRejectedValueOnce(new Error('runtime offline')) - const request = { - owner: { kind: 'ssh' as const, connectionId: 'ssh-1' }, - requestBase: '/d', - root: '/d', - entryRelativePath: 'a.html' - } - - await expect(ensureDocPreviewGrant('preview-3', request)).rejects.toThrow('runtime offline') - await expect(ensureDocPreviewGrant('preview-3', request)).resolves.toMatchObject({ - grantId: 'grant-1' - }) - }) }) describe('outside-workspace document grants', () => { diff --git a/src/renderer/src/lib/ime-composition-keyboard-event.test.ts b/src/renderer/src/lib/ime-composition-keyboard-event.test.ts index 7ef8114980f..055c3876fc4 100644 --- a/src/renderer/src/lib/ime-composition-keyboard-event.test.ts +++ b/src/renderer/src/lib/ime-composition-keyboard-event.test.ts @@ -98,28 +98,6 @@ describe('useImeEnterGestureOwnership', () => { } }) - // Only bare confirm redispatch is suppressed; held Cmd/Ctrl still requests deliberate submit. - it.each([ - ['meta', { metaKey: true }], - ['ctrl', { ctrlKey: true }], - ['alt', { altKey: true }] - ])('lets the %s+Enter chord through the carried redispatch', (_label, modifier) => { - const { result } = renderHook(() => useImeEnterGestureOwnership()) - result.current.setComposing(true) - - // The IME still owns the confirming keydown even with the modifier held. - expect( - result.current.ownsKeyDown( - gestureEvent({ key: 'Enter', keyCode: 13, isComposing: true, ...modifier }) - ) - ).toBe(true) - - result.current.setComposing(false) - const chord = gestureEvent({ key: 'Enter', keyCode: 13, ...modifier }) - expect(result.current.ownsKeyDown(chord)).toBe(false) - expect(chord.prevented).toBe(false) - }) - // Chorded redispatch must spend carry without suppressing submit or the next Enter would be swallowed. it('spends the carry on the chorded redispatch so the next Enter is not eaten', () => { const { result } = renderHook(() => useImeEnterGestureOwnership()) diff --git a/src/renderer/src/lib/open-mobile-emulator-tab.test.ts b/src/renderer/src/lib/open-mobile-emulator-tab.test.ts index a5dcd9a956f..765e2ae58c5 100644 --- a/src/renderer/src/lib/open-mobile-emulator-tab.test.ts +++ b/src/renderer/src/lib/open-mobile-emulator-tab.test.ts @@ -71,23 +71,6 @@ describe('openMobileEmulatorTab', () => { vi.unstubAllGlobals() }) - it('does not start a duplicate attach while a manual launch is already pending', async () => { - let resolveAttach: (value: typeof mockAttachResult) => void = () => {} - vi.mocked(callRuntimeRpc).mockImplementation( - () => - new Promise((resolve) => { - resolveAttach = resolve - }) - ) - - const firstLaunch = openMobileEmulatorTab('wt-1', { targetGroupId: 'group-1' }) - await expect(openMobileEmulatorTab('wt-1', { targetGroupId: 'group-1' })).resolves.toBe('sim-1') - - expect(callRuntimeRpc).toHaveBeenCalledTimes(1) - resolveAttach(mockAttachResult) - await firstLaunch - }) - it('shuts down the managed emulator if the tab closes before attach resolves', async () => { let resolveAttach: (value: typeof mockAttachResult) => void = () => {} vi.mocked(callRuntimeRpc).mockImplementation(async (_target, method) => { @@ -115,23 +98,6 @@ describe('openMobileEmulatorTab', () => { expect(consumePrelaunchedSimulatorSession('wt-1')).toBeNull() }) - it('allows a successful retry after simulator tab creation returns null', async () => { - vi.mocked(ensureSimulatorTab).mockReturnValueOnce(null).mockReturnValueOnce('sim-retry') - vi.mocked(callRuntimeRpc).mockResolvedValue(mockAttachResult) - - await expect(openMobileEmulatorTab('wt-1', { targetGroupId: 'group-1' })).resolves.toBeNull() - await expect(openMobileEmulatorTab('wt-1', { targetGroupId: 'group-1' })).resolves.toBe( - 'sim-retry' - ) - - expect(ensureSimulatorTab).toHaveBeenCalledTimes(2) - const attachCalls = vi - .mocked(callRuntimeRpc) - .mock.calls.filter(([, method]) => method === 'emulator.attach') - expect(attachCalls).toHaveLength(1) - expect(isManualSimulatorLaunchPending('wt-1')).toBe(false) - }) - it('does not release another in-flight launch when duplicate tab creation throws', async () => { let resolveAttach: (value: typeof mockAttachResult) => void = () => {} vi.mocked(callRuntimeRpc).mockImplementation( diff --git a/src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts b/src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts index 61c8ca49938..28f55502cdf 100644 --- a/src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts @@ -1,10 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { ManagedPaneInternal, ScrollState } from './pane-manager-types' -import { - attachPaneFitResizeObserver, - detachPaneFitResizeObserver, - requestStablePaneFit -} from './pane-fit-resize-observer' +import { requestStablePaneFit } from './pane-fit-resize-observer' import { beginTerminalScrollIntentBufferRebuild, endTerminalScrollIntentBufferRebuild @@ -113,21 +109,6 @@ describe('attachPaneFitResizeObserver', () => { vi.restoreAllMocks() }) - it('coalesces repeated observer callbacks and fits once the grid is stable', () => { - const pane = createPane() - - attachPaneFitResizeObserver(pane) - mockResizeObservers[0]?.trigger() - mockResizeObservers[0]?.trigger() - - expect(requestAnimationFrame).toHaveBeenCalledTimes(1) - expect(pane.fitAddon.fit).not.toHaveBeenCalled() - - flushAnimationFrames() - - expect(pane.fitAddon.fit).toHaveBeenCalledTimes(1) - }) - it('releases a hidden reattach continuation when the stable grid already matches', async () => { const pane = createPane() vi.mocked(pane.fitAddon.proposeDimensions).mockReturnValue(undefined) @@ -160,38 +141,4 @@ describe('attachPaneFitResizeObserver', () => { expect(pane.fitAddon.fit).toHaveBeenCalledTimes(1) expect(onSettled).toHaveBeenCalledTimes(1) }) - - it('throttles an endlessly unstable grid instead of fitting every frame', () => { - let cols = 80 - const pane = createPane(() => { - cols = cols === 80 ? 81 : 80 - return { cols, rows: 24 } - }) - - attachPaneFitResizeObserver(pane) - mockResizeObservers[0]?.trigger() - - for (let i = 0; i < 10; i += 1) { - flushAnimationFrames() - } - - expect(pane.fitAddon.fit).toHaveBeenCalledTimes(1) - expect(pane.pendingObservedFitRafId).toBeNull() - }) - - it('disconnects the observer and cancels any queued fit', () => { - const pane = createPane() - - attachPaneFitResizeObserver(pane) - mockResizeObservers[0]?.trigger() - const scheduledRafId = pane.pendingObservedFitRafId - - detachPaneFitResizeObserver(pane) - flushAnimationFrames() - - expect(mockResizeObservers[0]?.disconnect).toHaveBeenCalledTimes(1) - expect(cancelAnimationFrame).toHaveBeenCalledWith(scheduledRafId) - expect(pane.fitAddon.fit).not.toHaveBeenCalled() - expect(pane.pendingObservedFitRafId).toBeNull() - }) }) diff --git a/src/renderer/src/lib/pane-manager/terminal-scroll-intent-input-resync.test.ts b/src/renderer/src/lib/pane-manager/terminal-scroll-intent-input-resync.test.ts index fee56276711..5924589a914 100644 --- a/src/renderer/src/lib/pane-manager/terminal-scroll-intent-input-resync.test.ts +++ b/src/renderer/src/lib/pane-manager/terminal-scroll-intent-input-resync.test.ts @@ -57,22 +57,6 @@ afterEach(() => { }) describe('terminal scroll-intent input resync', () => { - it('does not apply input resync after tracking is disposed', () => { - vi.stubGlobal('requestAnimationFrame', () => 0) - vi.stubGlobal('Element', TestElement) - const { terminal, capturedInput, capturedUserInput } = createTerminalWithInputCapture(42, 100) - const host = new TestElement() as unknown as HTMLElement - const disposable = attachTerminalScrollIntentTracking(terminal, host) - - markTerminalPinnedViewport(terminal) - capturedUserInput.listener?.() - disposable.dispose() - terminal.buffer.active.viewportY = terminal.buffer.active.baseY - capturedInput.listener?.('a') - - expect(getTerminalScrollIntentKind(terminal)).toBe('pinnedViewport') - }) - it('defers real typing intent until a snapshot rebuild completes', () => { vi.stubGlobal('requestAnimationFrame', () => 0) vi.stubGlobal('Element', TestElement) diff --git a/src/renderer/src/store/slices/agent-hibernation-live-anchor-e2e.test.ts b/src/renderer/src/store/slices/agent-hibernation-live-anchor-e2e.test.ts index e1b0d848f70..8766eed073c 100644 --- a/src/renderer/src/store/slices/agent-hibernation-live-anchor-e2e.test.ts +++ b/src/renderer/src/store/slices/agent-hibernation-live-anchor-e2e.test.ts @@ -61,40 +61,6 @@ describe('a finished Claude turn reaches the hibernation planner', () => { resetHibernationPaneAgeForTests() }) - // Pin planner/anchor seam: broad resumable-agent recovery must match sleep exemption, formerly Pi-only (#10238). - it('writes a live resume anchor that no longer blocks it', () => { - const store = createTestStore() - seedWorkspace(store) - store - .getState() - .setAgentStatus( - PANE, - { state: 'working', prompt: 'summarize the diff', agentType: 'claude' }, - 'Claude', - { updatedAt: DONE_AT - 5_000, stateStartedAt: DONE_AT - 5_000 }, - ROUTING, - { providerSession: PROVIDER_SESSION } - ) - store - .getState() - .setAgentStatus( - PANE, - { state: 'done', prompt: 'summarize the diff', agentType: 'claude' }, - 'Claude', - { updatedAt: DONE_AT, stateStartedAt: DONE_AT }, - ROUTING, - { providerSession: PROVIDER_SESSION } - ) - - const state = store.getState() - expect(state.agentStatusByPaneKey[PANE]?.state).toBe('done') - expect(state.sleepingAgentSessionsByPaneKey[PANE]).toMatchObject({ - agent: 'claude', - origin: 'live' - }) - expect(planFrom(state, DONE_AT)).toEqual([PANE]) - }) - it('is not restarted by a done→done repaint carrying a fresh updatedAt', () => { const store = createTestStore() seedWorkspace(store) diff --git a/src/renderer/src/store/slices/browser-annotation-retention.test.ts b/src/renderer/src/store/slices/browser-annotation-retention.test.ts index e46b05aefdd..a89aeffbef1 100644 --- a/src/renderer/src/store/slices/browser-annotation-retention.test.ts +++ b/src/renderer/src/store/slices/browser-annotation-retention.test.ts @@ -35,30 +35,6 @@ async function createAnnotatedPage() { afterEach(() => vi.unstubAllGlobals()) describe('saved browser note lifetime and marker eligibility', () => { - it('retires same-URL geometry without changing the saved array or object references', async () => { - const { store, pageId } = await createAnnotatedPage() - const saved = store.getState().browserAnnotationsByPageId[pageId] - const note = saved[0] - expect(store.getState().browserAnnotationMarkerIdsByPageId[pageId]).toEqual([note.id]) - - store.getState().setBrowserPageUrl(pageId, 'https://example.com') - expect(store.getState().browserAnnotationMarkerIdsByPageId[pageId]).toEqual([note.id]) - store.getState().invalidateBrowserPageAnnotationGeometry(pageId) - - expect(store.getState().browserAnnotationsByPageId[pageId]).toBe(saved) - expect(store.getState().browserAnnotationsByPageId[pageId][0]).toBe(note) - expect(store.getState().browserAnnotationMarkerIdsByPageId[pageId]).toBeUndefined() - const listener = vi.fn() - const unsubscribe = store.subscribe(listener) - store.getState().invalidateBrowserPageAnnotationGeometry(pageId) - expect(listener).not.toHaveBeenCalled() - unsubscribe() - - store.getState().addBrowserPageAnnotation(makeAnnotation(pageId, 'fresh')) - expect(store.getState().browserAnnotationsByPageId[pageId][0]).toBe(note) - expect(store.getState().browserAnnotationMarkerIdsByPageId[pageId]).toEqual(['fresh']) - }) - it('removes an accepted delivery snapshot after navigation while preserving later edits and additions', async () => { const { store, pageId } = await createAnnotatedPage() store.getState().addBrowserPageAnnotation(makeAnnotation(pageId, 'edited')) diff --git a/src/renderer/src/store/slices/editor/buffers/editor-buffer-reconcile-identity.test.ts b/src/renderer/src/store/slices/editor/buffers/editor-buffer-reconcile-identity.test.ts index 4ae8b9cfc5c..bf8aced72be 100644 --- a/src/renderer/src/store/slices/editor/buffers/editor-buffer-reconcile-identity.test.ts +++ b/src/renderer/src/store/slices/editor/buffers/editor-buffer-reconcile-identity.test.ts @@ -1,17 +1,9 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { useAppStore } from '@/store' import { replaceRuntimeEnvironmentRevisions } from '@/runtime/runtime-environment-revision' import { assertEditorBufferInvariant } from '../../../editor-buffer-record-lifecycle' -import { makeWorktree } from '../../worktrees-slice-test-fixtures' -import type { OpenFile } from '../types/open-file' -import type * as identityModule from './editor-buffer-identity' -import { editorBufferIdentityOf } from './editor-buffer-identity' -// Counts identity resolutions, so the scale test can prove the index never re-resolves per keystroke. -vi.mock('./editor-buffer-identity', async (importOriginal) => { - const actual = await importOriginal() - return { ...actual, editorBufferIdentityOf: vi.fn(actual.editorBufferIdentityOf) } -}) +import type { OpenFile } from '../types/open-file' function tab(id: string, patch: Partial = {}): OpenFile { return { @@ -37,7 +29,6 @@ beforeEach(() => { afterEach(() => { replaceRuntimeEnvironmentRevisions([]) - vi.mocked(editorBufferIdentityOf).mockClear() }) describe('a retired runtime', () => { @@ -95,59 +86,3 @@ describe('SSH identity through the record index', () => { expect(state().editorDrafts.w2).toBe('edits') }) }) - -describe('scale', () => { - const FILES = 20 - const WORKSPACES = 10 - - it('indexes 200 tabs across 20 files with one resolution per tab, and none per keystroke', () => { - const worktrees = Array.from({ length: WORKSPACES }, (_, w) => - makeWorktree({ id: `wt-${w}`, repoId: 'repo-1', path: '/repo', hostId: 'local' }) - ) - const tabs = Array.from({ length: FILES * WORKSPACES }, (_, i) => { - const file = i % FILES - return tab(`t${i}`, { - filePath: `/repo/f${file}.ts`, - relativePath: `f${file}.ts`, - worktreeId: `wt-${Math.floor(i / FILES)}` - }) - }) - const resolutions = vi.mocked(editorBufferIdentityOf) - useAppStore.setState({ worktreesByRepo: { 'repo-1': worktrees } }) - resolutions.mockClear() - - expect(() => useAppStore.setState({ openFiles: tabs })).not.toThrow() - expect(resolutions).toHaveBeenCalledTimes(tabs.length) - expect(Object.keys(state().editorBuffers)).toHaveLength(FILES) - for (const viewIds of Object.values(state().editorBufferViews)) { - expect(viewIds).toHaveLength(WORKSPACES) - } - expect(() => assertEditorBufferInvariant(state())).not.toThrow() - - // Going dirty rewrites only the record's own views. - resolutions.mockClear() - state().setEditorDraft('t0', 'e') - state().markFileDirty('t0', true) - expect(resolutions).toHaveBeenCalledTimes(WORKSPACES) - resolutions.mockClear() - state().setEditorDraft('t0', 'edits') - expect(resolutions).not.toHaveBeenCalled() - const dirty = state().openFiles.filter((file) => file.isDirty) - expect(dirty.map((file) => file.id)).toEqual( - Array.from({ length: WORKSPACES }, (_, w) => `t${w * FILES}`) - ) - - // One more tab resolves only itself. - useAppStore.setState((s) => ({ - openFiles: [...s.openFiles, tab('late', { worktreeId: 'wt-3', filePath: '/repo/f0.ts' })] - })) - expect(resolutions).toHaveBeenCalledTimes(1) - expect(recordOf('late')).toBe(recordOf('t0')) - expect(view('late')?.isDirty).toBe(true) - - // A route change re-resolves each tab once. - resolutions.mockClear() - state().reconcileEditorBufferIdentities() - expect(resolutions).toHaveBeenCalledTimes(tabs.length + 1) - }) -}) diff --git a/src/renderer/src/store/slices/removed-worktree-diff-memory.test.ts b/src/renderer/src/store/slices/removed-worktree-diff-memory.test.ts index 2c309753a13..8de548eddee 100644 --- a/src/renderer/src/store/slices/removed-worktree-diff-memory.test.ts +++ b/src/renderer/src/store/slices/removed-worktree-diff-memory.test.ts @@ -12,10 +12,8 @@ import { import { createTestStore, makeWorktree } from './store-test-helpers' import { buildWorktreePurgeState } from './worktrees/teardown/worktree-purge-state' import { applyRemoveWorktreeSuccessState } from './worktrees/teardown/remove-worktree-store-cleanup' -import { buildWorktreeRenameState } from './worktrees/session/worktree-identity-rename-state' const removed = 'repo::/workspace/deleted' -const kept = 'repo::/workspace/kept' function mountDiff(worktreeId: string): { key: string; lease: CombinedDiffViewLease } { const key = `${worktreeId}::all-diffs::uncommitted::pane` @@ -59,44 +57,6 @@ afterEach(() => { }) describe('removed workspace diff memory', () => { - it('preserves cached bodies and scroll through rename, then releases them on deletion', () => { - const key = seedDiff(removed) - const keptKey = seedDiff(kept) - const renamed = 'repo::/workspace/renamed' - const store = createTestStore() - const sections = readCombinedDiffViewMemory(key)?.view?.sections - store.setState(buildWorktreeRenameState(store.getState(), removed, renamed)) - expect(readCombinedDiffViewMemory(key)).toMatchObject({ - worktreeId: renamed, - scrollTop: 42, - scrollAnchor: { key: 'source', offset: 0 } - }) - expect(readCombinedDiffViewMemory(key)?.view?.sections).toBe(sections) - buildWorktreePurgeState(store.getState(), [removed]) - expect(readCombinedDiffViewMemory(key)).toBeDefined() - buildWorktreePurgeState(store.getState(), [renamed]) - expect(readCombinedDiffViewMemory(key)).toBeUndefined() - expect(readCombinedDiffViewMemory(keptKey)).toBeDefined() - }) - - it.each(['listing', 'explicit'] as const)( - 'releases cached file bodies on %s removal and preserves another workspace', - (path) => { - const removedKey = seedDiff(removed) - const keptKey = seedDiff(kept) - const overlappingKey = seedDiff(`${removed}::all-diffs::nested-directory`) - const store = createTestStore() - if (path === 'listing') { - store.setState(buildWorktreePurgeState(store.getState(), [removed])) - } else { - applyRemoveWorktreeSuccessState(store.setState, removed, new Set()) - } - expect(readCombinedDiffViewMemory(removedKey)).toBeUndefined() - expect(readCombinedDiffViewMemory(keptKey)).toBeDefined() - expect(readCombinedDiffViewMemory(overlappingKey)).toBeDefined() - } - ) - it('ignores the unmount write of a viewer whose workspace was already removed', () => { const { key, lease } = mountDiff(removed) const store = createTestStore() @@ -109,23 +69,6 @@ describe('removed workspace diff memory', () => { expect(readCombinedDiffViewMemory(key)).toEqual({ worktreeId: removed, scrollTop: 7 }) }) - it('protects a mounted viewer from LRU eviction until release', () => { - const { key, lease } = mountDiff(kept) - const memory = readCombinedDiffViewMemory(key) - expect(memory).toBeDefined() - const oldestInactiveKey = seedDiff('repo::/workspace/other-0') - for (let index = 1; index < 20; index += 1) { - seedDiff(`repo::/workspace/other-${index}`) - } - expect(readCombinedDiffViewMemory(key)).toBe(memory) - expect(readCombinedDiffViewMemory(oldestInactiveKey)).toBeUndefined() - releaseCombinedDiffView(lease) - seedDiff('repo::/workspace/other-20') - expect(readCombinedDiffViewMemory(key)).toBeUndefined() - rememberCombinedDiffView(lease, { scrollTop: 9 }) - expect(readCombinedDiffViewMemory(key)).toEqual({ worktreeId: kept, scrollTop: 9 }) - }) - it('keeps cached views when a different host still owns the same workspace id', () => { const key = seedDiff(removed) const store = createTestStore()