diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 0a93d192d00..83162d49aa1 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -5942,13 +5942,13 @@ "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": "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. Hydrate a session carrying preview chrome from a build that made previews editor tabs and require it dropped while the ordinary editor tab for the same document survives. 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. Drive that notice directly for a guest bound to a live grant, for repeated attempts inside and outside its interval, for two previews at once, and for a contents no preview is bound to; 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/lib/file-preview.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.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 src/renderer/src/runtime/web-session-tabs-sync-client-owned-placement.test.ts src/renderer/src/runtime/web-session-tabs-sync-layout-groups.test.ts", + "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-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.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 src/renderer/src/runtime/web-session-tabs-sync-client-owned-placement.test.ts src/renderer/src/runtime/web-session-tabs-sync-layout-groups.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts", - "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/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", + "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/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", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-session-partition-policies.test.ts src/main/browser/doc-preview-protocol.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/doc-preview-download-block-notice.test.ts src/main/browser/browser-session-partition-policies.test.ts src/main/browser/doc-preview-guest-policy.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-manager-guest-policy-profile.test.ts src/main/window/main-window-webview-security.test.ts src/main/browser/doc-preview-guest-policy.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts src/renderer/src/lib/file-preview.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx src/shared/doc-preview-scheme.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts", // The hardening round: the third url door, the group projection, and both directions of the focus handoff. @@ -5977,7 +5977,6 @@ "testFiles": [ "src/main/runtime/orca-runtime-browser.test.ts", "src/main/runtime/rpc/methods/browser.test.ts", - "src/renderer/src/lib/file-preview.test.ts", "src/renderer/src/runtime/web-session-browser-placement.test.ts", "src/renderer/src/runtime/web-runtime-browser-capability-cleanup.test.ts", "src/renderer/src/runtime/web-runtime-session.test.ts", @@ -6036,15 +6035,6 @@ "direct creation removes the caller-declared new split without issuing an RPC" ] }, - { - "file": "src/renderer/src/lib/file-preview.test.ts", - "assertions": [ - "a paired-runtime HTML preview opens a local doc preview instead of a runtime browser tab", - "a local workspace still opens the embedded browser tab at a file URL", - "a paired document outside the worktree names that boundary and creates no split", - "an SSH document outside the worktree root still previews" - ] - }, { "file": "src/renderer/src/runtime/web-runtime-session.test.ts", "assertions": [ @@ -6356,14 +6346,14 @@ "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." }, { + "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", - "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", "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.", - "currentCommand": "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/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" + "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/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/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", @@ -6420,13 +6410,14 @@ "summary": "175/175 at the relocated paths on the unified tool authority, replacing the 2026-08-27 record of the same command at 174/174. That run killed twelve of fourteen mutants as sole deltas — dropping the document branch from the one authority door, removing either browsing-registration refusal or the mint-side refusal that is its converse, deleting the registration notification or widening it to the worktree-wide and any-tab waits, disposing revoked state by grant instead of by page, dropping the hosting-renderer or destroyed-contents check, rebuilding the renderer tool target from the grant, and deleting the teardown identity check under a re-mint — and reported two as ill-posed rather than as kills. The relocation changed no file it names except their directory." }, { + "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts", + "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts src/renderer/src/lib/file-preview.test.ts", "date": "2026-08-27", "runner": "local", "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser-workspace-doc-location.test.ts src/renderer/src/lib/file-preview.test.ts", "result": "passed", "durationSeconds": 22.09, - "summary": "29/29 on the candidate that makes the preview a browser tab. The preview action now creates a page located by the document; reopening the same document activates the tab it is already in rather than minting a second grant on one file; and closing that tab revokes its grant, which nothing else does now that the editor tab's close hook is gone. Red-green with each mutant as the sole delta: dropping the reuse lookup opens a second tab for a document already on screen, and dropping the release on close leaves the document readable through a grant nothing revokes until the process ends. Both are paired with presence preconditions in the same runs — a second, different document still gets its own tab, and a URL tab closed beside the document tab revokes nothing, so a release fired for every close would fail rather than pass." + "summary": "29/29 on the candidate that makes the preview a browser tab. The preview action now creates a page located by the document; reopening the same document activates the tab it is already in rather than minting a second grant on one file; and closing that tab revokes its grant, which nothing else does now that the editor tab's close hook is gone. Red-green with each mutant as the sole delta: dropping the reuse lookup opens a second tab for a document already on screen, and dropping the release on close leaves the document readable through a grant nothing revokes until the process ends. Both are paired with presence preconditions in the same runs — a second, different document still gets its own tab, and a URL tab closed beside the document tab revokes nothing, so a release fired for every close would fail rather than pass. 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." }, { "date": "2026-09-06", @@ -6600,14 +6591,14 @@ "summary": "The same exact-page hold, timeout, rollback, client cleanup, no-fallback, editor, and terminal oracle passed against isolated headless serve." }, { + "command": "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/lib/file-preview.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.test.ts src/renderer/src/runtime/remote-server-parity.test.ts", "date": "2026-08-12", "runner": "local", "platform": "macos", - "command": "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/lib/file-preview.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.test.ts src/renderer/src/runtime/remote-server-parity.test.ts", "result": "passed", "durationSeconds": 3.48, - "summary": "Seven focused host and renderer contract files passed 183/183 tests, covering owning-runtime routing, focused and unfocused creation, placement, reconciliation, failure cleanup, and teardown.", - "currentCommand": "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/lib/file-preview.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.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 src/renderer/src/runtime/web-session-tabs-sync-client-owned-placement.test.ts src/renderer/src/runtime/web-session-tabs-sync-layout-groups.test.ts" + "summary": "Seven focused host and renderer contract files passed 183/183 tests, covering owning-runtime routing, focused and unfocused creation, placement, reconciliation, failure cleanup, and teardown. 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/runtime/orca-runtime-browser.test.ts src/main/runtime/rpc/methods/browser.test.ts src/renderer/src/runtime/web-session-browser-placement.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.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 src/renderer/src/runtime/web-session-tabs-sync-client-owned-placement.test.ts src/renderer/src/runtime/web-session-tabs-sync-layout-groups.test.ts" }, { "date": "2026-08-13", diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 76362777b18..d62c7c7a0e5 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -10631,27 +10631,16 @@ "src/renderer/src/lib/feedback-image-attachments.test.ts": 557, "src/renderer/src/lib/file-owner-runtime-target.test.ts": 278, "src/renderer/src/lib/file-preview-link-open.test.ts": 2311, - "src/renderer/src/lib/file-preview.test.ts": 1468, - "src/renderer/src/lib/file-search-result-owner.test.ts": 48, "src/renderer/src/lib/file-search-selection.test.ts": 51, - "src/renderer/src/lib/file-search-shortcut-policy.test.ts": 236, "src/renderer/src/lib/file-type-icons.test.ts": 147, "src/renderer/src/lib/find-query-bounds.test.ts": 45, "src/renderer/src/lib/finished-agent-resume-resurrection.test.ts": 1872, "src/renderer/src/lib/fix-checks-agent-launch.test.ts": 730, "src/renderer/src/lib/flatten-retained-slice.test.ts": 389, - "src/renderer/src/lib/floating-workspace-panel-reveal.test.ts": 240, - "src/renderer/src/lib/floating-workspace-terminal-actions.test.ts": 613, - "src/renderer/src/lib/floating-workspace-tour-interaction-snapshot.test.ts": 56, "src/renderer/src/lib/focus-terminal-tab-surface.split-focus.test.ts": 331, - "src/renderer/src/lib/folder-workspace-path-status.test.ts": 213, "src/renderer/src/lib/foreground-terminal-tabs.test.ts": 55, - "src/renderer/src/lib/frame-drawn-chat-tab-placement.test.ts": 203, "src/renderer/src/lib/github-links.test.ts": 76, - "src/renderer/src/lib/github-pr-start-point.test.ts": 326, "src/renderer/src/lib/github-source-runtime-context.test.ts": 62, - "src/renderer/src/lib/github-work-item-source-lookup.test.ts": 350, - "src/renderer/src/lib/github-work-item-workspace-attachment.test.ts": 77, "src/renderer/src/lib/gitlab-links.test.ts": 56, "src/renderer/src/lib/hook-command-delayed-delivery.test.ts": 1904, "src/renderer/src/lib/host-chat-program.test.ts": 2419, diff --git a/src/renderer/src/lib/file-owner-runtime-target.test.ts b/src/renderer/src/lib/file-owner-runtime-target.test.ts index e5e8e86116b..900056074ad 100644 --- a/src/renderer/src/lib/file-owner-runtime-target.test.ts +++ b/src/renderer/src/lib/file-owner-runtime-target.test.ts @@ -55,11 +55,6 @@ describe('getRuntimeTargetForWorktreeOwner', () => { expect(getRuntimeTargetForWorktreeOwner(legacy, 'legacy-repo::wt')).toEqual({ kind: 'local' }) }) - it('stays on this app for no workspace or a workspace with no row yet', () => { - expect(getRuntimeTargetForWorktreeOwner(state, null)).toEqual({ kind: 'local' }) - expect(getRuntimeTargetForWorktreeOwner(state, 'unknown::wt')).toEqual({ kind: 'local' }) - }) - it('has no transport when rows disagree, and a caller with an error path refuses', () => { const ambiguous: WorktreeOperationRouteState = { settings, diff --git a/src/renderer/src/lib/file-preview.test.ts b/src/renderer/src/lib/file-preview.test.ts deleted file mode 100644 index cbaf5d39520..00000000000 --- a/src/renderer/src/lib/file-preview.test.ts +++ /dev/null @@ -1,606 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { - canShowWorkspaceFileBrowserAction, - convertBrowserPageToWorkspaceDoc, - getWorkspaceFilePreviewPlan, - openFileInBrowserTab, - openFilePreviewToSide -} from './file-preview' -import { TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE } from './workspace-file-browser-open-target' -import { localAndSshTwinRows } from './workspace-twin-rows.test-fixture' -import { folderWorkspaceKey } from '../../../shared/workspace-scope' -import { useAppStore } from '@/store' - -function browserActionState(connectionId: string | null = null): never { - return { - repos: [{ id: 'repo-1', connectionId }], - worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] }, - getKnownWorktreeById: () => ({ id: 'wt-1', path: '/repo' }) - } as never -} - -const mocks = vi.hoisted(() => ({ - browserAvailability: { - state: 'enabled' as const, - provider: 'local-client' as 'local-client' | 'paired-runtime' - } as - | { state: 'enabled'; provider: 'local-client' | 'paired-runtime' } - | { state: 'hidden'; reason: string }, - createBrowserTab: vi.fn(), - createEmptySplitGroup: vi.fn(() => 'group-2'), - setActiveBrowserTab: vi.fn(), - setActiveBrowserPage: vi.fn(), - setActiveWorktree: vi.fn(), - activeWorktreeId: 'wt-1', - focusGroup: vi.fn(), - activateTab: vi.fn(), - unifiedTabsByWorktree: {} as Record, - browserTabsByWorktree: {} as Record, - browserPagesByWorkspace: {} as Record, - convertBrowserPage: vi.fn(), - environmentId: null as string | null, - connectionId: null as string | null, - twin: false, - layoutByWorktree: {} as Record, - toastError: vi.fn() -})) - -vi.mock('sonner', () => ({ toast: { error: mocks.toastError } })) - -vi.mock('@/lib/client-creation-action-policy', () => ({ - getWorkspaceClientCreationActionPolicy: () => ({ 'managed-browser': mocks.browserAvailability }) -})) - -vi.mock('@/lib/worktree-runtime-owner', () => ({ - runtimeEnvironmentIdOrLocalUnlessTwin: () => mocks.environmentId -})) - -vi.mock('@/store', () => ({ - useAppStore: { - getState: () => ({ - createBrowserTab: mocks.createBrowserTab, - createEmptySplitGroup: mocks.createEmptySplitGroup, - setActiveBrowserTab: mocks.setActiveBrowserTab, - setActiveBrowserPage: mocks.setActiveBrowserPage, - setActiveWorktree: mocks.setActiveWorktree, - activeWorktreeId: mocks.activeWorktreeId, - focusGroup: mocks.focusGroup, - activateTab: mocks.activateTab, - unifiedTabsByWorktree: mocks.unifiedTabsByWorktree, - browserTabsByWorktree: mocks.browserTabsByWorktree, - browserPagesByWorkspace: mocks.browserPagesByWorkspace, - convertBrowserPage: mocks.convertBrowserPage, - getKnownWorktreeById: () => ({ id: 'wt-1', path: '/srv/repo' }), - groupsByWorktree: {}, - layoutByWorktree: mocks.layoutByWorktree, - ...(mocks.twin - ? localAndSshTwinRows('repo-1', 'wt-1') - : { - repos: [{ id: 'repo-1', connectionId: mocks.connectionId }], - worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] } - }) - }) - } -})) - -beforeEach(() => { - vi.clearAllMocks() - mocks.browserAvailability = { state: 'enabled', provider: 'local-client' } - mocks.environmentId = null - mocks.connectionId = null - mocks.layoutByWorktree = {} - mocks.browserTabsByWorktree = {} - mocks.browserPagesByWorkspace = {} - mocks.unifiedTabsByWorktree = {} - mocks.activeWorktreeId = 'wt-1' - mocks.twin = false -}) - -// Preview tabs identify documents rather than URLs; callers choose foreground opening versus side/background placement. -function docPreviewCall(filePath: string, extra: Record = {}): unknown[] { - return [ - 'wt-1', - 'data:text/html,', - { - docLocation: { kind: 'workspace-doc', worktreeId: 'wt-1', filePath }, - title: filePath.slice(filePath.lastIndexOf('/') + 1), - targetGroupId: undefined, - browserRuntimeEnvironmentId: null, - activate: false, - ...extra - } - ] -} - -function mockOpenDocument(filePath: string): void { - const docLocation = { kind: 'workspace-doc' as const, worktreeId: 'wt-1', filePath } - mocks.browserTabsByWorktree = { 'wt-1': [{ id: 'browser-9', docLocation }] } - mocks.browserPagesByWorkspace = { - 'browser-9': [{ id: 'page-doc', worktreeId: 'wt-1', docLocation }] - } -} - -describe('openFileInBrowserTab', () => { - it('reuses a document on an inactive page instead of creating a duplicate preview', () => { - mocks.connectionId = 'ssh-1' - mocks.browserTabsByWorktree = { 'wt-1': [{ id: 'browser-9', docLocation: null }] } - mocks.browserPagesByWorkspace = { - 'browser-9': [ - { id: 'page-web', worktreeId: 'wt-1' }, - { - id: 'page-doc', - worktreeId: 'wt-1', - docLocation: { - kind: 'workspace-doc', - worktreeId: 'wt-1', - filePath: '/home/alice/report.html' - } - } - ] - } - - openFileInBrowserTab({ filePath: '/home/alice/report.html', worktreeId: 'wt-1' }) - - expect(mocks.setActiveBrowserPage).toHaveBeenCalledWith('browser-9', 'page-doc') - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - }) - it('keeps a remote document preview in the requested group', () => { - mocks.environmentId = 'env-1' - openFileInBrowserTab({ - filePath: '/srv/repo/docs/report.html', - worktreeId: 'wt-1', - targetGroupId: 'group-2' - }) - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/srv/repo/docs/report.html', { activate: true, targetGroupId: 'group-2' }) - ) - }) - it('opens a local file URL in the Orca browser with the filename as title', () => { - openFileInBrowserTab({ - filePath: '/tmp/example file.html', - worktreeId: 'wt-1' - }) - - expect(mocks.createBrowserTab).toHaveBeenCalledWith('wt-1', 'file:///tmp/example%20file.html', { - title: 'example file.html', - activate: true - }) - }) - - it('renders a paired-runtime file as a local doc preview instead of a runtime browser tab', () => { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'enabled', provider: 'paired-runtime' } - - const plan = openFileInBrowserTab({ - filePath: '/srv/repo/docs/example.html', - worktreeId: 'wt-1' - }) - - expect(plan).toEqual({ status: 'doc-preview' }) - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/srv/repo/docs/example.html', { activate: true }) - ) - }) - - // Reuse one document tab or duplicate tabs would mint separate grants for the same file. - it('activates the tab a document is already open in', () => { - mocks.connectionId = 'ssh-1' - mockOpenDocument('/home/alice/report.html') - - openFileInBrowserTab({ filePath: '/home/alice/report.html', worktreeId: 'wt-1' }) - - expect(mocks.setActiveBrowserTab).toHaveBeenCalledWith('browser-9') - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - }) - - // Activate the unified tab, not browser state alone, or the terminal remains in front of the preview. - it('brings an already-open document to the front of its group', () => { - mocks.connectionId = 'ssh-1' - mockOpenDocument('/home/alice/report.html') - mocks.unifiedTabsByWorktree = { - 'wt-1': [ - { - id: 'tab-terminal', - worktreeId: 'wt-1', - contentType: 'terminal', - entityId: 'term-1', - groupId: 'group-1' - }, - { - id: 'tab-doc', - worktreeId: 'wt-1', - contentType: 'browser', - entityId: 'browser-9', - groupId: 'group-1' - } - ] - } - - openFileInBrowserTab({ filePath: '/home/alice/report.html', worktreeId: 'wt-1' }) - - expect(mocks.focusGroup).toHaveBeenCalledWith('wt-1', 'group-1') - expect(mocks.activateTab).toHaveBeenCalledWith('tab-doc', { worktreeId: 'wt-1' }) - expect(mocks.setActiveBrowserTab).toHaveBeenCalledWith('browser-9') - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - }) - - // Side previews must leave the reader working in the source, including when reusing a tab. - it('leaves the reader in place when a side preview reuses an open document', () => { - mocks.connectionId = 'ssh-1' - mockOpenDocument('/home/alice/report.html') - mocks.unifiedTabsByWorktree = { - 'wt-1': [{ id: 'tab-doc', contentType: 'browser', entityId: 'browser-9', groupId: 'group-1' }] - } - - openFilePreviewToSide({ - language: 'html', - filePath: '/home/alice/report.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.activateTab).not.toHaveBeenCalled() - expect(mocks.focusGroup).not.toHaveBeenCalled() - }) - - // Why: the split is part of the tab create, so reusing an open document must leave no empty group. - it('creates no split when a side preview reuses an open document', () => { - mocks.connectionId = 'ssh-1' - mockOpenDocument('/home/alice/report.html') - - openFilePreviewToSide({ - language: 'html', - filePath: '/home/alice/report.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - expect(mocks.setActiveBrowserTab).toHaveBeenCalledWith('browser-9') - }) - - it('opens a second document in its own tab', () => { - mocks.connectionId = 'ssh-1' - mockOpenDocument('/home/alice/a.html') - - openFileInBrowserTab({ filePath: '/home/alice/b.html', worktreeId: 'wt-1' }) - - expect(mocks.setActiveBrowserTab).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/home/alice/b.html', { activate: true }) - ) - }) - - it('renders an SSH file as a local doc preview', () => { - mocks.connectionId = 'ssh-1' - - const plan = openFileInBrowserTab({ - filePath: '/home/alice/report.html', - worktreeId: 'wt-1' - }) - - expect(plan).toEqual({ status: 'doc-preview' }) - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/home/alice/report.html', { activate: true }) - ) - }) - - it('previews a paired runtime whose managed browser is unavailable', () => { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'hidden', reason: 'streaming unavailable' } - - openFilePreviewToSide({ - language: 'html', - filePath: '/srv/repo/example.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.toastError).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).toHaveBeenCalledOnce() - }) - - // Keep the background split unfocused; host snapshots interpret activated empty groups as terminals. - it('creates paired-runtime side previews in an unfocused right-hand split', () => { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'enabled', provider: 'paired-runtime' } - - openFilePreviewToSide({ - language: 'html', - filePath: '/srv/repo/example.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/srv/repo/example.html', { - split: { sourceGroupId: 'group-1', splitDirection: 'right', activate: false } - }) - ) - }) - - it('creates local side previews in an activated right-hand split', () => { - openFilePreviewToSide({ - language: 'html', - filePath: '/tmp/example.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).toHaveBeenCalledWith('wt-1', 'file:///tmp/example.html', { - title: 'example.html', - split: { sourceGroupId: 'group-1', splitDirection: 'right', activate: true }, - activate: true - }) - }) - - it('rejects a local workspace whose managed browser is unavailable before creating a split', () => { - mocks.browserAvailability = { state: 'hidden', reason: 'browser unavailable' } - - openFilePreviewToSide({ - language: 'html', - filePath: '/tmp/example.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.toastError).toHaveBeenCalledWith('browser unavailable') - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - }) - - it('previews a paired document outside the workspace without a server browser', () => { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'hidden', reason: 'streaming unavailable' } - - openFilePreviewToSide({ - language: 'html', - filePath: '/tmp/agent-scratch/report.html', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall('/tmp/agent-scratch/report.html', { - split: { sourceGroupId: 'group-1', splitDirection: 'right', activate: false } - }) - ) - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('keeps previewing an SSH document outside the worktree root', () => { - mocks.connectionId = 'ssh-1' - - const plan = openFileInBrowserTab({ - filePath: '/tmp/agent-scratch/report.html', - worktreeId: 'wt-1' - }) - - expect(plan).toEqual({ status: 'doc-preview' }) - expect(mocks.createBrowserTab).toHaveBeenCalledOnce() - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('ignores languages that have no preview surface', () => { - openFilePreviewToSide({ - language: 'typescript', - filePath: '/tmp/example.ts', - worktreeId: 'wt-1', - sourceGroupId: 'group-1' - }) - - expect(mocks.createEmptySplitGroup).not.toHaveBeenCalled() - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - }) -}) - -describe('canShowWorkspaceFileBrowserAction', () => { - it('permits paired runtimes regardless of managed-browser capability', () => { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'hidden', reason: 'streaming unavailable' } - expect( - canShowWorkspaceFileBrowserAction(browserActionState(), 'wt-1', '/repo/report.html') - ).toBe(true) - - mocks.browserAvailability = { state: 'enabled', provider: 'paired-runtime' } - expect( - canShowWorkspaceFileBrowserAction(browserActionState(), 'wt-1', '/repo/report.html') - ).toBe(true) - }) - - // Keep the action visible so activation can explain the workspace boundary. - it('keeps the action visible for a paired document outside the worktree', () => { - mocks.environmentId = 'runtime-1' - - expect( - canShowWorkspaceFileBrowserAction(browserActionState(), 'wt-1', '/tmp/outside/report.html') - ).toBe(true) - }) - - it('permits both local and SSH files', () => { - expect( - canShowWorkspaceFileBrowserAction(browserActionState(), 'wt-1', '/repo/report.html') - ).toBe(true) - expect( - canShowWorkspaceFileBrowserAction(browserActionState('ssh-1'), 'wt-1', '/repo/report.html') - ).toBe(true) - }) - - it('hides the action while a file has no resolved owner', () => { - const workspaceId = folderWorkspaceKey('folder-1') - const state = { - folderWorkspaces: [ - { - id: 'folder-1', - projectGroupId: 'group-1', - folderPath: '/workspace' - } - ], - projectGroups: [{ id: 'group-1', parentGroupId: null }], - repos: [ - { - id: 'repo-local', - path: '/workspace/local', - projectGroupId: 'group-1' - }, - { - id: 'repo-ssh', - path: '/workspace/remote', - projectGroupId: 'group-1', - connectionId: 'ssh-1' - } - ], - worktreesByRepo: {} - } as never - - expect( - canShowWorkspaceFileBrowserAction(state, workspaceId, '/workspace/local/report.html') - ).toBe(true) - expect( - canShowWorkspaceFileBrowserAction(state, workspaceId, '/workspace/remote/report.html') - ).toBe(true) - expect( - canShowWorkspaceFileBrowserAction(state, workspaceId, '/workspace/unknown/report.html') - ).toBe(false) - }) -}) - -describe('getWorkspaceFilePreviewPlan', () => { - it('plans no local browser tab for a project two hosts publish', () => { - mocks.twin = true - expect( - getWorkspaceFilePreviewPlan(useAppStore.getState(), 'wt-1', '/srv/repo/report.html') - ).toEqual({ - status: 'unsupported', - message: TWIN_FILE_BROWSER_UNSUPPORTED_MESSAGE, - reason: 'no-channel' - }) - }) -}) - -// Address-bar conversion must also reuse the document’s existing grant/tab. -describe('convertBrowserPageToWorkspaceDoc', () => { - const DOC_LOCATION = { - kind: 'workspace-doc' as const, - worktreeId: 'wt-1', - filePath: '/home/alice/report.html' - } - - // Inspect page docLocation: a mixed workspace’s mirror is null when its document page is inactive. - it('activates the tab already showing the document, even as an inactive page', () => { - mocks.browserTabsByWorktree = { - 'wt-1': [{ id: 'browser-9', docLocation: null }] - } - mocks.browserPagesByWorkspace = { - 'browser-9': [ - { id: 'page-web', worktreeId: 'wt-1' }, - { id: 'page-doc', worktreeId: 'wt-1', docLocation: DOC_LOCATION } - ] - } - - const outcome = convertBrowserPageToWorkspaceDoc('page-1', DOC_LOCATION) - - expect(outcome).toBe('activated-existing') - expect(mocks.convertBrowserPage).not.toHaveBeenCalled() - expect(mocks.setActiveBrowserTab).toHaveBeenCalledWith('browser-9') - expect(mocks.setActiveBrowserPage).toHaveBeenCalledWith('browser-9', 'page-doc') - }) - - it('converts the page in place when no tab shows the document', () => { - mocks.browserPagesByWorkspace = { - 'browser-1': [{ id: 'page-1', worktreeId: 'wt-1' }] - } - mocks.convertBrowserPage.mockReturnValue({ id: 'new-page' }) - - const outcome = convertBrowserPageToWorkspaceDoc('page-1', DOC_LOCATION) - - expect(outcome).toBe('converted') - expect(mocks.convertBrowserPage).toHaveBeenCalledWith( - 'page-1', - { kind: 'workspace-doc', docLocation: DOC_LOCATION }, - undefined - ) - }) - - // Open another worktree’s document there; converting this row would leave its guest/links unable to take focus. - it('opens a document from another worktree in that worktree instead of converting', () => { - mocks.connectionId = 'ssh-1' - mocks.activeWorktreeId = 'wt-other' - mocks.browserPagesByWorkspace = { - 'browser-1': [{ id: 'page-1', worktreeId: 'wt-other' }] - } - - const outcome = convertBrowserPageToWorkspaceDoc('page-1', DOC_LOCATION) - - expect(outcome).toBe('opened-in-owning-worktree') - expect(mocks.convertBrowserPage).not.toHaveBeenCalled() - // Activate the document’s worktree so opening cannot succeed invisibly. - expect(mocks.setActiveWorktree).toHaveBeenCalledWith('wt-1') - expect(mocks.createBrowserTab).toHaveBeenCalledWith( - ...docPreviewCall(DOC_LOCATION.filePath, { activate: true }) - ) - }) - - // History converts this tab in place; reusing another document tab would leave this tab’s history looping there. - it.each(['history-return', 'history-advance'] as const)( - 'skips reuse on the %s leg and converts in place', - (leg) => { - mocks.browserTabsByWorktree = { - 'wt-1': [{ id: 'browser-9', docLocation: null }] - } - mocks.browserPagesByWorkspace = { - 'browser-9': [{ id: 'page-doc', worktreeId: 'wt-1', docLocation: DOC_LOCATION }], - 'browser-1': [{ id: 'page-1', worktreeId: 'wt-1' }] - } - mocks.convertBrowserPage.mockReturnValue({ id: 'new-page' }) - - const outcome = convertBrowserPageToWorkspaceDoc('page-1', DOC_LOCATION, { leg }) - - expect(outcome).toBe('converted') - expect(mocks.setActiveBrowserTab).not.toHaveBeenCalled() - expect(mocks.convertBrowserPage).toHaveBeenCalledWith( - 'page-1', - { kind: 'workspace-doc', docLocation: DOC_LOCATION }, - { leg } - ) - } - ) -}) - -// Web clients cannot host desktop preview webviews; hide the action instead of creating an unusable tab (#22618). -describe('document previews in a paired web client', () => { - it('hides Open in Orca Browser and refuses with a reason instead of a silent no-op', () => { - vi.stubGlobal('__ORCA_WEB_CLIENT__', true) - try { - mocks.environmentId = 'runtime-1' - mocks.browserAvailability = { state: 'enabled', provider: 'paired-runtime' } - - expect( - canShowWorkspaceFileBrowserAction(browserActionState(), 'wt-1', '/repo/report.html') - ).toBe(false) - const plan = openFileInBrowserTab({ - filePath: '/srv/repo/docs/example.html', - worktreeId: 'wt-1' - }) - expect(plan).toMatchObject({ status: 'unsupported', reason: 'no-channel' }) - expect(mocks.createBrowserTab).not.toHaveBeenCalled() - - mocks.browserPagesByWorkspace = { 'browser-1': [{ id: 'page-1', worktreeId: 'wt-1' }] } - expect( - convertBrowserPageToWorkspaceDoc('page-1', { - kind: 'workspace-doc', - worktreeId: 'wt-1', - filePath: '/srv/repo/docs/example.html' - }) - ).toBe('failed') - expect(mocks.convertBrowserPage).not.toHaveBeenCalled() - expect(mocks.toastError).toHaveBeenCalledTimes(1) - } finally { - vi.unstubAllGlobals() - } - }) -}) diff --git a/src/renderer/src/lib/file-search-result-owner.test.ts b/src/renderer/src/lib/file-search-result-owner.test.ts deleted file mode 100644 index d62dfaad402..00000000000 --- a/src/renderer/src/lib/file-search-result-owner.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { createFileSearchResultOwner } from './file-search-result-owner' - -describe('createFileSearchResultOwner', () => { - it('captures the exact runtime used by the completed search', () => { - const owner = createFileSearchResultOwner('worktree-a', ' runtime-owner-a ') - - expect(owner).toEqual({ - worktreeId: 'worktree-a', - runtimeEnvironmentId: 'runtime-owner-a' - }) - }) - - it.each([null, '', ' '])('records %j as an explicit non-runtime owner', (environmentId) => { - expect(createFileSearchResultOwner('local-or-ssh-worktree', environmentId)).toEqual({ - worktreeId: 'local-or-ssh-worktree', - runtimeEnvironmentId: null - }) - }) -}) diff --git a/src/renderer/src/lib/file-search-selection.test.ts b/src/renderer/src/lib/file-search-selection.test.ts index 7f33ba04c3e..467fdb657dd 100644 --- a/src/renderer/src/lib/file-search-selection.test.ts +++ b/src/renderer/src/lib/file-search-selection.test.ts @@ -12,7 +12,6 @@ afterEach(() => { unregisterCallbacks.pop()?.() } vi.restoreAllMocks() - vi.unstubAllGlobals() }) function registerForTest(provider: () => string | null | undefined): void { @@ -20,19 +19,6 @@ function registerForTest(provider: () => string | null | undefined): void { } describe('normalizeSelectedTextForFileSearch', () => { - it('trims and collapses multi-line selections into the single-line search box shape', () => { - expect(normalizeSelectedTextForFileSearch(' foo\r\n bar\n\n baz ')).toBe('foo bar baz') - }) - - it('handles CR-only selected text line breaks', () => { - expect(normalizeSelectedTextForFileSearch(' foo\r bar\r\r baz ')).toBe('foo bar baz') - }) - - it('returns null for empty selections', () => { - expect(normalizeSelectedTextForFileSearch(' \n\t ')).toBeNull() - expect(normalizeSelectedTextForFileSearch(null)).toBeNull() - }) - it('bounds newline-heavy selected text without splitting it into arrays', () => { const split = vi.spyOn(String.prototype, 'split') const selectedText = Array.from({ length: 2000 }, (_, index) => `term-${index + 1}`).join('\n') @@ -46,20 +32,6 @@ describe('normalizeSelectedTextForFileSearch', () => { }) describe('getSelectedTextForFileSearch', () => { - it('uses the most recently registered non-empty provider', () => { - registerForTest(() => 'older') - registerForTest(() => 'newer') - - expect(getSelectedTextForFileSearch()).toBe('newer') - }) - - it('falls back through empty providers', () => { - registerForTest(() => 'needle') - registerForTest(() => ' ') - - expect(getSelectedTextForFileSearch()).toBe('needle') - }) - it('unregisters providers so closed editors do not accumulate stale selection readers', () => { registerForTest(() => 'active') const unregister = registerFileSearchSelectedTextProvider(() => 'stale') @@ -69,14 +41,4 @@ describe('getSelectedTextForFileSearch', () => { expect(getSelectedTextForFileSearch()).toBe('active') }) - - it('falls back to the DOM selection when no provider has selected text', () => { - vi.stubGlobal('window', { - getSelection: () => ({ - toString: () => 'dom selection' - }) - }) - - expect(getSelectedTextForFileSearch()).toBe('dom selection') - }) }) diff --git a/src/renderer/src/lib/file-search-shortcut-policy.test.ts b/src/renderer/src/lib/file-search-shortcut-policy.test.ts deleted file mode 100644 index 8d0a365f171..00000000000 --- a/src/renderer/src/lib/file-search-shortcut-policy.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -// @vitest-environment happy-dom -import { afterEach, describe, expect, it, vi } from 'vitest' -import { keybindingMatchesAction } from '../../../shared/keybindings' -import { fileSearchClaimsTextKey } from './file-search-shortcut-policy' - -afterEach(() => vi.restoreAllMocks()) - -describe('file search shortcut ownership', () => { - it.each(['Macintosh', 'Windows', 'Linux'])( - 'preserves editing and releases app chords on %s', - (platform) => { - vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(platform) - const target = document.createElement('input') - target.setAttribute('data-file-search-input', 'true') - const modifiers = platform === 'Macintosh' ? { metaKey: true } : { ctrlKey: true } - for (const key of ['p', 'j', 'b', 'g', '+', '-', 'ArrowUp', 'ArrowDown']) { - expect(fileSearchClaimsTextKey({ target, key, shiftKey: true, ...modifiers })).toBe(false) - } - for (const key of [ - 'a', - 'c', - 'v', - 'x', - 'z', - 'y', - 'ArrowLeft', - 'ArrowRight', - 'Home', - 'End', - 'Backspace', - 'Delete' - ]) { - expect(fileSearchClaimsTextKey({ target, key, ...modifiers })).toBe(true) - } - expect(fileSearchClaimsTextKey({ target, key: 'b' })).toBe(true) - expect(fileSearchClaimsTextKey({ target, key: 'с', code: 'KeyC', ...modifiers })).toBe(true) - if (platform !== 'Macintosh') { - expect(fileSearchClaimsTextKey({ target, key: 'Insert', ...modifiers })).toBe(true) - } - expect(fileSearchClaimsTextKey({ target, key: 'p', isComposing: true, ...modifiers })).toBe( - true - ) - expect(fileSearchClaimsTextKey({ target, key: 'p', altKey: true, ...modifiers })).toBe(false) - expect( - fileSearchClaimsTextKey({ target, key: 'a', code: 'KeyA', altKey: true, ...modifiers }) - ).toBe(false) - expect( - fileSearchClaimsTextKey({ target, key: 'r', code: 'KeyR', altKey: true, ...modifiers }) - ).toBe(false) - expect(fileSearchClaimsTextKey({ target, key: 'a', altKey: true })).toBe(true) - expect( - fileSearchClaimsTextKey({ - target, - key: 'a', - code: 'KeyA', - altKey: true, - altGraph: true, - ...modifiers - }) - ).toBe(true) - expect( - fileSearchClaimsTextKey({ - target, - key: 'a', - altKey: true, - getModifierState: () => true, - ...modifiers - }) - ).toBe(true) - if (platform !== 'Macintosh') { - expect( - keybindingMatchesAction( - 'floatingTerminal.toggle', - { key: 'a', code: 'KeyA', ctrlKey: true, altKey: true }, - platform === 'Windows' ? 'win32' : 'linux' - ) - ).toBe(true) - expect( - fileSearchClaimsTextKey({ target, key: '@', code: 'KeyQ', altKey: true, ...modifiers }) - ).toBe(true) - } - expect(fileSearchClaimsTextKey({ target, key: 'Delete', altKey: true, ...modifiers })).toBe( - true - ) - expect(fileSearchClaimsTextKey({ target, key: 'p', ctrlKey: true, metaKey: true })).toBe(true) - } - ) - - it('retains other text surface authority', () => { - for (const tag of ['input', 'textarea', 'select']) { - expect( - fileSearchClaimsTextKey({ target: document.createElement(tag), key: 'b', metaKey: true }) - ).toBe(true) - } - expect(fileSearchClaimsTextKey({ target: document.createElement('button'), key: 'b' })).toBe( - false - ) - }) -}) - -it('releases shipped Command+Option actions without reclaiming physical clipboard letters', () => { - vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Macintosh') - const target = document.createElement('input') - target.setAttribute('data-file-search-input', 'true') - for (const [action, key, code] of [ - ['workspace.rename', 'r', 'KeyR'], - ['floatingTerminal.toggle', 'å', 'KeyA'] - ] as const) { - const input = { target, key, code, metaKey: true, altKey: true } - expect(keybindingMatchesAction(action, input, 'darwin')).toBe(true) - expect(fileSearchClaimsTextKey(input)).toBe(false) - } -}) diff --git a/src/renderer/src/lib/find-query-bounds.test.ts b/src/renderer/src/lib/find-query-bounds.test.ts index 423976127ae..3c94581a0a9 100644 --- a/src/renderer/src/lib/find-query-bounds.test.ts +++ b/src/renderer/src/lib/find-query-bounds.test.ts @@ -3,10 +3,6 @@ import { describe, expect, it } from 'vitest' import { FIND_QUERY_MAX_BYTES, getFindRequestQuery, isFindQueryTooLarge } from './find-query-bounds' describe('find query bounds', () => { - it('keeps find queries within the byte budget', () => { - expect(getFindRequestQuery('needle')).toBe('needle') - }) - it('rejects oversized ASCII find queries', () => { const query = 'x'.repeat(FIND_QUERY_MAX_BYTES + 1) diff --git a/src/renderer/src/lib/fix-checks-agent-launch.test.ts b/src/renderer/src/lib/fix-checks-agent-launch.test.ts index e80567872fe..96d9f18556e 100644 --- a/src/renderer/src/lib/fix-checks-agent-launch.test.ts +++ b/src/renderer/src/lib/fix-checks-agent-launch.test.ts @@ -64,7 +64,6 @@ const mocks = vi.hoisted(() => { activateAndRevealWorktree: vi.fn(), findGithubPrWorkspaceAttachment: vi.fn(), focusTerminalTabSurface: vi.fn(), - getConnectionId: vi.fn(), launchAgentInNewTab: vi.fn(), launchWorkItemDirect: vi.fn(), pickSourceControlLaunchAgent: vi.fn(), @@ -93,10 +92,6 @@ vi.mock('sonner', () => ({ } })) -vi.mock('@/lib/connection-context', () => ({ - getConnectionId: mocks.getConnectionId -})) - vi.mock('@/lib/focus-terminal-tab-surface', () => ({ focusTerminalTabSurface: mocks.focusTerminalTabSurface })) @@ -170,7 +165,6 @@ describe('startFixChecksAgent', () => { mocks.store.ensureRemoteDetectedAgents.mockResolvedValue(['codex']) mocks.activateAndRevealWorktree.mockReturnValue(true) mocks.findGithubPrWorkspaceAttachment.mockReturnValue(null) - mocks.getConnectionId.mockReturnValue(null) mocks.launchAgentInNewTab.mockImplementation( (args: { beforeSurfaceOpen?: (surface: { kind: 'local-terminal' }) => boolean | void }) => { args.beforeSurfaceOpen?.({ kind: 'local-terminal' }) @@ -188,24 +182,6 @@ describe('startFixChecksAgent', () => { mocks.resolveRepoWorktreeLaunchPlatform.mockReturnValue('darwin') }) - it('activates the attached workspace as a surface-providing caller', async () => { - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(true) - - // The launcher owns creation; activation must not seed a sibling shell after the last terminal closed. - expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith('wt-1', { - providesInitialSurface: true - }) - }) - it('carries the actual use-default literal from Fix Checks into final startup and its recipe', async () => { const settings = { ...getDefaultSettings('/test'), @@ -259,157 +235,6 @@ describe('startFixChecksAgent', () => { expect(plans[0]?.launchCommand).not.toContain('high') }) - it('fails without launching when the requested worktree is missing', async () => { - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'missing-worktree', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(false) - - expect(mocks.store.ensureDetectedAgents).not.toHaveBeenCalled() - expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled() - }) - - it('fails without launching when agent detection finds no enabled agent', async () => { - mocks.store.ensureDetectedAgents.mockResolvedValue([]) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(false) - - expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled() - }) - - it('rejects without launching when remote agent detection fails', async () => { - mocks.store.repos = [{ ...mocks.store.repos[0], connectionId: 'conn-1' }] - mocks.getConnectionId.mockReturnValue('conn-1') - mocks.store.ensureRemoteDetectedAgents.mockRejectedValue(new Error('detection failed')) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).rejects.toThrow('detection failed') - - expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled() - }) - - it("resolves the attached workspace's own execution host", async () => { - mocks.store.repos = [ - { - ...mocks.store.repos[0], - connectionId: 'ssh-1' - } - ] - mocks.getConnectionId.mockReturnValue(undefined) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(true) - - expect(mocks.store.ensureRemoteDetectedAgents).toHaveBeenCalledWith('ssh-1') - expect(mocks.resolveRepoWorktreeLaunchPlatform).toHaveBeenCalledWith( - mocks.store, - expect.objectContaining({ id: 'repo-1', connectionId: 'ssh-1' }), - expect.objectContaining({ id: 'wt-1', path: '/repo/wt-1' }) - ) - }) - - it("plans a Linux server's launch for that server from a Windows client (#27971)", async () => { - mocks.resolveRepoWorktreeLaunchPlatform.mockReturnValue('linux') - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(true) - - expect(mocks.launchAgentInNewTab).toHaveBeenCalledWith( - expect.objectContaining({ launchPlatform: 'linux' }) - ) - }) - - it('fails without launching when the execution host has not reported its OS', async () => { - mocks.resolveRepoWorktreeLaunchPlatform.mockReturnValue(undefined) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(false) - - expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled() - }) - - it('leaves hidden CLI validation to the selected launch route', async () => { - mocks.resolveSourceControlActionRecipe.mockReturnValue({ - commandInputTemplate: '{basePrompt}', - agentArgs: '--model "unterminated' - }) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).resolves.toBe(true) - - expect(mocks.launchAgentInNewTab).toHaveBeenCalledWith( - expect.objectContaining({ agentArgs: '--model "unterminated' }) - ) - expect(mocks.toastError).not.toHaveBeenCalled() - }) - - it('rejects without focusing a terminal when agent launch throws', async () => { - mocks.launchAgentInNewTab.mockImplementation(() => { - throw new Error('launch failed') - }) - const { startFixChecksAgent } = await import('./fix-checks-agent-launch') - - await expect( - startFixChecksAgent({ - repoId: 'repo-1', - worktreeId: 'wt-1', - basePrompt: 'Fix checks', - launchSource: 'task_page' - }) - ).rejects.toThrow('launch failed') - - expect(mocks.focusTerminalTabSurface).not.toHaveBeenCalled() - }) - // Why: a saved chat stays implicit so a host that stops running chats falls back to a terminal. it('replays a saved terminal explicitly and leaves a saved chat to the settings route', async () => { Object.assign(mocks.store.settings, { disabledTuiAgents: ['codex'] }) diff --git a/src/renderer/src/lib/flatten-retained-slice.test.ts b/src/renderer/src/lib/flatten-retained-slice.test.ts index f94d28e0fff..6d8e03e1273 100644 --- a/src/renderer/src/lib/flatten-retained-slice.test.ts +++ b/src/renderer/src/lib/flatten-retained-slice.test.ts @@ -4,8 +4,6 @@ import { runProcess } from '@orca/process-host' import { z } from 'zod' import { describe, expect, it } from 'vitest' -import { flattenRetainedSlice } from './flatten-retained-slice' - // Use 1MB parents above V8’s SlicedString threshold so retained-parent versus copied-tail heap differences are measurable. const PARENT_CHARS = 1024 * 1024 const TAIL_CHARS = 512 @@ -89,13 +87,6 @@ async function measureTails(filler: string) { } describe('flattenRetainedSlice', () => { - it('preserves content exactly', () => { - expect(flattenRetainedSlice('')).toBe('') - expect(flattenRetainedSlice('a')).toBe('a') - const source = 'hello world, 漢字, , \u{1f600}' - expect(flattenRetainedSlice(source.slice(3))).toBe(source.slice(3)) - }) - it.each([ ['one-byte', 'a'], ['two-byte', '漢'] diff --git a/src/renderer/src/lib/floating-workspace-panel-reveal.test.ts b/src/renderer/src/lib/floating-workspace-panel-reveal.test.ts deleted file mode 100644 index 6bea5dd1540..00000000000 --- a/src/renderer/src/lib/floating-workspace-panel-reveal.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { getDefaultSettings } from '../../../shared/constants' -import type { AppState } from '@/store/types' -import { TOGGLE_FLOATING_TERMINAL_EVENT } from './floating-terminal' -import { revealFloatingWorkspacePanel } from './floating-workspace-panel-reveal' - -function panelState({ enabled, open }: { enabled: boolean; open: boolean }) { - return { - settings: { ...getDefaultSettings('/home/test'), floatingTerminalEnabled: enabled }, - floatingWorkspacePanelOpen: open, - updateSettings: vi.fn(async () => {}) - } -} - -async function settle(): Promise { - await new Promise((resolve) => setImmediate(resolve)) -} - -function stubWindow() { - const dispatchEvent = vi.fn() - const frames: FrameRequestCallback[] = [] - vi.stubGlobal('window', { dispatchEvent }) - vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => frames.push(callback)) - // A DOM a frame behind the store must not decide: it still shows the panel as it was. - vi.stubGlobal('document', { querySelector: vi.fn().mockReturnValue({}) }) - return { dispatchEvent, runFrames: () => frames.splice(0).forEach((frame) => frame(0)) } -} - -afterEach(() => vi.unstubAllGlobals()) - -describe('revealFloatingWorkspacePanel', () => { - it('toggles an enabled panel open only while the store says it is closed', () => { - const { dispatchEvent } = stubWindow() - - revealFloatingWorkspacePanel(panelState({ enabled: true, open: true })) - expect(dispatchEvent).not.toHaveBeenCalled() - - revealFloatingWorkspacePanel(panelState({ enabled: true, open: false })) - expect(dispatchEvent).toHaveBeenCalledOnce() - expect(dispatchEvent.mock.calls[0][0]).toMatchObject({ type: TOGGLE_FLOATING_TERMINAL_EVENT }) - }) - - it('enables a disabled panel, then opens it a frame later unless it was left open', async () => { - const { dispatchEvent, runFrames } = stubWindow() - const leftOpen = panelState({ enabled: false, open: true }) - const leftClosed = panelState({ enabled: false, open: false }) - - revealFloatingWorkspacePanel(leftOpen) - revealFloatingWorkspacePanel(leftClosed) - await settle() - expect(leftOpen.updateSettings).toHaveBeenCalledWith({ floatingTerminalEnabled: true }) - expect(leftClosed.updateSettings).toHaveBeenCalledWith({ floatingTerminalEnabled: true }) - expect(dispatchEvent).not.toHaveBeenCalled() - - runFrames() - expect(dispatchEvent).toHaveBeenCalledOnce() - }) -}) diff --git a/src/renderer/src/lib/floating-workspace-terminal-actions.test.ts b/src/renderer/src/lib/floating-workspace-terminal-actions.test.ts deleted file mode 100644 index 15f2fe018da..00000000000 --- a/src/renderer/src/lib/floating-workspace-terminal-actions.test.ts +++ /dev/null @@ -1,832 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { FLOATING_TERMINAL_WORKTREE_ID, getDefaultSettings } from '../../../shared/constants' -import type { Tab } from '../../../shared/tab-types' -import type { TerminalTab } from '../../../shared/terminal-tab-types' -import { - countVisibleFloatingWorkspaceItems, - createFloatingWorkspaceBrowserTab, - createFloatingWorkspaceMarkdownTab, - createFloatingWorkspaceTerminalTab, - handleEmptyFloatingWorkspacePanelCloseShortcut, - isFloatingWorkspacePanelFocused, - isFloatingWorkspacePanelShortcut, - isFloatingWorkspacePanelShortcutTarget, - isFloatingWorkspaceTerminalInputTarget, - matchFloatingWorkspacePanelChord, - shouldMinimizeFloatingWorkspacePanelOnCloseShortcut, - switchFloatingWorkspaceRecentTab, - switchFloatingWorkspaceTab -} from './floating-workspace-terminal-actions' -import { - matchFloatingWorkspacePanelOwnedAction, - FLOATING_WORKSPACE_SHORTCUT_SURFACE_SELECTOR -} from './floating-workspace-shortcut-policy' - -const activateWebRuntimeSessionTabMock = vi.hoisted(() => vi.fn()) -const createWebRuntimeSessionBrowserTabMock = vi.hoisted(() => vi.fn()) -const createWebRuntimeSessionTerminalMock = vi.hoisted(() => vi.fn()) -const createUntitledMarkdownFileWithTemplateSelectionMock = vi.hoisted(() => vi.fn()) -const focusTerminalTabSurfaceMock = vi.hoisted(() => vi.fn()) -const isWebRuntimeSessionActiveMock = vi.hoisted(() => vi.fn()) - -vi.mock('@/runtime/web-runtime-session-tab-lifecycle', () => ({ - activateWebRuntimeSessionTab: activateWebRuntimeSessionTabMock -})) -vi.mock('@/runtime/web-runtime-browser-creation', () => ({ - createWebRuntimeSessionBrowserTab: createWebRuntimeSessionBrowserTabMock -})) -vi.mock('@/runtime/web-runtime-terminal-creation', () => ({ - createWebRuntimeSessionTerminal: createWebRuntimeSessionTerminalMock -})) -vi.mock('@/runtime/web-runtime-session-environment', () => ({ - isWebRuntimeSessionActive: isWebRuntimeSessionActiveMock -})) - -vi.mock('./create-untitled-markdown', () => ({ - createUntitledMarkdownFileWithTemplateSelection: - createUntitledMarkdownFileWithTemplateSelectionMock -})) - -vi.mock('./connection-context', () => ({ - getConnectionId: vi.fn(() => null) -})) - -vi.mock('./focus-terminal-tab-surface', () => ({ - focusTerminalTabSurface: focusTerminalTabSurfaceMock -})) - -function shortcutEvent(overrides: Partial): KeyboardEvent { - return { - altKey: false, - ctrlKey: false, - key: 't', - metaKey: false, - shiftKey: false, - ...overrides - } as KeyboardEvent -} - -function shortcutSurfaceEvent(overrides: Partial): KeyboardEvent { - return shortcutEvent({ - target: makeElement({ - closestSelectors: [FLOATING_WORKSPACE_SHORTCUT_SURFACE_SELECTOR] - }), - ...overrides - }) -} - -function installFakeHTMLElement(): void { - vi.stubGlobal('HTMLElement', class {}) -} - -function makeElement({ - attributes = [], - classNames = [], - closestSelectors = [] -}: { - attributes?: string[] - classNames?: string[] - closestSelectors?: string[] -}): HTMLElement { - const element = { - classList: { - contains: vi.fn((token: string) => classNames.includes(token)) - }, - getAttribute: vi.fn((attribute: string) => (attributes.includes(attribute) ? '' : null)), - closest: vi.fn((selector: string) => (closestSelectors.includes(selector) ? {} : null)) - } - Object.setPrototypeOf(element, HTMLElement.prototype) - return element as unknown as HTMLElement -} - -function makeTab(id: string): TerminalTab { - return { - id, - ptyId: null, - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 0 - } -} - -function makeUnifiedTerminalTab(id: string, groupId = 'floating-group'): Tab { - return { - id, - entityId: id, - groupId, - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - contentType: 'terminal', - label: 'Terminal', - customLabel: null, - color: null, - sortOrder: 0, - createdAt: 0 - } -} - -afterEach(() => { - vi.unstubAllGlobals() -}) - -function floatingPanelState({ - enabled = true, - open = true, - tabs = [] -}: { enabled?: boolean; open?: boolean; tabs?: Tab[] } = {}) { - return { - settings: { ...getDefaultSettings('/home/me'), floatingTerminalEnabled: enabled }, - floatingWorkspacePanelOpen: open, - browserTabsByWorktree: {}, - openFiles: [], - tabsByWorktree: {}, - unifiedTabsByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: tabs } - } -} - -const FLOATING_CHAT_TAB = { - id: 'floating-chat-1', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - groupId: 'floating-group', - contentType: 'agent-session', - entityId: 'session-1', - label: 'Chat', - customLabel: null, - color: null, - sortOrder: 0, - createdAt: 0 -} satisfies Tab - -it('counts tabs in every floating split group when deciding whether a close emptied it', () => { - expect( - countVisibleFloatingWorkspaceItems( - floatingPanelState({ - tabs: [FLOATING_CHAT_TAB, { ...FLOATING_CHAT_TAB, id: 'chat-2', groupId: 'other-group' }] - }) - ) - ).toBe(2) -}) - -describe('isFloatingWorkspacePanelFocused', () => { - it('detects focus inside the floating workspace panel', () => { - installFakeHTMLElement() - const activeElement = makeElement({ - closestSelectors: ['[data-floating-terminal-panel]'] - }) - - expect(isFloatingWorkspacePanelFocused({ activeElement } as never)).toBe(true) - expect(activeElement.closest).toHaveBeenCalledWith('[data-floating-terminal-panel]') - }) - - it('ignores focus outside the floating workspace panel', () => { - installFakeHTMLElement() - const activeElement = makeElement({}) - - expect(isFloatingWorkspacePanelFocused({ activeElement } as never)).toBe(false) - }) -}) - -describe('isFloatingWorkspaceTerminalInputTarget', () => { - it('detects the xterm helper textarea inside the floating panel', () => { - installFakeHTMLElement() - const target = makeElement({ - classNames: ['xterm-helper-textarea'], - closestSelectors: ['[data-floating-terminal-panel]'] - }) - - expect(isFloatingWorkspaceTerminalInputTarget(target)).toBe(true) - }) - - it('detects targets inside xterm DOM inside the floating panel', () => { - installFakeHTMLElement() - const target = makeElement({ - closestSelectors: ['[data-floating-terminal-panel]', '.xterm'] - }) - - expect(isFloatingWorkspaceTerminalInputTarget(target)).toBe(true) - }) - - it('ignores terminal input outside the floating panel', () => { - installFakeHTMLElement() - const target = makeElement({ - classNames: ['xterm-helper-textarea'] - }) - - expect(isFloatingWorkspaceTerminalInputTarget(target)).toBe(false) - }) - - it('ignores non-terminal targets inside the floating panel', () => { - installFakeHTMLElement() - const target = makeElement({ - closestSelectors: ['[data-floating-terminal-panel]'] - }) - - expect(isFloatingWorkspaceTerminalInputTarget(target)).toBe(false) - }) -}) - -describe('isFloatingWorkspacePanelShortcut', () => { - beforeEach(() => { - installFakeHTMLElement() - }) - - it.each([ - ['Cmd+T', true, { key: 't', metaKey: true }], - ['Ctrl+T', false, { key: 't', ctrlKey: true }], - ['Cmd+W', true, { key: 'w', metaKey: true }], - ['Ctrl+W', false, { key: 'w', ctrlKey: true }], - ['Cmd+Shift+B', true, { key: 'b', metaKey: true, shiftKey: true }], - ['Ctrl+Shift+B', false, { key: 'b', ctrlKey: true, shiftKey: true }], - ['Cmd+Shift+M', true, { key: 'm', metaKey: true, shiftKey: true }], - ['Ctrl+Shift+M', false, { key: 'm', ctrlKey: true, shiftKey: true }], - ['Cmd+Shift+O', true, { key: 'o', metaKey: true, shiftKey: true }], - ['Ctrl+Shift+O', false, { key: 'o', ctrlKey: true, shiftKey: true }] - ])('claims %s', (_label, isMacPlatform, overrides) => { - expect(isFloatingWorkspacePanelShortcut(shortcutSurfaceEvent(overrides), isMacPlatform)).toBe( - true - ) - }) - - it.each([ - ['Cmd+B', true, { key: 'b', metaKey: true }], - ['Ctrl+B', false, { key: 'b', ctrlKey: true }] - ])('does not claim bare %s', (_label, isMacPlatform, overrides) => { - expect(isFloatingWorkspacePanelShortcut(shortcutSurfaceEvent(overrides), isMacPlatform)).toBe( - false - ) - }) - - it('claims shortcuts by produced logical key rather than physical key', () => { - expect( - isFloatingWorkspacePanelShortcut( - shortcutSurfaceEvent({ key: 'w', code: 'Comma', metaKey: true }), - 'darwin' - ) - ).toBe(true) - expect( - isFloatingWorkspacePanelShortcut( - shortcutSurfaceEvent({ key: ',', code: 'KeyW', metaKey: true }), - 'darwin' - ) - ).toBe(false) - }) - - it('honors customized tab shortcuts for the floating panel surface', () => { - expect( - isFloatingWorkspacePanelShortcut( - shortcutSurfaceEvent({ key: 'n', code: 'KeyN', ctrlKey: true }), - 'linux', - null, - { 'tab.newTerminal': ['Ctrl+N'] } - ) - ).toBe(true) - expect( - isFloatingWorkspacePanelShortcut( - shortcutSurfaceEvent({ key: 't', code: 'KeyT', ctrlKey: true }), - 'linux', - null, - { 'tab.newTerminal': ['Ctrl+N'] } - ) - ).toBe(false) - }) - - it('claims customized double-tap shortcuts for the floating panel surface', () => { - const event = shortcutSurfaceEvent({}) as KeyboardEvent & { doubleTapModifier: 'Shift' } - event.doubleTapModifier = 'Shift' - - expect( - isFloatingWorkspacePanelShortcut(event, 'linux', null, { - 'tab.newTerminal': ['DoubleTap+Shift'] - }) - ).toBe(true) - }) - - it('does not claim shortcuts with Alt or the wrong platform modifier', () => { - expect( - isFloatingWorkspacePanelShortcut(shortcutSurfaceEvent({ key: 't', metaKey: true }), false) - ).toBe(false) - expect( - isFloatingWorkspacePanelShortcut(shortcutSurfaceEvent({ key: 't', ctrlKey: true }), true) - ).toBe(false) - expect( - isFloatingWorkspacePanelShortcut( - shortcutSurfaceEvent({ key: 't', ctrlKey: true, altKey: true }), - false - ) - ).toBe(false) - }) - - it('only claims shortcuts from the panel root or shortcut surface', () => { - const panelRoot = makeElement({ - attributes: ['data-floating-terminal-panel'], - closestSelectors: ['[data-floating-terminal-panel]'] - }) - const panelContent = makeElement({ - closestSelectors: ['[data-floating-terminal-panel]'] - }) - - expect(isFloatingWorkspacePanelShortcutTarget(panelRoot, panelRoot)).toBe(true) - expect( - isFloatingWorkspacePanelShortcut( - shortcutEvent({ key: 't', ctrlKey: true, target: panelRoot }), - false - ) - ).toBe(true) - expect( - isFloatingWorkspacePanelShortcut( - shortcutEvent({ key: 't', ctrlKey: true, target: panelContent }), - false, - panelRoot - ) - ).toBe(false) - }) -}) - -describe('matchFloatingWorkspacePanelOwnedAction', () => { - beforeEach(() => { - installFakeHTMLElement() - }) - - it('returns the matched action so one pass serves the claim check and the dispatch branch', () => { - expect( - matchFloatingWorkspacePanelOwnedAction( - shortcutEvent({ key: 't', code: 'KeyT', ctrlKey: true, target: makeElement({}) }), - 'linux', - undefined, - { context: 'app' } - ) - ).toBe('tab.newTerminal') - expect( - matchFloatingWorkspacePanelOwnedAction( - shortcutEvent({ key: 'w', code: 'KeyW', ctrlKey: true, target: makeElement({}) }), - 'linux', - undefined, - { context: 'app' } - ) - ).toBe('tab.close') - }) - - it('does not gate on the event target — the panel owns these chords from any pane it hosts', () => { - expect( - matchFloatingWorkspacePanelOwnedAction( - shortcutEvent({ key: 'b', code: 'KeyB', target: makeElement({}) }), - 'linux', - undefined, - { context: 'app' } - ) - ).toBeNull() - }) -}) - -describe('matchFloatingWorkspacePanelChord', () => { - beforeEach(() => { - installFakeHTMLElement() - }) - - it('claims a creation chord from the shortcut surface', () => { - expect( - matchFloatingWorkspacePanelChord( - shortcutSurfaceEvent({ key: 't', code: 'KeyT', ctrlKey: true }), - 'linux', - null, - undefined, - { context: 'app' } - ) - ).toEqual({ kind: 'action', action: 'tab.newTerminal' }) - }) - - it('keeps creation chords target-gated', () => { - expect( - matchFloatingWorkspacePanelChord( - shortcutEvent({ key: 't', code: 'KeyT', ctrlKey: true, target: makeElement({}) }), - 'linux', - null, - undefined, - { context: 'app' } - ) - ).toBeNull() - }) - - it('claims indexed switching and rename regardless of the event target', () => { - expect( - matchFloatingWorkspacePanelChord( - shortcutEvent({ key: '2', code: 'Digit2', ctrlKey: true, target: makeElement({}) }), - 'linux', - null, - undefined, - { context: 'app' } - ) - ).toEqual({ kind: 'index', index: 1 }) - expect( - matchFloatingWorkspacePanelChord( - shortcutEvent({ key: 'r', code: 'KeyR', metaKey: true, target: makeElement({}) }), - 'darwin', - null, - undefined, - { context: 'app' } - ) - ).toEqual({ kind: 'action', action: 'tab.rename' }) - }) - - it('does not claim chords the panel has no shortcut for', () => { - expect( - matchFloatingWorkspacePanelChord( - shortcutSurfaceEvent({ key: 'b', code: 'KeyB', ctrlKey: true }), - 'linux', - null, - undefined, - { context: 'app' } - ) - ).toBeNull() - }) -}) - -describe('createFloatingWorkspaceTerminalTab', () => { - beforeEach(() => { - activateWebRuntimeSessionTabMock.mockReset() - createWebRuntimeSessionTerminalMock.mockReset() - focusTerminalTabSurfaceMock.mockReset() - isWebRuntimeSessionActiveMock.mockReset() - }) - - it('creates and focuses a local floating workspace terminal in the active floating group', async () => { - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - settings: { activeRuntimeEnvironmentId: null }, - createTab: vi.fn(), - activateTab: vi.fn() - } - createWebRuntimeSessionTerminalMock.mockResolvedValue(false) - - await createFloatingWorkspaceTerminalTab(store) - - expect(createWebRuntimeSessionTerminalMock).not.toHaveBeenCalled() - expect(store.createTab).toHaveBeenCalledWith( - FLOATING_TERMINAL_WORKTREE_ID, - 'floating-group', - undefined, - { id: expect.any(String) } - ) - expect(store.activateTab).not.toHaveBeenCalled() - expect(focusTerminalTabSurfaceMock).toHaveBeenCalledWith(store.createTab.mock.calls[0][3].id) - }) - - it('ignores the active runtime and keeps floating workspace terminals local', async () => { - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - settings: { activeRuntimeEnvironmentId: 'env-1' }, - createTab: vi.fn(), - activateTab: vi.fn() - } - createWebRuntimeSessionTerminalMock.mockResolvedValue(true) - - await createFloatingWorkspaceTerminalTab(store, 'pwsh') - - expect(createWebRuntimeSessionTerminalMock).not.toHaveBeenCalled() - expect(store.createTab).toHaveBeenCalledWith( - FLOATING_TERMINAL_WORKTREE_ID, - 'floating-group', - 'pwsh', - { id: expect.any(String) } - ) - expect(store.activateTab).not.toHaveBeenCalled() - expect(focusTerminalTabSurfaceMock).toHaveBeenCalledWith(store.createTab.mock.calls[0][3].id) - }) -}) - -describe('createFloatingWorkspaceBrowserTab', () => { - beforeEach(() => { - createWebRuntimeSessionBrowserTabMock.mockReset() - }) - - it('creates floating browser tabs in the active floating group', async () => { - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - browserDefaultUrl: 'about:blank', - settings: { activeRuntimeEnvironmentId: null }, - createBrowserTab: vi.fn() - } - createWebRuntimeSessionBrowserTabMock.mockResolvedValue(false) - - await createFloatingWorkspaceBrowserTab(store) - - expect(createWebRuntimeSessionBrowserTabMock).not.toHaveBeenCalled() - expect(store.createBrowserTab).toHaveBeenCalledWith( - FLOATING_TERMINAL_WORKTREE_ID, - 'about:blank', - { - title: 'New Browser Tab', - focusAddressBar: true, - targetGroupId: 'floating-group', - browserRuntimeEnvironmentId: null - } - ) - }) -}) - -describe('createFloatingWorkspaceMarkdownTab', () => { - beforeEach(() => { - createUntitledMarkdownFileWithTemplateSelectionMock.mockReset() - }) - - it('creates floating markdown tabs without activating the main workspace', async () => { - const fileInfo = { - filePath: '/tmp/orca/floating-workspace/untitled.md', - relativePath: 'untitled.md', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - language: 'markdown', - isUntitled: true, - mode: 'edit' - } - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - openFile: vi.fn() - } - createUntitledMarkdownFileWithTemplateSelectionMock.mockResolvedValue(fileInfo) - - await createFloatingWorkspaceMarkdownTab(store as never, '/tmp/orca/floating-workspace') - - expect(createUntitledMarkdownFileWithTemplateSelectionMock).toHaveBeenCalledWith( - '/tmp/orca/floating-workspace', - FLOATING_TERMINAL_WORKTREE_ID, - undefined, - { kind: 'local' } - ) - expect(store.openFile).toHaveBeenCalledWith(fileInfo, { - preview: false, - targetGroupId: 'floating-group', - suppressActiveRuntimeFallback: true - }) - }) - - it('does not open a floating markdown tab when template selection is cancelled', async () => { - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - openFile: vi.fn() - } - createUntitledMarkdownFileWithTemplateSelectionMock.mockResolvedValue(null) - - await createFloatingWorkspaceMarkdownTab(store as never, '/tmp/orca/floating-workspace') - - expect(createUntitledMarkdownFileWithTemplateSelectionMock).toHaveBeenCalledWith( - '/tmp/orca/floating-workspace', - FLOATING_TERMINAL_WORKTREE_ID, - undefined, - { kind: 'local' } - ) - expect(store.openFile).not.toHaveBeenCalled() - }) -}) - -describe('switchFloatingWorkspaceTab', () => { - beforeEach(() => { - activateWebRuntimeSessionTabMock.mockReset() - focusTerminalTabSurfaceMock.mockReset() - isWebRuntimeSessionActiveMock.mockReset() - isWebRuntimeSessionActiveMock.mockReturnValue(false) - }) - - it('cycles terminal tabs inside the floating workspace active group', () => { - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - activateTab: vi.fn(), - browserPagesByWorkspace: {}, - browserTabsByWorktree: {}, - groupsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - { - id: 'floating-group', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - activeTabId: 'tab-1', - tabOrder: ['tab-1', 'tab-2'], - recentTabIds: ['tab-1'] - } - ] - }, - openFiles: [], - setActiveTab: vi.fn(), - tabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [makeTab('tab-1'), makeTab('tab-2')] - }, - unifiedTabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - makeUnifiedTerminalTab('tab-1'), - makeUnifiedTerminalTab('tab-2') - ] - } - } - - expect(switchFloatingWorkspaceTab(store as never, 1, 'same-type')).toBe(true) - - expect(store.activateTab).toHaveBeenCalledWith('tab-2') - expect(store.setActiveTab).toHaveBeenCalledWith('tab-2') - expect(focusTerminalTabSurfaceMock).toHaveBeenCalledWith('tab-2') - expect(activateWebRuntimeSessionTabMock).not.toHaveBeenCalled() - }) - - it('cycles browser tabs locally while a web runtime is active', () => { - const notifyActiveTabChanged = vi.fn() - vi.stubGlobal('window', { api: { browser: { notifyActiveTabChanged } } }) - isWebRuntimeSessionActiveMock.mockReturnValue(true) - const browserTab = { - id: 'browser-2', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - url: 'https://example.com', - title: 'Browser', - loading: false, - faviconUrl: null, - canGoBack: false, - canGoForward: false, - loadError: null, - createdAt: 0, - activePageId: 'page-2' - } - const store = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - activateTab: vi.fn(), - browserPagesByWorkspace: {}, - browserTabsByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: [browserTab] }, - groupsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - { - id: 'floating-group', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - activeTabId: 'tab-1', - tabOrder: ['tab-1', 'tab-browser-2'], - recentTabIds: ['tab-1'] - } - ] - }, - openFiles: [], - setActiveTab: vi.fn(), - tabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [makeTab('tab-1')] - }, - unifiedTabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - makeUnifiedTerminalTab('tab-1'), - { - id: 'tab-browser-2', - entityId: 'browser-2', - groupId: 'floating-group', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - contentType: 'browser', - label: 'Browser', - customLabel: null, - color: null, - sortOrder: 1, - createdAt: 1 - } satisfies Tab - ] - } - } - - expect(switchFloatingWorkspaceTab(store as never, 1, 'all-types')).toBe(true) - - expect(store.activateTab).toHaveBeenCalledWith('tab-browser-2') - expect(activateWebRuntimeSessionTabMock).not.toHaveBeenCalled() - expect(notifyActiveTabChanged).toHaveBeenCalledWith({ browserPageId: 'page-2' }) - }) -}) - -describe('switchFloatingWorkspaceRecentTab', () => { - it('switches to the previous recent tab in the floating workspace', () => { - const activateTab = vi.fn() - const setActiveTab = vi.fn() - const store: Parameters[0] = { - activeGroupIdByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: 'floating-group' }, - activateTab, - browserTabsByWorktree: {}, - groupsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - { - id: 'floating-group', - worktreeId: FLOATING_TERMINAL_WORKTREE_ID, - activeTabId: 'tab-1', - tabOrder: ['tab-1', 'tab-2'], - recentTabIds: ['tab-2', 'tab-1'] - } - ] - }, - openFiles: [], - setActiveTab, - tabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [makeTab('tab-1'), makeTab('tab-2')] - }, - unifiedTabsByWorktree: { - [FLOATING_TERMINAL_WORKTREE_ID]: [ - makeUnifiedTerminalTab('tab-1'), - makeUnifiedTerminalTab('tab-2') - ] - } - } - - expect(switchFloatingWorkspaceRecentTab(store)).toBe(true) - expect(activateTab).toHaveBeenCalledWith('tab-2') - expect(setActiveTab).toHaveBeenCalledWith('tab-2') - }) -}) - -describe('shouldMinimizeFloatingWorkspacePanelOnCloseShortcut', () => { - const base = { - floatingTerminalOpen: true, - floatingVisibleTabCount: 0 - } - - it('allows Cmd/Ctrl+W to minimize any empty floating panel', () => { - expect(shouldMinimizeFloatingWorkspacePanelOnCloseShortcut(base)).toBe(true) - }) - - it('does not minimize when the floating panel is hidden or has tabs', () => { - expect( - shouldMinimizeFloatingWorkspacePanelOnCloseShortcut({ - ...base, - floatingVisibleTabCount: 1 - }) - ).toBe(false) - expect( - shouldMinimizeFloatingWorkspacePanelOnCloseShortcut({ - ...base, - floatingTerminalOpen: false - }) - ).toBe(false) - }) -}) - -describe('handleEmptyFloatingWorkspacePanelCloseShortcut', () => { - it('minimizes the visible empty floating workspace on Cmd/Ctrl+W', () => { - installFakeHTMLElement() - const dispatchEvent = vi.fn() - vi.stubGlobal('window', { dispatchEvent }) - vi.stubGlobal('document', { querySelector: vi.fn().mockReturnValue({}) }) - const event = { - altKey: false, - code: 'KeyW', - ctrlKey: false, - key: 'w', - metaKey: true, - preventDefault: vi.fn(), - repeat: false, - shiftKey: false, - stopImmediatePropagation: vi.fn(), - stopPropagation: vi.fn() - } as unknown as KeyboardEvent - - expect( - handleEmptyFloatingWorkspacePanelCloseShortcut(floatingPanelState(), event, 'darwin') - ).toBe(true) - - expect(event.preventDefault).toHaveBeenCalledWith() - expect(event.stopPropagation).toHaveBeenCalledWith() - expect(event.stopImmediatePropagation).toHaveBeenCalledWith() - expect(dispatchEvent).toHaveBeenCalledWith(expect.any(Event)) - const dispatchedEvent = dispatchEvent.mock.calls[0][0] as Event - expect(dispatchedEvent.type).toBe('orca-toggle-floating-terminal') - }) - - it('ignores non-close shortcuts and non-empty floating workspaces', () => { - vi.stubGlobal('window', { dispatchEvent: vi.fn() }) - vi.stubGlobal('document', { querySelector: vi.fn().mockReturnValue({}) }) - const nonCloseEvent = { - altKey: false, - code: 'KeyT', - ctrlKey: false, - key: 't', - metaKey: true, - preventDefault: vi.fn(), - repeat: false, - shiftKey: false, - stopImmediatePropagation: vi.fn(), - stopPropagation: vi.fn() - } as unknown as KeyboardEvent - - expect( - handleEmptyFloatingWorkspacePanelCloseShortcut(floatingPanelState(), nonCloseEvent, 'darwin') - ).toBe(false) - expect(nonCloseEvent.preventDefault).not.toHaveBeenCalled() - - const event = { - altKey: false, - code: 'KeyW', - ctrlKey: false, - key: 'w', - metaKey: true, - preventDefault: vi.fn(), - repeat: false, - shiftKey: false, - stopImmediatePropagation: vi.fn(), - stopPropagation: vi.fn() - } as unknown as KeyboardEvent - - for (const state of [ - floatingPanelState({ tabs: [FLOATING_CHAT_TAB] }), - floatingPanelState({ open: false }), - floatingPanelState({ enabled: false }) - ]) { - expect(handleEmptyFloatingWorkspacePanelCloseShortcut(state, event, 'darwin')).toBe(false) - } - expect(event.preventDefault).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/lib/floating-workspace-tour-interaction-snapshot.test.ts b/src/renderer/src/lib/floating-workspace-tour-interaction-snapshot.test.ts deleted file mode 100644 index 8940319765e..00000000000 --- a/src/renderer/src/lib/floating-workspace-tour-interaction-snapshot.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { createFloatingWorkspaceTourInteractionSnapshot } from './floating-workspace-tour-interaction-snapshot' -import type { FeatureInteractionState } from '../../../shared/feature-interactions' - -describe('createFloatingWorkspaceTourInteractionSnapshot', () => { - it('captures first-open state before recording the floating workspace interaction', () => { - const featureInteractions: FeatureInteractionState = {} - const persisted = Promise.resolve() - const recordFeatureInteraction = vi.fn(() => { - featureInteractions['floating-workspace'] = { - firstInteractedAt: 100, - interactionCount: 1 - } - return persisted - }) - - const snapshot = createFloatingWorkspaceTourInteractionSnapshot({ - featureInteractions, - persistedUIReady: true, - recordFeatureInteraction - }) - - expect(recordFeatureInteraction).toHaveBeenCalledOnce() - expect(recordFeatureInteraction).toHaveBeenCalledWith('floating-workspace') - expect(snapshot.wasPreviouslyInteracted).toBe(false) - expect(snapshot.persisted).toBe(persisted) - expect(snapshot.recordFeatureInteractionForTour).toBe(false) - }) -}) diff --git a/src/renderer/src/lib/folder-workspace-path-status.test.ts b/src/renderer/src/lib/folder-workspace-path-status.test.ts deleted file mode 100644 index e92d7fc2d1a..00000000000 --- a/src/renderer/src/lib/folder-workspace-path-status.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { i18n } from '@/i18n/i18n' -import type { FolderWorkspacePathStatus } from '../../../shared/folder-workspace-path-status' -import { - formatFolderWorkspaceCreateError, - humanizeFolderWorkspacePathError, - getFolderWorkspacePathErrorCopy, - getFolderWorkspacePathStatusDescription, - getFolderWorkspacePathStatusTitle -} from './folder-workspace-path-status' - -// RPC returns z.unknown and casts it; tests must cover newer-host wire values outside this build’s union. -function wireStatus(reason: unknown): FolderWorkspacePathStatus { - return { path: '/srv/scans', exists: false, reason } as unknown as FolderWorkspacePathStatus -} - -describe('getFolderWorkspacePathStatusTitle', () => { - it('still titles a broken folder when a newer host sends an undeclared reason', () => { - const title = getFolderWorkspacePathStatusTitle(wireStatus('permission-denied')) - - expect(typeof title).toBe('string') - expect(title).not.toBe('') - }) - - // hasOwn coerces [missing] into a valid key; reject arrays before the switch (#15002). - it('does not admit a non-string reason through the membership guard', () => { - const title = getFolderWorkspacePathStatusTitle(wireStatus(['missing'])) - - expect(typeof title).toBe('string') - expect(title).not.toBe('') - expect(title).not.toBe('Folder not found') - }) -}) - -describe('getFolderWorkspacePathStatusDescription', () => { - it('still describes a broken folder when a newer host sends an undeclared reason', () => { - const description = getFolderWorkspacePathStatusDescription(wireStatus('permission-denied')) - - expect(typeof description).toBe('string') - expect(description).not.toBe('') - expect(description).toContain('/srv/scans') - }) - - it('does not admit a non-string reason through the membership guard', () => { - const description = getFolderWorkspacePathStatusDescription(wireStatus(['missing'])) - - expect(typeof description).toBe('string') - expect(description).not.toBe('') - expect(description).not.toBe( - 'Orca cannot find /srv/scans. Remove and re-import this folder workspace.' - ) - }) -}) - -describe('getFolderWorkspacePathErrorCopy', () => { - it('maps the ambiguous-connection code the runtime throws without a path', () => { - expect( - getFolderWorkspacePathErrorCopy( - "Error invoking remote method 'pty:spawn': Error: folder_workspace_connection_ambiguous" - )?.title - ).toBe('Cannot determine connection') - }) -}) - -it.each(['/srv/project: folder', 'C:\\work\\My Project:backup', '/tmp/$& folder'])( - 'preserves a literal path %s and IPC wrapper', - (path) => { - const prefix = "Error invoking remote method 'pty:spawn': Error: " - expect(humanizeFolderWorkspacePathError(`${prefix}folder_workspace_path_missing:${path}`)).toBe( - `${prefix}Orca cannot find ${path}. Remove and re-import the folder.` - ) - } -) - -it.each([ - 'not_folder_workspace_path_missing:/x', - 'folder_workspace_path_missing_extra:/x', - 'Failed to read /tmp/folder_workspace_path_missing:/x', - 'folder_workspace_path_missing-more', - 'disk full' -])('passes through unknown error %s', (error) => { - expect(humanizeFolderWorkspacePathError(error)).toBe(error) - expect(getFolderWorkspacePathErrorCopy(error)).toBeNull() -}) - -it('preserves multiline paths in the existing create-folder formatter', () => { - const path = '/tmp/folder\nwith\rline breaks' - expect( - formatFolderWorkspaceCreateError(`folder_workspace_path_missing:${path}`).description - ).toBe(`Orca cannot find ${path}. Remove and re-import the folder.`) -}) - -it('uses the existing English fallback when a language pack has no folder-path translation', async () => { - await i18n.changeLanguage('test-missing-folder-copy') - try { - expect(humanizeFolderWorkspacePathError('folder_workspace_path_missing:/tmp/project')).toBe( - 'Orca cannot find /tmp/project. Remove and re-import the folder.' - ) - } finally { - await i18n.changeLanguage('en') - } -}) diff --git a/src/renderer/src/lib/foreground-terminal-tabs.test.ts b/src/renderer/src/lib/foreground-terminal-tabs.test.ts index 5b829fdf15a..b8e000d8e21 100644 --- a/src/renderer/src/lib/foreground-terminal-tabs.test.ts +++ b/src/renderer/src/lib/foreground-terminal-tabs.test.ts @@ -13,16 +13,6 @@ afterEach(() => { }) describe('foreground terminal tabs', () => { - it('returns the union of explicit foreground ids and visible terminal claims', () => { - setForegroundTerminalTabIds(['tab-explicit', null, '', undefined]) - const unregister = registerVisibleTerminalTab('tab-visible') - - expect(getForegroundTerminalTabIds().sort()).toEqual(['tab-explicit', 'tab-visible']) - - unregister() - expect(getForegroundTerminalTabIds()).toEqual(['tab-explicit']) - }) - it('keeps duplicate visible terminal tab claims until every token unregisters', () => { const unregisterFirst = registerVisibleTerminalTab('tab-visible') const unregisterSecond = registerVisibleTerminalTab('tab-visible') @@ -36,18 +26,6 @@ describe('foreground terminal tabs', () => { expect(getForegroundTerminalTabIds()).toEqual([]) }) - it('records last-seen timestamps for explicit foreground entries and clears them in tests', () => { - vi.useFakeTimers() - vi.setSystemTime(1_000) - - setForegroundTerminalTabIds(['tab-explicit', null, '', undefined]) - - expect(getForegroundTerminalTabLastSeenAtById()).toEqual({ 'tab-explicit': 1_000 }) - - resetForegroundTerminalTabIdsForTests() - expect(getForegroundTerminalTabLastSeenAtById()).toEqual({}) - }) - it('refreshes last-seen when explicit foreground ids leave the combined foreground set', () => { vi.useFakeTimers() vi.setSystemTime(1_000) diff --git a/src/renderer/src/lib/frame-drawn-chat-tab-placement.test.ts b/src/renderer/src/lib/frame-drawn-chat-tab-placement.test.ts deleted file mode 100644 index bc8d080f28d..00000000000 --- a/src/renderer/src/lib/frame-drawn-chat-tab-placement.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const mocks = vi.hoisted(() => { - const groupsByWorktree: Record = {} - const noted: string[][] = [] - const flags: { frameDrawn: boolean; routed: boolean; supported: boolean | null } = { - frameDrawn: true, - routed: true, - supported: true - } - return { - state: { groupsByWorktree }, - ...flags, - /** Tabs the server's frames hold, by desktop id → server id. */ - serverIds: new Map(), - noted - } -}) - -vi.mock('@/store', () => ({ useAppStore: { getState: () => mocks.state } })) -vi.mock('@/runtime/runtime-rpc-client', () => ({ - readCachedRuntimeEnvironmentCapability: () => mocks.supported -})) -vi.mock('@/runtime/web-session-tabs-sync/frame-drawn-server-workspaces', () => ({ - isFrameDrawnServerWorkspace: () => mocks.frameDrawn -})) -vi.mock('@/runtime/server-layout-apply-routes', () => ({ - serverLayoutApplyRoute: () => - mocks.routed ? { serverTabId: (id: string) => mocks.serverIds.get(id) ?? null } : null -})) -vi.mock('./desktop-created-server-tabs', () => ({ - noteDesktopCreatedServerTab: (...args: string[]) => mocks.noted.push(args) -})) - -import type { StructuredAgentSessionCreateParams } from '../../../shared/agent-session/structured-agent-session-create' -import { withFrameDrawnChatTabPlacement } from './frame-drawn-chat-tab-placement' - -const TAB = 'structured-agent-session-claude_1' -const params: StructuredAgentSessionCreateParams = { - envelope: { - sessionId: 'claude_1', - clientOperationId: 'op-1', - expectedRuntimeFence: null, - payloadFingerprint: 'fp' - }, - worktree: 'id:wt', - agent: 'claude' -} -const intent = { - target: { kind: 'environment', environmentId: 'env-1' } as const, - worktreeId: 'wt', - sessionId: 'claude_1', - params -} - -describe('a chat opened in a frame-drawn server workspace (L270)', () => { - beforeEach(() => { - mocks.frameDrawn = true - mocks.supported = true - mocks.routed = true - mocks.serverIds = new Map([ - ['web-terminal-pty-1', 'host-tab-1'], - ['editor-1', 'editor-1'], - ['editor-2', 'editor-2'] - ]) - mocks.noted = [] - mocks.state.groupsByWorktree = { - wt: [ - { id: 'g-1', tabOrder: ['editor-0'] }, - { id: 'g-2', tabOrder: ['editor-1', 'sim-1', TAB, 'editor-2'] } - ] - } - }) - - it("names the tab's group and the server tab before it, skipping tabs the server lacks", () => { - expect(withFrameDrawnChatTabPlacement(intent)).toEqual({ - ...params, - tabPlacement: { groupId: 'g-2', afterTabId: 'editor-1' } - }) - }) - - it("names the server's id for a terminal tab before the chat", () => { - mocks.state.groupsByWorktree.wt[1].tabOrder = ['web-terminal-pty-1', TAB] - expect(withFrameDrawnChatTabPlacement(intent)).toEqual({ - ...params, - tabPlacement: { groupId: 'g-2', afterTabId: 'host-tab-1' } - }) - }) - - it('names the group alone while the server takes no layout intents for the workspace', () => { - mocks.routed = false - expect(withFrameDrawnChatTabPlacement(intent)).toEqual({ - ...params, - tabPlacement: { groupId: 'g-2' } - }) - }) - - it('names the group alone when no server tab comes before the chat', () => { - mocks.state.groupsByWorktree.wt[1].tabOrder = [TAB, 'editor-2'] - expect(withFrameDrawnChatTabPlacement(intent)).toEqual({ - ...params, - tabPlacement: { groupId: 'g-2' } - }) - }) -}) diff --git a/src/renderer/src/lib/github-links.test.ts b/src/renderer/src/lib/github-links.test.ts index f4492b1cc76..b63067c111d 100644 --- a/src/renderer/src/lib/github-links.test.ts +++ b/src/renderer/src/lib/github-links.test.ts @@ -1,177 +1,8 @@ import { describe, expect, it } from 'vitest' -import { - buildGitHubRepoUrl, - normalizeGitHubLinkQuery, - parseGitHubIssueOrPRLink, - parseGitHubIssueOrPRNumber -} from './github-links' +import { normalizeGitHubLinkQuery } from './github-links' import { WORK_ITEM_LINK_QUERY_MAX_BYTES } from './work-item-link-query-bounds' -describe('buildGitHubRepoUrl', () => { - it('builds a GitHub repository URL from an owner/repo slug', () => { - expect(buildGitHubRepoUrl({ owner: 'stablyai', repo: 'orca' })).toBe( - 'https://github.com/stablyai/orca' - ) - }) - - it('encodes path segments', () => { - expect(buildGitHubRepoUrl({ owner: 'stably ai', repo: 'orca/tools' })).toBe( - 'https://github.com/stably%20ai/orca%2Ftools' - ) - }) - - it('links hosted slugs to their GitHub Enterprise server', () => { - expect(buildGitHubRepoUrl({ owner: 'team', repo: 'orca', host: 'github.acme-corp.com' })).toBe( - 'https://github.acme-corp.com/team/orca' - ) - }) -}) - -describe('parseGitHubIssueOrPRNumber', () => { - it('parses plain issue numbers and GitHub pull request URLs', () => { - expect(parseGitHubIssueOrPRNumber('42')).toBe(42) - expect(parseGitHubIssueOrPRNumber('#42')).toBe(42) - expect(parseGitHubIssueOrPRNumber('https://github.com/stablyai/orca/pull/123')).toBe(123) - expect(parseGitHubIssueOrPRNumber('https://github.com/stablyai/orca/issues/923')).toBe(923) - expect(parseGitHubIssueOrPRNumber('https://github.my-company.net/MyOrg/my_repo/pull/395')).toBe( - 395 - ) - }) - - it('parses GitHub item URLs with trailing page segments', () => { - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/changes')).toBe(1965) - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/files')).toBe(1965) - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/commits')).toBe(1965) - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923/comments')).toBe(923) - }) - - it('parses trailing segments with query, fragment, and repeated slashes', () => { - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/changes?diff=split')).toBe( - 1965 - ) - expect( - parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923/comments#issuecomment-1') - ).toBe(923) - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965//changes///')).toBe(1965) - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923///')).toBe(923) - }) - - it('rejects invalid GitHub item URLs', () => { - expect(parseGitHubIssueOrPRNumber('0')).toBeNull() - expect(parseGitHubIssueOrPRNumber('#0')).toBeNull() - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/0')).toBeNull() - expect( - parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/not-a-number/changes') - ).toBeNull() - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/')).toBeNull() - expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/123abc')).toBeNull() - expect(parseGitHubIssueOrPRNumber('https://github.com/owner/repo/pulls/123')).toBeNull() - }) -}) - -describe('parseGitHubIssueOrPRLink', () => { - it('parses slug, number, and type for direct item URLs', () => { - expect(parseGitHubIssueOrPRLink('https://github.com/stablyai/orca/pull/123')).toEqual({ - slug: { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - number: 123, - type: 'pr' - }) - - expect( - parseGitHubIssueOrPRLink('https://github.my-company.net/MyOrg/my_repo/pull/395') - ).toEqual({ - slug: { owner: 'MyOrg', repo: 'my_repo', host: 'github.my-company.net' }, - number: 395, - type: 'pr' - }) - - expect(parseGitHubIssueOrPRLink('https://git.corp.com/MyOrg/my_repo/pull/395')).toEqual({ - slug: { owner: 'MyOrg', repo: 'my_repo', host: 'git.corp.com' }, - number: 395, - type: 'pr' - }) - expect(parseGitHubIssueOrPRLink('https://github.com/stablyai/orca/issues/923')).toEqual({ - slug: { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - number: 923, - type: 'issue' - }) - }) - - it('derives item type from the route segment when trailing segments are present', () => { - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/1965/changes')).toEqual({ - slug: { owner: 'o', repo: 'r', host: 'github.com' }, - number: 1965, - type: 'pr' - }) - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/923/comments')).toEqual({ - slug: { owner: 'o', repo: 'r', host: 'github.com' }, - number: 923, - type: 'issue' - }) - }) - - it('accepts query, fragment, and repeated trailing slashes', () => { - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/1965/files?plain=1#diff')).toEqual( - { - slug: { owner: 'o', repo: 'r', host: 'github.com' }, - number: 1965, - type: 'pr' - } - ) - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/923/comments///')).toEqual({ - slug: { owner: 'o', repo: 'r', host: 'github.com' }, - number: 923, - type: 'issue' - }) - }) - - it('rejects non-GitHub and malformed item URLs', () => { - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/0')).toBeNull() - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/0')).toBeNull() - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/not-a-number/changes')).toBeNull() - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/')).toBeNull() - expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/123abc')).toBeNull() - expect(parseGitHubIssueOrPRLink('https://github.com/owner/repo/pulls/123')).toBeNull() - }) -}) - describe('normalizeGitHubLinkQuery', () => { - it('accepts full GitHub URLs whose slug differs from the selected repo slug', () => { - expect(normalizeGitHubLinkQuery('https://github.com/stablyai/orca/issues/923')).toEqual({ - query: 'https://github.com/stablyai/orca/issues/923', - directNumber: 923, - directLink: { - slug: { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - number: 923, - type: 'issue' - } - }) - }) - - it('preserves PR route intent for full GitHub URLs', () => { - expect(normalizeGitHubLinkQuery('https://github.com/stablyai/orca/pull/6934')).toEqual({ - query: 'https://github.com/stablyai/orca/pull/6934', - directNumber: 6934, - directLink: { - slug: { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - number: 6934, - type: 'pr' - } - }) - }) - - it('preserves route intent for URLs with uppercase schemes', () => { - expect(normalizeGitHubLinkQuery('HTTPS://github.com/stablyai/orca/pull/6934')).toEqual({ - query: 'HTTPS://github.com/stablyai/orca/pull/6934', - directNumber: 6934, - directLink: { - slug: { owner: 'stablyai', repo: 'orca', host: 'github.com' }, - number: 6934, - type: 'pr' - } - }) - }) - it('rejects oversized pasted link queries without echoing their content', () => { const secret = 'github-link-secret' const result = normalizeGitHubLinkQuery(secret + 'x'.repeat(WORK_ITEM_LINK_QUERY_MAX_BYTES)) diff --git a/src/renderer/src/lib/github-pr-start-point.test.ts b/src/renderer/src/lib/github-pr-start-point.test.ts deleted file mode 100644 index f110eb79585..00000000000 --- a/src/renderer/src/lib/github-pr-start-point.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { callRuntimeRpc } from '@/runtime/runtime-rpc-client' -import type * as RuntimeRpcClient from '@/runtime/runtime-rpc-client' -import { resolveGitHubPrStartPointForRepo } from './github-pr-start-point' - -vi.mock('@/runtime/runtime-rpc-client', async () => { - const actual = await vi.importActual('@/runtime/runtime-rpc-client') - return { - ...actual, - callRuntimeRpc: vi.fn() - } -}) - -describe('resolveGitHubPrStartPointForRepo', () => { - beforeEach(() => { - vi.mocked(callRuntimeRpc).mockReset() - vi.stubGlobal('window', { - api: { - worktrees: { - resolvePrBase: vi.fn() - } - } - }) - }) - - it('throws resolver errors so submit can abort before creating from the wrong base', async () => { - vi.mocked(window.api.worktrees.resolvePrBase).mockResolvedValue({ - error: 'Could not resolve PR head.' - }) - - await expect( - resolveGitHubPrStartPointForRepo({ - repoId: 'repo-1', - prNumber: 99, - target: { kind: 'local' } - }) - ).rejects.toThrow('Could not resolve PR head.') - }) - - it('marks creating from a PR as a user act, locally and on a paired host (E9)', async () => { - const startPoint = { baseBranch: 'origin/main' } - vi.mocked(window.api.worktrees.resolvePrBase).mockResolvedValue(startPoint) - vi.mocked(callRuntimeRpc).mockResolvedValue(startPoint) - - await resolveGitHubPrStartPointForRepo({ - repoId: 'repo-1', - prNumber: 7, - target: { kind: 'local' } - }) - await resolveGitHubPrStartPointForRepo({ - repoId: 'repo-1', - prNumber: 7, - target: { kind: 'environment', environmentId: 'env-1' } - }) - - expect(window.api.worktrees.resolvePrBase).toHaveBeenCalledWith( - expect.objectContaining({ wakeWslDistro: true }) - ) - expect(callRuntimeRpc).toHaveBeenCalledWith( - expect.anything(), - 'worktree.resolvePrBase', - expect.objectContaining({ wakeWslDistro: true }), - expect.anything() - ) - }) -}) diff --git a/src/renderer/src/lib/github-source-runtime-context.test.ts b/src/renderer/src/lib/github-source-runtime-context.test.ts index a6036698d4e..f1b9e8fe353 100644 --- a/src/renderer/src/lib/github-source-runtime-context.test.ts +++ b/src/renderer/src/lib/github-source-runtime-context.test.ts @@ -37,13 +37,6 @@ describe('getGitHubRepoRoutingTarget', () => { ).toEqual({ kind: 'environment', environmentId: 'owner-runtime' }) }) - it('routes a runtime-owned repo to its owner runtime when there is no source context', () => { - expect(resolveTarget(runtimeOwnedRepo, 'repo-1', null)).toEqual({ - kind: 'environment', - environmentId: 'owner-runtime' - }) - }) - it('lets a runtime source override the repo owner when both name runtimes', () => { expect( resolveTarget(runtimeOwnedRepo, 'repo-1', { @@ -122,16 +115,6 @@ describe('getGitHubRepoRoutingTarget', () => { }) }) - it('ignores runtime hosts on non-GitHub sources', () => { - expect( - resolveTarget(runtimeOwnedRepo, 'repo-1', { - ...runtimeSourceContext, - provider: 'gitlab', - hostId: 'runtime:source-runtime' - }) - ).toEqual({ kind: 'environment', environmentId: 'owner-runtime' }) - }) - it('never routes a repo without an explicit owner to the globally focused runtime', () => { const noOwnerRepo: RepoRuntimeOwnerState = { settings: { activeRuntimeEnvironmentId: 'focused-runtime' }, diff --git a/src/renderer/src/lib/github-work-item-source-lookup.test.ts b/src/renderer/src/lib/github-work-item-source-lookup.test.ts deleted file mode 100644 index c5b75394356..00000000000 --- a/src/renderer/src/lib/github-work-item-source-lookup.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { callRuntimeRpc } from '@/runtime/runtime-rpc-client' -import type * as RuntimeRpcClient from '@/runtime/runtime-rpc-client' -import { - lookupGitHubWorkItemDetailsForSource, - lookupGitHubWorkItemByOwnerRepoForSource -} from './github-work-item-source-lookup' -import type { TaskSourceContext } from '../../../shared/task-source-context' - -vi.mock('@/runtime/runtime-rpc-client', async () => { - const actual = await vi.importActual('@/runtime/runtime-rpc-client') - return { - ...actual, - callRuntimeRpc: vi.fn(), - runtimeEnvironmentSupportsCapability: vi.fn().mockResolvedValue(false) - } -}) - -const runtimeSourceContext: TaskSourceContext = { - kind: 'task-source', - provider: 'github', - projectId: 'project-1', - hostId: 'runtime:env-1', - repoId: 'runtime-repo' -} - -describe('GitHub source lookup routing', () => { - beforeEach(() => { - vi.mocked(callRuntimeRpc).mockReset() - vi.stubGlobal('window', { - api: { - gh: { - workItemDetails: vi.fn(), - workItemByOwnerRepo: vi.fn().mockResolvedValue(null) - } - } - }) - }) - - it.each(['local', 'ssh:build-box'] as const)( - 'forwards %s source identity to exact repository IPC', - async (hostId) => { - const sourceContext: TaskSourceContext = { ...runtimeSourceContext, hostId, repoId: 'repo' } - await lookupGitHubWorkItemByOwnerRepoForSource({ - repoPath: '/repo', - repoId: 'repo', - sourceContext, - owner: 'acme', - repo: 'orca', - host: 'github.com', - number: 42, - type: 'pr' - }) - expect(window.api.gh.workItemByOwnerRepo).toHaveBeenCalledWith({ - repoPath: '/repo', - repoId: 'repo', - sourceContext, - owner: 'acme', - repo: 'orca', - host: 'github.com', - number: 42, - type: 'pr' - }) - expect(callRuntimeRpc).not.toHaveBeenCalled() - } - ) - - it('routes exact repository lookup to its source runtime instead of local IPC', async () => { - vi.mocked(callRuntimeRpc).mockResolvedValue(null) - await lookupGitHubWorkItemByOwnerRepoForSource({ - repoPath: '/repo', - repoId: 'renderer-repo', - sourceContext: runtimeSourceContext, - owner: 'acme', - repo: 'orca', - host: 'github.com', - number: 42, - type: 'pr' - }) - expect(callRuntimeRpc).toHaveBeenCalledWith( - { kind: 'environment', environmentId: 'env-1' }, - 'github.workItemByOwnerRepo', - { - repo: 'runtime-repo', - owner: 'acme', - ownerRepo: 'orca', - host: 'github.com', - number: 42, - type: 'pr' - }, - expect.objectContaining({ timeoutMs: 30_000 }) - ) - expect(window.api.gh.workItemByOwnerRepo).not.toHaveBeenCalled() - }) - - it('routes runtime-owned GitHub details through runtime RPC', async () => { - vi.mocked(callRuntimeRpc).mockResolvedValue(null) - - await expect( - lookupGitHubWorkItemDetailsForSource({ - repoPath: '/home/runtime/app', - repoId: 'renderer-repo', - sourceContext: runtimeSourceContext, - number: 42, - type: 'issue', - ownerRepo: { owner: 'fork-owner', repo: 'widgets', host: 'github.com' } - }) - ).resolves.toBeNull() - - expect(callRuntimeRpc).toHaveBeenCalledWith( - { kind: 'environment', environmentId: 'env-1' }, - 'github.workItemDetails', - { repo: 'runtime-repo', number: 42, type: 'issue' }, - { timeoutMs: 30_000 } - ) - expect(window.api.gh.workItemDetails).not.toHaveBeenCalled() - }) - - it('uses the renderer repo id for runtime details when the source has no repo id', async () => { - vi.mocked(callRuntimeRpc).mockResolvedValue(null) - - await lookupGitHubWorkItemDetailsForSource({ - repoPath: '/home/runtime/app', - repoId: 'renderer-repo', - sourceContext: { ...runtimeSourceContext, repoId: null }, - number: 42, - type: 'pr' - }) - - expect(callRuntimeRpc).toHaveBeenCalledWith( - { kind: 'environment', environmentId: 'env-1' }, - 'github.workItemDetails', - { repo: 'renderer-repo', number: 42, type: 'pr' }, - { timeoutMs: 30_000 } - ) - }) - - it('keeps local GitHub details on Electron IPC with source context', async () => { - vi.mocked(window.api.gh.workItemDetails).mockResolvedValue(null) - const sourceContext: TaskSourceContext = { - ...runtimeSourceContext, - hostId: 'local', - repoId: 'local-repo' - } - - await expect( - lookupGitHubWorkItemDetailsForSource({ - repoPath: 'C:\\workspace\\app', - repoId: 'local-repo', - sourceContext, - number: 42, - type: 'issue' - }) - ).resolves.toBeNull() - - expect(window.api.gh.workItemDetails).toHaveBeenCalledWith({ - repoPath: 'C:\\workspace\\app', - repoId: 'local-repo', - sourceContext, - number: 42, - type: 'issue' - }) - expect(callRuntimeRpc).not.toHaveBeenCalled() - }) - - it('sends a local issue row repository to the repo-scoped details route', async () => { - const ownerRepo = { owner: 'fork-owner', repo: 'widgets', host: 'github.com' } - vi.mocked(window.api.gh.workItemDetails).mockResolvedValue(null) - - await lookupGitHubWorkItemDetailsForSource({ - repoPath: '/home/fixture/widgets', - repoId: 'local-repo', - number: 12, - type: 'issue', - ownerRepo - }) - - expect(window.api.gh.workItemDetails).toHaveBeenCalledWith( - expect.objectContaining({ repoPath: '/home/fixture/widgets', ownerRepo }) - ) - expect(callRuntimeRpc).not.toHaveBeenCalled() - }) - it('keeps the old runtime details path and refuses only unsupported canonical refresh', async () => { - vi.mocked(callRuntimeRpc).mockResolvedValue(null) - await expect( - lookupGitHubWorkItemDetailsForSource({ - repoPath: '/remote', - repoId: 'renderer', - sourceContext: runtimeSourceContext, - number: 42, - type: 'issue' - }) - ).resolves.toBeNull() - expect(callRuntimeRpc).toHaveBeenCalledTimes(1) - await expect( - lookupGitHubWorkItemDetailsForSource({ - repoPath: '/remote', - repoId: 'renderer', - sourceContext: runtimeSourceContext, - number: 42, - type: 'issue', - canonical: true, - ownerRepo: { owner: 'acme', repo: 'widgets' } - }) - ).resolves.toBeNull() - expect(callRuntimeRpc).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/renderer/src/lib/github-work-item-workspace-attachment.test.ts b/src/renderer/src/lib/github-work-item-workspace-attachment.test.ts deleted file mode 100644 index f01515abafb..00000000000 --- a/src/renderer/src/lib/github-work-item-workspace-attachment.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { - findGithubIssueWorkspaceAttachment, - findGithubPrWorkspaceAttachment -} from './github-work-item-workspace-attachment' -import type { Worktree } from '../../../shared/worktree/types' - -function worktree(overrides: Partial = {}): Worktree { - return { - id: overrides.id ?? 'wt-1', - repoId: overrides.repoId ?? 'repo-1', - path: overrides.path ?? '/tmp/repo-1/wt-1', - head: 'abc123', - branch: overrides.branch ?? 'refs/heads/feature/workspace-attachment', - isBare: false, - isMainWorktree: false, - displayName: overrides.displayName ?? 'GitHub workspace', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - ...overrides - } -} - -describe('GitHub work-item workspace attachment', () => { - it('finds the first non-archived workspace linked to the repo PR', () => { - const first = worktree({ id: 'first', linkedPR: 42 }) - const second = worktree({ id: 'second', linkedPR: 42 }) - - expect(findGithubPrWorkspaceAttachment([first, second], 'repo-1', 42)).toBe(first) - }) - - it('does not match workspaces from a different repo', () => { - const attachedElsewhere = worktree({ repoId: 'repo-2', linkedIssue: 42, linkedPR: 42 }) - - expect(findGithubPrWorkspaceAttachment([attachedElsewhere], 'repo-1', 42)).toBeNull() - expect(findGithubIssueWorkspaceAttachment([attachedElsewhere], 'repo-1', 42)).toBeNull() - }) - - it('does not match archived workspaces', () => { - const archived = worktree({ linkedIssue: 42, linkedPR: 42, isArchived: true }) - - expect(findGithubPrWorkspaceAttachment([archived], 'repo-1', 42)).toBeNull() - expect(findGithubIssueWorkspaceAttachment([archived], 'repo-1', 42)).toBeNull() - }) - - it('does not cross-match PR and issue slots with the same number', () => { - const prOnly = worktree({ id: 'pr', linkedPR: 42 }) - const issueOnly = worktree({ id: 'issue', linkedIssue: 42 }) - - expect(findGithubIssueWorkspaceAttachment([prOnly], 'repo-1', 42)).toBeNull() - expect(findGithubPrWorkspaceAttachment([issueOnly], 'repo-1', 42)).toBeNull() - }) -}) - -describe('multiple GitHub attachments', () => { - it('finds secondary reviews and issues', () => { - const attached = worktree({ - linkedPR: 1, - linkedIssue: 2, - linkedItems: [ - { provider: 'github', type: 'pr', number: 3 }, - { provider: 'github', type: 'issue', number: 4 } - ] - }) - expect(findGithubPrWorkspaceAttachment([attached], attached.repoId, 3)).toBe(attached) - expect(findGithubIssueWorkspaceAttachment([attached], attached.repoId, 4)).toBe(attached) - }) -}) - -describe('GitHub attachment repository scope', () => { - it('matches a foreign task by URL across workspace repositories', () => { - const attached = worktree({ - linkedItems: [ - { - provider: 'github', - type: 'issue', - number: 42, - url: 'https://github.com/foreign/project/issues/42' - } - ] - }) - expect( - findGithubIssueWorkspaceAttachment( - [attached], - 'foreign-repo', - 42, - 'https://github.com/foreign/project/issues/42' - ) - ).toBe(attached) - expect( - findGithubIssueWorkspaceAttachment( - [attached], - 'repo-1', - 42, - 'https://github.com/current/project/issues/42' - ) - ).toBeNull() - }) -}) diff --git a/src/renderer/src/lib/gitlab-links.test.ts b/src/renderer/src/lib/gitlab-links.test.ts index fce99049ff3..9ebdc93430b 100644 --- a/src/renderer/src/lib/gitlab-links.test.ts +++ b/src/renderer/src/lib/gitlab-links.test.ts @@ -1,54 +1,7 @@ import { describe, expect, it } from 'vitest' -import { normalizeGitLabLinkQuery, parseGitLabIssueOrMRLink } from './gitlab-links' +import { normalizeGitLabLinkQuery } from './gitlab-links' import { WORK_ITEM_LINK_QUERY_MAX_BYTES } from './work-item-link-query-bounds' -describe('parseGitLabIssueOrMRLink', () => { - it('preserves explicit ports on self-hosted GitLab hosts', () => { - expect(parseGitLabIssueOrMRLink('https://gitlab.example.com:8443/team/api/-/issues/7')).toEqual( - { - slug: { host: 'gitlab.example.com:8443', path: 'team/api' }, - number: 7, - type: 'issue' - } - ) - }) - - it('treats /-/work_items/ as an issue work item', () => { - expect(parseGitLabIssueOrMRLink('https://gitlab.com/stablyai/orca/-/work_items/923')).toEqual({ - slug: { host: 'gitlab.com', path: 'stablyai/orca' }, - number: 923, - type: 'issue' - }) - expect( - parseGitLabIssueOrMRLink('https://gitlab.example.com:8443/team/api/-/work_items/7') - ).toEqual({ - slug: { host: 'gitlab.example.com:8443', path: 'team/api' }, - number: 7, - type: 'issue' - }) - }) - - it('extracts slug, number, and type from URLs with trailing page segments', () => { - expect(parseGitLabIssueOrMRLink('https://gitlab.com/g/p/-/merge_requests/77/diffs')).toEqual({ - slug: { host: 'gitlab.com', path: 'g/p' }, - number: 77, - type: 'mr' - }) - expect( - parseGitLabIssueOrMRLink('https://gitlab.com/g/p/-/merge_requests/77/diffs.diff?diff_id=1') - ).toEqual({ - slug: { host: 'gitlab.com', path: 'g/p' }, - number: 77, - type: 'mr' - }) - expect(parseGitLabIssueOrMRLink('https://gitlab.com/g/p/-/issues/923/designs')).toEqual({ - slug: { host: 'gitlab.com', path: 'g/p' }, - number: 923, - type: 'issue' - }) - }) -}) - describe('normalizeGitLabLinkQuery', () => { it('rejects oversized pasted link queries without echoing their content', () => { const secret = 'gitlab-link-secret' diff --git a/src/renderer/src/lib/host-mirror-handle-gap-landed-handle.test.ts b/src/renderer/src/lib/host-mirror-handle-gap-landed-handle.test.ts index 4df8a72627f..b93d646fdb7 100644 --- a/src/renderer/src/lib/host-mirror-handle-gap-landed-handle.test.ts +++ b/src/renderer/src/lib/host-mirror-handle-gap-landed-handle.test.ts @@ -81,31 +81,4 @@ describe('handle-gap verdict, landed-handle eviction', () => { vi.advanceTimersByTime(HOST_MIRROR_HANDLE_GAP_DEADLINE_MS + 1) expect(replay).toHaveBeenCalledTimes(1) }) - - it('a wait re-parked under a new worktree is released by that worktree, not the old one', () => { - // Adopting an orphaned terminal re-keys `tabsByWorktree` without re-keying the record, so the - // re-park hands the live wait a new worktree. Retraction evidence about the OLD one says - // nothing about the wait that is actually running. - useAppStore.setState({ - tabsByWorktree: { - 'wt-old': [makeTerminalTab({ id: TAB_ID, worktreeId: 'wt-old', title: 't' })], - 'wt-new': [makeTerminalTab({ id: TAB_ID, worktreeId: 'wt-new', title: 't' })] - }, - ptyIdsByTabId: {} - }) - parkUntilHostMirrorHandleLands(ENV_ID, 'wt-old', TAB_ID, vi.fn()) - const replayAfterAdoption = vi.fn() - parkUntilHostMirrorHandleLands(ENV_ID, 'wt-new', TAB_ID, replayAfterAdoption) - - useAppStore.setState({ - tabsByWorktree: { - 'wt-new': [makeTerminalTab({ id: TAB_ID, worktreeId: 'wt-new', title: 't' })] - } - }) - expect(replayAfterAdoption).not.toHaveBeenCalled() - expect(countParkedHostMirrorHandleGapPanesForTests()).toBe(1) - - useAppStore.setState({ tabsByWorktree: {} }) - expect(replayAfterAdoption).toHaveBeenCalledTimes(1) - }) }) diff --git a/src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts b/src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts index 6cc66474f14..192351dba54 100644 --- a/src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts +++ b/src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts @@ -410,24 +410,6 @@ describe('resume across the mirror handle gap', () => { expect(Object.keys(useAppStore.getState().automaticAgentResumeClaimsByTabId)).toHaveLength(0) }) - it('re-arms the wait after a reconnect instead of inheriting the expired verdict', () => { - const worktree = makeRuntimeOwnedWorktree() - seedMirroredWorkspace(worktree) - seedActiveSleepingRecord(worktree.id) - markHostSessionMirrorHydrated(RUNTIME_ENV_ID) - expect(resumeSleepingAgentSessionsForWorktree(worktree.id)).toBe(0) - vi.advanceTimersByTime(HOST_MIRROR_HANDLE_GAP_DEADLINE_MS) - expect(Object.keys(useAppStore.getState().automaticAgentResumeClaimsByTabId)).toHaveLength(0) - - seedMirroredWorkspace(worktree) - const paneKey = seedActiveSleepingRecord(worktree.id) - setRuntimeEnvironmentConnectionGenerationForTests(RUNTIME_ENV_ID, 1) - markHostSessionMirrorHydrated(RUNTIME_ENV_ID) - - expect(resumeSleepingAgentSessionsForWorktree(worktree.id)).toBe(0) - expect(useAppStore.getState().sleepingAgentSessionsByPaneKey[paneKey]).toBeDefined() - }) - // Reconnect during an armed wait must invalidate its pending deadline, independently of already-recorded verdicts. it('does not let a wait armed on the previous connection decide the new one', () => { const worktree = makeRuntimeOwnedWorktree()