Files
photocraft/AGENTS.md
T
Brandon ThomasandClaude Opus 5.5 433b84fae7 Docs: where we stand and where we're going (roadmap, README, AGENTS.md) (#186)
roadmap.md: the honest parity assessment now breaks parity down by dimension with dated
measurements (PSD oracle 115/170, round trips 169/169, shortcut audit 0 failures, perf numbers,
RAW coverage, AI 0%, ~20 missing tools...), an overall estimate (feature surface ~60-70%,
pro-switchable ~25-35%), and a priority order for what's next.
README: the status note says plainly that PhotoCraft isn't yet a daily-driver Photoshop
replacement, what the biggest gaps are, and links the assessment.
AGENTS.md: agents read the assessment first when picking work and update it when numbers move.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-05 06:14:53 -04:00

12 KiB
Raw Blame History

AGENTS.md: guide for AI agents and contributors

PhotoCraft is an open-source, native, Photoshop-comparable image editor written in Rust only (no JavaScript or TypeScript). No Tauri, Electron or webview shells: the desktop app is native egui/eframe on wgpu, and the web build is the same Rust compiled to WebAssembly (trunk + wasm-bindgen). Never add Tauri (or any webview/JS UI framework) as a dependency, build step or packaging target. The product name is always written PhotoCraft ({Function}Craft in PascalCase, like its siblings ArtCraft, ArtCraftX, DesignCraft, DrawCraft, EffectCraft, FilmCraft, LightCraft, PrintCraft) in user-facing text: UI, window titles, About, installers, release names, docs prose. Machine names stay lowercase: crates (photocraft-*), binaries, file names, ids (ai.storyteller.photocraft). Standards and learnings shared across the crafting apps live in ../craftrules (read its README.md). Contribute reusable learnings there, never code; repos don't share code. The goal is 1:1 Photoshop parity (same menus, shortcuts, behaviour and file fidelity) with better performance, and every feature drivable by agents. Read this file first, then docs/.

1. Orientation (5 minutes)

Read Why
docs/architecture.md Crate map, dependency layers, the engine/UI seam, document model
docs/development.md Build, test, run, drive the app programmatically, debug tricks
docs/contributing.md Rules: clean-room, tests, layering, style, commits; the "add a command" checklist
docs/control-protocol.md JSON control channel: how agents drive and screenshot the running app
docs/ui-design.md Design tokens, themes, widgets, and how to match Photoshop's look
docs/roadmap.md Honest parity assessment (where we're lacking, where we're going), milestones, current focus
docs/parity.md Generated list of every Photoshop menu item, live or missing
crates/<name>/README.md (where present) Public API of that crate

2. Workspace map

crates/
  geom cms color raster      L0 foundation (geometry, ICC colour management, pixel formats + blend math, COW tiles)
  psd codecs                 L0 standalone format crates (no workspace deps; publishable)
  doc                        L1 document model (layers, masks, adjustments, effects, smart objects: pure data)
  ops paint algo text vector L2 history, brush engine, imaging algorithms, type engine, paths/shapes
  compose gpu format         L3 CPU compositor (the oracle), wgpu compositor, .pcraft native format
  io plugins                 L4 document <-> PSD / flat formats; sandboxed WebAssembly plug-ins
  engine                     L5 Session + command registry (every action is a command)
  ui-egui automation         L6 egui shell (thin: all actions go through the engine); MCP server
  testkit                    test helpers
apps/
  photocraft                 desktop app (eframe/wgpu), TCP control server
  photocraft-cli             headless CLI (convert/info/run/batch/commands/mcp)
  photocraft-web             the same app in the browser (trunk + wasm-bindgen)
xtask/                       cargo xtask layers | wasm | ci | stats | corpus | parity

Layering is enforced by cargo xtask layers. A crate may depend only on lower layers. psd, codecs and cms depend on nothing in the workspace. Nothing below ui-egui may use egui, eframe, winit or rfd. A new crate must be registered in xtask/src/layers.rs.

3. Golden rules

