photocraft · diff

git:20261022.ca0d7e4 to git:20261026.76415fe

91 added, 91 removed. Audit A to A.

---
name: photocraft
- description: PhotoCraft is the ArtCraft clean-room, pure-Rust reimplementation of Adobe Photoshop — layers, masks, adjustment layers, layer styles, live type, vectors, smart objects and real PSD read/write, with every action exposed as an engine command that the UI, a headless CLI, a JSON control channel and an MCP server all drive. Trigger this skill whenever the user mentions PhotoCraft, asks what it can or cannot do, which file formats it reads or writes, its keyboard shortcuts, how to script, automate or batch it, how to prepare assets for it, how to inspect, verify or repair a .pcraft or PSD it produced, or how to troubleshoot it — and also when they never name the app but describe the job, such as "edit this photo", "open this PSD", "make a transparent PNG", "batch-resize a folder of images", "remove the background with a quick selection" or "check what is in this layered file". It is early alpha — never promise Photoshop-grade PSD fidelity, AI features or a stable UI, and prefer the headless CLI and MCP over driving the GUI blind.
+ description: PhotoCraft is the ArtCraft clean-room, pure-Rust reimplementation of Adobe Photoshop — layers, masks, adjustment layers, layer styles, live type, vectors, smart objects and real PSD read/write, with every action exposed as an engine command that the UI, a headless CLI, a JSON control channel and a 26-tool MCP server all drive. Trigger this skill whenever the user mentions PhotoCraft, asks what it can or cannot do, which file formats it reads or writes, its keyboard shortcuts, how to script, automate or batch it, how to prepare assets for it, how to inspect, verify or repair a .pcraft, PSD, XCF or .ora it produced, or how to troubleshoot it — and also when they never name the app but describe the job, such as "edit this photo", "open this PSD", "make a transparent PNG", "batch-resize a folder of images", "remove the background with a quick selection", "convert these HEIC/NEF/PCX files" or "check what is in this layered file". It is early alpha — never promise Photoshop-grade PSD fidelity, AI features or a stable UI, and prefer the headless CLI and MCP over driving the GUI blind.
---
# PhotoCraft
+ **Audit basis.** This skill was audited against tag **v0.16.0**, commit **`b1794c6e0608ccfc887213dcf58398977c5cb75e`** ("Release 0.16.0"), whose tree `git -C <clone> rev-parse HEAD` confirms. The installed `/Applications/PhotoCraft.app` reports `CFBundleShortVersionString` 0.16.0 (bundle id `ai.storyteller.photocraft`, minimum macOS 11.0) and its binary prints `photocraft 0.16.0 (b1794c6e0, 2026-10-25)`, so the bundle matches this source; the GUI was never launched. **Verify against your installed version** — the ArtCraft Launcher auto-updates, so a newer build may differ; re-check the counts below against your build (every count here was measured from code, not copied from the previous skill or from README/docs). No `references/` directory ships with this skill: everything is inline, and the per-claim evidence table lives in `evidence.md`.
+
## Overview
- PhotoCraft is an open-source, native image editor: "Image editing; an open-source, clean-room reimplementation of Adobe Photoshop, rebuilt in pure Rust" (`README.md:13`), dual-licensed MIT/Apache-2.0, from the `storytold` org (repo `storytold/photocraft`). The desktop app is egui/eframe on wgpu with a CPU compositor as the reference oracle; the same Rust compiles to WebAssembly for the browser build (`apps/photocraft-web`, `Cargo.toml` `wasm-release` profile, `cargo xtask wasm`). Everything user-visible is an engine command, so the UI, `photocraft-cli`, the JSON control channel and the MCP server run the same code (`README.md:226`, `crates/engine/src/commands.rs`). Maturity is **early alpha**: ~76% feature breadth, ~45% "ready for real work" measured against Photoshop 2026 at 0.6.0, 953 open issues (2026-10-13) (`ROADMAP.md:5-30`, `docs/target-app-parity.md`); the six-workflow alpha gate passes (`docs/roadmap.md` "Alpha gate"). Every Photoshop menu item is *wired* — 631/631 live, enforced by `FLOOR = 631` (`crates/ui-egui/src/parity.rs:10`) — but wiring is not behaviour. The source inspected here is tag `v0.12.0` (commit `6a2d027ee2e7ca12afd248544ed2262b0fa078dd`, 2026-10-21), exactly the installed `/Applications/PhotoCraft.app`.
+ PhotoCraft is "Image editing; an open-source, clean-room reimplementation of Adobe Photoshop, rebuilt in pure Rust" (`README.md:17`), MIT/Apache-2.0 dual licence, `storytold/photocraft`. The native app is egui/eframe on wgpu with a CPU compositor kept as the reference oracle; the same engine and UI compile to WebAssembly (`apps/photocraft-web`, `[profile.wasm-release]` in `Cargo.toml`). Every menu item, tool and dialog is one engine command, so the UI, `photocraft-cli`, the loopback JSON control channel and the MCP server run the same code (`README.md:230`, `crates/engine/src/commands.rs:538` `build()`). Maturity is **early alpha**: feature breadth ~76% and "ready for real work" ~45% against Photoshop 2026, measured at 0.6.0 and not re-measured since (`ROADMAP.md:20-30`, which is itself stale — see Known limitations). All 631 Photoshop menu items are *wired* to a command (`docs/parity-checklist.md:5`, floor enforced at `crates/ui-egui/src/parity.rs:10`); wiring is dispatch, not behaviour.
## Capabilities (verified)
### Editing core
- - **Documents and colour**: RGB, Grayscale, CMYK and Lab at 8/16/32-bit float, plus Bitmap, Indexed, Duotone and Multichannel; ICC colour management in pure Rust with Assign/Convert to Profile, rendering intents, black point compensation, soft proofing and Gamut Warning (`README.md:198-201`, `crates/cms`, `crates/engine/src/adjust_cmds.rs` — CMYK/Lab adjustments composite on the stored inks, 2026-10-13 fixes in `docs/gaps.md` revision history).
- - **Layers, done properly**: pixel layers, groups, clipping masks, pixel and vector masks (with Density/Feather properties), fill layers (solid/gradient/pattern), adjustment layers, live smart objects with smart filters and lossless transforms/warps/perspective (projective placement, `docs/scorecard.md` FILE-215-6 done), alpha channels, Quick Mask, layer comps, artboards, advanced blending (knockout, CPU render; GPU falls back) (`README.md:192-193`, `crates/doc`, `crates/engine`, `docs/scorecard.md` FILE-215-*).
- - **16 adjustment-layer commands**: `layer.newAdjustmentLayer.{blackWhite, brightnessContrast, channelMixer, colorBalance, colorLookup, curves, exposure, gradientMap, hueSaturation, invert, levels, photoFilter, posterize, selectiveColor, threshold, vibrance}` — **Color Lookup is new** (.cube/.3dl/.look files or built-in looks; `crates/engine/src/adjust_cmds.rs:100-139`, id built from `CMD = "colorLookup"` at line 106). Destructive-only extras: Shadows/Highlights, Replace Color, Match Color, HDR Toning, Desaturate, Equalize (`adjust_cmds.rs:1-5`). On a smart object, Image › Adjustments records a re-editable smart filter (`adjust_cmds.rs:259`, FILE-215-4 done).
- - **10 layer effects**, several instances per kind: DropShadow, InnerShadow, OuterGlow, InnerGlow, Stroke, ColorOverlay, GradientOverlay, PatternOverlay, Satin, BevelEmboss; contour curves with `.shc` import/export (`crates/doc/src/effects.rs:261-292`, `docs/scorecard.md` FILE-215-15 partial).
- - **Blend modes**: PassThrough (groups) + 27 Photoshop blend modes + 6 Paint.NET variants (`crates/color/src/blend.rs:11-43`).
- - **67 tools** including Remove, Spot/Healing Brush, Patch, Content-Aware Move (with Transform On Drop and coarse-then-exact live preview), Mixer Brush, Magnetic Lasso, Object/Quick Selection, Select Subject, Pen/Freeform/Curvature Pen, shapes, Type and Type Mask, Frame, Slice (`crates/ui-egui/src/state.rs:141` — `pub const ALL: [Tool; 67]`). Of Photoshop's classic 68, only Art History Brush is missing (`docs/gaps.md` G6, corrected 2026-10-13); the README's "15 missing tools" counts the full Photoshop 2026 toolbox — a known doc discrepancy.
- - **Selections**: marquee/lasso/wand, `select.quick`, `select.object`, `select.subject`, `select.sky`, `select.colorRange`, `select.focusArea`, `select.refineEdge`, `select.magneticLasso`; Select and Mask is a dialog with live view modes and a Refine Edge Brush, not a workspace (`crates/engine/src/smartselect_cmds.rs`, `crates/ui-egui/src/menus.rs`, `docs/scorecard.md` TOOL-214-1 partial).
- - **Filters** with live in-dialog preview: `crates/engine/src/filters.rs`, `filters_ext.rs`, `gallery_cmds.rs`; Field/Iris/Tilt-Shift/Spin Blur are edited on-canvas with Photoshop-style pins (Path Blur is still a dialog — `docs/gaps.md` G7); filters on a smart object become re-editable smart filters, and **41 filters map to PSD filterFX both ways** (`docs/file-format-parity.md` FILE-215-2, `crates/io/src/smart_map.rs`).
- - **Type**: point and paragraph text on canvas, Character/Paragraph controls, manual + optical kerning with PSD round trip, variable-font axis sliders, `type.warpText` with live preview, OpenType feature buttons, `type.resolveMissingFonts`; **type on a path is partial** — glyphs place rigidly along the curve (`type.attachToPath`/`detachFromPath`, `crates/engine/src/type_extra_cmds.rs:332-362,699`) but the caret does not follow and PSD saves it as point text (`docs/scorecard.md` TYPE-218-1). Headless type editing exists: `type.editText`, `type.hitTest`, `type.caret`, `type.navigate` (`docs/control-protocol.md`, FILE-215/AUTO-219-9 done).
- - **Vectors**: Pen/Freeform/Curvature tools, insert/delete/convert anchor, paths panel, work paths, shape layers with stroke controls and corner-radius handles, path-to-selection; SVG opens as editable shape layers or a vector smart object (`crates/io/src/svg.rs`, `docs/scorecard.md` FILE-215-9).
- - **Actions**: record/replay, `actions.list/get/record/stop/play/move/delete/toggle/rename`, nested calls to 16 levels with cycle rejection, sets with dialog toggles and Insert Stop, function-key bindings, Photoshop `.atn` import/export — the `EVENTS` table holds 92 entries and the generated scorecard counts 90 events mapped both ways; unmapped steps are kept verbatim and fail at playback by name (`crates/engine/src/atn_cmds.rs:2674`, `docs/scorecard.md` AUTO-219-2; older prose in `docs/gaps.md` says 112 — trust the scorecard/code).
- - **Photomerge**: `file.automate.photomerge` with reposition/perspective layouts, as a cancellable background job (`crates/engine/src/photo_cmds.rs:487-909`; real-world quality validation still open, `docs/roadmap.md` M12).
- - **Print**: File › Print renders one flattened PDF page and hands off via `lp`/the Windows spooler, or the new in-process pure-Rust IPP client (`crates/ipp` — works inside the Flatpak without `lp`), or the Linux print portal, or to a PDF file; `file.printers` lists queues, `"dryRun": true` previews the job (`docs/printing.md`, `crates/engine/src/print_cmds.rs:14-15,190`).
- - **Video-lite**: `timeline.create` and `file.export.renderVideo` (PNG sequence or animated GIF; animated GIFs open as frame sequences through New Video Layer from File) (`crates/engine/src/timeline_cmds.rs:71`, `video_cmds.rs:406-466`, `docs/file-format-parity.md` GIF row).
+ - **Documents and colour**: RGB, Grayscale, CMYK, Lab, Bitmap, Indexed, Multichannel, Duotone at 8/16/32-bit; ICC management in pure Rust with Assign/Convert to Profile, four rendering intents, black-point compensation, soft proofing and Gamut Warning (`README.md:205`, `crates/cms`, `crates/engine/src/color_cmds.rs` `specs()`).
+ - **Layers**: pixel layers, groups, clipping masks, pixel and vector masks with Density/Feather, fill layers (solid/gradient/pattern), adjustment layers, live smart objects with smart filters and lossless transforms/warps (including Cylinder), artboards, alpha channels, Quick Mask, layer comps, advanced blending (`crates/doc/src/lib.rs`, `crates/doc/src/effects.rs:277`).
+ - **16 adjustment layers**: `layer.newAdjustmentLayer.{brightnessContrast, levels, curves, exposure, vibrance, hueSaturation, colorBalance, blackWhite, photoFilter, channelMixer, invert, posterize, threshold, gradientMap, selectiveColor, colorLookup}` plus the same 16 as destructive `image.adjustments.*`, all generated from the 16-entry `ADJ` table (`crates/engine/src/commands.rs:1364-1450`). Extra destructive-only: Shadows/Highlights, Replace Color, Match Color, HDR Toning (`crates/engine/src/adjust_cmds.rs:537-575`).
+ - **10 layer effects**, several instances per kind: DropShadow, InnerShadow, OuterGlow, InnerGlow, Stroke, ColorOverlay, GradientOverlay, PatternOverlay, Satin, BevelEmboss, driven by 10 `layer.layerStyle.*` commands (`crates/doc/src/effects.rs:277`, `crates/engine/src/layer_style.rs` `style_cmd!`).
+ - **Blend modes**: PassThrough + 27 Photoshop modes + 6 Paint.NET-only modes = 34 enum variants (`crates/color/src/blend.rs:11-51`).
+ - **67 tools** — `pub const ALL: [Tool; 67]` (`crates/ui-egui/src/state.rs:209`), including Remove, Spot/Healing/Patch, Content-Aware Move, Mixer Brush, Magnetic Lasso, Object/Quick Selection, Pen/Freeform/Curvature Pen, Type and Type Mask, Perspective Crop, Frame, Slice. Of Photoshop's 68 classic tools only **Art History Brush** is missing (`docs/gaps.md` G6).
+ - **Selections**: marquee/lasso/wand, `select.quick`, `select.object`, `select.subject`, `select.colorRange`, `select.focusArea`, `select.refineEdge`, `select.magneticLasso`; Select and Mask is still a **dialog**, not a workspace (`crates/engine/src/smartselect_cmds.rs`, `docs/gaps.md` G7).
+ - **124 `filter.*` command ids**: 30 in `crates/engine/src/filters.rs:408` + 33 in `filters_ext.rs:595` + 47 Filter Gallery filters from the table in `crates/algo/src/artistic.rs` (each registered by `crates/engine/src/gallery_cmds.rs:174`, plus `filter.filterGallery` itself) + 13 workspace/lifecycle entries (`filter.blurGallery.{field,iris,path,spin,tiltShift}`, `filter.liquify`, `filter.adaptiveWideAngle`, `filter.cameraRaw`, `filter.vanishingPoint`, `filter.lensCorrection`, `filter.lastFilter`, `filter.convertForSmartFilters`). Filters applied to a smart object become re-editable smart filters; **54 PSD filter classes map to 49 distinct commands** through `filterFX` (`crates/io/src/smart_map.rs:155`).
+ - **Type**: point/area/paragraph text on canvas, Character/Paragraph controls, manual+optical kerning with PSD round trip, variable-font axes, `type.warpText`, `type.resolveMissingFonts`; headless editing via `type.editText`, `type.hitTest`, `type.caret`, `type.navigate`. Type on a path places glyphs rigidly only (`type.attachToPath`, `crates/engine/src/type_extra_cmds.rs`, `docs/gaps.md` G8).
+ - **Vectors**: Pen/Freeform/Curvature, anchor add/delete/convert, Paths panel, work paths, shape layers with dashed/aligned strokes and corner radii, path↔selection, SVG as editable shape layers or vector smart object (`crates/engine/src/vector_cmds.rs`, `docs/shape-strokes.md`).
+ - **Actions**: 16 `actions.*` commands (`list/get/record/stop/play/move/delete/toggle/rename/load/save/insert/dialog/assignSet/setColor/conditional`) with nesting to 16 levels and cycle rejection (`crates/engine/src/actions_cmds.rs:191`), plus **114 Photoshop event mappings** in the `EVENTS` table (`crates/engine/src/atn_cmds.rs:3405`) for `.atn` import/export; unmapped steps are kept verbatim and fail at playback by name.
+ - **Photomerge**: `file.automate.photomerge` (reposition/perspective layouts) as a cancellable background job (`crates/engine/src/photo_cmds.rs:900`).
+ - **Print**: File › Print renders one flattened page and prints through an in-process pure-Rust IPP client, the Linux print portal, the Windows spooler or to a PDF file; `file.printers` lists queues, `"dryRun": true` previews (`crates/engine/src/print_cmds.rs:15,1275`, `docs/printing.md`).
+ - **Video-lite**: 7 `timeline.*` commands (`create/delete/info/setProps/setFrame/nextFrame/previousFrame`, `crates/engine/src/timeline_cmds.rs:140`) and `file.export.renderVideo` (PNG sequence or animated GIF); animating GIF and WebP open every frame through New Video Layer from File (`crates/engine/src/video_cmds.rs`, `docs/file-format-parity.md` GIF/WebP rows).
+ - **Update check**: Help › Check for Updates plus an opt-out once-per-launch release-feed check, none inside Flatpak (`crates/engine/src/updates_cmds.rs:117`, `docs/scorecard.md` DIST-220-1 partial — **new since 0.12.0**, which had no update check).
### Files — read / write
- - **Native**: `.pcraft` (zip of STORE entries or a directory) = `manifest.json` (format version 1, migrations in `crates/format/src/migrate.rs`) + content-addressed BLAKE3/zstd `tiles/` and `blobs/` + `thumb.png` + `composite/preview.png`; incremental, atomic (temp/fsync/rename) saves, crash-recovery autosaves (`crates/format/src/lib.rs:1-60`, `crates/format/src/atomic.rs`, `autosave.rs`).
- - **Read + write**: PSD and PSB (layers, groups, masks, vector masks, adjustment and fill layers, effects, text, shapes, smart objects as embedded SoLd+lnk2, smart filters, alpha channels, slices, comps; streaming) — `crates/io/src/psd_import.rs`, `psd_export.rs`, `crates/psd`; **export auto-switches to PSB above an estimated 2 GB** (`docs/scorecard.md` REL-212-2 done); optional image previews written into the PSD (`crates/io/src/lib.rs` export path). OpenRaster `.ora` with layers/groups/blend modes (`crates/io/src/ora.rs`); layered TIFF (Photoshop layer data, both byte orders; extra alpha channels and a Quick Mask are not written) (`crates/io/src/tiff_layers.rs`); and via `photocraft-codecs`: PNG (8/16-bit), JPEG, TIFF (8/16/32f, BigTIFF), WebP (lossless + own lossy VP8), GIF, BMP, TGA, ICO, **DDS (read + write: uncompressed or BC1/BC2/BC3, optional full mip chain — new writer)**, Netpbm (PBM/PGM/PPM/PAM/PFM), QOI, OpenEXR (f16/f32), Radiance HDR, FITS (header cards kept), DPX (8/10/12/16-bit read and write), Cineon (read-only) (`crates/codecs/README.md` capability matrix; export dispatch `crates/io/src/lib.rs` `export_stages`).
- - **Save keeps what it opened**: a JPEG's quality is estimated from its quantisation tables and reused; a WebP keeps lossy-at-its-quality or lossless (`docs/file-format-parity.md` JPEG/WebP rows, #2399).
- - **Read-only imports**: Paint.NET PDN3 (editable layers, 14 blend modes), Krita `.kra`/`.krz`, Affinity `.af`/`.afdesign`/`.afphoto`/`.afpub` (own `crates/affinity` crate: pages/artboards, layers, vectors, text, images, masks; damaged files fall back to the embedded preview), SVG, DDS (top mip of a 2D texture), Cineon, HEIC/HEIF (pure-Rust `heic-rs` in the new `crates/heif`, behind the `heif` feature, on in official builds), and camera raws; import-only writers refuse with a naming error (`crates/io/src/lib.rs:1-60` and the `export_stages` PDN/Affinity/KRA refusals, `crates/io/src/pdn.rs`, `kra.rs`, `affinity.rs`, `svg.rs`, `crates/codecs/README.md`).
- - **Camera raw decode**: DNG (uncompressed, deflate, lossless-JPEG, **lossy-JPEG (compression 34892, now decodes)** and JPEG XL, CFA and LinearRaw), CR2, TIFF/EP raws (NEF, ARW, PEF), Nikon compressed NEF (lossless and lossy), Sony cRAW, Panasonic RW2, uncompressed Olympus ORF, uncompressed Fujifilm RAF (Bayer and X-Trans); measured per-camera profiles/image areas (Nikon D4, D5100, D90); a per-file colour look is fitted to the camera's own JPEG when there is no calibration; everything else reports Unsupported, and CR3 gives only its embedded JPEG preview + orientation, not sensor data (`crates/raw/src/lib.rs:29-37,92-241`, `docs/file-format-parity.md` Camera raw table).
- - **PDF**: write only, one flattened RGB/Gray/CMYK page with ICC — Save As / Save a Copy / CLI, print, and artboards-to-PDF (`crates/io/src/pdf.rs:1-12`, `docs/file-format-parity.md` Photoshop PDF row).
- - **Export As** formats: `png`, `jpg`, `webp`, `tif`, `tga`, `ico`, with Photoshop's six resample methods remembered (`crates/engine/src/export_preset_cmds.rs:13`, `docs/scorecard.md` FILE-215-18 done). **Save for Web**: `gif`, `png8`, `png24`, `jpeg`, `wbmp`, with editable colour tables (`crates/engine/src/web_cmds.rs:40-66`, `docs/control-protocol.md`). Quick Export as PNG (`crates/engine/src/shell_commands.rs`).
- - **Depth honesty**: `caps(format)` and `fidelity_warnings` report what an export will lose before it is written; CLI and desktop surface those as warnings (`crates/codecs/README.md`).
+ - **Native `.pcraft`**: directory bundle or zip of STORE entries = `manifest.json` (format version **1**, `crates/format/src/manifest.rs:18`; migrations in `migrate.rs`) + content-addressed BLAKE3/zstd `tiles/`+`blobs/` + `thumb.png` + `composite/preview.png`; incremental, atomic (temp/fsync/rename, `crates/format/src/atomic.rs`), crash-recovery autosaves (`crates/format/src/autosave.rs`).
+ - **Read + write**: PSD/PSB/PSDT (`crates/io/src/psd_import.rs`, `psd_export.rs`, `crates/psd`), layered TIFF (`crates/io/src/tiff_layers.rs`), OpenRaster `.ora` (`crates/io/src/ora.rs`, `docs/ora.md`), PDF write-only one flattened page (`crates/io/src/pdf.rs`), and the flat codecs: PNG, JPEG, TIFF, WebP (lossless + own lossy VP8), GIF, BMP, TGA, ICO, Netpbm (PBM/PGM/PPM/PAM/PFM), QOI, DDS (uncompressed or BC1/BC2/BC3 + optional mip chain), OpenEXR, Radiance HDR, FITS, DPX, **Cineon (write added since 0.12.0 — `.cin` Saves now)** (`crates/codecs/README.md` matrix, `crates/codecs/src/format.rs:108` `Format::ALL [Format; 22]`, dispatch `crates/io/src/lib.rs:514` `export_stages`).
+ - **22 codec formats** in `Format` (`crates/codecs/src/format.rs:7`); every one is read+write except the documented asymmetries (`ASYMMETRIC_EXCEPTIONS`, `format.rs:73`): **read-only** PCX, XPM, DICOM and Amiga IFF (the 0.12.0 skill listed PCX, DICOM and IFF as unsupported; XPM it never mentioned), AVIF write-only behind the non-default `avif` feature, HEIF/HEIC read-only behind `heif` (on in official builds).
+ - **Layered imports (read-only)**: Paint.NET `.pdn`/PDN3 (editable layers, 14 blend modes), Krita `.kra`/`.krz`, **GIMP `.xcf` and `.xcf.gz` (new since 0.12.0**: pixel layers, groups, masks, blend modes, channels, saved selection, 8/16/32-bit, `crates/io/src/xcf.rs`, `docs/xcf.md`), Affinity `.af`/`.afphoto`/`.afdesign`/`.afpub` (`crates/affinity`), SVG (`crates/io/src/svg.rs`). Writing an import-only target is refused with a naming error in `export_stages` (`crates/io/src/lib.rs:516-532`).
+ - **Camera raw**: DNG (uncompressed/deflate/lossless-JPEG/lossy-JPEG/JPEG XL, CFA and LinearRaw), CR2, TIFF/EP raws (NEF, ARW, PEF), Nikon compressed NEF, Sony cRAW, Panasonic RW2, uncompressed Olympus ORF, uncompressed Fujifilm RAF (Bayer and X-Trans); measured profiles/image areas for Nikon D4/D5100/D90/D800E and Sony ILCE-7; everything else falls back to the embedded preview and **CR3 gives only its JPEG preview + orientation, not sensor data** (`crates/raw/src/lib.rs:34-41`, `cr3.rs:1-5`, `docs/file-format-parity.md` Camera raw).
+ - **Export As** offers exactly `png, jpg, webp, tif, tga, ico` (`crates/engine/src/export_preset_cmds.rs:13`); **Save for Web** offers `gif, png8, png24, jpeg, wbmp` (`crates/engine/src/web_cmds.rs:46-50`); Quick Export as PNG is a single menu item.
+ - **Honest exports**: `caps(format)` and `fidelity_warnings` report depth reduction, CMYK→RGB, dropped layers and missing fonts before writing; CLI/`info`/`app.save` surface them as warnings (`crates/codecs/README.md`).
- ### Automation surfaces
+ ### Automation surfaces (counts re-measured at v0.16.0)
- - **One command registry**: 631/631 menu items live (`crates/ui-egui/src/parity.rs:10`, `docs/parity-checklist.md`). Wiring is not behaviour; check `enabled`/`disabledReason`/`needsDocument` per command (`docs/control-protocol.md`).
- - **CLI `photocraft-cli`** — 8 subcommands, all flags enforced (unknown flag = usage error, exit 2): `convert`, `info`, `run`, `batch`, `droplet`, `commands`, `mcp`, `serve`; plus `--version` (`apps/photocraft-cli/src/lib.rs:14-107`). New export flags: `--dds-compression <none|bc1|bc2|bc3>` and `--dds-mipmaps` on convert/run/batch (lib.rs:18-53).
- - **Control channel**: `photocraft --control <port>` serves authenticated JSON lines on `127.0.0.1` only; first frame must be `{"id":"auth","method":"auth","params":{"token":"<64 hex>"}}`; 60-second reply timeout. Methods include `engine.execute` (with `wait: false` background jobs), `engine.commands`, `jobs.list/cancel`, `ui.inspect`, `ui.set`, `ui.menu.invoke/list`, `ui.pointer`, `ui.click`, `ui.move`, `ui.key`, `ui.type`, `ui.resize`, `ui.focus`, `ui.context.choose`, `ui.dialog.*`, `ui.screenshot`, `ui.window.open/close`, `ui.gpu.simulateLoss`, `app.open`, `app.save` (now with `tgaBits`/`ddsCompression`/`ddsMipmaps`), `app.quit` (`docs/control-protocol.md`, `apps/photocraft/src/control_server.rs`, `crates/ui-egui/src/control.rs`).
- - **MCP**: `photocraft-cli mcp` runs headless; `--bridge 127.0.0.1:<port>` wraps a running app (bridge-only: `ui_*`, `control_call`; headless-only: `doc_select`, `doc_close`). Tools: `command_list`, `command_run`, `command_batch`, `doc_inspect`, `doc_open/new/place/save/export/export_layers/export_data_sets/import_data_sets`, `render_preview`, `jobs_list/jobs_cancel`, `session_list`, `ui_inspect`, `ui_screenshot`, `ui_pointer`, `ui_menu_invoke`, `ui_set`, `control_call` (`docs/mcp.md`, `docs/control-protocol.md` "MCP bridge" table). Scripted agent acceptance tasks run over MCP in `crates/automation/tests/agent_tasks.rs` and `agent_tasks_more.rs`.
- - **Headless server**: `photocraft-cli serve` (stdio or `--port`) answers `engine.execute`, `engine.commands`, `jobs.list/cancel`, `doc.open/new/place/save/inspect/render/select/close/exportLayers/exportDataSets/importDataSets`, `session.list`, `batch`, `methods`; an `index` param targets a document without switching (`docs/control-protocol.md` "Headless server", `crates/automation/src/rpc.rs`).
- - **Plug-ins**: WebAssembly ABI v1 — `plugin.install/run/remove/reload/list`, wasmi sandbox, no WASI/filesystem/network, 32 MiB module, 512 MiB memory, 50M-instruction fuel (+4,000 per sample), 60 s budget per run (`docs/plugins.md`). ABI v2 (panels, progress) is missing.
- - **Batch/droplets**: `photocraft-cli batch` applies an actions JSON, an Actions set (steps may call sibling actions; self-calls stop), or a `.atn` set (`--action` picks by name) to a folder, with `{name}/{serial}/{date}` output templates; `.pcdroplet` droplets run via `file.automate.runDroplet` (`apps/photocraft-cli/src/lib.rs:388-443`, `crates/engine/src/automate_cmds.rs`, `docs/batch-naming.md`).
+ - **One command registry, 631/631 menu items live** (`crates/ui-egui/src/parity.rs:10`, `docs/parity-checklist.md:5`). Total registry size: **~854 command specs** — a static count of 822 `spec!`/`cmd!`/`filter_cmd!`/`CommandSpec` registration sites in `crates/engine/src` (test files excluded) plus the 32 commands generated from the 16-entry `ADJ` table (`commands.rs:1364-1450`); this number is **uncertain by a few dozen** because two table macros (`style_specs!`, `gallery_specs!`) expand to several specs each — get the authoritative count with `photocraft-cli commands | wc -l`. README's "500+" (`README.md:230`) and `docs/architecture.md`'s "~700" are floors, not measurements.
+ - **CLI `photocraft-cli` — 8 subcommands**: `convert, info, run, batch, droplet, commands, mcp, serve` (`apps/photocraft-cli/src/args.rs:31-40`, dispatch `apps/photocraft-cli/src/lib.rs:48-62`), exit codes 0 ok / 1 failure / 2 usage. **9 export flags** shared by `convert`/`run`/`batch`: `--format --quality --tiff-layers --tiff-compression --tga-bits --dpx-bits --dds-compression --dds-mipmaps --jpeg-format` (`args.rs:44-83`); **new since 0.12.0**: `--tiff-compression none|lzw|zip`, `--jpeg-format baseline|optimized|progressive`, and `--in-place` on `convert` and `run` (previously only `batch`). New also: shell completions (bash/zsh/fish/elvish/PowerShell) and man pages per subcommand, generated by `apps/photocraft-cli/examples/man-and-completions.rs` and shipped in the Linux/FreeBSD packages.
+ - **Control channel** (`photocraft --control <port>`, loopback only): first frame must be `{"id":"auth","method":"auth","params":{"token":"<64 hex>"}}`; then **28 methods** — `engine.execute`, `engine.commands`, `jobs.list`, `jobs.cancel`, `ui.inspect`, `ui.set`, `ui.menu.invoke`, `ui.menu.list`, `ui.pointer`, `ui.click`, `ui.move`, `ui.key`, `ui.type`, `ui.resize`, `ui.focus`, `ui.screenshot`, `ui.context.choose`, `ui.dialog.open/set/confirm/apply/cancel`, `ui.window.open/close`, `ui.gpu.simulateLoss`, `app.open`, `app.save`, `app.quit` (`crates/ui-egui/src/control.rs` dispatch; `docs/control-protocol.md` "Methods"). Transports: `apps/photocraft/src/control_server.rs`; 60 s reply timeout, 1 MiB request cap, 16 connections (`crates/automation/src/security.rs:15-29`). Same count as the previous skill, which listed them correctly.
+ - **MCP — 26 tools** (`crates/automation/src/server.rs:610-1016`): `session_list`, `doc_open`, `doc_place`, `doc_replace_smart_object`, `doc_export_layers`, `doc_export_data_sets`, `doc_import_data_sets`, `doc_new`, `doc_save`, `doc_export`, `doc_inspect`, `doc_render_preview`, `render_preview`, `doc_select`, `doc_close`, `command_list`, `command_run`, `jobs_list`, `jobs_cancel`, `command_batch`, `ui_inspect`, `ui_screenshot`, `ui_pointer`, `ui_menu_invoke`, `ui_set`, `control_call`. **New since 0.12.0**: `doc_replace_smart_object` (and `doc_render_preview` is a distinct registry entry the old skill never enumerated). Bridge-only: the five `ui_*` tools + `control_call`; headless-only: `doc_select`, `doc_close`. **2 MCP resources** (`photocraft://document`, `photocraft://commands`, `crates/automation/src/server/conventions.rs:7-8,63-90`) — a surface the previous skill omitted entirely.
+ - **Headless server** `photocraft-cli serve` (stdio or `--port`) answers **19 methods** (`crates/automation/src/rpc.rs:42-61` `METHODS`): `engine.execute`, `jobs.list`, `jobs.cancel`, `engine.commands`, `session.list`, `doc.open/place/replaceSmartObject/new/save/exportLayers/exportDataSets/importDataSets/inspect/render/select/close`, `batch`, `methods` — one more than 0.12.0 (`doc.replaceSmartObject`).
+ - **Automation budgets** (measured, `crates/automation/src/budgets.rs:13-19`): 8 MiB reply, 5 MiB PNG, preview `max_side` 2048, 64 M source pixels for renders, 256 batch steps (`security.rs:21`), 30 s IO timeout, 10 s auth deadline.
+ - **Plug-ins**: WebAssembly **ABI v1** only — `plugin.list/install/remove/reload/run` (5 commands, `crates/engine/src/plugin_cmds.rs:19,273-319`), wasmi sandbox, no WASI/filesystem/network/clock, 32 MiB module, 512 MiB memory, 50 M instructions + 4,000 per sample, 60 s per run (`docs/plugins.md`, `crates/plugins/src/runtime.rs:14,45-47`).
+ - **Batch/droplets**: `photocraft-cli batch` takes an actions JSON, an Actions set (`--action` picks by name) or a `.atn` set, applies it to a folder with `{name}/{serial}/{date}` output templates, and refuses `--out == --in` unless `--in-place` (`apps/photocraft-cli/src/args.rs:155-176`, `batch.rs:43-60`, `docs/batch-naming.md`). `.pcdroplet` droplets run via `file.automate.runDroplet`.
### Platform
- - macOS, Windows (x64/x86/ARM64), Linux, FreeBSD natives and a WebAssembly web build (`README.md` Downloads; `docs/scorecard.md` DIST rows — FreeBSD tarball layout checked but not yet produced by a release run).
- - GPU compositor on wgpu (Metal/Vulkan/DX12/WebGPU) with CPU fallback, `--safe-gpu`, automatic driver-crash fallback and `ui.gpu.simulateLoss` for testing (`apps/photocraft/src/gpu_startup.rs`, `docs/control-protocol.md`).
- - Pen tablets: pressure/tilt/rotation on macOS (isolated AppKit monitor crate), Windows (`WM_POINTER` via winit; tilt/rotation still missing), Linux X11 (XInput2), web (Pointer Events); native Wayland tablet-v2 still open (`crates/tablet`, `docs/gaps.md` G12).
+ - macOS (universal, signed+notarized), Windows x64/x86/ARM64, Linux x86_64/aarch64 (AppImage, Flatpak, deb, rpm, tarball, riscv64 tarball), FreeBSD 14 x86_64, and a WebAssembly web build (`README.md` Downloads; `docs/scorecard.md` DIST rows — Windows signing is skipped with a warning until the signing material exists, and the FreeBSD tarball layout is checked but not yet produced by a release run).
+ - GPU compositor on wgpu (Metal/Vulkan/DX12/WebGPU) with CPU fallback, `--safe-gpu`, automatic device-loss fallback, and `ui.gpu.simulateLoss` (with `destroy: true`) for testing (`apps/photocraft/src/gpu_startup.rs`, `docs/control-protocol.md`).
+ - Pen tablets (`docs/hardware-parity.md` Pen tablets, `crates/tablet`): macOS pressure/tilt/barrel-rotation (rotation not applied to tip angle, #1301); Windows **pressure only** (tilt and rotation missing, #2046/#2285); Linux X11 pressure+tilt with partial rotation; web pressure/tilt/twist; native Wayland tablet-v2 still open.
## Known limitations and not-implemented
- Read this before assigning work — several of these are the reason agents should not promise Photoshop behaviour.
+ Read this before assigning work — several of these are why agents must not promise Photoshop behaviour.
- - **Alpha, by measurement**: ~45% "ready for real work" and 953 open issues (2026-10-13) (`ROADMAP.md`); of the committed performance baseline's 60 scenarios, 3 are within budget, 22 over budget or failing, 9 tracked only, 22 not run and 4 not measurable — including a GPU-validation crash when nudging in a 150-layer document (P12) and a 954 s Content-Aware Scale against a 3 s target (`docs/scorecard.md` Performance).
- - **PSD fidelity is the blocking gap (G1)**: enforced corpus floors are *minimums* — Photoshop-authored oracles 188/256 render-pass (round-trip floor 258), psd-tools 249/309 (round trip 308), io corpus 148/170 (round trip 169) (`crates/io/tests/corpus.rs:151-159`); the README's measured numbers are 307/309 and 169/170 renders (`README.md:220`). A re-saved PSD is **not byte-identical** to its source (only the standalone `photocraft-psd` crate reproduces corpus files byte for byte), and no test opens exports in a third-party editor (FILE-216-4). Photoshop refusing our files is reported in #1281, #2469 (`docs/gaps.md` G1).
- - **No AI/generative features**: no `ml` crate and no ONNX runtime in the workspace; "ML backend not started" (`docs/roadmap.md` M10); no Neural Filters, generative fill/expand, sky replacement ML or generative Remove mode. Classical substitutes exist (Select Subject via GrabCut/max-flow, Remove tool by non-local patch completion) (`docs/gaps.md` G15, TOOL-213-19).
- - **No native Photoshop plug-ins**: `.8BF`, CEP and UXP are not hosted; only the Wasm plug-in ABI v1 (`docs/gaps.md` G16, `docs/plugins.md`).
- - **Workspaces that are dialogs, not workspaces (G7)**: Select and Mask opens a dialog; no Content-Aware Fill sampling workspace; Vanishing Point has a plane editor but no clone/paste-in-plane; Path Blur is a parameter dialog (`docs/gaps.md` G7, `docs/scorecard.md` TOOL-214-14/15).
- - **Type depth (G8)**: type on a path saves as point text in PSD; no paragraph-direction UI; no Numerals/Stylistic Sets submenus; PSD does not round-trip variable-font axis values (`docs/scorecard.md` Type rows).
- - **Formats absent or partial**: no PDF import (#2541), no Photoshop EPS/DCS; AVIF read missing and AVIF write only behind the non-default `avif` build feature; JPEG XL only inside DNG; JP2, DICOM, PCX, Pixar, IFF, WBMP (as a save format), MPO/JPS, PICT, Scitex CT, Photoshop Raw and Cloud PSD unsupported; Cineon has no writer (`crates/codecs/README.md`, `docs/file-format-parity.md`). Affinity, Krita and PDN are import-only. GIF opens first-frame-only via File › Open (use New Video Layer from File for frames); APNG keeps the first frame; per-frame GIF delays are not persisted (`docs/file-format-parity.md` PNG/GIF rows).
- - **Camera raw partial**: unsupported variants fall back to the embedded JPEG preview; "lossy after split" NEF no; no `.xmp` sidecars; CR3 sensor data no (`crates/raw/src/lib.rs`, `docs/file-format-parity.md` Camera raw).
- - **Dead preferences**: 24 of 171 settings do nothing, including `integrations.control_port` and the `raw_defaults` block (`docs/scorecard.md` "Workspace and UI" prefs audit).
- - **Automation is fail-closed**: ambient filesystem commands are refused — `file.open/openAs/save/saveAs/saveACopy`, `file.export.saveForWebLegacy`, `pattern.import`, smart-object export/replace/relink, `measurementLog.export`, `edit.colorSettings`; path params of other commands (`layer.exportAs`, `filter.distort.displace`, preset imports, plug-in install, `layer.setAdjustment {"file": …}`) are refused too; `prefs.set` rejects whole-preference updates and file-backed sections (`colorSettings`, `scriptEvents`, `historyLog`, `plugIns`, `scratchDisks`); the **desktop app additionally refuses `image.mode.*`** (it could load profile files) (`crates/automation/src/workspace.rs` `filesystem_commands_fail_closed` test, `docs/control-protocol.md`).
- - **Localization**: UI strings translated (12 key languages, 8 more shipped) but engine messages stay English; Hindi, Arabic (RTL shell only) and Vietnamese are 0% (`ROADMAP.md` Languages).
- - **No in-app update check** (`docs/scorecard.md` DIST-220-1 missing — updates come from the ArtCraft Launcher / release downloads); Windows builds are unsigned so far (DIST-2).
- - **Windows**: PSB over 4 GB via the app's File › Open is still issue #375, though a generated 4.38 GB PSB round-trips through the io layer on Windows (`docs/file-format-parity.md` PSB row, `crates/io/tests/large_files.rs`).
- - **Wayland**: no drag-and-drop (winit 0.30 limit) and XWayland fractional scaling is blurred (`README.md:285`).
- - Docs lag code between releases (the roadmap still carries pre-0.10 measurements; scorecard/parity-checklist are generated and fresher). Verify against your installed version before asserting a feature is missing.
+ - **Alpha, by measurement**: ~45% "ready for real work", 953 open issues as of the ROADMAP's 2026-10-13 measurement (`ROADMAP.md:41-45`); `docs/gaps.md` G3 quotes **579** open issues at 2026-10-10 — the two documents disagree, trust neither number exactly and check GitHub. Performance: 64 scenarios, **3 within budget, 22 over budget or failing**, 9 tracked only, 26 not run, 4 not measurable (`docs/scorecard.md:9`). The ROADMAP still says menus 628/628, tools 56/68 and formats "13+4 of 31" — the generated parity checklist says 631/631 and the code has 67 tools (`docs/gaps.md` G6), so ROADMAP headline rows are stale.
+ - **PSD fidelity is the blocking gap (G1)**: enforced corpus floors are *minimums* — Photoshop-authored oracles **190/256** render, psd-tools 249/309, io corpus 148/170, round trips 258/256, 308/309, 169/170 (`crates/io/tests/it/corpus.rs:151-159`; note the Photoshop oracle set is 256 or 258 files depending on the row — the floor is what CI enforces). A re-saved PSD is **never byte-identical** to its source (only the standalone `photocraft-psd` crate reproduces corpus files byte for byte), and Photoshop failing to reopen our files is tracked in #1281/#2469 (`docs/gaps.md` G1).
+ - **No AI/generative features**: no ONNX runtime or `ml` crate in the workspace; no Neural Filters, generative fill/expand, sky replacement or generative Remove. Classical substitutes only (Select Subject via GrabCut/max-flow, Remove by patch completion) (`docs/gaps.md` G15, `docs/scorecard.md` AUTO-219-7 missing).
+ - **No native Photoshop plug-ins**: `.8BF`, CEP and UXP are not hosted; only the Wasm ABI v1 (`docs/gaps.md` G16, `docs/plugins.md`; ABI v2 with panels/progress is `docs/scorecard.md` AUTO-219-6 missing).
+ - **Workspaces that are dialogs (G7)**: Select and Mask is a dialog, there is no Content-Aware Fill sampling workspace, Vanishing Point has a plane editor but no clone-in-plane, and **Path Blur is still a parameter dialog** while Field/Iris/Tilt-Shift/Spin are on-canvas (`docs/gaps.md` G7).
+ - **Type depth (G8)**: type on a path places glyphs rigidly; no RTL visual caret or PSD paragraph-direction storage; no OpenType Numerals/Stylistic Sets submenus; text free transform missing (`docs/gaps.md` G8).
+ - **Formats absent**: Photoshop PDF **read** (#2541); EPS/DCS and generic EPS/AI; Cloud PSD `.psdc`; JPEG 2000; Pixar; WBMP; MPO/JPS; PICT; Scitex CT; Photoshop Raw; AVIF read; JPEG XL only inside DNG. Affinity, Paint.NET, Krita and XCF are **import-only** (writable targets are refused in `crates/io/src/lib.rs:516-532`). File › Open takes only a GIF/WebP animation's first frame (use New Video Layer from File for all frames) and per-frame delays are not kept in `.pcraft` (`docs/file-format-parity.md`).
+ - **Camera raw partial**: CR3 sensor data no (preview only), "lossy after split" NEF no, no `.xmp` sidecars, no Fuji colour calibration, Olympus ORF and Fujifilm RAF only uncompressed (`crates/raw/src/lib.rs`, `docs/file-format-parity.md` Camera raw).
+ - **Dead preferences**: 20 of 177 settings do nothing (`docs/scorecard.md:12,188`; was 24 of 171 at 0.12.0). CI regenerates that count, so re-read the scorecard rather than trusting this line.
+ - **Automation is fail-closed**: these 14 commands are refused over control/MCP/`serve` — `file.open`, `file.openAs`, `file.save`, `file.saveAs`, `file.saveACopy`, `file.export.saveForWebLegacy`, `pattern.import`, `layer.smartObjects.{exportContents, replaceContents, relinkToFile, convertToLinked}`, `measurementLog.export`, `layer.videoLayers.reloadFrame`, `edit.colorSettings`; path params of other commands (`layer.exportAs`, `filter.distort.displace` `mapPath`, preset imports, plug-in install, `layer.setAdjustment {"file": …}`, `prefs.set` on file-backed sections) are refused too, and the **desktop app additionally refuses `image.mode.*`** (`crates/automation/src/workspace.rs:745` `filesystem_commands_fail_closed`; 14 entries now vs ~10 at 0.12.0 — `layer.smartObjects.convertToLinked` and `layer.videoLayers.reloadFrame` were added).
+ - **Localization**: UI strings translated, engine messages stay English; Hindi, Arabic (RTL shell only) and Vietnamese are 0% (`ROADMAP.md` Languages, `docs/gaps.md` G13).
+ - **Distribution gaps**: no in-app bug-report dialog (DIST-220-3 missing), no periodic update recheck while the app stays open (DIST-220-1 partial), Windows signing skipped without secrets (DIST-2 partial) (`docs/scorecard.md`).
+ - **Wayland**: no drag-and-drop (winit 0.30 limit) and XWayland fractional scaling is blurred (`README.md:289`).
+ - **Docs lag the code** — several generated/hand-written docs contradict this tag's source: `docs/file-format-parity.md`'s "ours only" table still lists Krita and GIMP XCF as unread although `crates/io/src/kra.rs`/`xcf.rs` import them; `docs/gaps.md` says 112 `.atn` events while the table has 114; the ROADMAP headline rows are pre-0.10. Verify against your installed version before asserting a feature is missing.
## How to operate
### Launch (macOS)
```sh
open -a PhotoCraft # normal GUI launch
open -a PhotoCraft /path/to/file.psd # open a file in the running instance
open -a PhotoCraft --args --control 7878 --control-token-file "$HOME/.photocraft/control.token" \
--automation-read-root "$PWD" --automation-write-root "$PWD"
```
- - Binary: `/Applications/PhotoCraft.app/Contents/MacOS/PhotoCraft`; the app bundle contains **no CLI** — get `photocraft-cli` from the release zip (`photocraft-cli-<ver>-macos-universal.zip`, signed and notarized), `cargo install --path apps/photocraft-cli --locked`, or the Linux `.deb`/`.rpm` (`/usr/bin/photocraft-cli`).
- - App options (verified in `apps/photocraft/src/main.rs` usage text, lines 168-198): `-h/--help`, `--version`, `--safe-gpu`, `--in-window-menus`, `--new-instance`, `--present-mode <fifo|autovsync|novsync>`, `--frame-latency <1..3>`, `--stroke-trace <stderr|file.csv>`, `--control <port>`, `--control-token <64-hex>`, `--control-token-file <path>`, `--automation-read-root <dir>`, `--automation-write-root <dir>`, `--`. Env equivalents exist (`PHOTOCRAFT_CONTROL_PORT`, `PHOTOCRAFT_CONTROL_TOKEN`, …, `PHOTOCRAFT_CONFIG_DIR`).
- - If no token is given, a fresh one is generated and printed to stderr; a token file is created with mode 0600. Treat it as a credential: the token grants the whole non-filesystem control surface, and the protocol is unencrypted loopback (`docs/control-protocol.md` security note).
- - Do not launch the GUI just to inspect a file: use the CLI (below). macOS does not render occluded windows, so live screenshots steal focus (`ui.screenshot` raises the window by default; `docs/development.md`).
+ - Binary: `/Applications/PhotoCraft.app/Contents/MacOS/PhotoCraft`. The bundle contains **no CLI** — get `photocraft-cli` from `photocraft-cli-<ver>-macos-universal.zip` (signed, notarized), `cargo install --path apps/photocraft-cli --locked`, or the Linux `.deb`/`.rpm`, which install `photocraft-cli` on `PATH`.
+ - App flags, parsed by clap so a typo is a usage error (exit 2): files, `-h/--help`, `--version`, `--safe-gpu`, `--in-window-menus`, `--new-instance`, `--present-mode <fifo|vsync|autovsync|novsync|immediate|mailbox|autonovsync>`, `--frame-latency <1..3>`, `--stroke-trace <stderr|FILE.csv>`, `--control <port>`, `--control-token <64-hex>`, `--control-token-file <path>`, `--automation-read-root <dir>`, `--automation-write-root <dir>`, `--` (`apps/photocraft/src/launch_args.rs:27-74`). Env equivalents: `PHOTOCRAFT_CONTROL_PORT`, `PHOTOCRAFT_CONTROL_TOKEN(_FILE)`, `PHOTOCRAFT_AUTOMATION_{READ,WRITE}_ROOT`, `PHOTOCRAFT_CONFIG_DIR`, `PHOTOCRAFT_NEW_INSTANCE`.
+ - Without a token, a fresh 256-bit one is generated and printed to stderr; a token file is created mode 0600. Treat it as a credential: it grants the whole non-filesystem control surface over unencrypted loopback (`crates/automation/src/security.rs:15-29`, `docs/control-protocol.md`).
+ - Never launch the GUI just to inspect a file — use the CLI. On macOS occluded windows stop rendering, so `ui.screenshot` raises the window and steals focus (`ui.screenshot {focus:false}` avoids it; `docs/development.md`).
### Headless CLI
- Build or run from source: `cargo run -p photocraft-cli -- <subcommand>`.
-
```sh
photocraft-cli convert in.psd out.pcraft [--format <ext>] [--quality 1-100] [--tiff-layers] \
- [--tga-bits 24|32] [--dpx-bits 8|10|12|16] [--dds-compression none|bc1|bc2|bc3] [--dds-mipmaps]
- photocraft-cli info out.pcraft [--compact] # JSON: size, mode, depth, layer tree, warnings
+ [--tiff-compression none|lzw|zip] [--jpeg-format baseline|optimized|progressive] \
+ [--tga-bits 24|32] [--dpx-bits 8|10|12|16] [--dds-compression none|bc1|bc2|bc3] [--dds-mipmaps] [--in-place]
+ photocraft-cli info out.pcraft [--compact] # JSON: size, mode, depth, layer tree, warnings
photocraft-cli run in.png --cmd layer.new.layer --params '{"name":"Ink"}' \
--cmd filter.blur.gaussianBlur --params '{"radius":3}' \
- --out out.psd [--format <ext>] <same export flags>
+ --out out.psd <same export flags>
photocraft-cli run --new '{"width":800,"height":600,"background":"white"}' --cmd document.inspect --out new.pcraft
photocraft-cli batch --actions actions.json [--action <name>] --in ./raw --out ./graded [--format jpg] [--in-place]
photocraft-cli droplet job.pcdroplet ./images [--out ./done]
photocraft-cli commands [--json] [--filter blur]
- photocraft-cli mcp [--bridge 127.0.0.1:7878] [--control-token <64-hex> | --control-token-file <path>] \
+ photocraft-cli mcp [--bridge 127.0.0.1:7878] [--control-token <hex> | --control-token-file <path>] \
[--automation-read-root <dir>] [--automation-write-root <dir>]
- photocraft-cli serve [--port <port>] [--control-token <64-hex> | --control-token-file <path>] \
+ photocraft-cli serve [--port <port>] [--control-token <hex> | --control-token-file <path>] \
[--automation-read-root <dir>] [--automation-write-root <dir>]
```
- Behaviour that bites: each `--params` must follow its `--cmd`; `run` needs exactly one of `<file>` or `--new <json>`; every command's JSON result is one line on stdout while warnings go to stderr (missing fonts are reported once per run by `convert`, `run` and `info`); exit codes are 0 ok / 1 failure / 2 usage; `batch` refuses an `--out` equal to `--in` unless `--in-place` (canonicalized, so symlinks count) and refuses to overwrite a same-named output; `--quality` sets JPEG and lossy WebP (without it WebP is lossless) (`apps/photocraft-cli/src/lib.rs`).
+ Behaviour that bites: each `--params` belongs to the `--cmd` before it; `run` needs exactly one of `<FILE>` or `--new`, and at least one `--cmd` or `--out`; command results are one JSON line each on stdout while warnings go to stderr; `convert`/`run` refuse an `<OUT>` that is the `<IN>` unless `--in-place`; `batch` refuses an `--out` equal to `--in` (canonicalised, so symlinks count), refuses to overwrite a same-named output, and fails the file whose nested action fails; `--quality` sets JPEG and lossy WebP (without it WebP is lossless); MCP `--bridge` and `serve --port` refuse automation roots/token flags they cannot honour (`apps/photocraft-cli/src/args.rs:365-420`, `batch.rs:43-60`).
### Document and window model
- One window holds the open documents as tabs (the control channel exposes them as `window.document.<index>` in tab order); `ui.window.open` adds an extra window. On Windows and Linux a second launch hands its files to the running instance as new tabs; `--new-instance` and any `--control` launch start a separate process (macOS relies on LaunchServices). (`apps/photocraft/src/main.rs:13-15`, `docs/control-protocol.md`.)
+ One window holds the open documents as tabs (the control channel exposes them as `window.document.<index>` in tab order, active one checked); `ui.window.open` adds an extra window. On Windows and Linux a second launch hands its files to the running instance as new tabs; `--new-instance` and any `--control` launch start a separate process (macOS relies on LaunchServices) — `apps/photocraft/src/single_instance.rs`, `docs/control-protocol.md`.
### Open / save / export flow
- - Open: File › Open (`Cmd+O`), drag-and-drop, or the CLI. Save (`Cmd+S`) writes back to the document's own `.psd`, `.psb` or `.pcraft`; Save As (`Cmd+Shift+S`) picks the format from the file extension — `.pcraft` for the lossless native bundle, PSD/PSB for Photoshop handoff, any writable codec extension for flat output, `.pdf` for one flattened page. Long opens/saves run as cancellable background jobs with progress (`docs/control-protocol.md` "Background jobs").
- - Over the control channel or MCP use `app.open` / `app.save` (or `doc.*` on serve/MCP) with paths **relative to the automation roots** — the refused ambient commands are listed under Known limitations. Embedded smart objects are the exception that works: `layer.smartObjects.editContents`, `layer.smartObjects.saveContents`, `layer.smartObjects.convertToLayers` run over automation (pathless forms only; save nested objects from the deepest contents outward) (`docs/control-protocol.md` "Embedded Smart Objects", `crates/automation/src/workspace.rs`).
- - Export As (`Cmd+Alt+Shift+W`) offers png/jpg/webp/tif/tga/ico with resample methods; Save for Web offers gif/png8/png24/jpeg/wbmp; Quick Export as PNG is one menu item away.
- - Keyboard shortcuts: the authoritative tables are `crates/engine/src/shell_commands.rs` (shell + tool defaults) and `crates/ui-egui/src/menu_catalog.rs` (menu items). Verified examples: `Cmd+F` Search…, `Cmd+J` Layer via Copy (`menu_catalog.rs:257`), `Cmd+D` Deselect (`:507`), `Cmd+O`/`Cmd+S`/`Cmd+Shift+S` (`shell_commands.rs:21-23`), `Cmd+Alt+Shift+W` Export As (`:26`), `F7` Layers, `F6` Color, `F5` Brush Settings (`:54-58`). Photoshop shortcut sets import/export through `edit.keyboardShortcuts` (`.kys`, including Photoshop's live installed set on first launch).
+ - Open: File › Open (`Cmd+O`), drag-and-drop, `open -a PhotoCraft <file>`, `photocraft-cli`, or `app.open`/`doc.open` over automation. Save (`Cmd+S`) writes back to the document's own PSD/PSB/`.pcraft`; Save As (`Cmd+Shift+S`) picks the format from the extension — `.pcraft` for lossless native, PSD/PSB for Photoshop handoff, any writable codec extension for flat output, `.pdf` for one flattened page, `.cin` for Cineon, `.dds` with DDS Options. Long opens/saves run as cancellable background jobs with progress (`docs/control-protocol.md` "Background jobs").
+ - Over the control channel/MCP use `app.open`/`app.save` (desktop) or `doc.*` (serve/MCP) with paths **relative to the automation roots**; the refused ambient commands are listed above. Embedded smart objects are the exception that works: `layer.smartObjects.editContents`, `saveContents`, `convertToLayers` run pathless, and the capability-scoped `doc_place` (MCP), `doc.replaceSmartObject` (serve) and `doc_replace_smart_object` read beneath the read root (`docs/mcp.md:112-116`, `docs/control-protocol.md:404`).
+ - Modal workspaces gate menu invocation: while Camera Raw, Liquify, Content-Aware Fill, Filter Gallery or Adaptive Wide Angle is open, `ui.menu.invoke` refuses every other item and accepts only that workspace's `{"ui":{…}}` requests — finish with `{"ui":{"commit":true}}` or `{"ui":{"cancel":true}}` first (`docs/control-protocol.md` "Methods"). `engine.execute` is *not* gated.
+ - Keyboard shortcuts: authoritative tables are `crates/engine/src/shell_commands.rs` (shell + tool defaults) and `crates/ui-egui/src/menu_catalog.rs` (menu items). Verified examples: `Cmd+O`/`Cmd+S`/`Cmd+Shift+S`, `Cmd+Alt+Shift+W` Export As, `Cmd+F` Search…, `Cmd+J` Layer via Copy, `Cmd+D` Deselect, `F5` Brush Settings, `F6` Color, `F7` Layers. Photoshop `.kys` sets import/export through `edit.keyboardShortcuts` (including a one-time offer to import Photoshop's live installed set) — `crates/engine/src/kys.rs`, `docs/control-protocol.md:236`.
### State on disk
- - macOS settings dir: `~/Library/Application Support/Photocraft/` — `preferences.json`, `Presets/` (brushes as `.pcbrushes` + content-addressed `tips/`, the Actions list as `actions.json`, gradient presets), and `ui.ron` for window/dock geometry; crash recovery next to it in `Recovery/`. Override with `PHOTOCRAFT_CONFIG_DIR`; portable mode (a `portable.txt` or `PhotoCraft.portable` marker beside the executable) keeps everything in `<exe dir>/PhotoCraftData/` (`apps/photocraft/src/app_dirs.rs:10-11,481`, `docs/control-protocol.md` "Preferences").
- - Recovery autosaves are `<Recovery>/<key>.pcraft` directory bundles plus `<key>.json` metadata; the entry is deleted only when the document is saved or closed, and recovery is offered at the next launch. Autosave interval: preference `fileHandling.autosaveMinutes`, default 10, valid 1-240 (`crates/format/src/autosave.rs`, `crates/engine/src/prefs.rs:453,477,1133`).
+ - Settings: macOS `~/Library/Application Support/Photocraft/` (`preferences.json`, `Presets/` with `.pcbrushes`, `actions.json`, gradient/style presets, `ui.ron` for window and dock geometry); Windows `%APPDATA%\Photocraft`; Linux `$XDG_CONFIG_HOME/photocraft` or `~/.config/photocraft`. Override with `PHOTOCRAFT_CONFIG_DIR`; portable mode (`portable.txt` or `PhotoCraft.portable` beside the executable) keeps everything in `<exe dir>/PhotoCraftData` (`apps/photocraft/src/app_dirs.rs:7-22`).
+ - Crash recovery: `<data dir>/Recovery/<key>.pcraft` directory bundles plus `<key>.json` metadata, offered at the next launch and deleted when the document is saved or closed. Autosave interval is preference `fileHandling.autosaveMinutes`, default 10, valid 1–240 (`crates/format/src/autosave.rs`, `crates/engine/src/prefs.rs:1205`).
## Working with an AI agent
Preferred ladder — use the highest rung that answers the question:
- 1. **Headless CLI** for convert/inspect/batch; it needs no GUI and no token.
- 2. **MCP headless** (`photocraft-cli mcp --automation-read-root … --automation-write-root …`) when the task is a sequence of engine commands with inspection; without roots it has no file authority (by design).
- 3. **Control channel on a live app** when UI state, dialogs, pointer gestures or screenshots are genuinely required.
- 4. **GUI pointer events last** — `ui.pointer`/`ui.key` are validated and replayable, but they depend on window focus on macOS.
+ 1. **Headless CLI** for convert/inspect/batch; no GUI, no token (`photocraft-cli convert|info|run|batch|droplet|commands`).
+ 2. **MCP headless** (`photocraft-cli mcp --automation-read-root … --automation-write-root …`) for command sequences plus inspection; without roots it has no file authority (by design).
+ 3. **Control channel on a live app** (`--bridge`, or `photocraft --control`) when UI state, dialogs, pointer gestures or screenshots are genuinely required.
+ 4. **GUI pointer events last** — `ui.pointer`/`ui.key` are validated and replayable but depend on window focus on macOS.
Legitimate work:
- - **Prepare inputs**: convert any supported source to `.pcraft`/PSD/PNG (or DDS textures with `--dds-compression`), resize/grade via `batch`, generate actions JSON or droplets, pre-export assets for other Craft apps.
- - **Read and interpret documents**: `photocraft-cli info <file>` JSON (size, mode, depth, layer tree, warnings) or `document.inspect`, `layer.list`, `document.pixel` over MCP/control. `.pcraft` `manifest.json` is plain JSON and can be read directly.
- - **Verify exports**: reopen with `photocraft-cli info`, read stderr warnings, probe pixels (`document.pixel`), compare size/alpha. Do not byte-compare a re-saved PSD to its source: PhotoCraft rewrites image resources, layer records and the composite on save (unknown PSD blocks are carried through verbatim), so only reopening the file tells you it is right.
- - **Repair by regeneration, not by hand**: `.pcraft` tiles/blobs are content-addressed (BLAKE3) zstd objects — never edit them in place. Rebuild from the source image, an earlier bundle, or the app: a bundle written by a newer format version is refused with a clear error (`crates/format/src/lib.rs` `FormatError::TooNew`, `migrate.rs`).
- - **Batch safely**: `batch` will not silently overwrite and fails a file whose nested action fails; MCP `command_batch` is not atomic (earlier steps stay applied), so inspect history afterwards and undo per history entry. Long steps accept `wait: false` and are polled/cancelled through `jobs.list`/`jobs.cancel`.
+ - **Prepare inputs**: convert any supported source to `.pcraft`/PSD/PNG (or DDS textures with `--dds-compression`/`--dds-mipmaps`), resize/grade via `batch`, generate actions JSON or droplets, pre-export assets for other Craft apps. HEIC, NEF/CR2/DNG, XCF, KRA, PDN and Affinity files are *sources*, not deliverables.
+ - **Read and interpret documents**: `photocraft-cli info <file>` JSON, or `document.inspect`/`layer.list`/`document.pixel` over MCP; `.pcraft` `manifest.json` is plain JSON and can be read directly.
+ - **Verify exports**: reopen with `photocraft-cli info`, read stderr warnings, probe pixels, compare size/alpha. Do not byte-compare a re-saved PSD to its source — PhotoCraft rewrites image resources, layer records and the composite, so only reopening the file tells you it is right.
+ - **Repair by regeneration, not by hand**: `.pcraft` tiles/blobs are content-addressed zstd objects — never edit them in place. Rebuild from the source image, an earlier bundle or the app; a bundle with a newer `format_version` is refused with a clear error (`crates/format/src/migrate.rs`).
+ - **Batch safely**: `batch` will not silently overwrite and fails a file whose nested action fails; MCP `command_batch` is **not** atomic (earlier steps stay applied, no rollback), so inspect history afterwards and undo per entry. Long steps accept `wait: false` and are polled/cancelled with `jobs.list`/`jobs.cancel`.
- Do not: assign the app AI/generative work, expect Photoshop to always open its PSD exports, assume a format is supported because Photoshop has it, drive the GUI blind when a headless command exists, run two instances that share one recovery folder, apply whole-Preferences updates over the control channel (change individual keys with `prefs.set`), use `image.mode.*` from desktop automation, or rely on doc statements that the code contradicts (docs lag by weeks — trust the source).
+ Do not: assign AI/generative work, expect Photoshop to always open our PSD exports, assume a format is supported because Photoshop has it, drive the GUI blind when a headless command exists, use `image.mode.*` from desktop automation, assume XCF/KRA/PDN/Affinity files can be saved back, or trust doc statements that the code contradicts.
## Verification protocol
PhotoCraft is alpha: verify every artefact, never trust a silent export.
- 1. **Round-trip smoke test** before trusting any output (in a scratch directory):
- `photocraft-cli run --new '{"width":64,"height":64,"background":"white"}' --cmd layer.new.layer --out t.pcraft`
- then `photocraft-cli info t.pcraft`, then `photocraft-cli convert t.pcraft t.png` and open `t.png` in another viewer. For edits, add a command between `--new` and `--out` and read the per-command JSON lines.
- 2. **Read warnings.** Fidelity warnings (bit-depth reduction, CMYK→RGB, dropped layers, missing fonts) print to stderr on `convert`/`run`/`batch` and appear in `info`/`app.save` `warnings`. An export without a warning is the only export you should believe.
- 3. **Version projects.** Keep the `.pcraft` for lossless re-edits and hand Photoshop the PSD/PSB; keep the original source. PhotoCraft's re-saved PSD is not byte-identical and older/newer formats migrate on load.
- 4. **Update only between projects.** A version bump can change how files save. This skill documents 0.12.0 (`v0.12.0`, commit `6a2d027ee`); if the installed app differs, re-verify format and command claims against the new build (`photocraft --version` prints version, commit and build date, `crates/engine/src/build_info.rs`).
- 5. **Prefer reversible operations.** Work on copies (`image.duplicate` or `history.newDocument` creates a new active document), inspect `document.inspect` history, and remember automation saves never fire script events.
+ 1. **Round-trip smoke test** in a scratch directory: `photocraft-cli run --new '{"width":64,"height":64,"background":"white"}' --cmd layer.new.layer --out t.pcraft`, then `photocraft-cli info t.pcraft`, then `photocraft-cli convert t.pcraft t.png` and open the PNG in another viewer. For edits, insert a command between `--new` and `--out` and read each command's JSON line.
+ 2. **Read the warnings.** Depth reduction, CMYK→RGB, dropped layers, clipped values and missing fonts print to stderr on `convert`/`run`/`batch` and appear in `info`/`app.save` `warnings`. An export with no warning is the only export you should believe.
+ 3. **Version projects.** Keep the `.pcraft` for lossless re-edits and hand Photoshop the PSD/PSB; keep the original source. Re-saved PSDs are not byte-identical, and `.pcraft` migrates on load — never downgrade a bundle.
+ 4. **Update only between projects.** This skill documents 0.16.0 (`v0.16.0`, commit `b1794c6e0`); a launcher auto-update can change how files save. If the installed app differs, re-verify formats and counts (`photocraft --version` prints version, commit and build date — `crates/engine/src/build_info.rs:32`).
+ 5. **Prefer reversible operations.** Work on copies (`image.duplicate`, or `history.newDocument` which creates a new active document by default), inspect `document.inspect` history, and remember automation saves never fire script events.
## Sources & evidence
- - Repo: https://github.com/storytold/photocraft — inspected at tag `v0.12.0`, commit `6a2d027ee2e7ca12afd248544ed2262b0fa078dd` (2026-10-21); date of inspection 2026-10-22.
- - Installed app: `/Applications/PhotoCraft.app` — its CLI reports `photocraft 0.12.0 (6a2d027ee, 2026-10-21)`, matching this pin; `CFBundleShortVersionString` 0.12.0, bundle id `ai.storyteller.photocraft`, minimum macOS 11.0 (`Contents/Info.plist`; the app was not launched). Users with other builds may lag or lead this release — verify against your installed version.
- - Key files read: `README.md`, `ROADMAP.md`, `Cargo.toml`, `AGENTS.md`, `apps/photocraft/src/main.rs`, `apps/photocraft/src/app_dirs.rs`, `apps/photocraft-cli/src/lib.rs`, `crates/engine/src/commands.rs`, `atn_cmds.rs`, `adjust_cmds.rs`, `export_preset_cmds.rs`, `web_cmds.rs`, `shell_commands.rs`, `photo_cmds.rs`, `video_cmds.rs`, `print_cmds.rs`, `prefs.rs`, `build_info.rs`, `crates/ui-egui/src/state.rs`, `parity.rs`, `menu_catalog.rs`, `crates/doc/src/effects.rs`, `crates/color/src/blend.rs`, `crates/format/src/lib.rs`, `autosave.rs`, `crates/io/src/lib.rs`, `pdf.rs`, `crates/io/tests/corpus.rs`, `crates/codecs/README.md`, `crates/raw/src/lib.rs`, `crates/heif/src/lib.rs`, `crates/ipp/src/lib.rs`, `crates/automation/src/workspace.rs`, `crates/automation/tests/agent_tasks.rs`, `agent_tasks_more.rs`, `docs/control-protocol.md`, `docs/mcp.md`, `docs/scorecard.md`, `docs/gaps.md`, `docs/roadmap.md`, `docs/parity-checklist.md`, `docs/file-format-parity.md`, `docs/plugins.md`, `docs/printing.md`, `docs/batch-naming.md`.
- - Shared libraries: `storytold/craft-libs`, pinned by git revision in `Cargo.toml` (`craft-ui` rev `5c874d51…`, `craft-print` rev `3d37da38…`, `craft-docpath` rev `82a24b00…`); consulted to confirm the pinning relationship, not for app behaviour.
+ - Repo: https://github.com/storytold/photocraft — inspected at tag **`v0.16.0`**, commit **`b1794c6e0608ccfc887213dcf58398977c5cb75e`**, inspection date **2026-10-26**. The clone is shallow (one commit, "Release 0.16.0"), so no `v0.12.0…v0.16.0` diff exists locally; the deltas below were derived by re-measuring this checkout and comparing with the previous skill and the repo's own revision histories (`docs/file-format-parity.md:141-180`, `docs/gaps.md:3`, `docs/scorecard.md`).
+ - Installed app: `/Applications/PhotoCraft.app` — version 0.16.0, bundle id `ai.storyteller.photocraft`, minimum macOS 11.0, `Contents/MacOS/PhotoCraft` the only binary (no CLI, no `Resources/bin`) (`Contents/Info.plist`); the app was not launched.
+ - Key files read: `README.md`, `ROADMAP.md`, `Cargo.toml`, `AGENTS.md`, `apps/photocraft/src/launch_args.rs`, `app_dirs.rs`, `control_server.rs`, `single_instance.rs`, `gpu_startup.rs`, `apps/photocraft-cli/src/{lib,args,document,batch,automation,actions,output}.rs`, `apps/photocraft-cli/examples/man-and-completions.rs`, `crates/engine/src/{commands,atn_cmds,actions_cmds,adjust_cmds,filters,filters_ext,gallery_cmds,layer_style,plugin_cmds,print_cmds,photo_cmds,timeline_cmds,video_cmds,export_preset_cmds,web_cmds,prefs,build_info,updates_cmds,kys}.rs`, `crates/ui-egui/src/{state,parity,control,menu_catalog}.rs`, `crates/doc/src/effects.rs`, `crates/color/src/blend.rs`, `crates/algo/src/artistic.rs`, `crates/codecs/src/format.rs`, `crates/codecs/README.md`, `crates/io/src/{lib,psd_import,psd_export,xcf,kra,pdn,affinity,ora,tiff_layers,pdf,svg,smart_map}.rs`, `crates/io/tests/it/corpus.rs`, `crates/format/src/{manifest,migrate,atomic,autosave}.rs`, `crates/raw/src/{lib,cr3}.rs`, `crates/plugins/src/{runtime,manifest}.rs`, `crates/automation/src/{server,server/conventions,rpc,security,budgets,workspace,args,bridge,headless}.rs`, `docs/{control-protocol,mcp,scorecard,gaps,roadmap,parity-checklist,file-format-parity,plugins,printing,batch-naming,ora,xcf,kra,pdn,smart-filters,shape-strokes,hardware-parity}.md`.
+ - Shared libraries: `storytold/craft-libs`, pinned by git revision in `Cargo.toml` (`craft-ui` rev `5c874d51…`, `craft-print` rev `3d37da38…`, `craft-docpath` rev `82a24b00…`, `craft-jpeg` rev `6e7f2851…`); only the pinning relationship was consulted, not shared-library behaviour.
+ - Per-claim evidence and the automation-surface count deltas: `evidence.md` next to this file.