diff --git a/apps/web/src/shortcuts/chatRouteCommands.test.ts b/apps/web/src/shortcuts/chatRouteCommands.test.ts index a4c6b53279c84..3979368ff9032 100644 --- a/apps/web/src/shortcuts/chatRouteCommands.test.ts +++ b/apps/web/src/shortcuts/chatRouteCommands.test.ts @@ -3,11 +3,14 @@ import type { AnyRouter } from "@tanstack/react-router"; import { describe, expect, it, vi } from "vite-plus/test"; import * as commandPalette from "../commandPaletteBus"; +import { DraftId, useComposerDraftStore } from "../composerDraftStore"; +import * as previewActions from "../components/preview/previewActionBus"; import * as settings from "../hooks/useSettings"; import * as storage from "../hooks/useLocalStorage"; import * as threadActions from "../lib/chatThreadActions"; import * as entities from "../state/entities"; import * as projectsState from "../state/projects"; +import * as session from "../state/session"; import { Atom } from "effect/reactivity"; import type { Project } from "../types"; import { installChatRouteCommands } from "./chatRouteCommands"; @@ -73,3 +76,33 @@ describe("new thread commands", () => { } }); }); + +describe("preview commands", () => { + it.each([ + { command: "preview.find", action: "find" }, + { command: "preview.refresh", action: "refresh" }, + { command: "preview.focusUrl", action: "focus-url" }, + ] as const)("$command reaches the right panel on a draft thread", ({ command, action }) => { + const draftId = DraftId.make("draft-1"); + useComposerDraftStore + .getState() + .setProjectDraftThreadId( + { environmentId: EnvironmentId.make("local"), projectId: ProjectId.make("active") }, + draftId, + ); + using stack = new DisposableStack(); + stack.defer(() => useComposerDraftStore.getState().clearDraftThread(draftId)); + using _scope = vi.spyOn(session, "readEnvironmentScope").mockReturnValue(true); + using dispatch = vi.spyOn(previewActions, "dispatchPreviewAction").mockImplementation(() => {}); + using register = vi.spyOn(dispatcher, "registerCommands").mockReturnValue(() => {}); + const router = { + state: { matches: [{ routeId: "/_chat/draft/$draftId", params: { draftId } }] }, + } as unknown as AnyRouter; + stack.defer(installChatRouteCommands(router)); + const event = { preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as KeyboardEvent; + + register.mock.calls[0]![0][command]!(event); + + expect(dispatch).toHaveBeenCalledWith(action); + }); +}); diff --git a/apps/web/src/shortcuts/chatRouteCommands.ts b/apps/web/src/shortcuts/chatRouteCommands.ts index a08e2fa5a46f4..81c5cc29a2cfb 100644 --- a/apps/web/src/shortcuts/chatRouteCommands.ts +++ b/apps/web/src/shortcuts/chatRouteCommands.ts @@ -55,6 +55,13 @@ export function installChatRouteCommands(router: AnyRouter): () => void { const target = readRouteTarget(); return target?.kind === "server" ? target.threadRef : null; }; + const readRouteEnvironmentId = () => { + const target = readRouteTarget(); + if (!target) return null; + return target.kind === "server" + ? target.threadRef.environmentId + : (useComposerDraftStore.getState().getDraftSession(target.draftId)?.environmentId ?? null); + }; const readPreviewOpen = () => { const threadRef = readRouteThreadRef(); // Store-only on purpose: the URL-aware arbitration lives in ChatView's @@ -114,8 +121,8 @@ export function installChatRouteCommands(router: AnyRouter): () => void { (action: PreviewAction): CommandHandler => (event) => { claim(event); - const threadRef = readRouteThreadRef(); - if (threadRef && canOperatePreview(threadRef.environmentId)) dispatchPreviewAction(action); + const environmentId = readRouteEnvironmentId(); + if (environmentId && canOperatePreview(environmentId)) dispatchPreviewAction(action); }; return registerCommands(