diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000..35049cbc
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --"
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..49e1685c
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,25 @@
+# DrawCraft — instructions for agents
+
+DrawCraft is a clean-room, open-source, Rust-native vector illustration app targeting Adobe Illustrator parity (and beyond). It runs natively on macOS, Windows and Linux, and on the web via WASM. Siblings: `../photocraft` (Photoshop-class) and `../printcraft` (Acrobat-class); same conventions.
+
+## Start every session here
+1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md` §3 and the relevant `plan/architecture.md` section. Behaviour reference: `plan/illustrator/*.md`.
+2. Follow the autonomous operation protocol (`plan/execution-plan.md` §7): orient → plan → implement + test → verify → record → commit. Don't stop to ask unless §7 lists the decision as the user's.
+
+`plan/` is gitignored (local only).
+
+## Non-negotiables
+- **Clean-room.** Never read, disassemble or copy anything inside the Illustrator bundle (names/listings only). Never copy Adobe icons, artwork, presets or wording beyond feature names. Behaviour comes from public docs and black-box observation of the running app with synthetic documents only (screenshots by window id, stored under `plan/illustrator/screenshots/`, never committed). Never copy GPL/AGPL code (Inkscape, lib2geom…).
+- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*` (id, label, menu path, shortcut, params doc, `enabled`, `run`) + tests. Tools emit commands (Begin/Preview/Commit). UI-only commands live in `crates/ui-egui/src/menus.rs` (`UI_COMMANDS`). The control channel and MCP reach all of them.
+- **Layering** is enforced by `cargo xtask layers`. Nothing below L6 depends on egui/eframe/winit/rfd.
+- **The UI is thin**: panels read engine state and act through `app.run(id, params)`. Colours come from `theme::Tokens`.
+- **Rust only** (no handwritten JS/TS). **Never break wasm** (`cargo xtask wasm`).
+- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, wasm). One task id per commit (`M2.1: pen tool`).
+
+## Running and looking at the app
+- `cargo run --release -p drawcraft -- --control 7979 [file.svg|file.drawcraft]`
+- Drive it: JSON lines on `127.0.0.1:7979`, e.g. `{"id":1,"method":"engine.execute","params":{"command":"shape.rectangle","params":{"x":10,"y":10,"width":100,"height":50}}}` then `{"id":2,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}`. Methods: `crates/ui-egui/src/control.rs`.
+- **For UI work, look at the result** (take `ui.screenshot`, read the PNG) and compare with `plan/illustrator/02-ui-ux.md` / `10-observed-ui.md`.
+- MCP: `drawcraft-cli mcp` (see `docs/mcp.md`).
+- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`.
+- Parallel agents: separate `CARGO_TARGET_DIR` per agent; edit only the crates you own; write manifests atomically.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..49e1685c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,25 @@
+# DrawCraft — instructions for agents
+
+DrawCraft is a clean-room, open-source, Rust-native vector illustration app targeting Adobe Illustrator parity (and beyond). It runs natively on macOS, Windows and Linux, and on the web via WASM. Siblings: `../photocraft` (Photoshop-class) and `../printcraft` (Acrobat-class); same conventions.
+
+## Start every session here
+1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md` §3 and the relevant `plan/architecture.md` section. Behaviour reference: `plan/illustrator/*.md`.
+2. Follow the autonomous operation protocol (`plan/execution-plan.md` §7): orient → plan → implement + test → verify → record → commit. Don't stop to ask unless §7 lists the decision as the user's.
+
+`plan/` is gitignored (local only).
+
+## Non-negotiables
+- **Clean-room.** Never read, disassemble or copy anything inside the Illustrator bundle (names/listings only). Never copy Adobe icons, artwork, presets or wording beyond feature names. Behaviour comes from public docs and black-box observation of the running app with synthetic documents only (screenshots by window id, stored under `plan/illustrator/screenshots/`, never committed). Never copy GPL/AGPL code (Inkscape, lib2geom…).
+- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*` (id, label, menu path, shortcut, params doc, `enabled`, `run`) + tests. Tools emit commands (Begin/Preview/Commit). UI-only commands live in `crates/ui-egui/src/menus.rs` (`UI_COMMANDS`). The control channel and MCP reach all of them.
+- **Layering** is enforced by `cargo xtask layers`. Nothing below L6 depends on egui/eframe/winit/rfd.
+- **The UI is thin**: panels read engine state and act through `app.run(id, params)`. Colours come from `theme::Tokens`.
+- **Rust only** (no handwritten JS/TS). **Never break wasm** (`cargo xtask wasm`).
+- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, wasm). One task id per commit (`M2.1: pen tool`).
+
+## Running and looking at the app
+- `cargo run --release -p drawcraft -- --control 7979 [file.svg|file.drawcraft]`
+- Drive it: JSON lines on `127.0.0.1:7979`, e.g. `{"id":1,"method":"engine.execute","params":{"command":"shape.rectangle","params":{"x":10,"y":10,"width":100,"height":50}}}` then `{"id":2,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}`. Methods: `crates/ui-egui/src/control.rs`.
+- **For UI work, look at the result** (take `ui.screenshot`, read the PNG) and compare with `plan/illustrator/02-ui-ux.md` / `10-observed-ui.md`.
+- MCP: `drawcraft-cli mcp` (see `docs/mcp.md`).
+- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`.
+- Parallel agents: separate `CARGO_TARGET_DIR` per agent; edit only the crates you own; write manifests atomically.
diff --git a/Cargo.lock b/Cargo.lock
index 9951aed0..2b22c3ac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -802,6 +802,10 @@ dependencies = [
[[package]]
name = "drawcraft-cli"
version = "0.1.0"
+dependencies = [
+ "drawcraft-mcp",
+ "serde_json",
+]
[[package]]
name = "drawcraft-color"
@@ -883,6 +887,17 @@ dependencies = [
[[package]]
name = "drawcraft-mcp"
version = "0.1.0"
+dependencies = [
+ "drawcraft-doc",
+ "drawcraft-engine",
+ "drawcraft-format",
+ "drawcraft-geom",
+ "drawcraft-render",
+ "drawcraft-svg",
+ "drawcraft-tools",
+ "log",
+ "serde_json",
+]
[[package]]
name = "drawcraft-pathops"
@@ -988,6 +1003,18 @@ dependencies = [
[[package]]
name = "drawcraft-web"
version = "0.1.0"
+dependencies = [
+ "drawcraft-engine",
+ "drawcraft-ui-egui",
+ "eframe",
+ "egui",
+ "js-sys",
+ "log",
+ "rfd",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
[[package]]
name = "ecolor"
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..3130911b
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,13 @@
+DrawCraft
+Copyright the DrawCraft contributors. Licensed under MIT OR Apache-2.0.
+
+This product bundles third-party material under the following licences:
+
+- Lucide icons (assets/icons/*.svg except dc-*.svg) — ISC License. See assets/icons/LICENSE-lucide.txt.
+- DrawCraft icons (assets/icons/dc-*.svg) — original work, MIT OR Apache-2.0.
+- Source Sans 3 and Source Serif 4 (assets/fonts/Source*) — SIL Open Font License 1.1. See assets/fonts/OFL-SourceSans3.txt, OFL-SourceSerif4.txt.
+- Inter (assets/fonts/Inter-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-Inter.txt.
+- JetBrains Mono (assets/fonts/JetBrainsMono-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-JetBrainsMono.txt.
+
+Rust dependencies (vello_cpu, kurbo, peniko, skrifa, harfrust, usvg, linesweeper, egui, eframe, …) are
+permissively licensed (MIT/Apache-2.0/BSD/ISC/Zlib) and fetched through crates.io.
diff --git a/README.md b/README.md
index c9ca187b..52eba9be 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,19 @@
DrawCraft
=========
-By the artcraft team
+By the artcraft team.
+
+A fast, open-source, cross-platform vector illustration app written in pure Rust — a clean-room
+reimplementation of the Adobe Illustrator workflow (tools, panels, menus, shortcuts) that runs on
+macOS, Windows, Linux and the web (WASM), and is fully drivable by agents over a control channel
+and MCP.
+
+```sh
+cargo run --release -p drawcraft # desktop app
+cargo run --release -p drawcraft -- --control 7979 # + JSON control channel
+cargo run --release -p drawcraft-cli -- mcp # MCP server (stdio)
+cargo xtask ci # fmt, clippy, tests, layering, wasm
+```
+
+Workspace: `crates/{geom,color,doc,pathops,text,effects,render,svg,pdf,format,tools,engine,ui-egui,mcp,testkit}`,
+`apps/{drawcraft,drawcraft-cli,drawcraft-web}`. See `CLAUDE.md` and `docs/`.
diff --git a/apps/drawcraft-cli/Cargo.toml b/apps/drawcraft-cli/Cargo.toml
index 60dd2088..70c358e1 100644
--- a/apps/drawcraft-cli/Cargo.toml
+++ b/apps/drawcraft-cli/Cargo.toml
@@ -4,8 +4,15 @@ version.workspace = true
edition.workspace = true
license.workspace = true
publish = false
+description = "DrawCraft command line: MCP server (stdio) and headless batch runs"
+
+[[bin]]
+name = "drawcraft-cli"
+path = "src/main.rs"
[lints]
workspace = true
[dependencies]
+drawcraft-mcp = { workspace = true }
+serde_json = { workspace = true }
diff --git a/apps/drawcraft-web/Cargo.toml b/apps/drawcraft-web/Cargo.toml
index c1a77c39..1b97ae73 100644
--- a/apps/drawcraft-web/Cargo.toml
+++ b/apps/drawcraft-web/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "drawcraft-web"
+description = "DrawCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,4 +9,21 @@ publish = false
[lints]
workspace = true
+[[bin]]
+name = "drawcraft-web"
+path = "src/main.rs"
+
[dependencies]
+drawcraft-engine = { workspace = true }
+drawcraft-ui-egui = { workspace = true }
+egui = { workspace = true }
+log = { workspace = true }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+# `wgpu` pulls egui-wgpu's defaults: WebGPU plus the WebGL2 fallback.
+eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
+rfd = { workspace = true }
+wasm-bindgen = "0.2"
+wasm-bindgen-futures = "0.4"
+js-sys = "0.3"
+web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] }
diff --git a/apps/drawcraft-web/Trunk.toml b/apps/drawcraft-web/Trunk.toml
new file mode 100644
index 00000000..5a4fff4e
--- /dev/null
+++ b/apps/drawcraft-web/Trunk.toml
@@ -0,0 +1,10 @@
+# Build with: trunk build --release (from apps/drawcraft-web)
+[build]
+target = "index.html"
+dist = "../../dist/web"
+public_url = "./"
+filehash = true
+
+[serve]
+addresses = ["127.0.0.1"]
+port = 8766
diff --git a/apps/drawcraft-web/index.html b/apps/drawcraft-web/index.html
new file mode 100644
index 00000000..beb2938a
--- /dev/null
+++ b/apps/drawcraft-web/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ DrawCraft
+
+
+
+
+
+
Loading DrawCraft…
+
+
diff --git a/apps/drawcraft-web/src/main.rs b/apps/drawcraft-web/src/main.rs
index f328e4d9..b0f087d4 100644
--- a/apps/drawcraft-web/src/main.rs
+++ b/apps/drawcraft-web/src/main.rs
@@ -1 +1,27 @@
-fn main() {}
+//! DrawCraft in the browser.
+//!
+//! Runs the same [`drawcraft_ui_egui::DrawcraftApp`] as the desktop app through eframe's web
+//! runner (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release`
+//! from this directory; see `docs/development.md` ("Web build").
+//!
+//! Differences from the desktop app:
+//! - no TCP control server (browsers can't listen on sockets);
+//! - File → Open uses the browser file picker; bytes arrive asynchronously through
+//! `Services::inbox`;
+//! - Save / Export trigger a browser download;
+//! - dropped files are read asynchronously by `web::WebShell` and delivered through the inbox.
+//!
+//! URL query flag: `?webgl` forces the WebGL2 backend instead of WebGPU.
+
+#[cfg(target_arch = "wasm32")]
+mod web;
+
+#[cfg(target_arch = "wasm32")]
+fn main() {
+ web::start();
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {
+ eprintln!("drawcraft-web only runs in the browser: build it with `trunk build --release` in apps/drawcraft-web");
+}
diff --git a/apps/drawcraft-web/src/web.rs b/apps/drawcraft-web/src/web.rs
new file mode 100644
index 00000000..d8925911
--- /dev/null
+++ b/apps/drawcraft-web/src/web.rs
@@ -0,0 +1,171 @@
+//! The browser shell: web `Services`, drag-and-drop, and the eframe web runner.
+
+use std::sync::{Arc, Mutex};
+
+use drawcraft_engine::Session;
+use drawcraft_ui_egui::{DrawcraftApp, Services};
+use wasm_bindgen::JsCast as _;
+
+type Inbox = Arc)>>>;
+
+const OPEN_EXTS: &[&str] = &["drawcraft", "svg", "png", "jpg", "jpeg", "gif", "webp"];
+const CANVAS_ID: &str = "drawcraft_canvas";
+const LOADING_ID: &str = "drawcraft_loading";
+
+pub fn start() {
+ eframe::WebLogger::init(log::LevelFilter::Info).ok();
+ wasm_bindgen_futures::spawn_local(async {
+ let Some(document) = web_sys::window().and_then(|w| w.document()) else {
+ log::error!("no document");
+ return;
+ };
+ let Some(canvas) = document
+ .get_element_by_id(CANVAS_ID)
+ .and_then(|e| e.dyn_into::().ok())
+ else {
+ log::error!("missing