diff --git a/web/src/spawnForm.test.ts b/web/src/spawnForm.test.ts index c4cc563..412a4c0 100644 --- a/web/src/spawnForm.test.ts +++ b/web/src/spawnForm.test.ts @@ -26,15 +26,21 @@ describe("buildSpawnRequest", () => { expect(res).toEqual({ req: { cwd: base.cwd, command: base.command, metadata: {} } }); }); - it("requests structured view only for Claude Code", () => { - expect(buildSpawnRequest({ ...base, structuredView: true }, null)).toEqual({ - req: { - cwd: base.cwd, - command: "claude", - metadata: { name: "amber-fox" }, - view: "Structured", - }, - }); + it.each(["claude", "FOO=1 /usr/local/bin/codex", "env -i codex"])( + "requests structured view for resolved %s commands", + (command) => { + expect(buildSpawnRequest({ ...base, command, structuredView: true }, null)).toEqual({ + req: { + cwd: base.cwd, + command, + metadata: { name: "amber-fox" }, + view: "Structured", + }, + }); + }, + ); + + it("does not request structured view for unsupported commands", () => { expect( buildSpawnRequest({ ...base, command: "opencode", structuredView: true }, null), ).toEqual({ diff --git a/web/src/spawnForm.ts b/web/src/spawnForm.ts index c2e3c20..24dd35f 100644 --- a/web/src/spawnForm.ts +++ b/web/src/spawnForm.ts @@ -15,6 +15,13 @@ import { splitCommand } from "./toolIcon.js"; import { abbreviateCwd } from "./types.js"; import type { BranchInfo, CreateSessionRequest, Project, Session, SpawnPreset } from "./types.js"; +const STRUCTURED_VIEW_TOOLS = new Set(["claude", "codex"]); + +function supportsStructuredView(command: string): boolean { + const tool = splitCommand(command).tool; + return tool !== null && STRUCTURED_VIEW_TOOLS.has(tool); +} + /** Where a worktree's branch comes from: freshly created (default) or picked * from the repository's existing branches (#61). */ export type WorktreeBranchMode = "new" | "existing"; @@ -68,7 +75,7 @@ export function buildSpawnRequest( metadata, }; - if (values.structuredView && splitCommand(values.command).tool === "claude") { + if (values.structuredView && supportsStructuredView(values.command)) { req.view = "Structured"; } @@ -369,7 +376,7 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle { let commandTouched = false; function updateStructuredAvailability(): void { - const supported = splitCommand(command).tool === "claude"; + const supported = supportsStructuredView(command); structuredWrap.hidden = !supported; if (!supported) structuredView.checked = false; } diff --git a/web/src/spawnFormStructured.test.ts b/web/src/spawnFormStructured.test.ts index 12a0205..0b84651 100644 --- a/web/src/spawnFormStructured.test.ts +++ b/web/src/spawnFormStructured.test.ts @@ -44,7 +44,7 @@ describe("spawn form structured view", () => { vi.unstubAllGlobals(); }); - it("is default-off and shown only for the resolved Claude Code command", async () => { + it("is default-off and shown for resolved Claude Code and Codex commands", async () => { stubApi(); handle = createSpawnForm({ onSuccess() {} }); document.body.appendChild(handle.el); @@ -59,26 +59,33 @@ describe("spawn form structured view", () => { expect(wrap.hidden).toBe(false); expect(checkbox.checked).toBe(false); - handle.el.querySelector('[data-id="opencode"]')!.click(); - expect(wrap.hidden).toBe(true); - - handle.el.querySelector('[data-id="claude"]')!.click(); + const custom = handle.el.querySelector('[data-id="custom"]')!; + custom.click(); + const command = handle.el.querySelector(".cmd-raw")!; + command.value = "FOO=1 /usr/local/bin/codex"; + command.dispatchEvent(new Event("input")); expect(wrap.hidden).toBe(false); + + command.value = "opencode"; + command.dispatchEvent(new Event("input")); + expect(wrap.hidden).toBe(true); }); - it("clears a checked value when the command changes away from Claude Code", async () => { + it("clears a checked value when Codex changes to an unsupported command", async () => { stubApi(); handle = createSpawnForm({ onSuccess() {} }); document.body.appendChild(handle.el); await drain(); + handle.el.querySelector('[data-id="custom"]')!.click(); + const command = handle.el.querySelector(".cmd-raw")!; + command.value = "env -i codex"; + command.dispatchEvent(new Event("input")); + const checkbox = handle.el.querySelector("input[name=structured_view]")!; checkbox.click(); expect(checkbox.checked).toBe(true); - const custom = handle.el.querySelector('[data-id="custom"]')!; - custom.click(); - const command = handle.el.querySelector(".cmd-raw")!; command.value = "opencode"; command.dispatchEvent(new Event("input")); @@ -86,7 +93,7 @@ describe("spawn form structured view", () => { expect(checkbox.checked).toBe(false); }); - it("emits Structured in the submitted request when selected", async () => { + it.each(["claude", "codex"])("emits Structured for a selected %s command", async (tool) => { const requests = stubApi(); handle = createSpawnForm({ seed: { cwd: "/srv/work", cwdRawVisible: true, worktreeEnabled: false }, @@ -95,13 +102,20 @@ describe("spawn form structured view", () => { document.body.appendChild(handle.el); await drain(); + if (tool === "codex") { + handle.el.querySelector('[data-id="custom"]')!.click(); + const command = handle.el.querySelector(".cmd-raw")!; + command.value = "codex"; + command.dispatchEvent(new Event("input")); + } + handle.el.querySelector("input[name=structured_view]")!.click(); handle.el.requestSubmit(); await drain(); expect(requests).toHaveLength(1); expect(JSON.parse(String(requests[0].body))).toMatchObject({ - command: "claude", + command: tool, view: "Structured", }); });