Artifacts canvas beside chat
A coding-agent layout in the shape of Claude Artifacts or v0: the conversation drives the work on the left, and the right side pairs a <kai-file-tree> with a <kai-artifact> canvas. Pick a file in the tree and it loads into the canvas — HTML pages frame live in the sandboxed preview, every file opens its source on the Code tab.
How it works
Section titled “How it works”Three elements compose the shell. <kai-resizable> splits chat from the canvas column; a nested vertical <kai-resizable> stacks the tree above the artifact. Selecting a file in <kai-file-tree> fires kai-select; the handler sets the artifact’s activeFile (and, for files with a hosted url, its src) so the canvas follows the tree.
<kai-resizable orientation="horizontal" style="display:block;height:620px"> <!-- left: the conversation --> <kai-resizable-item size="42%" min="280px"> <kai-chat id="chat" chat-title="Build agent"></kai-chat> </kai-resizable-item>
<!-- right: file tree above the artifact canvas --> <kai-resizable-item min="320px"> <kai-resizable orientation="vertical" style="display:block;height:100%"> <kai-resizable-item size="34%" min="120px" max="60%"> <kai-file-tree id="tree"></kai-file-tree> </kai-resizable-item> <kai-resizable-item min="200px"> <kai-artifact id="canvas" iframe-title="Project preview" open-in-tab sandbox="allow-scripts allow-forms allow-same-origin" ></kai-artifact> </kai-resizable-item> </kai-resizable> </kai-resizable-item></kai-resizable>
<script type="module"> import '@kitn.ai/ui/elements'; // registers the custom elements
const tree = document.getElementById('tree'); const canvas = document.getElementById('canvas');
// Each file: { path, url?, code?, language?, type? }. `url` points at a page // your backend hosts; `code` feeds the Code tab. Folders derive from `/`. const files = [ { path: 'index.html', type: 'html', language: 'html', code: '<!DOCTYPE …', url: 'https://your-backend.example/artifacts/abc/index.html' }, { path: 'about.html', type: 'html', language: 'html', code: '<!DOCTYPE …', url: 'https://your-backend.example/artifacts/abc/about.html' }, { path: 'styles.css', type: 'other', language: 'css', code: ':root { … }', url: 'https://your-backend.example/artifacts/abc/styles.css' }, { path: 'src/theme.ts', type: 'other', language: 'ts', code: 'export const theme = …' }, ];
tree.files = files; // both take the same array, set in JS tree.activeFile = 'index.html';
canvas.files = files; canvas.activeFile = 'index.html'; canvas.src = files[0].url; // the preview frames this URL
// Tree → canvas: load the picked file into the artifact. tree.addEventListener('kai-select', (e) => { const file = files.find((f) => f.path === e.detail.path); tree.activeFile = e.detail.path; canvas.activeFile = e.detail.path; if (file.url) { canvas.src = file.url; canvas.tab = 'preview'; } else { canvas.tab = 'code'; } // code-only file → show the source });</script><kai-artifact> self-navigates its sandboxed iframe. The back/forward/reload/home toolbar and the address field always track navigations the canvas starts — picking a file, editing the path, home, reload. Tracking in-frame relative-link clicks (clicking “About →” inside the preview) also needs allow-same-origin, because the default allow-scripts allow-forms sandbox gives the framed document an opaque origin the component cannot read. This canvas opts in with sandbox="allow-scripts allow-forms allow-same-origin", so it observes those clicks and keeps the address field and back/forward truthful. Add allow-same-origin only for first-party artifacts you trust — it lets the framed document reach its own origin.
Next steps
Section titled “Next steps”kai-artifactreference —src,files,activeFile,tab, theno-*toolbar flags, andkai-navigate/kai-tab-change/kai-file-select.kai-file-treereference — thefilesshape,active-file,default-expanded, and thekai-selectevent.kai-resizablereference —orientation, itemsize/min/max/locked/hidden, nesting, and the maximize protocol.- Resizable split layout — the two-pane chat shell this canvas layout builds on.