You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(studio): treat artifacts as data and delegate rich formats to canvas viewers
The previous artifact increment compiled `.tsx`/`.jsx` artifacts into executable
React modules, which promoted untrusted run output to executable code while the
formats reviewers actually asked for (PPTX, XLSX, DOCX, GLB, Lottie) stayed
unrendered. Replace that contract: every artifact kind is now an inert data
presentation (code, diff, image, json, svg, text), and executable viewer code
comes only from the operator-controlled Canvas viewer root discovered under
`$QODER_HOME/canvas/canvases`.
Adds `artifact-viewers.ts` (viewer discovery and renderer selection),
`artifact-viewer-runtime.ts` (Canvas SDK runtime resolution and file serving),
and `canvas-viewer-compile.ts`; removes `artifact-compile.ts` and the in-sandbox
`artifact-host` bundle plus its build entry. Artifact indexing now rejects
symlinks and paths escaping the physical root, route dispatch no longer crashes
Studio on a rejected handler, and `--canvas-viewers`/`--canvas-sdk-root`/
`--canvas-sdk-media` make the viewer roots configurable. Artifact directories
become optional preloads, so Studio can start without any input flag.
Validated with `npx vitest run test/artifact-viewers.test.ts test/artifact-poc.test.ts test/server.test.ts test/studio-shell-model.test.ts` (56 passing); spec `docs/specs/2026-08-20-harness-studio-artifact-view.md` updated to Implemented.
Co-authored-by: QoderAI <qoder_ai@qoder.com>
<divclassName="inspector-scroll"id="state-inspector-panel"role="tabpanel"><divclassName="inspector-comparison"><strong>{INSPECTOR_TABS.find((tab)=>tab.id===props.activeTab)?.label} at {event.timestamp}</strong><span>Compared with {previous?.timestamp??"session start"}</span></div><InspectorContentsession={props.session}tab={props.activeTab}cursor={props.cursor}/></div>
563
+
<divclassName="inspector-scroll"id="state-inspector-panel"role="tabpanel"><divclassName="inspector-comparison"><strong>{INSPECTOR_TABS.find((tab)=>tab.id===props.activeTab)?.label} at {event.timestamp}</strong><span>Compared with {previous?.timestamp??"session start"}</span></div><InspectorContentsession={props.session}tab={props.activeTab}cursor={props.cursor}artifactEndpoint={props.artifactEndpoint}/></div>
return<><InspectorSectiontitle="Retained artifacts"><ulclassName="simple-rows"><li><ImageSquaresize={13}/><span>acp-debugger-reference.png</span><em>Input</em></li><li><BracketsCurlysize={13}/><span>session-debugger-state.json</span><em>{event.timestamp}</em></li></ul></InspectorSection><InspectorSectiontitle="Boundary"><pclassName="inspector-note">Artifact rows belong to this recorded sample. They are not a restorable workspace snapshot.</p></InspectorSection></>;
if(endpoint===undefined)return<InspectorSectiontitle="Retained artifacts"><pclassName="inspector-empty">No artifact directory is configured for this Studio run.</p></InspectorSection>;
return<><InspectorSectiontitle="Retained artifacts"count={artifacts.length}>{artifacts.length===0 ? <pclassName="inspector-empty">The configured artifact directory is empty.</p> : <ulclassName="simple-rows">{artifacts.map((artifact)=><likey={artifact.id}><FileTextsize={13}/><span>{artifact.label}</span><em>{artifact.renderer}</em></li>)}</ul>}</InspectorSection><InspectorSectiontitle="Boundary"><pclassName="inspector-note">These rows come from the configured read-only artifact catalog. They are not a restorable workspace snapshot.</p></InspectorSection></>;
0 commit comments