The review round of the #7071 stack, which #9167 merged without
(it took the pre-review heads):
- A bare --help or -h before `--` is help after any parse error, not
only after a missing value: `mcp stray.psd --help` and
`serve --port abc --port 1 -h` print the subcommand's help.
- mcp and serve --help carry #8035's rules: what the automation roots
grant, that bridge roots are refused, and that the token flags need
--bridge or --port (or PHOTOCRAFT_CONTROL_PORT).
- check_server takes the subcommand and whether a transport is set;
the bridge-roots check moves next to the other mcp checks.
- The server tests match clap's "a value is required for" text, and
mcp.md in the book and docs names PHOTOCRAFT_CONTROL_PORT.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Say which packages install them and how to generate and install them
by hand from a checkout. The desktop app has neither.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The FreeBSD tarball carries them in the ports layout: man/man1,
share/bash-completion/completions, share/zsh/site-functions and
share/fish/vendor_completions.d.
The man-and-completions example is built in the same cargo build as
the binaries (--bins --example), and package.sh runs it from the
target dir. With --skip-build it must already be built, or the script
says so and fails. A dry run builds nothing, so it leaves them out.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The deb, rpm and tarball install the man pages in /usr/share/man/man1
and the bash, zsh and fish completions in bash-completion's
completions, /usr/share/zsh/site-functions and fish's
vendor_completions.d. Fedora's and openSUSE's zsh read site-functions;
Debian's reads /usr/share/zsh/vendor-completions instead (its zsh is
configured with --enable-additional-fpath), so the deb also carries a
symlink there.
The man-and-completions example is built in the same cargo build as
the binaries (--bins --example, same profile, features and target),
and package.sh runs it from the target dir. A cross build runs it
through $EMULATOR, as the --version smoke test does, and without one
packages no pages or completions, with a warning. With --skip-build the
example must already be built, or the script says so and fails. The
pages aren't gzipped, like the other packaged docs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Say that `photocraft --help` lists the flags and their variables, that
a flag wins and an empty variable is unset, and what is a usage error;
that a bad --present-mode or --frame-latency is one while a bad
variable is still ignored; list PHOTOCRAFT_IN_WINDOW_MENUS; and that a
token with a token file is a usage error for the control server too.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With the environment fallbacks, clap's conflicts_with on the token pair
made a stale exported PHOTOCRAFT_CONTROL_TOKEN and
PHOTOCRAFT_CONTROL_TOKEN_FILE a usage error for every mcp and serve, though
headless mcp and stdio serve read no token. As on main, the pair is now
refused only by serve on TCP and mcp bridging, still as clap's either/or
usage error naming each half as it was given.
An automation root on the command line with PHOTOCRAFT_CONTROL_PORT set
is a usage error too: the variable would switch mcp to bridging, which
drops the roots, with only a note on stderr.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Say that PHOTOCRAFT_CONTROL_PORT is the fallback for serve --port and
mcp --bridge (with the stderr notice and the empty-is-unset escape),
and list the CLI's variables on the book's CLI page, which also says
the automation root variables stay the desktop app's.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Step 4 of the issue: the control token flags and the automation roots
move to photocraft-automation's new args module as two clap structs,
which mcp and serve flatten and the desktop app can flatten in step 5
without moving them again. clap is a plain dependency of the crate.
- ControlArgs holds --control-token and --control-token-file;
AutomationArgs holds the roots, and AutomationArgs::workspace opens
them (replacing the CLI's automation_workspace).
- The port value parser is photocraft_automation::args::control_port,
with its unit tests, for serve --port and --bridge now and the
desktop app's --control later. It ignores surrounding spaces and
refuses anything else with a typed NotAPort.
- --bridge parses to Bridge::Port or Bridge::Addr: it takes a bare
port, meaning 127.0.0.1:<port>, or any value containing a ':', which
the bridge client then validates. Anything else is a usage error
(exit 2) instead of a bridge error.
- 127.0.0.1 addresses are built from Ipv4Addr::LOCALHOST.
mcp_bridge_takes_a_port_or_an_address is new: `--bridge nope` is a
usage error, and `--bridge 1` bridges to 127.0.0.1:1, which a tool
call names when nothing listens there.
No new environment behaviour: the token variables are still read at
run time through security::token_inputs, and the roots stay flag-only.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* perf(seam): drop the original-column map from the shrink proxy, same pixels (-15 % at 24 MP)
Content-Aware Scale's proxy seam search tracked each panel column's original
column in an idx map, one usize per proxy pixel, shifted by every seam. The
original column is x plus the number of columns already taken at or left of it,
so a per-row list of the taken columns answers the same question without a
buffer the size of the energy map.
The recorded P28 budget number predates the proxies: the 2026-10-05 baseline
commit carves at full height and recomputes energy per seam, and measures
266 s for the 24 MP case on the machine this was timed on, against 2.6 s now.
Documented in docs/seam-carving-performance.md; enforce stays off until a
budgeted-class run can vouch for the whole row.
* review: generate exactly ch samples per pixel in bench_seam, reject ch > 4
The fixture flattened four samples per pixel and then truncated to w*h*ch, so a
three-channel run grouped samples across pixel boundaries and any ch above four
built a buffer shorter than the rows carve_width indexes.
Generate ch samples inside the per-pixel closure and refuse ch outside 1..=4.
The three-channel row is dropped from the perf note rather than re-measured
here: it moved inside the timer noise either way, and the other agents' builds
on this machine make a fresh number worthless. The four-channel hashes are
unchanged, which is the check that this edit touched only the fixture.
* perf(seam): find the removed column by binary search, not a linear scan
Recording a seam recovered each removed original column by scanning the row's
list of already-taken columns, so a deep shrink paid a comparison per removed
column on every row and every seam: quadratic in the seam count, and worse than
the index map this PR replaced once a shrink got deep enough.
`o - i` never decreases along the row's sorted list, so that count is a binary
search, and the original column belongs where the search stopped; the rows are
independent and update in parallel, like the pixel shift. What is left per
removed column is one insert.
Output is unchanged: same columns, same order, same hashes (10 % and 50 % width
cases below), and `counted_removed_columns_match_the_tracked_map` now runs a
tracking and a counting carver side by side to one column and compares every
row's list after every seam.
docs/seam-carving-performance.md gains those cases, states that the ~104x
figure is the two example runs rather than the baseline row, and names
perf/baseline.json as the source of the 954 s.
---------
Co-authored-by: ebosido444-ai <ebosido444-ai@users.noreply.github.com>
* Speed up Crystallize and Pointillize with cell-space rasterization
* Measure pixelate commands on unlocked layers and Background separately
* Record pixel-exact 24 MP filter and command benchmarks
An agent's ui.menu.invoke reached the main window's menus while a full-window workspace was
open: window.document.N switched documents and the workspace closed, dropping its work.
Photoshop makes these dialogs modal.
menus::modal_allows now refuses other menu commands while one is open (distort_ui::
modal_workspace), so ui.menu.invoke and the macOS menu bar wait for OK or Cancel. The
control channel's Camera Raw continuation is generalised: the open workspace's own
{"ui": {...}} requests still pass, unless another dialog, a discard prompt or a crop is
pending. The menus' cache key includes the open workspace, so opening or cancelling one
without input rebuilds them. engine.execute (MCP run_command) is not a menu click and is
not gated.
Puppet Warp, Perspective Warp and Vanishing Point are on-canvas modes here, with the
in-window menus live, so they stay out.
* Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900)
* Image › Adjustments: ⌥ while choosing an item opens it at its last settings (#8900)
* Tests: the ⌥-click test picks the Image menu's Adjustments row, not the panel tab (#8900)
* Image › Adjustments: an item opened with ⌥ lands in Edit › Search's recents too (#8900)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(blend): Soft Light uses the W3C curve over dark backdrops (#8804)
Photoshop's Soft Light moves the backdrop towards D(cb) in the upper half,
and D(cb) is the cubic ((16·cb − 12)·cb + 4)·cb below a quarter, not
sqrt(cb). With sqrt the Soft Light cell of psd-tools rgb-blend-modes.psd
was up to 20 levels too light over the dark rainbow; with the cubic it is
within 4 of Photoshop's merged image.
Same change in compose.wgsl so the GPU compositor stays at CPU parity.
Krita's own soft_light (the sqrt curve) now opens as the approximation and
svg:soft-light / krita:soft_light_svg as the exact match, without warning.
Fixes#8804
* compose: the see-through Soft Light test expects the W3C curve over a dark backdrop (#8804)
The #6232 test computed Photoshop's Soft Light of yellow as sqrt(b) for red and
green. Its backdrop #04170F is below a quarter in both channels, where the W3C
curve (Photoshop's, measured in #8804) uses ((16b - 12)b + 4)b instead.
* Crop default mode: moving the image under the box snaps (#8580)
In the default (non-Classic) crop mode a drag inside the box moves the
image under it, but snapping was switched off for that drag, so content
couldn't be centred in a larger box without leaving for the Move tool.
The drag now snaps like a Classic Mode frame move: the frame moves by the
image's travel the other way over the document, its edges and centre snap
to the document, layers, guides and grid (smart guides when those are
empty), and the pointer is corrected in the press camera, as crop_ui reads
the drag. A turned box still doesn't snap.
Fixes#8580
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Crop image move: smart guides snap when no Snap To target is in reach (#8622 review)
The document's bounds keep the regular targets non-empty, so the smart
guides were never tried: with View › Snap To › Layers off, the box never
snapped to a layer while the image moved under it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Khawar Zamman Wani <wanikhawar@Khawars-MacBook-Air.local>
Delete the coverage tests, the per-language parity checks and the tests
of one language's exact wording. Catalog integrity, placeholder and
plural-form checks stay, as do the engine tests (locale resolution,
plural rules, fallback, language scoping, drawing in a language); those
compare against tr() or the plural rule rather than catalog wording.
Docs describe the report instead of a failing test.
swept_blocks_match_the_pixel_search skips the cases where the colour mode reaches extrude only
through a channel count and ink setting an earlier mode already covered. It and
damaged_layer_data_opens_flattened run a quarter of their cases per run, a different quarter each
UTC day (photocraft-testkit's Sweep). PHOTOCRAFT_FULL_SWEEP=1 runs every case, and
PHOTOCRAFT_SWEEP_PART=<n> replays the part a failing run printed.
cargo xtask test and the ci test step run cargo nextest over libs, bins and tests when nextest is
installed (cargo test otherwise, with a notice), then cargo test --doc. The wasm check is one
cargo check with --keep-going over the wasm-safe crates, plus one call per optional web feature.
Clippy runs with --keep-going.
select.refineEdge takes `edits`: the Select and Mask workspace's Quick
Selection, Brush, Lasso, Object Selection, Select Subject, Clear Selection
and Invert, applied in order to the starting selection (or layer mask)
before the edge is refined. Cancel discards them, OK is one history step,
and agents drive them over MCP and the control channel.
- engine/select_mask_edits.rs: parse (bounded, every bad field an Err)
and a pure apply/apply_one the workspace preview can share
- algo matting::brush_region: soft round brush coverage (hardness)
- ATN export reports steps with edits instead of dropping them
- "Edits" label in the 21 catalogs
Part of #7069 and #8148 (the workspace UI follows in a second PR).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Features row said Type 5 / 4 / 1 and "RTL paragraph direction UI
missing"; scorecard/type.toml has 5 done / 5 partial / 0 missing, as the
status line says, and TYPE-218-2 is partial: the direction buttons exist,
the visual RTL caret is missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bridge forwards Replace Contents to the app, which runs it on its
active document, so a given `index` was silently dropped and could replace
another document's contents. It is now refused before anything reaches the
app, as bridge previews and saves already do; documented in the schema and
control-protocol.md. A fake-app test checks the indexed call never arrives.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main's #8644 builds each crate's integration tests as one binary; #3356's
cmyk_export.rs and #5769's cylinder_warp.rs move into crates/io/tests/it.
docs/scorecard.md regenerated for the batch (#5485 DIST-220-1, #6055
UI-217-17, preferences).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>