Skip to content

Commit edf2dc7

Browse files
committed
fix(desktop): complete canvas context wiring
Signed-off-by: framethespace <68256458+framethespace@users.noreply.github.com>
1 parent e805c75 commit edf2dc7

9 files changed

Lines changed: 61 additions & 57 deletions

File tree

.changeset/canvas-context-tab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@open-codesign/desktop": patch
3+
---
4+
5+
Add a pinned Canvas tab in the desktop preview pane so sketches can live alongside generated files, and send canvas context back with the next prompt when the sketch changes.

apps/desktop/src/preload/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ export interface Preferences {
117117
diagnosticsLastReadTs: number;
118118
}
119119

120+
export interface CanvasStoredState {
121+
sceneJson: string | null;
122+
importedFiles: LocalInputFile[];
123+
}
124+
125+
export interface CanvasContextFile {
126+
name: string;
127+
content: string;
128+
encoding?: 'utf8' | 'base64';
129+
}
130+
120131
/**
121132
* Streaming events emitted by the (future) Agent runtime. Phase 1 emits
122133
* turn_start / text_delta / turn_end. Phase 2 adds tool_call_*. Kept
@@ -528,6 +539,27 @@ const api = {
528539
snapshotId,
529540
}) as Promise<CommentRow[]>,
530541
},
542+
canvas: {
543+
loadState: (designId: string) =>
544+
ipcRenderer.invoke('canvas:v1:load-state', {
545+
schemaVersion: 1,
546+
designId,
547+
}) as Promise<CanvasStoredState>,
548+
saveState: (input: {
549+
designId: string;
550+
sceneJson: string | null;
551+
importedFiles: LocalInputFile[];
552+
}) =>
553+
ipcRenderer.invoke('canvas:v1:save-state', {
554+
schemaVersion: 1,
555+
...input,
556+
}) as Promise<{ ok: true }>,
557+
writeContextFiles: (input: { designId: string; files: CanvasContextFile[] }) =>
558+
ipcRenderer.invoke('canvas:v1:write-context-files', {
559+
schemaVersion: 1,
560+
...input,
561+
}) as Promise<LocalInputFile[]>,
562+
},
531563
diagnostics: {
532564
log: (entry: {
533565
schemaVersion: 1;

apps/desktop/src/renderer/src/components/CanvasErrorBar.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { useCodesignStore } from '../store';
77
* from broken snapshots surface as "Inline Babel script" — the user can't
88
* do anything about them except regenerate, so say that plainly.
99
*/
10-
function humanizeError(raw: string, t: (k: string, d?: Record<string, unknown>) => string): string {
10+
export function humanizePreviewError(
11+
raw: string,
12+
t: (k: string, d?: Record<string, unknown>) => string,
13+
): string {
1114
if (/Inline Babel script/i.test(raw) || /Unexpected token/.test(raw)) {
1215
return t('preview.error.brokenJsx', {
1316
defaultValue:
@@ -29,7 +32,7 @@ export function CanvasErrorBar() {
2932
if (errors.length === 0) return null;
3033
const latest = errors[errors.length - 1];
3134
if (!latest) return null;
32-
const friendly = humanizeError(latest, t);
35+
const friendly = humanizePreviewError(latest, t);
3336
return (
3437
<div
3538
role="alert"

apps/desktop/src/renderer/src/components/CanvasSketchView.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export function CanvasSketchView() {
2727
const canvasSceneLoaded = useCodesignStore((s) => s.canvasSceneLoaded);
2828
const canvasSeed = useCodesignStore((s) => s.canvasSeed);
2929
const ensureCurrentDesign = useCodesignStore((s) => s.ensureCurrentDesign);
30-
const loadCanvasStateForCurrentDesign = useCodesignStore((s) => s.loadCanvasStateForCurrentDesign);
30+
const loadCanvasStateForCurrentDesign = useCodesignStore(
31+
(s) => s.loadCanvasStateForCurrentDesign,
32+
);
3133

3234
const apiRef = useRef<ExcalidrawImperativeAPI | null>(null);
3335
const saveTimerRef = useRef<number | null>(null);

apps/desktop/src/renderer/src/components/PreviewPane.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ export function PreviewPane({ onPickStarter }: PreviewPaneProps) {
286286
const canvasTabs = useCodesignStore((s) => s.canvasTabs);
287287
const activeCanvasTab = useCodesignStore((s) => s.activeCanvasTab);
288288
const errorMessage = useCodesignStore((s) => s.errorMessage);
289+
const iframeErrors = useCodesignStore((s) => s.iframeErrors);
289290
const retry = useCodesignStore((s) => s.retryLastPrompt);
290291
const clearError = useCodesignStore((s) => s.clearError);
292+
const clearIframeErrors = useCodesignStore((s) => s.clearIframeErrors);
291293
const pushIframeError = useCodesignStore((s) => s.pushIframeError);
292294
const selectCanvasElement = useCodesignStore((s) => s.selectCanvasElement);
293295
const previewViewport = useCodesignStore((s) => s.previewViewport);
@@ -469,6 +471,9 @@ export function PreviewPane({ onPickStarter }: PreviewPaneProps) {
469471

470472
const activeHasHtml =
471473
currentDesignId !== null && poolEntries.some((e) => e.id === currentDesignId);
474+
const latestIframeError = iframeErrors[iframeErrors.length - 1] ?? null;
475+
const friendlyIframeError =
476+
latestIframeError !== null ? humanizePreviewError(latestIframeError, t) : null;
472477

473478
// When a design already has persisted content (thumbnail from a prior save,
474479
// or chat history), the preview IS coming — we're just waiting on the IPC

apps/desktop/src/renderer/src/components/Sidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,15 @@ export function Sidebar({ prompt, setPrompt, onSubmit }: SidebarProps) {
216216
<span
217217
className="inline-flex max-w-full items-center gap-[6px] rounded-full border border-[var(--color-border)] bg-[var(--color-background-secondary)] px-[10px] py-[5px] text-[11px] text-[var(--color-text-secondary)]"
218218
title={
219-
canvasWillBeSent
220-
? t('canvas.contextReady')
221-
: t('canvas.contextUpToDate')
219+
canvasWillBeSent ? t('canvas.contextReady') : t('canvas.contextUpToDate')
222220
}
223221
>
224222
<span className="text-[10px] uppercase tracking-[0.08em] text-[var(--color-text-muted)]">
225223
{t('canvas.canvasTab')}
226224
</span>
227-
<span>{canvasWillBeSent ? t('canvas.contextReady') : t('canvas.contextUpToDate')}</span>
225+
<span>
226+
{canvasWillBeSent ? t('canvas.contextReady') : t('canvas.contextUpToDate')}
227+
</span>
228228
</span>
229229
) : null}
230230
{referenceUrl.trim() ? (

apps/desktop/src/renderer/src/store.test.ts

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -258,39 +258,6 @@ describe('useCodesignStore generation cancellation', () => {
258258
});
259259
});
260260

261-
describe('useCodesignStore auto-continue incomplete todos', () => {
262-
it('fires one silent continuation per design until a new explicit prompt resets the guard', async () => {
263-
const sendPromptMock = vi.fn(async () => undefined);
264-
const mockedSendPrompt = sendPromptMock as unknown as SendPromptFn;
265-
266-
useCodesignStore.setState({
267-
currentDesignId: 'design-incomplete',
268-
isGenerating: false,
269-
autoContinueIncompleteFired: new Set<string>(),
270-
sendPrompt: mockedSendPrompt,
271-
});
272-
273-
const first = useCodesignStore
274-
.getState()
275-
.tryAutoContinueIncomplete('design-incomplete', ['Finish the final screen']);
276-
const second = useCodesignStore
277-
.getState()
278-
.tryAutoContinueIncomplete('design-incomplete', ['Finish the final screen']);
279-
280-
expect(first).toBe(true);
281-
expect(second).toBe(false);
282-
expect(sendPromptMock).toHaveBeenCalledTimes(1);
283-
expect(sendPromptMock).toHaveBeenCalledWith(
284-
expect.objectContaining({
285-
silent: true,
286-
prompt: expect.stringContaining('Finish the final screen'),
287-
}),
288-
);
289-
290-
useCodesignStore.setState({ sendPrompt: initialState.sendPrompt });
291-
});
292-
});
293-
294261
describe('useCodesignStore canvas context attachments', () => {
295262
it('shows canvas context in the user chat payload and sends fresh canvas files when dirty', async () => {
296263
const append = vi.fn(async (input: { designId: string; kind: string; payload: unknown }) => ({
@@ -303,10 +270,12 @@ describe('useCodesignStore canvas context attachments', () => {
303270
createdAt: new Date().toISOString(),
304271
schemaVersion: 1,
305272
}));
306-
const generate = vi.fn(async (payload: { attachments: Array<{ path: string; name: string }> }) => ({
307-
artifacts: [],
308-
message: 'done',
309-
}));
273+
const generate = vi.fn(
274+
async (payload: { attachments: Array<{ path: string; name: string }> }) => ({
275+
artifacts: [],
276+
message: 'done',
277+
}),
278+
);
310279

311280
vi.stubGlobal('window', {
312281
codesign: {

apps/desktop/src/renderer/src/store.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,15 +1622,6 @@ export const useCodesignStore = create<CodesignState>((set, get) => ({
16221622
const shouldIncludeCanvasContext =
16231623
canvasRevisionAtSend > get().lastGeneratedCanvasRevision &&
16241624
(hasCanvasContent(canvasSceneAtSend) || canvasImportedFilesAtSend.length > 0);
1625-
if (
1626-
designIdAtStart &&
1627-
!input.silent &&
1628-
get().autoContinueIncompleteFired.has(designIdAtStart)
1629-
) {
1630-
const nextFired = new Set(get().autoContinueIncompleteFired);
1631-
nextFired.delete(designIdAtStart);
1632-
set({ autoContinueIncompleteFired: nextFired });
1633-
}
16341625
set(() => ({
16351626
isGenerating: true,
16361627
activeGenerationId: generationId,
@@ -1688,9 +1679,7 @@ export const useCodesignStore = create<CodesignState>((set, get) => ({
16881679
kind: 'user',
16891680
payload: {
16901681
text: request.prompt,
1691-
...(shouldIncludeCanvasContext
1692-
? { contextBadges: [tr('canvas.contextBadge')] }
1693-
: {}),
1682+
...(shouldIncludeCanvasContext ? { contextBadges: [tr('canvas.contextBadge')] } : {}),
16941683
},
16951684
});
16961685
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-

0 commit comments

Comments
 (0)