docs: Chat mode guide, spec, mock-ups and end-to-end screenshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Marcos Paulo
2026-10-21 22:43:11 -03:00
parent 13bc2d582e
commit 45f197250b
36 changed files with 262 additions and 3 deletions
+8
View File
@@ -76,6 +76,14 @@ are credited in `THIRD_PARTY.md`.
* **Not carried over**: comments, design tokens, AI image generation, video export, teams;
moving layers between pages; remote image URLs (see README Known gaps).
### 2026-10-21: Chat mode
* Chat layout next to the canvas (⌘J, `Design | Chat`), with three backends: Claude Code and Codex through their CLIs (`claude -p` stream-json, `codex exec --json`) against the app's MCP server, and the Anthropic API with a key in the OS credential store. One event stream for all three; tool calls run through the same MCP dispatch, so runs, review and revert behave as before.
* Thread: markdown replies, thinking row, tool cards, step groups, plan from `TodoWrite`, approval for destructive tools on the API backend, run checkpoints with Restore to here, usage line and context meter, `@ Selection` and image attachments, Stop.
* Chats live in the project's `chats/` folder. Settings → Chat holds CLI paths, the default backend and the API key.
* Offline CLI fix: an offline save writes no camera, and a top-level shape created without a position goes to the next free spot instead of the origin.
* Evidence: `src/chat/`, `src/ui/chat.rs`, `src/ui/chat_msgs.rs`, `src/ui/chat_md.rs`, `src/ui/settings.rs`, `src/mcp/exec.rs`, `tests/fixtures/chat/`, `specs/chat/`, `docs/chat.md`.
### 2026-10-21: Agent runs and change review
* Agent editing calls group into runs, snapshot each edited page as `Before agent run N`, and end on 30 seconds idle, `finish_working_on_nodes`, or project switch. Runs persist under `runs/` and keep the newest 50.
+12 -1
View File
@@ -136,6 +136,16 @@ EOF
<sub><code>preview.png</code> from an offline shell script, with no app running.</sub>
</p>
### Chat
Chat mode (⌘J, or `Design | Chat` in the top bar) puts a conversation next to the canvas. It runs Claude Code or Codex through their CLIs with PaperCraft's MCP server, or the Anthropic API with your own key, and it shows tool calls, checkpoints and run review in the thread. Chats are saved in the project's `chats/` folder. See [docs/chat.md](docs/chat.md).
<p align="center">
<img src="docs/screenshots/readme/chat.png" alt="Chat mode: the chat column on the left shows a finished agent run with its Review, Revert and Accept actions, and the canvas on the right" width="100%">
<br>
<sub>Chat mode with a finished agent run: review, revert or accept from the thread.</sub>
</p>
### Skills
[`skills/`](skills) holds agent skills: `papercraft` (the build, screenshot, lint, fix loop and tool cheat sheet), `papercraft-design` (design-quality rules) and `papercraft-critique`. [docs/agents.md](docs/agents.md) is the full guide: install, registering with agents, scripts, runs and review, safety. [docs/design-system.md](docs/design-system.md) covers variables, components and libraries.
@@ -212,6 +222,7 @@ The scenario tests drive the editor with synthetic pointer and keyboard events;
| ⌘⌥⇧V | Variables | ⌘⌥C / ⌘⌥V | Copy / paste properties |
| ⌘= / ⌘- / ⇧0 | Zoom in / out / 100% | ⇧1 / ⇧2 | Zoom to fit / selection |
| PageUp / PageDown | Previous / next page | ⌘\\ | Show / hide UI |
| ⌘J | Design / Chat mode | ⌘L | Add selection to chat (Chat mode) |
Draw a frame around existing shapes and it adopts them; dragging a layer into or out of a frame re-parents it. Hold ⌘ while dragging to suspend snapping.
@@ -226,7 +237,7 @@ Draw a frame around existing shapes and it adopts them; dragging a layer into or
* **Variables:** gradients keep fixed colors; there are no text styles and no component variants or properties.
* **Booleans are destructive:** the result is a new vector path with flattened curves.
* **`write_html`** ignores margin, tables and transforms (it reports them as `ignoredStyles`) and does not fetch remote images; pass a local path or a `data:` URI.
* **Agents:** one project is open at a time in the app, and `move_nodes` stays on one page. There is no in-app prompt box; agents run outside and connect over MCP or the CLI.
* **Agents:** one project is open at a time in the app, and `move_nodes` stays on one page. The in-app Chat mode runs Claude Code, Codex or your own Anthropic key; other agents run outside and connect over MCP or the CLI.
* **Sharing** has no live presence (cursors, selections) yet.
* **Pen pressure** is simulated from stroke velocity on macOS tablets, because winit 0.30 does not expose it for mouse-like pen events.
* **Releases** cover macOS (Apple silicon) and Linux x86_64. Windows packaging scripts exist but are not part of releases.
+2 -2
View File
@@ -2,7 +2,7 @@
PaperCraft is an early alpha: a local-first infinite-canvas design app that agents can drive. This
file lists what is still missing, in priority order, with an honest status. Done items are checked
against `src/mcp/tools.rs` and the UI code (2026-10-12); rows 7, 8, 12, 15, 23, 25, 32, 34, 39, 40, 45, 46 and section 6 were updated 2026-10-21. Milestone history is in
against `src/mcp/tools.rs` and the UI code (2026-10-12); rows 7, 8, 12, 15, 23, 25, 32, 34, 39, 40, 44, 45, 46 and section 6 were updated 2026-10-21. Milestone history is in
[PROGRESS.md](PROGRESS.md); known rendering and import limits are in the README.
Status: **done** (works today), **partial** (some of it works), **todo**.
@@ -84,5 +84,5 @@ Spec: [specs/agent-runs/spec.md](specs/agent-runs/spec.md), tasks: [specs/agent-
| 41 | Agent presence on the canvas | todo | Outline the node a tool call touches, label it, pause or stop the run. |
| 42 | `get_context` tool | todo | Selection, variables, nearby components and lint summary in one call, so an agent orients in one round trip. |
| 43 | Comments assigned to agents | todo | `@agent` in a thread triggers an edit and a reply. Needs threaded comments. |
| 44 | In-app prompt bar | todo | Run a prompt on the selection through the user's own key or local CLI. Largest scope: provider config, key storage, cost. |
| 44 | In-app prompt bar | **done** | Chat mode: Claude Code, Codex or your own Anthropic key; see `docs/chat.md`. |
| 45 | Correct tool annotations | **done** | `lint_design` no longer sends `readOnlyHint`; `fix: true` edits are classified. |
+2
View File
@@ -8,6 +8,8 @@ PaperCraft exposes one set of 61 tools three ways. They all run the same code in
| MCP over stdio, `papercraft-mcp` | the client only starts stdio servers | for calls; `tools/list` works offline |
| Shell, `papercraft-cli` | the agent has a terminal and you want no tool schemas in its context | for `call`; `list`, `schema` and `icons` work offline |
If you would rather talk to the agent inside the app, Chat mode (⌘J, or `Design | Chat` in the top bar) is the in-app alternative. It runs Claude Code or Codex through their CLIs against the same MCP server, or the Anthropic API with your own key, and keeps the conversation next to the canvas, with the same runs, review and revert. The setup and the thread are in [chat.md](chat.md). The rest of this guide covers driving the app from your own terminal or agent client.
## Install
1. Get the app and the tools: the DMG and the `papercraft-cli` zip from the [releases](https://git.marcospaulo.dev.br/masi/papercraft/releases), or `cargo build --release` (binaries `papercraft`, `papercraft-cli`, `papercraft-mcp` in `target/release/`).
+92
View File
@@ -0,0 +1,92 @@
# Chat mode
Chat mode puts a conversation with an agent next to the canvas. Press ⌘J, or use the `Design | Chat` switch in the top bar, to turn it on. The chat column sits on the left (420 to 640 px, 480 by default, resizable) and the live canvas fills the rest. The layers and inspector are hidden; the inspector can be opened as a narrow overlay on the canvas's right edge. Design mode works as before, and a chat keeps running in the background when you switch back.
The mode is remembered per project. Entering a project that was last left in Chat mode opens in Chat mode and shows its newest chat.
Chat mode is native only. The web build shows the switch disabled with a note.
## Backends
Chat drives the same tools as an agent connected over MCP, so the edits, runs, undo and revert behave the same whichever backend you use. There are three kinds of backend.
**Claude Code** runs `claude -p` with streaming JSON in and out. PaperCraft writes an MCP config that points the CLI at the app's own server on `127.0.0.1`, allows the `mcp__papercraft__*` tools, and passes `--strict-mcp-config`, so the CLI sees only PaperCraft's server. The PaperCraft skill text goes in through `--append-system-prompt-file`. The working directory is the project folder. Follow-up messages resume the CLI session by its id, so the agent keeps its context.
**Codex** runs `codex exec --json` in the project folder. The PaperCraft server is added with a `-c mcp_servers.papercraft.url=...` override, and its tools are pre-approved (`default_tools_approval_mode="approve"`), so Codex does not stop to ask for each one. The PaperCraft instructions are prepended to the first prompt of a chat; resumed sessions do not repeat them. Follow-ups resume the Codex session the same way.
**API endpoints** call a model over HTTPS with your own key. Settings → Chat holds a list of endpoints, each with a name, a format, a base URL, a model and an optional key. The app's tool list is sent as the API tools, and every tool call runs through the same code path as an MCP call on the UI thread. The loop continues until the model ends its turn, for up to 40 tool rounds per message.
* **Format**: `Anthropic` is the Messages API (`POST {base}/v1/messages`, `x-api-key`). `OpenAI` is Chat Completions (`POST {base}/chat/completions`, `Authorization: Bearer`, streaming `choices[].delta`, tools as `{"type":"function",...}`, tool results as `tool` messages). Both give the same thread: text, tool cards, approvals, Stop, the usage line.
* **Base URL and model** are free text, for example `https://api.anthropic.com`, any Anthropic-compatible URL, or `http://127.0.0.1:11434/v1`. The key is optional, so local servers need none.
* A default endpoint named `Anthropic` (`https://api.anthropic.com`, `claude-opus-5-5`) exists from the start, so the first run needs only a key.
* The agent picker in the chat header lists Claude Code, Codex, then every endpoint by name. Each chat remembers its endpoint.
* **Claude-only fields** (effort, the thinking summary, the refusal fallback and its beta header, eager tool-input streaming, prompt-cache breakpoints) are sent only when the base URL host is `api.anthropic.com`. For that host the model menu offers `claude-opus-5-5`, `claude-sonnet-5-5` and `claude-haiku-5-5`, effort is `medium` by default (`low` and `high` in the menu), and the Thinking row shows the summary. Every other endpoint gets a plain request. Missing `usage`, thinking or cache fields in the reply are fine.
* **OpenAI format and images**: attached images are sent as `image_url` data URIs. A tool message in this format carries text only, so a tool result with an image (such as `get_screenshot`) goes back as the text `[image omitted]`, and the image follows as an extra user message in the same round.
* **Usage line**: input, output and cached tokens, as the server reports them; nothing when it reports none. A cost estimate shows only for the Claude models in the small price table in `src/chat/endpoint.rs`. It is not a bill.
* `ANTHROPIC_API_KEY` in the environment is used for `api.anthropic.com` when no key is stored. `PAPERCRAFT_ANTHROPIC_BASE_URL` points Anthropic-format requests at another URL (for tests and proxies).
## Setup
Chat looks for `claude` and `codex` on `PATH`. A missing CLI is not an error: its row says `Not found` and shows the install command:
* Claude Code: `npm install -g @anthropic-ai/claude-code`
* Codex: `npm install -g @openai/codex`
Install one of them, or add a key, then press Detect again on the Chat page. Settings → Chat shows each CLI found with its version.
The Chat page also holds:
* **Paths**: an override for the `claude` and `codex` binaries, for when they are not on `PATH` or you want a specific install. Empty means the first one on `PATH`.
* **Default backend**: the backend a new chat starts with. The agent picker in the chat header changes it per chat.
* **API endpoints**: add, edit and remove endpoints (name, format, base URL, model) and set the default one. Each has a key field with Save and Remove. Keys are stored in the macOS Keychain, Windows Credential Manager or the Linux Secret Service, under the service name `PaperCraft` and the user `api-key:<base URL>`. An older single key (`anthropic-api-key`) is still read for `api.anthropic.com` and moves to the new entry the next time you save it. Removing a key makes that endpoint stop until a key is saved again (for `api.anthropic.com`; other endpoints work without one).
* **Defaults for new chats**: the backend, the endpoint, and the effort for `api.anthropic.com`.
Settings from before endpoints existed (`api_model`) move into the default endpoint on first load. These defaults and the endpoint list (never the keys) are saved in `chat_settings.json` in your config folder (`~/Library/Application Support/papercraft/` on macOS). They are not part of any project.
## The thread
The header has the chat title (rename), the chat list (new chat, switch, delete), the agent picker (for example `Claude Code · Opus 5.5`) and the context meter. The meter shows the tokens in the context at the last model call against a 200k window, and turns amber past 75%. It is hidden for Codex, which reports no per-call usage.
Replies are markdown: paragraphs, lists, code blocks with a copy button, inline code and links. While a turn runs, the reply streams in.
* **Thinking**: when the backend reports reasoning, a row shows "Thinking…" while it runs and collapses to a summary when it ends. Expand it to read the text.
* **Tool cards**: one card per tool call with the verb and target (for example "Drawing layout · Card"), a spinner while it runs, and a failed state with the error. Args and result are collapsible. `get_screenshot` results show a thumbnail.
* **Step groups**: consecutive tool calls in one reply fold into one line ("Built the card · 5 steps") that expands to the cards.
* **Plan**: when the agent keeps a todo list (Claude Code's `TodoWrite`), the latest list is pinned above the composer with a "done of total" count.
* **Approval**: on the API backend, the destructive tools (`delete_pages`, `disable_library`, `import_design_system`, and `delete_nodes` on more than 20 layers) wait for Approve or Deny before they run. Deny returns an error to the model, which can then carry on. The CLI backends do not show this card. They use the CLI's own permission rules, and in non-interactive mode a tool that needs permission is denied. The papercraft tools are pre-allowed on both CLIs.
* **Checkpoint**: when an agent run starts, the thread shows "Checkpoint · before run N". "Restore to here" reverts that run and every run after it, through the same revert as the Agent view.
* **Run card**: when a run ends, "Agent finished, N changes" with Review, Revert and Accept. These are the same actions as the run review on the canvas.
* **Errors**: a failed turn shows a red row with Retry. A missing CLI or key shows a setup row instead.
* **Canvas follow**: when a tool call targets a node, the canvas pans to it, unless you moved the camera in the last 10 seconds.
## Composer and attachments
Type in the composer, ⏎ sends and ⇧⏎ adds a line. The empty state offers suggestions built from the project, such as "Design a sign-in screen using this project's variables" or "Lint this page and fix warnings".
* **Selection**: the `@ Selection` chip adds the current selection to the message (also ⌘L). It is sent as a short line with the layer name and keys.
* **Images**: the image button opens a file picker. You can also paste a clipboard image or drop image files on the column (PNG, JPEG, GIF, WebP).
For the API backend, images are sent as image blocks, so the model sees them. For the CLIs, each image is saved under `.papercraft-chat-tmp/` in the project and its path is added to the prompt for the agent to read.
While a turn runs, the send button becomes **Stop**. Stop asks the backend to interrupt: the CLIs get an interrupt, or SIGINT, then a kill after 3 seconds. Every backend ends within 3 seconds. Tools that already ran stay applied, and the partial run can be reviewed or reverted like any other.
## Storage
A chat is one file in the project: `chats/<id>.json`, holding the title, the backend (kind, model, effort, CLI session id) and the messages with their tool calls, usage and run ids. The API backend also keeps `chats/<id>.api.json`, the raw messages array (Messages format with thinking blocks, or Chat Completions format, whichever the endpoint speaks), so a follow-up replays exactly. The chat records its endpoint id. Deleting a chat asks first and removes both files, and its saved images.
Projects without a `chats/` folder load unchanged. The newest chat opens when you enter Chat mode.
## Privacy
PaperCraft never writes an API key into a project file, a chat or a log. The key lives in the OS credential store. The CLIs run with your own logins and do not get the API keys from the environment: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `OPENAI_API_KEY` and `CODEX_API_KEY` are removed from the CLI's environment, so Claude Code and Codex bill your account, not a key you set for PaperCraft. The MCP server listens on loopback only, as it does for any other client.
Prompts, project content and tool results go to whichever backend you chose: the CLI's model service, or the base URL of the endpoint you picked. Nothing goes to any other service. If a server echoes your key in an error message, the app hides it before showing the message.
## Limits
* One turn per project at a time. Sending while a turn runs is refused with "A turn is already running"; use Stop first. Switching to another project stops a running turn.
* The web build has no Chat mode.
* The canvas shows the run's outlines after the turn ends, when the run is reviewed. They are not drawn live while the agent works.
* The API backend stops after 40 tool rounds in one message and says so.
* Codex reports no per-call usage, so the context meter is hidden for it.
* The CLI backends need the CLI installed and logged in. Chat does not install or log in for you.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 KiB

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 KiB

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 KiB

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 KiB

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 KiB

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 KiB

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 KiB

After

Width:  |  Height:  |  Size: 864 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

+113
View File
@@ -0,0 +1,113 @@
# Chat layout: design by conversation
## Why
Today an agent drives PaperCraft from outside: a terminal running Claude Code or Codex, pointed at the MCP server. The app shows a working pill, the agent runs and their review, but the conversation itself lives elsewhere. OpenDesign and Cursor's agent view put the conversation next to the result. PaperCraft already has everything behind that: 61 tools, agent runs with auto-versions and review, skills, design systems and libraries.
## Decisions (user, 2026-10-21)
* Backend: **both** local agent CLIs (Claude Code, Codex) and a bring-your-own-key Anthropic API path.
* Layout: a separate **Chat layout**, chat column left, live canvas right, switched from the top bar (`Design | Chat`).
* Conversations live **in the project** (`chats/`), linked to the agent runs they produce.
* Mock-ups first.
## What exists today
* MCP server on `127.0.0.1:<PAPERCRAFT_MCP_PORT>` (default 29980), loopback only, rejects `Origin` headers (`src/mcp/server.rs`). Every tool runs on the UI thread through `App::run_mcp_calls` and `mcp::exec::call`.
* Agent runs (`src/runs.rs`): editing calls grouped into runs with auto-versions, review, revert, the finish toast.
* Tool list and schemas: `mcp::server::tool_list()`, `crate::mcp::tools::edits_doc`.
* Skills: `skills/papercraft`, `skills/papercraft-design`, `skills/papercraft-design-system` (in repo) and the installed copies under `~/.agents/skills/`.
* No HTTP client for outbound API calls except `reqwest` (already in `Cargo.lock` through rmcp) and `curl` used by the CLI's icon download.
## Layout
* **Mode switch** in the top bar: `Design | Chat` (segmented control), shortcut ⌘J. The mode is per window and remembered per project.
* **Chat mode**: left column 420–640 px wide (resizable, default 480), canvas on the right with the normal top bar, zoom and a slim tool strip; the layers, inspector and side views are hidden (a toggle brings the inspector back as a narrow overlay).
* **Chat column**, top to bottom:
* header: chat title (rename), chat list menu (new chat, switch, delete with confirm), agent picker chip (`Claude Code · Opus 5.5`, `Codex · gpt-6.1-sol`, `API · Opus 5.5`) and a settings icon;
* message list: user messages; assistant text streamed as it arrives (markdown: paragraphs, lists, code, inline code, links); thinking/progress shown as a collapsed one-line "Thinking…" row when the backend provides it; **tool-call cards**: verb + target ("Drawing layout · Card", from `agent_verb`), status (running spinner, done, failed with the error), collapsible args/result JSON, a thumbnail for `get_screenshot` results; a **run card** when an agent run ends (`Agent finished, N changes` with Review, Revert, Accept, reusing the run review); errors as a red row with Retry;
* composer: multi-line input (⏎ sends, ⇧⏎ newline), attachment chips (current selection as `@Frame name` chips added by a button or ⌘L, images by paste/drag, the active page), a Stop button while a turn runs, a model/effort menu for the API backend;
* empty state: suggestions built from the project ("Design a sign-in screen using this project's variables", "Lint this page and fix warnings", "Make a dark mode").
* **Patterns from shadcn AI Elements** (ui.shadcn.com/docs/ai-elements; React components, so only their patterns and names are reused, built from `src/ui/kit.rs`):
* **Chat** thread with a message scroller that sticks to the bottom while streaming and keeps your place when you scroll up (a "Jump to latest" pill appears).
* **Message**: user and assistant turns; assistant actions on hover (copy, retry).
* **Reasoning**: collapsible, streams while open, collapses to "Thought for 12s" when done.
* **Tool**: one card per call, states pending / running / done / error, input and output sections collapsible; a screenshot output shows inline.
* **Task / Chain of Thought**: consecutive tool calls in one assistant turn group into a step list ("Built the card · 5 steps") that expands.
* **Plan / Queue**: when the agent keeps a todo list (Claude Code `TodoWrite`, or the API backend's plan), show remaining and completed items pinned above the composer.
* **Confirmation**: approve or deny before destructive tools run (`delete_pages`, `delete_nodes` on more than 20 layers, `disable_library`, `import_design_system`); API backend pauses the loop; CLI backends rely on the CLI's own permission prompt where it exists, else the tool stays allowed and the run can be reverted.
* **Checkpoint**: each agent run's auto-version shows as a checkpoint in the thread; "Restore to here" reverts the runs after it (reuses `runs::revert`).
* **Context**: a small meter in the header with tokens used of the model's context window (API and Claude Code report usage).
* **Model Selector**: backend + model menu and an effort menu.
* **Prompt Input**: multi-line field, attachments (selection, images, page), send / stop.
* **Suggestion**: one-click prompts in the empty state and after a run ("Lint this", "Make a dark mode", "Review changes").
* **Subagent**: a nested, collapsible block when a CLI agent spawns a sub-agent.
* **Code Block**: fenced code in replies with a copy button.
* Not used: Sources, Inline Citation, File Tree, Terminal, Stack Trace, Test Results (not design work).
* **Canvas side**: follows the agent: when a tool call targets a node, the canvas pans to it unless the user moved the camera in the last 10 s; the run overlay (green/yellow/red outlines) shows while a run is open.
* Design mode keeps working as today; the chat keeps running in the background when you switch.
## Backends
All backends drive the same tools, so runs, undo, review and revert work the same.
### Local agent CLIs
* **Claude Code**: spawn `claude -p` with streaming JSON in and out (`--input-format stream-json --output-format stream-json --verbose`), an MCP config pointing at the app's server (`--mcp-config` with `{"mcpServers":{"papercraft":{"type":"http","url":"http://127.0.0.1:<port>/mcp"}}}`), the papercraft tools allowed (`--allowedTools "mcp__papercraft__*"`), working directory = the project folder, and the system prompt appended with the PaperCraft skill text. Resume with `--resume <session_id>` (stored on the chat). Exact flags are checked against `claude --help` at implementation time and wrapped in one adapter.
* **Codex**: spawn `codex exec --json` with the server added through `-c mcp_servers.papercraft.url=...` (or the current equivalent, checked against `codex exec --help`), resume with its session id. Same adapter shape.
* **Detection**: Settings → Chat lists the CLIs found on `PATH` (plus a path override) with their version; a missing CLI shows install hints, never an error dialog.
* **Events**: each adapter turns the CLI's JSON lines into one internal event stream: `Text(delta)`, `Thinking(delta)`, `ToolStart{id,name,args}`, `ToolEnd{id,result|error}`, `TurnEnd{usage?}`, `Error`. Tool calls also arrive through the MCP server, so the card status is confirmed by the app itself (`log_agent`), not only by the CLI's report.
* **Stop**: sends an interrupt (stdin control message where supported, else SIGINT, then kill after 3 s). The run so far stays reviewable.
* **Security**: the CLI runs with the user's own permissions and logins; PaperCraft passes no keys. Only papercraft tools are pre-allowed; other tools follow the CLI's own permission settings (non-interactive: denied).
### Anthropic API (bring your own key)
* Raw HTTPS to `POST https://api.anthropic.com/v1/messages` with `stream: true` (no Rust SDK exists), using `reqwest` (already a dependency).
* **Key**: entered in Settings → Chat, stored in the macOS Keychain (Linux: Secret Service; Windows: Credential Manager) through the `keyring` crate, never in project files or logs. `ANTHROPIC_API_KEY` in the environment is used when no key is stored.
* **Model**: default `claude-opus-5-5`; menu also offers `claude-sonnet-5-5` and `claude-haiku-5-5`. Thinking adaptive (default on these models), `output_config.effort` set explicitly (default `medium`, menu low/medium/high), `thinking.display: "summarized"` so the Thinking row has text. Server-side refusal fallback on (`fallbacks: "default"` with beta `server-side-fallback-2026-07-01`; for Haiku, show the refusal instead).
* **Tools**: the app's own tool list (`tool_list()` converted to Messages API `tools`: name, description, input_schema), with `eager_input_streaming: true`; inputs are parsed tolerantly, validated against the schema before running, and run through `mcp::exec::call` on the UI thread like any MCP call (so runs and undo behave the same). Several `tool_use` blocks in one turn run in order and all `tool_result` blocks go back in one user message; failures go back as `is_error: true`. No forced `tool_choice` (not supported on these models).
* **Loop**: stream → collect `tool_use` → run → append `response.content` unchanged (thinking blocks included) + tool results → next request, until `stop_reason` is `end_turn`; `max_tokens` → continue once with a note; `refusal` → show the category; `pause_turn` → continue. Max 40 tool rounds per user message (then stop and say so).
* **Caching**: `cache_control` breakpoints on the tool list and on the system prompt (skill text + project context), stable ordering so they hit; per-turn volatile context (selection, active page) goes in the user message, not the system prompt. `usage.cache_read_input_tokens` is logged to verify.
* **Images**: `get_screenshot` results go back as image blocks so the model sees what it made; user-attached images too.
* **Cost line**: per turn input/output/cache tokens and an estimated cost from a small price table, shown under the turn in a muted row.
* Implementers read the bundled claude-api skill files `curl/examples.md`, `shared/tool-use-concepts.md`, `shared/prompt-caching.md`, `shared/model-migration.md` (Opus 5.5 section) before writing this adapter.
### Custom endpoints (generic, no provider list)
The API backend is not tied to Anthropic. Settings → Chat holds one or more **endpoints**, each `{ name, format, base_url, model, key }`:
* `format`: `anthropic` (Messages API, `POST {base}/v1/messages`, `x-api-key`) or `openai` (Chat Completions, `POST {base}/chat/completions`, `Authorization: Bearer`, streaming SSE `choices[].delta`, tools as `{"type":"function","function":{name,description,parameters}}`, `tool_calls` with `arguments` streamed as string fragments, results as `role: "tool"` messages).
* `base_url`: free text (e.g. `https://api.anthropic.com`, any Anthropic-compatible URL, `http://127.0.0.1:11434/v1`). `model`: free text. `key`: optional (local servers need none), stored in the OS keychain under the endpoint's base URL, never in settings files.
* Claude-only request fields (`output_config.effort`, `thinking.display`, `fallbacks` and its beta header, `eager_input_streaming`) are sent only when the base URL host is `api.anthropic.com`; other Anthropic-format endpoints get a plain request (adaptive thinking omitted), and are tolerant of missing `usage`, `thinking` blocks, or `cache_*` fields.
* The cost line shows a price only for models in the built-in Claude price table; otherwise tokens only (or nothing when the server reports no usage).
* No provider names, presets or logos in the app. A default endpoint `Anthropic` (`anthropic`, `https://api.anthropic.com`, `claude-opus-5-5`) exists so the first-run path stays one key away.
* The agent picker lists every configured endpoint by its name.
### Not in scope (v1)
* Named provider presets.
* Multiple chats running at once in one project (one active turn per project).
* Voice, file uploads other than images.
## Storage
* `chats/<id>.json` in the project folder: `{ id, title, created, updated, backend: {kind, model?, effort?, cli_session?}, messages: [ {role, text, tools:[{id,name,args,result,error,ms}], run_id?, usage?, at} ] }`. API backend also keeps the raw `messages` array (for exact replay, including thinking blocks) in `chats/<id>.api.json`.
* Chats sync with the project (relay). Deleting a chat deletes both files after a confirm.
* The project's newest chat opens when entering Chat mode.
## MCP and CLI
* No new tools needed. `papercraft-cli` is unchanged.
## Acceptance
* Chat mode toggles from the top bar and ⌘J; layout matches the mock-ups at 1440p and 1280x800.
* With Claude Code installed: "Create a 390x844 artboard named Test with a centred title" streams text, shows tool cards for `create_artboard` and `write_html`, the canvas follows, a run card appears at the end, Review/Revert work; a follow-up message continues the same CLI session.
* Same with Codex.
* With an API key: the same prompt works through the API backend; thinking row shows a summary; cost line shows tokens; a second turn reads from cache (`cache_read_input_tokens > 0`).
* Stop during a long turn ends it within 3 s; tools already applied stay and are revertible.
* Missing CLI and missing key show setup states, not errors; a bad key shows the API error message.
* Chats persist in `chats/` and reopen after restart; a project without `chats/` loads unchanged.
* The key never appears in project files, logs or crash output (test greps the project folder and the log after a run).
* Tests: event parsing for each adapter from recorded JSON fixtures (Claude Code stream-json, Codex --json, Anthropic SSE incl. thinking, tool_use with input_json_delta, refusal, max_tokens); tool-schema conversion; chat file round trip; the API loop with a fake HTTP server (two tool rounds then end_turn).
* `cargo test --no-fail-fast`, wasm and Windows checks pass (chat backends are native-only; the web build shows Chat mode disabled with a note).
+33
View File
@@ -0,0 +1,33 @@
# Tasks
Stop on failure. Tasks in one wave touch different files and run in parallel; a wave starts when the previous one passes `cargo test --lib`. A read-only review after waves 2 and 4; real findings are fixed before moving on.
## Wave 0: design (parallel with wave 1)
- [x] 0. **Mock-ups** in the `PaperCraft` design file, new page `Chat`: `Chat / Empty`, `Chat / Streaming` (text + tool cards + thinking row), `Chat / Run finished` (run card, canvas overlay), `Chat / Agent picker` (menu open, CLIs found/missing, API), `Chat / Setup` (no CLI, no key), `Settings / Chat`, at 2560x1440 and a 1280x800 variant of `Chat / Streaming`. The user's app had another project open, so this ran through the 0.5.0 offline CLI on the `PaperCraft` project (each call refuses if the app has it open); never `open_file` in the user's app.
## Wave 1: model and storage (parallel)
- [x] 1. **Chat model and storage** (new `src/chat/mod.rs`, `src/chat/store.rs`): types from the spec, `chats/` load/save/list/delete, legacy project without `chats/`. Tests: round trip, listing order, delete.
- [x] 2. **Event stream and adapters' parsing** (new `src/chat/events.rs`, `src/chat/claude_code.rs`, `src/chat/codex.rs`, `src/chat/anthropic.rs` parsing half): the internal `Event` enum and pure parsers from recorded fixtures (`tests/fixtures/chat/`). Record real fixtures by running the CLIs once on a scratch project with a throwaway prompt (no user data); for the API, build SSE fixtures from the documented event shapes. Tests per backend.
## Wave 2: backends (after wave 1; 3, 4, 5 parallel)
- [x] 3. **Claude Code process** (`src/chat/claude_code.rs`): detect, spawn with MCP config and allowed tools, stream stdin/stdout on a worker thread, resume, stop. Integration test gated on `claude` being on PATH (skipped otherwise).
- [x] 4. **Codex process** (`src/chat/codex.rs`): same for `codex exec --json`. Gated test.
- [x] 5. **Anthropic API loop** (`src/chat/anthropic.rs`, `keyring` dependency): key storage, request building (tools from `tool_list()`, caching, effort, thinking display, fallbacks), SSE streaming, tool loop through a channel to the UI thread (`mcp::exec::call`), usage and cost. Test with a fake HTTP server.
## Wave 3: UI (after wave 2 and the mock-ups; 6 then 7 and 8 parallel)
- [x] 6. **Chat layout and mode switch** (`src/ui/chat.rs`, `src/ui/mod.rs`): Design | Chat toggle, ⌘J, column + canvas layout, resizable column, remembered per project.
- [x] 7. **Messages and composer** (`src/ui/chat.rs`): markdown text, thinking row, tool cards, run card (reuse run review actions), errors with Retry, composer with attachments, Stop, empty state suggestions, agent picker, chat list. Canvas follows tool targets.
- [x] 8. **Settings → Chat** (`src/ui/settings.rs`): CLI detection and paths, API key (Keychain), default backend/model/effort.
## Wave 4: finish
- [x] 9. **End-to-end check** with each available backend on a scratch copy of a project (own app instance on its own port and `PAPERCRAFT_HOME`), screenshots against the mock-ups, key-leak grep.
- [x] 10. **Docs and release notes**: `docs/chat.md`, README section and screenshot, `docs/agents.md`, ROADMAP row 44 done, PROGRESS entry; full test suite, wasm and Windows checks.
## Model per task
Claude Sonnet: 0, 2, 3, 5, 6, 7, 9. Claude Haiku: 1, 8, 10. Codex `gpt-6.1-sol`: 4 and the reviews (Claude Sonnet if Codex is unavailable).