@tpitre/story-ui 4.16.8 → 4.16.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkB5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAuIvE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAmCrD;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc1D;AAgED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBvD;AAID,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CA0GtE"}
1
+ {"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkB5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAwIvE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAmCrD;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc1D;AAgED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBvD;AAID,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CA0GtE"}
@@ -120,18 +120,19 @@ render(<Canvas />);\`;
120
120
 
121
121
  export const Default: StoryObj = {
122
122
  render: () => {
123
- const [code, setCode] = useState(() => {
124
- try { return localStorage.getItem('${LS_KEY}') || PLACEHOLDER; }
125
- catch { return PLACEHOLDER; }
126
- });
123
+ // Always start with the placeholder — no localStorage restore.
124
+ // Code updates arrive exclusively via postMessage from the parent panel.
125
+ // This prevents stale code from a previous session causing errors.
126
+ const [code, setCode] = useState(PLACEHOLDER);
127
127
 
128
128
  useEffect(() => {
129
+ // Clear any stale code left in localStorage from older versions
130
+ try { localStorage.removeItem('${LS_KEY}'); } catch {}
131
+
129
132
  const handler = (e: MessageEvent) => {
130
- // Only accept messages from same origin to prevent cross-origin code injection
131
133
  if (e.origin !== window.location.origin) return;
132
134
  if (e.data?.type === 'VOICE_CANVAS_UPDATE' && typeof e.data.code === 'string') {
133
135
  setCode(e.data.code);
134
- try { localStorage.setItem('${LS_KEY}', e.data.code); } catch {}
135
136
  }
136
137
  };
137
138
  window.addEventListener('message', handler);
@@ -1974,6 +1974,7 @@
1974
1974
  flex-direction: column;
1975
1975
  height: 100%;
1976
1976
  background: hsl(var(--background));
1977
+ color: hsl(var(--foreground));
1977
1978
  position: relative;
1978
1979
  }
1979
1980
 
@@ -1 +1 @@
1
- {"version":3,"file":"VoiceCanvas.d.ts","sourceRoot":"","sources":["../../../../templates/StoryUI/voice/VoiceCanvas.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAwE,MAAM,OAAO,CAAC;AAY7F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAID,eAAO,MAAM,WAAW,4FA+wBtB,CAAC"}
1
+ {"version":3,"file":"VoiceCanvas.d.ts","sourceRoot":"","sources":["../../../../templates/StoryUI/voice/VoiceCanvas.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAwE,MAAM,OAAO,CAAC;AAY7F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAID,eAAO,MAAM,WAAW,4FA6wBtB,CAAC"}
@@ -71,15 +71,10 @@ export const VoiceCanvas = React.forwardRef(function VoiceCanvas({ apiBase, prov
71
71
  const iframeLoadedRef = useRef(false);
72
72
  // ── Code → iframe bridge ─────────────────────────────────────
73
73
  /**
74
- * Persist code and push it to the story preview iframe.
75
- * Safe to call before the iframe is loaded the code is stored in localStorage
76
- * and the story reads it on mount.
74
+ * Push code to the story preview iframe via postMessage.
75
+ * No localStorage persistenceeach session starts clean.
77
76
  */
78
77
  const sendCodeToIframe = useCallback((code) => {
79
- try {
80
- localStorage.setItem(LS_KEY, code);
81
- }
82
- catch { }
83
78
  if (iframeRef.current?.contentWindow && iframeLoadedRef.current) {
84
79
  iframeRef.current.contentWindow.postMessage({ type: 'VOICE_CANVAS_UPDATE', code }, IFRAME_ORIGIN);
85
80
  }
@@ -163,10 +158,6 @@ export const VoiceCanvas = React.forwardRef(function VoiceCanvas({ apiBase, prov
163
158
  }
164
159
  lastPromptRef.current = transcript;
165
160
  setLastPrompt(transcript);
166
- try {
167
- localStorage.setItem(LS_PROMPT_KEY, firstPromptRef.current);
168
- }
169
- catch { }
170
161
  conversationRef.current.push({ role: 'user', content: transcript }, { role: 'assistant', content: '[Generated canvas component]' });
171
162
  if (conversationRef.current.length > 40) {
172
163
  conversationRef.current = conversationRef.current.slice(-40);
@@ -592,5 +583,5 @@ export const VoiceCanvas = React.forwardRef(function VoiceCanvas({ apiBase, prov
592
583
  // ── Render ─────────────────────────────────────────────────────
593
584
  return (_jsxs("div", { className: "sui-canvas-container", children: [_jsxs("div", { className: "sui-canvas-preview", children: [!storyReady && !isGenerating && (_jsxs("div", { className: "sui-canvas-empty", children: [_jsx("div", { className: "sui-canvas-empty-icon", children: _jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), _jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), _jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }) }), _jsx("h2", { className: "sui-canvas-empty-title", children: "Voice Canvas" }), _jsx("p", { className: "sui-canvas-empty-desc", children: speechSupported
594
585
  ? 'Speak or type to build interfaces live with your design system components.'
595
- : 'Type a prompt to build interfaces live with your design system components.' }), _jsx("p", { className: "sui-canvas-empty-hint", children: "Try: \"Create a product card with an image, title, price, and buy button\"" })] })), !storyReady && isGenerating && (_jsxs("div", { className: "sui-canvas-progress", children: [_jsx("div", { className: "sui-canvas-progress-spinner" }), _jsx("span", { className: "sui-canvas-progress-text", children: statusText || 'Building...' })] })), storyReady && (_jsxs("div", { className: "sui-canvas-live-wrapper", children: [isGenerating && (_jsxs("div", { className: "sui-canvas-regen-overlay", children: [_jsx("div", { className: "sui-canvas-progress-spinner sui-canvas-progress-spinner--sm" }), _jsx("span", { children: statusText || 'Regenerating...' })] })), _jsx("iframe", { ref: iframeRef, src: iframeSrc, title: "Voice Canvas Preview", className: "sui-canvas-iframe", onLoad: handleIframeLoad }, iframeKey)] })), !isGenerating && errorMessage && (_jsxs("div", { className: "sui-canvas-error", children: [_jsx("span", { children: errorMessage }), _jsx("button", { type: "button", className: "sui-canvas-error-dismiss", onClick: () => setErrorMessage(''), "aria-label": "Dismiss error", children: "\u00D7" })] })), savedMessage && (_jsxs("div", { className: "sui-canvas-saved-toast", children: [_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }), _jsxs("span", { children: ["Saved: ", savedMessage] })] }))] }), statusText && !isGenerating && (_jsx("div", { className: "sui-canvas-status-bar", children: _jsx("span", { className: "sui-canvas-explanation", children: statusText }) })), _jsxs("div", { className: `sui-canvas-bar ${isListening ? 'sui-canvas-bar--active' : ''}`, children: [_jsxs("div", { className: "sui-canvas-bar-left", children: [speechSupported && (_jsxs("button", { type: "button", className: `sui-canvas-mic ${isListening ? 'sui-canvas-mic--active' : ''}`, onClick: toggleListening, "aria-label": isListening ? 'Stop voice input' : 'Start voice input', children: [_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), _jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), _jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }), isListening && _jsx("span", { className: "sui-canvas-mic-pulse" })] })), (isListening || isGenerating) && (_jsx("div", { className: "sui-canvas-transcript", children: isGenerating ? (_jsx("span", { className: "sui-canvas-status-rendering", children: statusText || 'Building interface...' })) : interimText ? (_jsxs("span", { className: "sui-canvas-status-interim", children: [pendingTranscript ? pendingTranscript + ' ' : '', interimText] })) : pendingTranscript ? (_jsx("span", { className: "sui-canvas-status-final", children: pendingTranscript })) : (_jsx("span", { className: "sui-canvas-status-listening", children: "Listening... describe what you want to build" })) })), !isListening && !isGenerating && (_jsx("input", { ref: textInputRef, type: "text", className: "sui-canvas-text-input", placeholder: "Type what to build...", value: textInput, onChange: (e) => setTextInput(e.target.value), onKeyDown: handleTextSubmit, disabled: isGenerating }))] }), _jsxs("div", { className: "sui-canvas-bar-right", children: [canUndo && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: undo, title: "Undo (Cmd+Z)", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("polyline", { points: "1 4 1 10 7 10" }), _jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })] }) })), canRedo && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: redo, title: "Redo (Cmd+Shift+Z)", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("polyline", { points: "23 4 23 10 17 10" }), _jsx("path", { d: "M20.49 15a9 9 0 1 1-2.13-9.36L23 10" })] }) })), hasContent && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: saveStory, title: "Save as story", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }), _jsx("polyline", { points: "17 21 17 13 7 13 7 21" }), _jsx("polyline", { points: "7 3 7 8 15 8" })] }) })), hasContent && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: clear, title: "Clear canvas", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M3 6h18" }), _jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), _jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })] }) }))] })] })] }));
586
+ : 'Type a prompt to build interfaces live with your design system components.' }), _jsx("p", { className: "sui-canvas-empty-hint", children: "Try: \"Create a product card with an image, title, price, and buy button\"" })] })), !storyReady && isGenerating && (_jsxs("div", { className: "sui-canvas-progress", children: [_jsx("div", { className: "sui-canvas-progress-spinner" }), _jsx("span", { className: "sui-canvas-progress-text", children: statusText || 'Building...' })] })), storyReady && (_jsxs("div", { className: "sui-canvas-live-wrapper", children: [isGenerating && (_jsxs("div", { className: "sui-canvas-regen-overlay", children: [_jsx("div", { className: "sui-canvas-progress-spinner sui-canvas-progress-spinner--sm" }), _jsx("span", { children: statusText || 'Regenerating...' })] })), _jsx("iframe", { ref: iframeRef, src: iframeSrc, title: "Voice Canvas Preview", className: "sui-canvas-iframe", onLoad: handleIframeLoad }, iframeKey)] })), !isGenerating && errorMessage && (_jsxs("div", { className: "sui-canvas-error", children: [_jsx("span", { children: errorMessage }), _jsx("button", { type: "button", className: "sui-canvas-error-dismiss", onClick: () => setErrorMessage(''), "aria-label": "Dismiss error", children: "\u00D7" })] })), savedMessage && (_jsxs("div", { className: "sui-canvas-saved-toast", children: [_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }), _jsxs("span", { children: ["Saved: ", savedMessage] })] }))] }), statusText && !isGenerating && (_jsx("div", { className: "sui-canvas-status-bar", children: _jsx("span", { className: "sui-canvas-explanation", children: statusText }) })), _jsxs("div", { className: `sui-canvas-bar ${isListening ? 'sui-canvas-bar--active' : ''}`, children: [_jsxs("div", { className: "sui-canvas-bar-left", children: [speechSupported && (_jsxs("button", { type: "button", className: `sui-canvas-mic ${isListening && !isGenerating ? 'sui-canvas-mic--active' : ''}`, onClick: toggleListening, disabled: isGenerating, "aria-label": isListening ? 'Stop voice input' : 'Start voice input', children: [_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), _jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), _jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }), isListening && !isGenerating && _jsx("span", { className: "sui-canvas-mic-pulse" })] })), (isListening || isGenerating) && (_jsx("div", { className: "sui-canvas-transcript", children: isGenerating ? (_jsx("span", { className: "sui-canvas-status-rendering", children: statusText || 'Building interface...' })) : interimText ? (_jsxs("span", { className: "sui-canvas-status-interim", children: [pendingTranscript ? pendingTranscript + ' ' : '', interimText] })) : pendingTranscript ? (_jsx("span", { className: "sui-canvas-status-final", children: pendingTranscript })) : (_jsx("span", { className: "sui-canvas-status-listening", children: "Listening... describe what you want to build" })) })), !isListening && !isGenerating && (_jsx("input", { ref: textInputRef, type: "text", className: "sui-canvas-text-input", placeholder: "Type what to build...", value: textInput, onChange: (e) => setTextInput(e.target.value), onKeyDown: handleTextSubmit, disabled: isGenerating }))] }), _jsxs("div", { className: "sui-canvas-bar-right", children: [canUndo && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: undo, title: "Undo (Cmd+Z)", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("polyline", { points: "1 4 1 10 7 10" }), _jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })] }) })), canRedo && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: redo, title: "Redo (Cmd+Shift+Z)", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("polyline", { points: "23 4 23 10 17 10" }), _jsx("path", { d: "M20.49 15a9 9 0 1 1-2.13-9.36L23 10" })] }) })), hasContent && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: saveStory, title: "Save as story", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }), _jsx("polyline", { points: "17 21 17 13 7 13 7 21" }), _jsx("polyline", { points: "7 3 7 8 15 8" })] }) })), hasContent && (_jsx("button", { type: "button", className: "sui-canvas-action", onClick: clear, title: "Clear canvas", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M3 6h18" }), _jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), _jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })] }) }))] })] })] }));
596
587
  });
@@ -107,12 +107,10 @@ function VoiceCanvas({
107
107
  // ── Code → iframe bridge ─────────────────────────────────────
108
108
 
109
109
  /**
110
- * Persist code and push it to the story preview iframe.
111
- * Safe to call before the iframe is loaded the code is stored in localStorage
112
- * and the story reads it on mount.
110
+ * Push code to the story preview iframe via postMessage.
111
+ * No localStorage persistenceeach session starts clean.
113
112
  */
114
113
  const sendCodeToIframe = useCallback((code: string) => {
115
- try { localStorage.setItem(LS_KEY, code); } catch {}
116
114
  if (iframeRef.current?.contentWindow && iframeLoadedRef.current) {
117
115
  iframeRef.current.contentWindow.postMessage(
118
116
  { type: 'VOICE_CANVAS_UPDATE', code },
@@ -212,7 +210,6 @@ function VoiceCanvas({
212
210
  }
213
211
  lastPromptRef.current = transcript;
214
212
  setLastPrompt(transcript);
215
- try { localStorage.setItem(LS_PROMPT_KEY, firstPromptRef.current); } catch {}
216
213
  conversationRef.current.push(
217
214
  { role: 'user', content: transcript },
218
215
  { role: 'assistant', content: '[Generated canvas component]' },
@@ -731,8 +728,9 @@ function VoiceCanvas({
731
728
  {speechSupported && (
732
729
  <button
733
730
  type="button"
734
- className={`sui-canvas-mic ${isListening ? 'sui-canvas-mic--active' : ''}`}
731
+ className={`sui-canvas-mic ${isListening && !isGenerating ? 'sui-canvas-mic--active' : ''}`}
735
732
  onClick={toggleListening}
733
+ disabled={isGenerating}
736
734
  aria-label={isListening ? 'Stop voice input' : 'Start voice input'}
737
735
  >
738
736
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -740,7 +738,7 @@ function VoiceCanvas({
740
738
  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
741
739
  <line x1="12" x2="12" y1="19" y2="22" />
742
740
  </svg>
743
- {isListening && <span className="sui-canvas-mic-pulse" />}
741
+ {isListening && !isGenerating && <span className="sui-canvas-mic-pulse" />}
744
742
  </button>
745
743
  )}
746
744
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpitre/story-ui",
3
- "version": "4.16.8",
3
+ "version": "4.16.10",
4
4
  "description": "AI-powered Storybook story generator with dynamic component discovery",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1974,6 +1974,7 @@
1974
1974
  flex-direction: column;
1975
1975
  height: 100%;
1976
1976
  background: hsl(var(--background));
1977
+ color: hsl(var(--foreground));
1977
1978
  position: relative;
1978
1979
  }
1979
1980
 
@@ -107,12 +107,10 @@ function VoiceCanvas({
107
107
  // ── Code → iframe bridge ─────────────────────────────────────
108
108
 
109
109
  /**
110
- * Persist code and push it to the story preview iframe.
111
- * Safe to call before the iframe is loaded the code is stored in localStorage
112
- * and the story reads it on mount.
110
+ * Push code to the story preview iframe via postMessage.
111
+ * No localStorage persistenceeach session starts clean.
113
112
  */
114
113
  const sendCodeToIframe = useCallback((code: string) => {
115
- try { localStorage.setItem(LS_KEY, code); } catch {}
116
114
  if (iframeRef.current?.contentWindow && iframeLoadedRef.current) {
117
115
  iframeRef.current.contentWindow.postMessage(
118
116
  { type: 'VOICE_CANVAS_UPDATE', code },
@@ -212,7 +210,6 @@ function VoiceCanvas({
212
210
  }
213
211
  lastPromptRef.current = transcript;
214
212
  setLastPrompt(transcript);
215
- try { localStorage.setItem(LS_PROMPT_KEY, firstPromptRef.current); } catch {}
216
213
  conversationRef.current.push(
217
214
  { role: 'user', content: transcript },
218
215
  { role: 'assistant', content: '[Generated canvas component]' },
@@ -731,8 +728,9 @@ function VoiceCanvas({
731
728
  {speechSupported && (
732
729
  <button
733
730
  type="button"
734
- className={`sui-canvas-mic ${isListening ? 'sui-canvas-mic--active' : ''}`}
731
+ className={`sui-canvas-mic ${isListening && !isGenerating ? 'sui-canvas-mic--active' : ''}`}
735
732
  onClick={toggleListening}
733
+ disabled={isGenerating}
736
734
  aria-label={isListening ? 'Stop voice input' : 'Start voice input'}
737
735
  >
738
736
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -740,7 +738,7 @@ function VoiceCanvas({
740
738
  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
741
739
  <line x1="12" x2="12" y1="19" y2="22" />
742
740
  </svg>
743
- {isListening && <span className="sui-canvas-mic-pulse" />}
741
+ {isListening && !isGenerating && <span className="sui-canvas-mic-pulse" />}
744
742
  </button>
745
743
  )}
746
744