VectorCraft 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 ../pdfcraft (Acrobat-class); same conventions.
Formerly DrawCraft (renamed 2026-10-01): old .drawcraft files and "format": "drawcraft" headers still open (vectorcraft_format::LEGACY_EXTENSION), and preferences migrate from the old config folder. Keep those paths working; use the new name everywhere else.
ROADMAP.md (stage, numbers by dimension, what’s next) and the ranked work list in docs/gaps.md. Unless the user gives you a task, pick work from that list.plan/STATUS.md (local session notes, may lag the ROADMAP), then the task in plan/execution-plan.md §3 and the relevant plan/architecture.md section. Behaviour reference: public documentation only (see Clean-room below); plan/illustrator/*.md holds notes from it.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).
cargo xtask cleanroom (run by cargo xtask ci) fails on Adobe copyright notices and on fixtures made with Adobe applications.draw-mode and cdr branches; for .cdr support and the draw-mode layout). Corel’s terms very likely forbid reverse engineering too. So everything above applies with “Corel” (CorelDRAW, PowerTRACE, CorelCAD, PHOTO-PAINT and the rest) in place of “Adobe”:
.cdr is not publicly specified. Its behaviour comes only from files users send us that they own (used locally, never committed), openly licensed files with a recorded source and SHA-256 (fetched pinned, like the Affinity corpus), and written descriptions that are public. Don’t copy code from any .cdr reader, including libcdr (MPL-2.0), unless the branch owner has approved its licence (docs/draw-mode.md, decision D4); if approved, it goes in a crate of its own and is credited in NOTICE.cargo xtask brands rejects Corel names there as it does Adobe’s.ASSETS.md (path, author, source URL, licence, notes). Put licence texts next to the assets (e.g. assets/fonts/OFL-*.txt) and summarize them in NOTICE.cargo xtask assets (run by cargo xtask ci) fails on any unattributed asset.standards/fonts.md). Never commit a font file here; add new fonts to craft-fonts. The app uses it only through the optional CRAFT_FONTS_DIR build option (CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo xtask ci; use an absolute path), read by crates/text/build.rs into vectorcraft_text::CRAFT_FONTS; never add it to a Cargo.toml. Everything must build, test and run with CRAFT_FONTS empty, and tests of its glyphs skip when it is. Details: docs/development.md › Fonts.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 VectorCraft.plan/ and are never committed, published or used as assets.Result (or Option); callers handle it or propagate it with ?. Failures reach the user as an error message (EngineError for commands, the crate’s own error type or String below the engine), never as an abort.cargo xtask ci fails on them: unwrap(), expect(), panic!, unreachable!, todo!, unimplemented!. Use instead: ?, .ok_or(…)?, let … else { return … }, if let, unwrap_or / unwrap_or_default / unwrap_or_else, .first() / .last() / .split_last(), and a real error for a match arm you believe is impossible..get(i) instead of v[i] or &s[a..b] when the index comes from data. Don’t divide integers by something that can be zero, cap sizes and counts read from input (allocations, images, loops), and check that floats are finite before casting them or looping on them.let _ = or .ok() only where ignoring the failure is the right behaviour, with a comment saying why.#[test], #[cfg(test)], tests/, testkit). Integration-test files start with an #![allow(…)] for that.vectorcraft_engine::guard is the last line of defence. Every entry point (commands, tool events, MCP requests, the UI frame, control requests) catches a panic, rolls the document back and reports an internal error. It exists for bugs and doesn’t replace Result: wasm aborts on panic, so the web app has no safety net.engine/tests/import_fuzz.rs, engine/tests/command_sweep.rs, format/tests/prop_format.rs, mcp/tests/protocol_props.rs). Extend these when you add an importer, a parser or a protocol method.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.Cursor variant (crates/tools/src/lib.rs, with a doc comment), return it from the tool’s cursor() with a tool test, cover it in cursor_icon (crates/ui-egui/src/canvas.rs), draw it in crates/ui-egui/src/cursors.rs or give it a stand-in in Cursor::stand_in() until it’s drawn, and add its [[cursor]] entry to crates/tools/src/cursors.toml (when it shows, in plain behaviour terms, and its hotspot; never another app’s look). A missing entry only warns (cargo xtask cursors): the cursor still works.cargo xtask layers. Nothing below L6 depends on egui/eframe/winit/rfd.app.run(id, params). Colours come from theme::Tokens.cargo xtask wasm).chrono (workspace dependency). Never hand-roll UTC/calendar conversion (days-to-date algorithms, leap-year math); a crate owns only its output format string. The one exception is range extension: past the years chrono can represent, shift the date by whole 400-year Gregorian cycles (146,097 days, after which the calendar repeats), let chrono compute the month and day, and add the cycles back to the year (splitting seconds into hours, minutes and seconds is plain arithmetic).storytold/photocraft-corpus, explained in craftrules standards/test-corpora.md. Never commit large binary fixtures to this repo; fetch them pinned by commit and sha256-verified, as PhotoCraft does with cargo xtask corpus.cargo xtask ci (fmt, clippy -D warnings including the no-panic lints, tests, layers, wasm). One task id per commit (M2.1: pen tool).cargo run --release -p vectorcraft -- --control 7979 [file.svg|file.vectorcraft] (sibling apps’ agents use the same default port: if the log says it failed to bind, pick another port — otherwise your requests reach a different app).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.ui.screenshot, read the PNG) and compare with the public documentation (plan/illustrator/02-ui-ux.md notes). ui.screenshot needs a presented frame: if it errors (screen locked), check the art with ui.render / vectorcraft-cli run … --export x.png, and cover panels with a headless egui frame test (see panels/transparency.rs tests).vectorcraft-cli perf checks the budgets (render/pan/zoom, hit test, save/open, SVG, Pathfinder) on a synthetic 50k-path document; vectorcraft-cli bench FILE times one file. Run them before and after renderer, format or geometry changes, on an idle machine (the report warns when the load average makes timings noise).vectorcraft-cli mcp (see docs/mcp.md).mv/cp are aliased interactive here — use /bin/mv -f / /bin/cp -f.CARGO_TARGET_DIR per agent; edit only the crates you own; write manifests atomically. Each target dir grows to ~30 GB: delete yours when you finish (a full disk fails links with errno=28).The progress docs follow craftrules’ standards/progress-docs.md:
ROADMAP.md: stage, headline numbers, dimensions, features, languages, upcoming, progress log.docs/target-app-parity.md: the authoritative parity assessment (area table, methodology, calibration, evidence of what ships).docs/gaps.md: every known shortfall, ranked; the work list. docs/roadmap.md: milestones and current focus.ui-parity.md, file-format-parity.md, hardware-parity.md, localization-parity.md, effects-parity.md, type-parity.md; architecture.md.When a task lands, update in the same PR: the area row in docs/target-app-parity.md (score, missing items, hours) and its evidence list, the gap entry in docs/gaps.md if it closed or shrank, the checklist it belongs to, the milestone row in docs/roadmap.md, and the ROADMAP’s tables and progress log when a headline number moves. Update each touched doc’s status line and revision history.
plan/illustrator/ notes), not by whether a menu item exists. Scores are self-assessed, so err low; mark each number measured or estimated.contributors/contributors.json
(commit stats; generated, never hand-edit) and contributors/people.toml (names people chose for
themselves). See docs/contributors.md.[people.<username>] 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.contributors.json.python3 ../../craftrules/scripts/contributors.py . (it also
re-verifies who wrote each people.toml entry; --check only verifies).