# Build the "Artifact canvas" pattern: `ArtifactCanvas`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
Substantial output moves out of the transcript into a durable side surface that can be edited and versioned.

Use it when: Documents, code, tables and designs — anything the conversation is about rather than made of.

## What to build
A side surface beside the conversation that holds one substantial artifact (a document, code file or table) with a stable identity, a version list, diffs, editing and export. The chat refers to versions instead of repeating the whole thing. It renders as a resizable pane next to chat, or a full-screen sheet on narrow screens.

### Props
- `artifact`: { id: string; title: string; kind: "document" | "code" | "table" }.
- `versions`: Array<{ id: string; number: number; createdAt: Date; author: "agent" | "human"; summary: string }>, e.g. "v2 · Wrote the Pricing section".
- `latestVersionId`: string, plus `viewingVersionId`: string and `onViewVersion(id: string)`.
- `content`: ReactNode, the version being viewed, and `diff?`: Array<{ type: "added" | "removed" | "same"; text: string }> against the previous version.
- `mode`: "view" | "edit" | "diff" and `onModeChange(mode)`.
- `status`: "idle" | "agent-writing" | "saving" | "error", with `writingSection?`: string naming where the agent is working.
- `onSaveEdit(content: string)`, `onRestore(versionId: string)`, `onExport(format: string)` and `onClose()`: callbacks. The component never persists versions itself.

### States it must render
- Latest: title, version number and content, with the version picker and Export in the header.
- Agent writing: the section being changed is marked with the AI accent and "Writing Pricing"; the rest stays readable.
- Older version: a banner "Viewing v1" with Back to latest and Restore.
- Diff: additions and removals since the previous version, marked with more than colour.
- Edited by a person: saving creates a new version authored by "You".
- Saving and error: a busy indicator; on error, unsaved edits stay in the editor with Retry.

### Interactions
- Picking a version calls `onViewVersion`; Back to latest returns to `latestVersionId`.
- Restore calls `onRestore`, which creates a new version from the old one; nothing is deleted.
- Edit switches to an editor; Save calls `onSaveEdit`; Escape cancels after confirming if there are changes.

### Edge cases
- The person edits while the agent is writing: the agent's change waits and a banner offers to review it, so neither overwrites the other.
- Many versions: the picker lists newest first with summaries and stays scrollable.
- Under 640 px the canvas opens as a sheet with a Back to chat control that keeps the viewed version.
- A diff too large to show inline collapses unchanged runs ("12 unchanged lines").

## Must avoid
Long deliverables trapped in chat scroll, where every revision creates another buried copy.

## Engineering constraint
Version artifacts server-side with stable IDs; diff between versions rather than re-emitting whole.

## Accessibility
- The canvas is a `region` labelled with the title and version; announce new versions once ("v3 ready").
- Opening the canvas moves focus to its heading; closing returns focus to the reference that opened it.

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
