From 450edfa66a77fb18a922d1ce49ef32c3d2469b2f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 18:19:13 +0000 Subject: [PATCH 1/4] fix: handle read-only queries gracefully in workspace intent bar The workspace submitIntent treated all orchestrator responses as action requests, erroring when no operations were generated. Simple questions and read-only queries now display reasoning text and suggestions instead of showing "No operations generated" error. https://claude.ai/code/session_01MdvMfftU9ACHPEvmrwQrs4 --- components/workspace/intent-bar.tsx | 51 +++++++++++++++++-- components/workspace/workspace-provider.tsx | 55 ++++++++++++++++----- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/components/workspace/intent-bar.tsx b/components/workspace/intent-bar.tsx index 03fc5ea..dd9dd23 100644 --- a/components/workspace/intent-bar.tsx +++ b/components/workspace/intent-bar.tsx @@ -73,6 +73,8 @@ export function IntentBar({ cancelDraft, draftChangeset, executionError, + infoResponse, + dismissInfo, } = useWorkspace(); const { isDemo } = useLayout(); const [input, setInput] = useState(""); @@ -90,13 +92,14 @@ export function IntentBar({ if (draftChangeset && (phase === "preview" || phase === "executing")) { return "Review changeset above"; } + if (phase === "complete" && infoResponse) return "Ask another question or describe a change..."; if (phase === "complete") return "Changes applied"; if (selectedProducts.length === 0) return "Describe a commerce change..."; if (selectedProducts.length === 1) return `${selectedProducts[0].name} — what to change?`; return `${selectedProducts.length} products — what to change?`; - }, [selectedProducts, draftChangeset, phase, voiceActive, voiceConnecting]); + }, [selectedProducts, draftChangeset, phase, voiceActive, voiceConnecting, infoResponse]); const chips = useMemo(() => { if (selectedProducts.length === 0) return []; @@ -107,9 +110,10 @@ export function IntentBar({ const handleSubmit = useCallback(() => { const trimmed = input.trim(); if (!trimmed || phase === "executing" || phase === "preview") return; + if (phase === "complete" && !infoResponse) return; setInput(""); submitIntent(trimmed); - }, [input, phase, submitIntent]); + }, [input, phase, submitIntent, infoResponse]); const isBusy = phase === "executing" || phase === "preview"; const hasDraft = !!draftChangeset && phase === "preview"; @@ -125,14 +129,51 @@ export function IntentBar({ )} - {/* Completion indicator */} - {phase === "complete" && ( + {/* Completion indicator (changeset executed) */} + {phase === "complete" && !infoResponse && (
Changes applied successfully
)} + {/* Informational response (read-only query / no operations) */} + {phase === "complete" && infoResponse && ( +
+

{infoResponse.reasoning}

+ {infoResponse.readerText && ( +
+              {infoResponse.readerText}
+            
+ )} + {infoResponse.suggestions && infoResponse.suggestions.length > 0 && ( +
+ {infoResponse.suggestions.map((s) => ( + + ))} +
+ )} + +
+ )} + {/* Error indicator */} {phase === "error" && (
@@ -203,7 +244,7 @@ export function IntentBar({ value={input} onChange={(e) => setInput(e.target.value)} placeholder={placeholder} - disabled={isBusy || phase === "complete"} + disabled={isBusy || (phase === "complete" && !infoResponse)} className="flex-1" /> void; selectedIds: Set; draftChangeset: ChangeSet | null; phase: WorkspacePhase; @@ -136,9 +139,10 @@ const ChangeSetSchema = z.object({ }).passthrough(); const OrchestratorResponseSchema = z.object({ - changeSet: ChangeSetSchema, + changeSet: ChangeSetSchema.nullable(), reasoning: z.string(), readerText: z.string().optional(), + suggestions: z.array(z.string()).optional(), }); const ExecutorResponseSchema = z.object({ @@ -453,6 +457,11 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { const [chatActivityPhase, setChatActivityPhase] = useState("idle"); const [chatActivityChangeset, setChatActivityChangeset] = useState(null); const [executionError, setExecutionError] = useState(null); + const [infoResponse, setInfoResponse] = useState<{ + reasoning: string; + readerText?: string; + suggestions?: string[]; + } | null>(null); const [fetchAttempt, setFetchAttempt] = useState(0); const executeInFlightRef = useRef(false); @@ -645,6 +654,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { async (text: string): Promise => { setPhase("preview"); setExecutionError(null); + setInfoResponse(null); setDraftChangeset(null); // clear stale draft immediately try { const selectedProducts = products.filter((p) => @@ -675,16 +685,20 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { setPhase("error"); return null; } - // Schema validates structural shape; cast through unknown since Zod passthrough - // infers a wider type than the full ChangeSet with its deep nested types - const cs = validated.data.changeSet as unknown as ChangeSet; - if (!Array.isArray(cs.operations) || cs.operations.length === 0) { - console.error("[workspace] Orchestrator returned changeset with 0 operations"); - setExecutionError("No operations generated — try a more specific request (e.g. \"Change price to $79\")"); + + const { changeSet: rawCs, reasoning, readerText, suggestions } = validated.data; + + // Read-only / informational response (no operations to draft) + if (!rawCs || !Array.isArray(rawCs.operations) || rawCs.operations.length === 0) { setDraftChangeset(null); - setPhase("error"); + setInfoResponse({ reasoning, readerText, suggestions }); + setPhase("complete"); return null; } + + // Schema validates structural shape; cast through unknown since Zod passthrough + // infers a wider type than the full ChangeSet with its deep nested types + const cs = rawCs as unknown as ChangeSet; setDraftChangeset(cs); return cs; } catch { @@ -700,6 +714,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { async (text: string, productId: string): Promise => { setPhase("preview"); setExecutionError(null); + setInfoResponse(null); setDraftChangeset(null); // clear stale draft immediately try { const targetProduct = products.find((p) => p.id === productId); @@ -731,14 +746,18 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { setPhase("error"); return null; } - const cs = validated.data.changeSet as unknown as ChangeSet; - if (!Array.isArray(cs.operations) || cs.operations.length === 0) { - console.error("[workspace] Orchestrator returned changeset with 0 operations"); - setExecutionError("No operations generated — try a more specific request (e.g. \"Change price to $79\")"); + + const { changeSet: rawCs, reasoning, readerText, suggestions } = validated.data; + + // Read-only / informational response (no operations to draft) + if (!rawCs || !Array.isArray(rawCs.operations) || rawCs.operations.length === 0) { setDraftChangeset(null); - setPhase("error"); + setInfoResponse({ reasoning, readerText, suggestions }); + setPhase("complete"); return null; } + + const cs = rawCs as unknown as ChangeSet; setDraftChangeset(cs); return cs; } catch { @@ -829,10 +848,16 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { const cancelDraft = useCallback(() => { setDraftChangeset(null); setExecutionError(null); + setInfoResponse(null); executeInFlightRef.current = false; setPhase("idle"); }, []); + const dismissInfo = useCallback(() => { + setInfoResponse(null); + setPhase("idle"); + }, []); + const applyExecutedChangeset = useCallback((cs: ChangeSet) => { const opsToApply = filterSuccessfulOps( cs.operations, @@ -863,6 +888,8 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { loading, fetchError, executionError, + infoResponse, + dismissInfo, selectedIds, draftChangeset, phase, @@ -890,6 +917,8 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { loading, fetchError, executionError, + infoResponse, + dismissInfo, selectedIds, draftChangeset, phase, From bc891537491199cde9a0d4db15d716cc910543db Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 18:19:46 +0000 Subject: [PATCH 2/4] docs: add changelog entry for workspace query fix https://claude.ai/code/session_01MdvMfftU9ACHPEvmrwQrs4 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b92590e..42dfc20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this project will be documented in this file. - Update AGENTS.md and llms.txt agent descriptions: add `create_product` (writer) and `send_execution_receipt` (notifier) - Document Google Sheets data contract and inventory dual-format model in AGENTS.md +### Fixes + +- Fix workspace intent bar erroring on simple questions / read-only queries instead of showing response text + ### Features - Add production Google account connection endpoint at `/api/auth/connect-google` (PR `#64`) From af79ff3263deb9ad50503456062cd4c224352670 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 18:33:31 +0000 Subject: [PATCH 3/4] refactor: address review feedback on workspace query fix - Fix duplicate ### Fixes header in CHANGELOG.md - Constrain readerText pre height with max-h-48 overflow-y-auto - Extract resolveOrchestratorResponse helper to deduplicate submit functions https://claude.ai/code/session_01MdvMfftU9ACHPEvmrwQrs4 --- CHANGELOG.md | 5 +- components/workspace/intent-bar.tsx | 2 +- components/workspace/workspace-provider.tsx | 57 +++++++++++---------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42dfc20..0e7ddd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,6 @@ All notable changes to this project will be documented in this file. - Update AGENTS.md and llms.txt agent descriptions: add `create_product` (writer) and `send_execution_receipt` (notifier) - Document Google Sheets data contract and inventory dual-format model in AGENTS.md -### Fixes - -- Fix workspace intent bar erroring on simple questions / read-only queries instead of showing response text - ### Features - Add production Google account connection endpoint at `/api/auth/connect-google` (PR `#64`) @@ -39,6 +35,7 @@ All notable changes to this project will be documented in this file. ### Fixes +- Fix workspace intent bar erroring on simple questions / read-only queries instead of showing response text - Fix bottom bar button/input height mismatch on desktop (Send and Mic buttons now match input height at sm+ breakpoint) - Fix missing voice and visual feedback after changeset execution via voice approval (PR `#29`) - Add error handling and logging to Gemini Live tool response pipeline to prevent silent failures (PR `#29`) diff --git a/components/workspace/intent-bar.tsx b/components/workspace/intent-bar.tsx index dd9dd23..e84e859 100644 --- a/components/workspace/intent-bar.tsx +++ b/components/workspace/intent-bar.tsx @@ -142,7 +142,7 @@ export function IntentBar({

{infoResponse.reasoning}

{infoResponse.readerText && ( -
+            
               {infoResponse.readerText}
             
)} diff --git a/components/workspace/workspace-provider.tsx b/components/workspace/workspace-provider.tsx index b308fc2..8327e51 100644 --- a/components/workspace/workspace-provider.tsx +++ b/components/workspace/workspace-provider.tsx @@ -145,6 +145,33 @@ const OrchestratorResponseSchema = z.object({ suggestions: z.array(z.string()).optional(), }); +/** + * Resolve an orchestrator response into either a ChangeSet (action) or an + * informational response (read-only query / no operations). + */ +function resolveOrchestratorResponse( + data: z.infer, + setDraftChangeset: (cs: ChangeSet | null) => void, + setInfoResponse: (info: { reasoning: string; readerText?: string; suggestions?: string[] } | null) => void, + setPhase: (phase: WorkspacePhase) => void, +): ChangeSet | null { + const { changeSet: rawCs, reasoning, readerText, suggestions } = data; + + // Read-only / informational response (no operations to draft) + if (!rawCs || !Array.isArray(rawCs.operations) || rawCs.operations.length === 0) { + setDraftChangeset(null); + setInfoResponse({ reasoning, readerText, suggestions }); + setPhase("complete"); + return null; + } + + // Schema validates structural shape; cast through unknown since Zod passthrough + // infers a wider type than the full ChangeSet with its deep nested types + const cs = rawCs as unknown as ChangeSet; + setDraftChangeset(cs); + return cs; +} + const ExecutorResponseSchema = z.object({ changeSet: ChangeSetSchema, }).passthrough(); @@ -686,21 +713,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { return null; } - const { changeSet: rawCs, reasoning, readerText, suggestions } = validated.data; - - // Read-only / informational response (no operations to draft) - if (!rawCs || !Array.isArray(rawCs.operations) || rawCs.operations.length === 0) { - setDraftChangeset(null); - setInfoResponse({ reasoning, readerText, suggestions }); - setPhase("complete"); - return null; - } - - // Schema validates structural shape; cast through unknown since Zod passthrough - // infers a wider type than the full ChangeSet with its deep nested types - const cs = rawCs as unknown as ChangeSet; - setDraftChangeset(cs); - return cs; + return resolveOrchestratorResponse(validated.data, setDraftChangeset, setInfoResponse, setPhase); } catch { setDraftChangeset(null); setPhase("error"); @@ -747,19 +760,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { return null; } - const { changeSet: rawCs, reasoning, readerText, suggestions } = validated.data; - - // Read-only / informational response (no operations to draft) - if (!rawCs || !Array.isArray(rawCs.operations) || rawCs.operations.length === 0) { - setDraftChangeset(null); - setInfoResponse({ reasoning, readerText, suggestions }); - setPhase("complete"); - return null; - } - - const cs = rawCs as unknown as ChangeSet; - setDraftChangeset(cs); - return cs; + return resolveOrchestratorResponse(validated.data, setDraftChangeset, setInfoResponse, setPhase); } catch { setDraftChangeset(null); setPhase("error"); From 86a27f6848e4f505e11cb2b5745a4f3e4d8c6da5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 18:38:54 +0000 Subject: [PATCH 4/4] fix: add x-demo-session header to workspace orchestrator/executor fetches The workspace submitIntent and executeChangeset calls were missing the x-demo-session header required by isDemoSession(), causing 401 errors on the demo dashboard. The reader fetch already included this header. https://claude.ai/code/session_01MdvMfftU9ACHPEvmrwQrs4 --- components/workspace/workspace-provider.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/workspace/workspace-provider.tsx b/components/workspace/workspace-provider.tsx index 8327e51..dbce1dc 100644 --- a/components/workspace/workspace-provider.tsx +++ b/components/workspace/workspace-provider.tsx @@ -692,9 +692,11 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { ? `\n\nSelected products: ${selectedProducts.map((p) => `${p.name} (${p.sku})`).join(", ")}` : ""; + const fetchHeaders: Record = { "Content-Type": "application/json" }; + if (isDemo) fetchHeaders["x-demo-session"] = "1"; const res = await fetch("/api/orchestrator", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: fetchHeaders, body: JSON.stringify({ message: text + context }), }); @@ -720,7 +722,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { return null; } }, - [products, selectedIds], + [products, selectedIds, isDemo], ); const submitIntentForProduct = useCallback( @@ -739,9 +741,11 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { } const context = `\n\nSelected products: ${targetProduct.name} (${targetProduct.sku})`; + const fetchHeaders: Record = { "Content-Type": "application/json" }; + if (isDemo) fetchHeaders["x-demo-session"] = "1"; const res = await fetch("/api/orchestrator", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: fetchHeaders, body: JSON.stringify({ message: text + context }), }); @@ -767,7 +771,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { return null; } }, - [products], + [products, isDemo], ); const executeChangeset = useCallback(async (overrideCs?: ChangeSet): Promise<{ success: boolean; status?: string; error?: string }> => { @@ -781,9 +785,11 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { setPhase("executing"); setExecutionError(null); try { + const execHeaders: Record = { "Content-Type": "application/json" }; + if (isDemo) execHeaders["x-demo-session"] = "1"; const res = await fetch("/api/orchestrator/execute", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: execHeaders, body: JSON.stringify({ changeSet: cs }), }); if (!res.ok) { @@ -844,7 +850,7 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) { executeInFlightRef.current = false; return { success: false, status: "failed", error: msg }; } - }, [draftChangeset]); + }, [draftChangeset, isDemo]); const cancelDraft = useCallback(() => { setDraftChangeset(null);