Never crash (outranks feature work)

People trust PhotoCraft with their work, and a crash loses it. A malformed file, a bad command or MCP param, a corrupt settings file, an odd keystroke or a full disk must produce an error the user or agent can act on, never a panic. Don't ship a feature by adding a panic path; fix a crash before building on top of it. The shared standard is ../craftrules/standards/never-crash.md.

  • Non-test code never panics. No unwrap(), expect(), panic!, unreachable!, todo! or unimplemented!. Return the crate's error type and propagate with ?; use ok_or(..)?, let .. else { return Err(..) }, if let, or unwrap_or* where a fallback is truly correct (never one that silently corrupts a document). Unfinished features return an "unsupported" error. The only exception is a provably infallible literal: #[allow(clippy::expect_used)] plus .expect("why it can't fail").
  • No unsafe. The workspace sets unsafe_code = "forbid".
  • Input-derived numbers are hostile. Use get() rather than [i]/[a..b] for indices from files, params, selections or arithmetic on them; slice strings only at char boundaries; use checked_*/saturating_* for lengths, offsets and counts; guard division by zero and NaN/inf casts; cap allocations sized by input.
  • Bound recursion with depth limits or seen-sets (documents can be deep or cyclic).
  • Don't cascade. Handle lock poisoning (lock().unwrap_or_else(PoisonError::into_inner)) and treat thread joins as Results.
  • Last-resort guard. The app shell must catch an escaped panic around command dispatch and file import/export, reports it as an error and keeps the document. It's a safety net, not a licence to panic. Keep panic = "unwind".
  • Prove it. Every crash fix comes with a small synthetic regression test that panicked before the fix.
  • Enforced by clippy. clippy.toml allows unwrap/expect/panic/indexing in tests only. Clean crates carry #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]; new crates start with it.
  1. Everything is a command. New user-visible behaviour = a command in the engine (crates/engine/src/*_cmds.rs, registered in commands.rs) with id, label, menu path, shortcut, params doc, enabled and run, plus tests. The UI, CLI, control channel and MCP all dispatch commands by id. Use the exact id from crates/ui-egui/src/menu_catalog.rs and the menu item goes live automatically. Only pure view/window state (zoom, panels, screen mode) belongs to the shell (menus.rs UI_COMMANDS).

  2. No format or colour assumptions. Bit depth (8/16/32f) and colour model (RGB/Gray/CMYK/Lab…) are runtime data. Never introduce a u8-only pixel path in public APIs. Never assume sRGB: colour conversions go through photocraft-cms (Transform, transform::cached). Test at several depths.

  3. Clean-room. We studied Photoshop and other proprietary editors for behaviour and look only. Never copy their code, shaders, profiles or assets. Implement from public specs (Adobe PSD spec, ICC, ISO 32000 blend modes, papers) and observation. Third-party assets must be permissively licensed, keep their license file next to them, and get a row in ATTRIBUTION.md (path, title, author, source, license) in the same change; so do original assets. The ArtCraft logos in docs/brand/ are not open source (docs/brand/LICENSE-brand.txt).

  4. Tests are the gate. Every change comes with tests. Format crates use round-trip, synthetic-generator, oracle and fuzz tests. Keep the PSD corpus results and the parity floor (crates/ui-egui/src/parity.rs) from regressing.

  5. The UI is thin and data-driven. UI state lives in ui-egui/src/state.rs (serde), so the control channel can read and drive it. Colours and radii come from theme::Tokens, never hard-coded.

  6. Verify UI changes visually. Render offscreen with cargo run -p photocraft-ui-egui --example snapshot (no window, no focus stealing), or launch with --control and take ui.screenshot. Look at the PNG. Demo images must be public-domain art, never personal photos. When fetching assets, never put a person's name, email or other personal details in requests (User-Agent, headers, URLs); use a generic Photocraft-dev User-Agent.

  7. Never break wasm. L0–L6 must cargo check --target wasm32-unknown-unknown (run cargo xtask wasm). File-system code is cfg(not(target_arch = "wasm32")) or goes through the platform services.

  8. Performance is a feature. Benchmark heavy operations on a 24–36 MP image in release. Work per tile in parallel (rayon), skip empty tiles, never scan a full surface per frame (cache per revision), and record before/after timings in the dev log.

  9. Never panic on input (see Never crash above). A command's run closure and anything it calls must return Err, never panic, for any params or document state: validate params, check bounds before indexing or dividing, and reject absurd sizes before allocating. The panic_hunt integration test fuzzes every command with adversarial params and must stay green.

4. Picking work

Priorities: important infrastructure first, then low-hanging parity, then the long tail.

  1. Read docs/roadmap.md → "Honest parity assessment" first. It says, dimension by dimension, where PhotoCraft is lacking and the priority order of where we're going. docs/parity.md (menu wiring) is not a measure of behaviour. When your work moves a measured number (PSD oracle, round trips, workflow tests, performance), update that section with the dated figure.
  2. docs/roadmap.md → Current focus.
  3. cargo xtask parity → docs/parity.md lists every missing menu item, grouped by menu. Low-hanging fruit is usually a missing command whose algorithm already exists in algo, paint, vector or text.
  4. log/devlog.md → the "Still open" bullets of recent entries.

When parity rises, raise FLOOR in crates/ui-egui/src/parity.rs (never lower it).

5. Before you finish a task

cargo test -p <crates you touched>
cargo clippy -p <crates> --all-targets -- -D warnings
cargo xtask layers
cargo xtask wasm            # if you touched L0–L6
cargo xtask parity          # if you added commands; commit the regenerated docs/parity.md
cargo test -p photocraft-engine --test panic_hunt -- --ignored   # if you added/changed commands: no panic on adversarial input (Rule 9)

Commands must never panic on bad input (Rule 9): every run closure and the code it calls returns Err, not a panic, for any params or document state. New commands come with a graceful-failure test (empty/out-of-range/wrong-type params → Err, not a crash).

Then append a terse entry to log/devlog.md (what landed, numbers, what's still open). Sessions can end abruptly (crashes, context limits), so the dev log plus a green tree is how the next agent picks up. Keep the tree building at every step.

6. Parallel agents

  • Use your own target dir (CARGO_TARGET_DIR=target/agent-<name>) to avoid the Cargo build lock, and edit only the files you own. Shared files (engine/src/lib.rs, the v.extend(...) list in engine/src/commands.rs, ui-egui/src/menus.rs, state.rs) get small, surgical edits; re-read before editing.
  • Put new commands in a new module (engine/src/<area>_cmds.rs with a specs() function) rather than growing a shared file.
  • If someone else's in-progress edit breaks the build, wait and retry; don't fix their files.
  • Keep every Cargo.toml valid at all times: the crates/* glob means one broken manifest breaks everyone's build. Create or rewrite manifests atomically: write to a temp file outside crates/, then mv it into place.
  • Disk space: each target dir is about 10 GB. Delete target/agent-* dirs of finished agents.

7. Where things are tracked

  • docs/roadmap.md: milestones M0–M12, status and the current focus.
  • docs/parity.md: generated Photoshop menu coverage.
  • docs/releasing.md: cutting a release (cargo xtask version, the release branch), signing secrets, packaging scripts in packaging/.
  • ../craftrules/release/playbook.md: how every storytold app builds signed release binaries (the canonical recipe; docs/release-playbook.md just points there); docs/releasing.md is PhotoCraft's specifics.
  • plan/ (local, gitignored): research, parity plan, execution plan, estimates.
  • log/ (local, gitignored): the dev log.

8. Keeping the native format complete

photocraft-format deliberately fails to compile when a photocraft-doc struct gains a field, so nothing is silently dropped from .pcraft saves. When you add a doc field, add it to crates/format/src/manifest.rs and convert.rs with #[serde(default)] so older files still load. If the field has a PSD equivalent, map it in crates/io too, and keep unknown PSD blocks verbatim.