Conflict in crates/engine/src/jobs.rs: main's save commit point (#5333,
OPEN/COMMITTED/CANCELLED phase) met the batch's cancel state
(CANCEL_REQUESTED/WORK_FINISHED). Both now share one atomic with a
COMMITTED bit; commit vs cancel and the batch's late-cancel guard each
stay one compare-and-swap. A Batch that refuses a repeated or late cancel
reports CancelRefused::Finishing ("finishing") in jobs.cancel's
notCancelled list, still {"cancelled": 0}. control-protocol.md documents both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Type: add OpenType Numerals submenu (figure styles)
Type › OpenType › Numerals sets lnum/onum/pnum/tnum as a radio style
(Default through Tabular Oldstyle). Checkmarks follow the selection
start; TYPE-218-5 scorecard/gaps updated; parity floor 640.
* refactor(engine): one style-at-character lookup for the OpenType menus
feature_on_at and numerals_style_at each walked the runs the same way;
they now share style_at. NumeralsStyle::from_id and numerals_style_of
read the existing tables instead of repeating them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(text): persist OpenType numeral tags through PSD EngineData
Map lnum/pnum/tnum as EngineData bools and the width×height combos as
FigureStyle (Krita/Illustrator), so Numerals choices survive PSD round-trip.
* docs(gaps): note Numerals PSD figure-tag round-trip
* revert(text): keep PSD EngineData to the keys Photoshop writes
Backs out the PSD half of 18610324 and its gaps note (58813de3); the
engine reformat in that commit stays.
- `LiningFigures`, `ProportionalFigures` and `TabularFigures` are not
Photoshop EngineData keys (the commit says Photoshop only has
`OldStyle`), so writing them puts keys of our own into users' PSDs.
- The `/FigureStyle` code table (1-4) has no oracle: no Photoshop-authored
file in photocraft-corpus, and neither ag-psd nor psd-tools reads it.
AGENTS.md rule 3 asks for public specs or observation. PSD persistence
of the numeral styles can come back with a Photoshop-authored oracle.
The Numerals menu still round-trips through .pcraft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(text): lock PSD EngineData to Photoshop OldStyle for numerals
Document AGENTS.md clean-room: only onum survives TySh round-trip; lnum/pnum/tnum stay .pcraft-only until a Photoshop-authored corpus oracle.
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
When an action saves and then closes or switches away, Batch writes the
saved snapshot inside the action closure. A cancel landing after that write
made process_one return Cancelled, so the input was reported as skipped
although its output exists. The snapshot write is now final: the cancel
stops only the inputs after it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Offer TIFF compression, image pyramid and transparency options on Save As and Save a Copy, with matching CLI and automation parameters.
Follow-up: preserve the unsaved state when a copy discards layers over the document's own file, and check the other export formats for the same issue.
Closes#8827.
A path under a regular file can't be resolved on Linux (ENOTDIR), so Batch
refused the whole run before writing and the test failed there. A folder
taking the result's name resolves on every platform and fails the write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(plugins): the install error names the format the file actually is (#10362)
Plug-ins > Install Plug-in pointed at an old Photoshop plug-in (.8bt) answered
with "not a valid plug-in module: not a WebAssembly binary (missing \0asm
header)". That was one fixed message for every file that is not wasm: it said
what we wanted and nothing about the file the person is holding.
format_hint in runtime.rs now reads the magic number and the error says what the
file looks like. For a Windows module the message is (one line in the code,
wrapped here):
not a valid plug-in module: not a WebAssembly binary (missing \0asm header);
this file looks like a Windows native executable or library (a .dll, or a
Photoshop plug-in such as .8bf/.8bt) - PhotoCraft plug-ins are WebAssembly
modules (.wasm)
7F 45 4C 46 says "a Linux native library (ELF)", CE FA ED FE / CF FA ED FE /
CA FE BA BE / BE BA FE CA say "a macOS native binary (Mach-O, thin or fat)",
50 4B 03 04 / 50 4B 05 06 say "a zip archive". Any other header keeps today's
message byte for byte, so nothing keyed on it changes. The signatures are public
format facts, read with starts_with/get so a short or hostile file cannot panic.
The error variant, the enum, the ABI check, check_code_section and the wasmi
setup are untouched, and native-code loading stays out of scope (PR #7907).
Fail-first, before the production change:
cargo test -p photocraft-plugins --lib --tests
lib: test result: ok. 10 passed; 0 failed
abi: test result: FAILED. 19 passed; 1 failed; 0 ignored; 0 measured
assertion line: "windows dll or .8bt: not a valid plug-in module:
not a WebAssembly binary (missing \0asm header)" (the MZ fixture that
was already in the file)
cargo test -p photocraft-engine --lib -- plugin
test result: FAILED. 7 passed; 1 failed; 0 ignored; 0 measured; 2518 filtered out
After:
cargo test -p photocraft-plugins
lib 10 passed; abi 20 passed; doc-tests 0
cargo test -p photocraft-engine --lib -- plugin
test result: ok. 8 passed; 0 failed; 2518 filtered out
The new engine test drives the real plugin.install command with a `path` whose
file starts with MZ bytes, so it covers the menu path (registry::load_file) end
to end, not only the library check.
Gates: cargo clippy -p photocraft-plugins --all-targets -- -D warnings and
cargo clippy -p photocraft-engine --all-targets -- -D warnings (clean; the only
stderr noise is the pre-existing "patch `egui_*` was not used in the crate
graph" cargo warnings); cargo xtask layers ("OK: 31 crates, no layering
violations."); cargo xtask wasm (" ok 24 crates", " ok photocraft-
codecs --features heif", no FAIL line); cargo fmt -p photocraft-plugins --
--check and cargo fmt -p photocraft-engine -- --check (silent, exit 0). The
whole engine --lib suite is not run here: it fails on clean main in this
checkout for font/i18n reasons (no CRAFT_FONTS_DIR). No doc quoted the old
message (git grep "WebAssembly binary" finds only this crate), so nothing to
update there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(plugins): one install-error test per recognised format, tempfile in the engine test (#10362)
Review feedback on #10633. The single looped test in abi.rs is now one test
per signature (PE/COFF MZ, ELF, Mach-O in all four byte orders, zip), one for
an empty or unrecognised file (including signatures cut short, which keep the
old message byte for byte) and one showing a valid WebAssembly module still
loads. The engine test uses tempfile::tempdir (already a dependency of
photocraft-engine) so the directory is removed on a panic, and asserts the
message does not contain the temp path: the hint is static text, so the
message adds no path.
Gates: cargo test -p photocraft-plugins --lib --tests (10 lib, 26 tests);
cargo test -p photocraft-engine --lib -- plugin (8 passed); clippy -D warnings
on both crates; cargo fmt --all -- --check; cargo xtask layers (OK).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(engine): keep the non-wasm install-error test off wasm32, as tempfile is (#10362)
tempfile is a non-wasm32 dependency of photocraft-engine, so the test that
uses it is gated like the crate's other file-system tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- A failed input stays in `errors` when `jobs.cancel` or Esc lands while it runs, instead of
showing up as skipped without its reason.
- Esc after the last input is refused: the worker closes the interrupt window once no
checkpoint is left, so `request_batch_interrupt` no longer answers true for a question
that never comes.
- `photocraft-cli batch` without `--format` saves a file PhotoCraft only opens as PSD, the
format `--naming`'s {ext} already plans, instead of asking the encoder for the input's
import-only extension.
- `--recursive` help says the output is flat.
- The app bridge keeps a cancelled Batch's partial result in the error it returns to MCP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One RAM detector: the cgroup v1/v2, macOS reclaimable and Windows commit providers move from
photocraft-io into photocraft_raster::memory::system (L0), which the residency budget (#5330)
and photocraft_io::memory both read. Unreadable container limits still fail closed, swap is
never counted as RAM. sysinfo is unified on =0.40.1 (raster only).
While the scratch disk is spilling (on, not paused), import admission credits the tile pixels
of an estimate against spill::scratch_capacity() (free space above the reserve, within the
Scratch Size Limit); decode buffers, conversion copies and merged planes stay RAM-only. Spilling
off/paused and the web keep the RAM-only policy unchanged. PsdMemoryEstimate gains tile_bytes;
the flat TIFF streaming check credits its tile surface the same way; engine approveMemory's
fast pre-check uses the credited hard limit and the import re-checks exactly.
Merge 655295b2a2. Resolve generated scorecard conflict with exact upstream text only; actual source-bound regeneration and combined validation remain pending.
Keep the PSD memory admission work and upstream's scratch storage
together. Conflicts resolved:
- Cargo.lock: both sysinfo versions are required. 0.40.1 serves
photocraft-io's adaptive import budget; 0.37.2 serves
photocraft-raster's scratch volume and memory supervision. Cargo
metadata resolved the lock with no other changes.
- crates/engine/src/lib.rs: Session keeps pending_preflights and
next_pending_preflight beside scratch_runtime and scratch_epoch.
Session derives Default, so neither set needs a manual initializer.
Upstream: 655295b2a2
`load_frames`'s single-file path called `file_cmds::import`, which uses default
import options, so the footage command's lowered PSD/layered-TIFF memory limit
never reached a single-page layered TIFF or PSD. Multi-page TIFF pages already
passed their `opts`.
Give the engine import one shared `import_result_with` path and an
`import_with_options` entry that keep the allocation checkpoint, native
`.pcraft` and format dispatch, typed memory refusals and the preview-only
refusal; the existing `import` delegates with default options, so every other
caller behaves exactly as before. The single-file fallback now passes `opts`.
Add a command-level regression: a single-page layered TIFF under a thread-scoped
`psd_max_pixel_bytes` of 1 must reach `file.import.videoFramesToLayers` as a
typed `EngineError::PsdHardBudget` with its name, required and limit fields
intact and the document unchanged, then import as one frame when the limit is
not lowered.
The multi-page TIFF branch of `load_frames` pushed each flattened page
straight into `frames`, bypassing the `keep` closure, so `MAX_FOOTAGE_BYTES`
was never checked for those pages: many large pages could retain more than
the cap without an error. Route every flattened page through `keep`, with the
same file-context error mapping as the single-file and GIF/WebP paths, and
keep typed page admission refusals typed.
Add command-level regressions. A per-thread test scope lowers the footage and
PSD limits, so neither test allocates gibibytes: one proves an over-budget
multi-page TIFF is refused with nothing imported, and one proves a page with
Photoshop layer data reaches `file.import.videoFramesToLayers` as a typed
`EngineError::PsdHardBudget` with its name, required and limit fields intact
and the document unchanged.
Keep command-local history, purge and rollback intact, then consolidate affected documents at action completion or dialog suspension. Preserve semantic no-op identity and redo, visibility semantics and row identifiers. Bind dialog continuation to its exact document/history, inherit the setting in scratch sessions and carry droplet action names. Add the review counterexamples and focused continuation, Batch/droplet and history-option controls. Source is formatted; compilation and runtime qualification remain pending.
The multipage TIFF import loop mapped every page-import error to EngineError::Other, so a PsdApprovalRequired or PsdImportBudget from a layered page reached the UI, control channel and agents as text only, without the required/limit fields. Route those two through io_open_error and keep the page-specific text for every other error.
Add the default-on Actions history setting and a grouping scope shared by ordinary engine and shell playback entry points. Define public regressions for whole-play Undo/Redo, the off setting, failure prefixes, nesting, no-op and repeated-play boundaries, and persistence. This is an unvalidated draft increment: dialog continuation, direct compound edits, Batch/droplet boundaries and the complete edge-case qualification remain unfinished.
- CLI batch: recursive inputs keep the engine's tree order for {serial};
--format picks the encoder whatever extension --naming writes; the
summary prints before --error-log is written.
- Batch: a jobs.cancel during a write keeps that file in `files`;
Opened Files refuses a default-named result that replaces another open
document's file; a recorded Save leaves a document changed when Batch
writes nothing for it.
- Recording: folded Move-tool nudges add whole-pixel steps; a null
Check Spelling `action` is recorded as Check Spelling.
- Linked files: a document without links lists none missing; a look
whose links changed while it ran is dropped.
- Save for Web: a null or empty destination opens the picker; a rename
while the picker is open writes the picked file.
- Actions: a Check Spelling step fails instead of replacing a dialog
opened by hand.
- Docs: desktop Batch cancel flow, Check Spelling step shape, filter row
history count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now takes the manifest envelope from craft-pcraft-format, which has
no full_composite member. The save writes the member after the envelope
when it writes composite/full.png, and read_full_composite reads it back.
- Check Spelling: with #8726's measured form a `change` made in the dialog records no step
(the reference application records one `spellCheck` step), so #4514's two tests that
expected recorded `change` steps are removed; its portable recording stays for all else.
- Clippy `chunks_exact_to_as_chunks` in #10765's tests; a single-element loop in #10755's.
- docs/parity-checklist.md and docs/scorecard.md regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Link Layers keeps main's measured toggle mapping (batch 12, #10628): played twice in the
reference application it links, then unlinks. #10755's second, explicit `linked: true`
entry for the same event was unreachable on import and contradicted that measurement, so
it is dropped; Unlink stays explicit. Its tests now cover Unlink only, and `{}` exports.
- #10765: an ordinary Gradient Map now saves as `gradientMapEvent` (#10702 landed in batch 12).
- #10755: flattened pixels read with `Buffer::into_px()` since `Buffer` implements `Drop`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>