From 09a708288742febdd1aa334c42f7010822dea75c Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 9 Oct 2026 13:05:34 -0400 Subject: [PATCH] M0.8: AGENTS.md/CLAUDE.md, ATTRIBUTION.md, parity report, wasm compatibility Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 126 +++++++++++++++++++++++++++++++++++ ATTRIBUTION.md | 31 +++++++++ CLAUDE.md | 126 +++++++++++++++++++++++++++++++++++ crates/text/src/fontdb.rs | 1 + crates/ui-egui/src/canvas.rs | 13 +++- docs/parity.md | 18 +++++ xtask/src/main.rs | 12 +++- 7 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 AGENTS.md create mode 100644 ATTRIBUTION.md create mode 100644 CLAUDE.md create mode 100644 docs/parity.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..7b603657 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,126 @@ +# FigCraft — instructions for agents + +FigCraft is a clean-room, open-source, pure-Rust (egui) reimplementation of the Figma design workflow: +interface design, auto layout, components and variants, styles and variables, prototyping and +inspection. It runs natively on macOS, Windows, Linux and FreeBSD, and in the browser via WASM. +Siblings: `../vectorcraft` (Illustrator-class), `../photocraft` (Photoshop-class), `../designcraft` +(InDesign-class) and the other Crafting Apps; same conventions. + +Standards and learnings shared across the crafting apps live in `../../craftrules` (checked out +next to `craft-apps/`, or `storytold/craftrules`). Read its `AGENTS.md` at the start of a session, +follow its standards, and contribute reusable learnings back there. Never code: repos don't share +code. + +## Start every session here +1. Read [`ROADMAP.md`](ROADMAP.md): where we stand, the prioritized gap list, and the time estimates. + Unless the user gives you a task, pick work from that list. +2. Read `plan/STATUS.md` and `plan/execution-plan.md` (local, gitignored) if present; behaviour + reference notes are in `plan/figma/*.md`. +3. Orient → plan → implement + test → verify (screenshot) → record (ROADMAP, devlog) → commit. + +`plan/` is gitignored (local only). + +## Non-negotiables + +### Clean-room +- Behaviour comes from public documentation and black-box observation of the running incumbent app + with synthetic documents only. Never read, disassemble or copy anything inside the incumbent's + application bundle, web bundle or network traffic (names and public listings only). +- Never copy GPL/AGPL code. +- Reference screenshots of the incumbent stay local under the gitignored `plan/figma/screenshots/` + and are never committed, published or used as assets. + +### Assets: no third-party product iconography or images — ever (absolute rule from the project owner) +- **Never add, copy, trace, redraw-from, embed or ship any icon, image, illustration, cursor, logo, + template, UI kit, preset library, font file or screenshot from Figma, Adobe, Avid, Autodesk or any + other product, or from any source whose licence doesn't allow it.** This includes "lookalike" + redraws of their icons. Breaking this rule is the worst thing you can do in this repo. +- Every image, icon, font or other asset must be one of: + - original work created for FigCraft by a contributor (licensed MIT OR Apache-2.0); + - OSI open source; + - public domain / CC0; + - Creative Commons with redistribution allowed. +- **Every asset file must have a row in [`ATTRIBUTION.md`](ATTRIBUTION.md)** (path, author, source, + licence, notes). `cargo xtask assets` (part of `cargo xtask ci`) fails on any unlisted file under + `assets/`, `docs/brand/` or `docs/images/`. +- FigCraft's UI icons are drawn in code (`crates/ui-egui/src/icons.rs`) from simple primitives and + are original. Keep it that way: new icons are new code, designed from scratch. +- The app icon is generated in code (`cargo xtask icon`, `xtask/src/icon.rs`). +- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this + repo.** The app uses it only through the optional `CRAFT_FONTS_DIR` build input + (`.cargo/config.toml` points it at `../craft-fonts` for local builds; `crates/text/build.rs` + embeds it). Everything must build, test and run without it (system fonts are scanned at runtime). +- Demo and test content is synthetic or public domain; never personal photos or data. +- Code and original assets are MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, `NOTICE`). The + ArtCraft logos in `docs/brand/` are trademarks, not open source, under + `docs/brand/LICENSE-brand.txt`: never modify them or use them outside FigCraft. +- If the provenance of an asset is unclear, don't add it. + +### Never crash +- No `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!` in shipped code + (workspace clippy lints deny them; tests may use them). Return `Result`/`Option`; use `.get()` for + data-derived indexes; slice strings only at char boundaries; check floats are finite; cap sizes. +- `figcraft_engine::guard` turns a panicking command into `EngineError::Internal` and the document + is rolled back; the desktop app also guards each UI frame and writes recovery copies. These are + safety nets, not substitutes for `Result`. +- `unsafe_code = "forbid"` workspace-wide. +- Every crash fix comes with a regression test. The engine has a junk-parameter sweep over every + command (`crates/engine/src/tests.rs`): extend fuzz/sweep tests when you add parsers or commands. + +### Architecture +- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*` + (`spec!(id, label, params doc, undoable, run)`) + tests. The menus, shortcuts, Actions palette, + CLI, control channel and MCP server all dispatch the same command ids. Command ids follow the + incumbent's menu catalog in `crates/engine/src/catalog.rs` (the parity metric: `cargo xtask parity`). +- **Programmatic calls never open dialogs.** Commands only *request* UI (`Session::request_ui`); the + control channel drops dialog requests. +- **Layering** is enforced by `cargo xtask layers`: geom/color (L0) → doc (L1) → layout/text/pathops + (L2) → render/format (L3) → svg/pdf (L4) → engine (L5) → ui-egui/mcp (L6) → apps. Nothing below + L6 depends on egui/eframe/rfd/winit. **The UI crate is swappable.** +- **The UI is thin**: panels read engine state and act through `app.run(id, params)` / + `app.set_props(ids, props)`. 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, assets, wasm). One task per commit with a +milestone id in the message (`M2.1: auto layout wrap`). Commit after every landed arc of work; push +to `main`. + +## Running and looking at the app +- `cargo run -p figcraft -- --control [file.figcraft|file.svg|image]`. Pick a free port + (`python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])'`); + sibling apps use the control channel too. +- Drive it with JSON lines, 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 are documented + in `crates/ui-egui/src/control.rs` and `docs/control-protocol.md`. +- **For UI work, look at the result** (take a screenshot and read the PNG). Compare with the + incumbent side by side where the owner allows (`plan/figma/`). +- Headless: `figcraft-cli` (`info`, `export`, `run`, `commands`, `mcp`). MCP: `figcraft-cli mcp` + (see `docs/mcp.md`). +- Shell gotcha on the owner's machine: `mv`/`cp` may be aliased interactive — use `/bin/mv -f`. +- Parallel agents: separate `CARGO_TARGET_DIR` per agent, edit only the crates/files you own, + write manifests atomically, delete your target dir when done (disk is limited). + +## Roadmap +`ROADMAP.md` (committed) is the shared picture: status by dimension, the gap list, milestones, the +parity table and time-to-parity estimates. When a task lands, update it in the same commit. Grade by +behaviour against the incumbent, not by whether a menu item exists; err low. + +## Contributor credits (About window) + +- About ▸ Contributors are compiled into the binary from `contributors/contributors.json` + (commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for + themselves). See craftrules `standards/contributors.md`. +- **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub + username has a `[people.]` entry in `contributors/people.toml`. If not, ask them once + whether they want to be credited by more than their username: a real name, a display name, and/or + their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry + (copy the template at the top of the file, or run + `python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "…" --sync-github-name`) + and include it in their PR, committed as them. If no, change nothing: they are credited as + `@username` anyway. +- Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or + GitHub profiles). Never hand-edit `contributors.json`. +- Maintainers refresh the stats with `python3 ../../craftrules/scripts/contributors.py .` (it also + re-verifies who wrote each `people.toml` entry; `--check` only verifies). diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md new file mode 100644 index 00000000..0b40635e --- /dev/null +++ b/ATTRIBUTION.md @@ -0,0 +1,31 @@ +# Attribution + +Every asset bundled in this repository (images, icons, fonts, data files) is listed here with its +author, source and licence. `cargo xtask assets` fails if a file under `assets/`, `docs/brand/` or +`docs/images/` is missing from this list. + +FigCraft's user-interface icons are drawn in code (`crates/ui-egui/src/icons.rs`): original work, +MIT OR Apache-2.0. No third-party icon sets are used. + +## Assets in this repository + +| Path | Author | Source | Licence | Notes | +|---|---|---|---|---| +| `assets/app-icon/` (all files: `figcraft-*.png`, `figcraft.ico`, `hicolor/*/apps/ai.storyteller.figcraft.png`) | FigCraft contributors | Generated by `cargo xtask icon` (`xtask/src/icon.rs`) with FigCraft's own renderer | MIT OR Apache-2.0 | Original geometric mark (stacked layer cards and a cursor) | +| `docs/brand/artcraft-logo.svg`, `docs/brand/artcraft-logo.png`, `docs/brand/artcraft-logo-white.svg`, `docs/brand/artcraft-logo-white.png`, `docs/brand/artcraft-mark.svg`, `docs/brand/artcraft-mark.png`, `docs/brand/artcraft-mark-black.svg`, `docs/brand/artcraft-mark-black.png` | ArtCraft Team | storytold/craftrules `assets/brand/` | ArtCraft brand terms (`docs/brand/LICENSE-brand.txt`), not open source | Trademarks; unmodified use within FigCraft only | +| `docs/images/` (all screenshots) | FigCraft contributors | Screenshots of FigCraft showing synthetic demo documents made in FigCraft | MIT OR Apache-2.0 | No third-party artwork | + +## Fonts (build input, not in this repository) + +Release builds embed fonts from the optional craft-fonts build input +([storytold/craft-fonts](https://github.com/storytold/craft-fonts), `CRAFT_FONTS_DIR`). Its own +`ATTRIBUTION.md` and licence files are authoritative; the packages ship each font's licence. + +| Family | Licence | Upstream | +|---|---|---| +| Inter | SIL Open Font License 1.1 | https://github.com/rsms/inter | +| Roboto, Montserrat, Lato, Open Sans, Poppins, Nunito Sans, Merriweather, Playfair Display, Source Sans 3, Source Serif 4, Carlito, Caladea | SIL Open Font License 1.1 | Google Fonts / upstream repositories (see craft-fonts) | +| Liberation Sans, Serif, Mono | SIL Open Font License 1.1 | https://github.com/liberationfonts | +| BIZ UDPGothic, BIZ UDMincho, Shippori Mincho, Noto Sans Arabic | SIL Open Font License 1.1 | Google Fonts (see craft-fonts) | + +egui's default fonts (compiled into the `egui`/`epaint` crates) keep their own licences (OFL / Apache-2.0 / Ubuntu Font Licence); see those crates. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..7b603657 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# FigCraft — instructions for agents + +FigCraft is a clean-room, open-source, pure-Rust (egui) reimplementation of the Figma design workflow: +interface design, auto layout, components and variants, styles and variables, prototyping and +inspection. It runs natively on macOS, Windows, Linux and FreeBSD, and in the browser via WASM. +Siblings: `../vectorcraft` (Illustrator-class), `../photocraft` (Photoshop-class), `../designcraft` +(InDesign-class) and the other Crafting Apps; same conventions. + +Standards and learnings shared across the crafting apps live in `../../craftrules` (checked out +next to `craft-apps/`, or `storytold/craftrules`). Read its `AGENTS.md` at the start of a session, +follow its standards, and contribute reusable learnings back there. Never code: repos don't share +code. + +## Start every session here +1. Read [`ROADMAP.md`](ROADMAP.md): where we stand, the prioritized gap list, and the time estimates. + Unless the user gives you a task, pick work from that list. +2. Read `plan/STATUS.md` and `plan/execution-plan.md` (local, gitignored) if present; behaviour + reference notes are in `plan/figma/*.md`. +3. Orient → plan → implement + test → verify (screenshot) → record (ROADMAP, devlog) → commit. + +`plan/` is gitignored (local only). + +## Non-negotiables + +### Clean-room +- Behaviour comes from public documentation and black-box observation of the running incumbent app + with synthetic documents only. Never read, disassemble or copy anything inside the incumbent's + application bundle, web bundle or network traffic (names and public listings only). +- Never copy GPL/AGPL code. +- Reference screenshots of the incumbent stay local under the gitignored `plan/figma/screenshots/` + and are never committed, published or used as assets. + +### Assets: no third-party product iconography or images — ever (absolute rule from the project owner) +- **Never add, copy, trace, redraw-from, embed or ship any icon, image, illustration, cursor, logo, + template, UI kit, preset library, font file or screenshot from Figma, Adobe, Avid, Autodesk or any + other product, or from any source whose licence doesn't allow it.** This includes "lookalike" + redraws of their icons. Breaking this rule is the worst thing you can do in this repo. +- Every image, icon, font or other asset must be one of: + - original work created for FigCraft by a contributor (licensed MIT OR Apache-2.0); + - OSI open source; + - public domain / CC0; + - Creative Commons with redistribution allowed. +- **Every asset file must have a row in [`ATTRIBUTION.md`](ATTRIBUTION.md)** (path, author, source, + licence, notes). `cargo xtask assets` (part of `cargo xtask ci`) fails on any unlisted file under + `assets/`, `docs/brand/` or `docs/images/`. +- FigCraft's UI icons are drawn in code (`crates/ui-egui/src/icons.rs`) from simple primitives and + are original. Keep it that way: new icons are new code, designed from scratch. +- The app icon is generated in code (`cargo xtask icon`, `xtask/src/icon.rs`). +- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this + repo.** The app uses it only through the optional `CRAFT_FONTS_DIR` build input + (`.cargo/config.toml` points it at `../craft-fonts` for local builds; `crates/text/build.rs` + embeds it). Everything must build, test and run without it (system fonts are scanned at runtime). +- Demo and test content is synthetic or public domain; never personal photos or data. +- Code and original assets are MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, `NOTICE`). The + ArtCraft logos in `docs/brand/` are trademarks, not open source, under + `docs/brand/LICENSE-brand.txt`: never modify them or use them outside FigCraft. +- If the provenance of an asset is unclear, don't add it. + +### Never crash +- No `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!` in shipped code + (workspace clippy lints deny them; tests may use them). Return `Result`/`Option`; use `.get()` for + data-derived indexes; slice strings only at char boundaries; check floats are finite; cap sizes. +- `figcraft_engine::guard` turns a panicking command into `EngineError::Internal` and the document + is rolled back; the desktop app also guards each UI frame and writes recovery copies. These are + safety nets, not substitutes for `Result`. +- `unsafe_code = "forbid"` workspace-wide. +- Every crash fix comes with a regression test. The engine has a junk-parameter sweep over every + command (`crates/engine/src/tests.rs`): extend fuzz/sweep tests when you add parsers or commands. + +### Architecture +- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*` + (`spec!(id, label, params doc, undoable, run)`) + tests. The menus, shortcuts, Actions palette, + CLI, control channel and MCP server all dispatch the same command ids. Command ids follow the + incumbent's menu catalog in `crates/engine/src/catalog.rs` (the parity metric: `cargo xtask parity`). +- **Programmatic calls never open dialogs.** Commands only *request* UI (`Session::request_ui`); the + control channel drops dialog requests. +- **Layering** is enforced by `cargo xtask layers`: geom/color (L0) → doc (L1) → layout/text/pathops + (L2) → render/format (L3) → svg/pdf (L4) → engine (L5) → ui-egui/mcp (L6) → apps. Nothing below + L6 depends on egui/eframe/rfd/winit. **The UI crate is swappable.** +- **The UI is thin**: panels read engine state and act through `app.run(id, params)` / + `app.set_props(ids, props)`. 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, assets, wasm). One task per commit with a +milestone id in the message (`M2.1: auto layout wrap`). Commit after every landed arc of work; push +to `main`. + +## Running and looking at the app +- `cargo run -p figcraft -- --control [file.figcraft|file.svg|image]`. Pick a free port + (`python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])'`); + sibling apps use the control channel too. +- Drive it with JSON lines, 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 are documented + in `crates/ui-egui/src/control.rs` and `docs/control-protocol.md`. +- **For UI work, look at the result** (take a screenshot and read the PNG). Compare with the + incumbent side by side where the owner allows (`plan/figma/`). +- Headless: `figcraft-cli` (`info`, `export`, `run`, `commands`, `mcp`). MCP: `figcraft-cli mcp` + (see `docs/mcp.md`). +- Shell gotcha on the owner's machine: `mv`/`cp` may be aliased interactive — use `/bin/mv -f`. +- Parallel agents: separate `CARGO_TARGET_DIR` per agent, edit only the crates/files you own, + write manifests atomically, delete your target dir when done (disk is limited). + +## Roadmap +`ROADMAP.md` (committed) is the shared picture: status by dimension, the gap list, milestones, the +parity table and time-to-parity estimates. When a task lands, update it in the same commit. Grade by +behaviour against the incumbent, not by whether a menu item exists; err low. + +## Contributor credits (About window) + +- About ▸ Contributors are compiled into the binary from `contributors/contributors.json` + (commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for + themselves). See craftrules `standards/contributors.md`. +- **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub + username has a `[people.]` entry in `contributors/people.toml`. If not, ask them once + whether they want to be credited by more than their username: a real name, a display name, and/or + their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry + (copy the template at the top of the file, or run + `python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "…" --sync-github-name`) + and include it in their PR, committed as them. If no, change nothing: they are credited as + `@username` anyway. +- Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or + GitHub profiles). Never hand-edit `contributors.json`. +- Maintainers refresh the stats with `python3 ../../craftrules/scripts/contributors.py .` (it also + re-verifies who wrote each `people.toml` entry; `--check` only verifies). diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index ca612db8..1e71a1f4 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -378,6 +378,7 @@ impl FontDb { } fn system_font_dirs() -> Vec { + #[allow(unused_mut)] let mut v: Vec = Vec::new(); #[cfg(target_os = "macos")] { diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 1886df78..be749cc6 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -184,7 +184,7 @@ fn input(app: &mut FigApp, ui: &mut Ui, resp: &egui::Response) { let pt = hover_pos.map(|h| app.to_page(h)).unwrap_or_default(); if path.exists() { app.run("file.import", json!({"path": path.to_string_lossy(), "x": pt.x, "y": pt.y})); - } else if let Ok(bytes) = f.bytes() { + } else if let Some(bytes) = dropped_bytes(&f) { use base64::Engine as _; let b64 = base64::engine::general_purpose::STANDARD.encode(&bytes); let name = path.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "dropped.png".into()); @@ -289,6 +289,17 @@ fn input(app: &mut FigApp, ui: &mut Ui, resp: &egui::Response) { } } +#[cfg(not(target_arch = "wasm32"))] +fn dropped_bytes(f: &std::sync::Arc) -> Option> { + f.bytes().ok() +} + +#[cfg(target_arch = "wasm32")] +fn dropped_bytes(_f: &std::sync::Arc) -> Option> { + // Browser drops arrive asynchronously; not supported yet. + None +} + fn stroke(c: Color32, w: f32) -> Stroke { Stroke::new(w, c) } diff --git a/docs/parity.md b/docs/parity.md new file mode 100644 index 00000000..fd8296b0 --- /dev/null +++ b/docs/parity.md @@ -0,0 +1,18 @@ +# Parity report + +Generated by `cargo xtask parity`: FigCraft commands registered for the incumbent's main menu and toolbar (278 commands in the engine). + +**191 / 199 menu items (96.0%)** + +## Missing + +- `file.exportFramesPdf` +- `file.openRecent` +- `view.outlinesSelection` +- `view.maskOutlines` +- `vector.deleteHeal` +- `plugins.runLast` +- `plugins.manage` +- `libraries.publish` + +Menu coverage measures breadth only; see ROADMAP.md for depth and fidelity. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 16ea94cc..b0efc6b7 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -70,7 +70,17 @@ fn assets() -> bool { if name.starts_with('.') || name.ends_with(".md") || name.ends_with(".txt") { return; } - if !attribution.contains(&rel) && !attribution.contains(&format!("{}/", rel.rsplit_once('/').map(|x| x.0).unwrap_or(""))) { + // Listed by path, or by any ancestor directory written as `dir/`. + let mut listed = attribution.contains(&format!("`{rel}`")); + let mut cur = rel.as_str(); + while let Some((parent, _)) = cur.rsplit_once('/') { + if attribution.contains(&format!("`{parent}/`")) { + listed = true; + break; + } + cur = parent; + } + if !listed { missing.push(rel); } });