1237 Commits

Author SHA1 Message Date
aiongg c65b5d25d3 Merge pull request #9061 from wanikhawar/wanikhawar/modal-workspaces
fix(ui): Liquify, Content-Aware Fill, Filter Gallery and Adaptive Wide Angle are modal, like Camera Raw
2026-10-24 22:11:26 -04:00
Khawar Zamman Wani 874ff42a9e docs(control): Adaptive Wide Angle is a modal workspace too 2026-10-25 06:49:07 +05:30
Ivan 487e18d726 fix(cli): help after a mistake, and mcp/serve option notes in --help (#7071)
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>
2026-10-25 00:08:14 +03:00
Ivan 8544c98517 docs(cli): installing and generating completions and man pages (#7071)
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>
2026-10-24 19:45:27 +03:00
Ivan cb730955f6 feat(freebsd): ship photocraft-cli's man pages and completions (#7071)
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>
2026-10-24 19:45:27 +03:00
Ivan 92863642a7 feat(linux): ship photocraft-cli's man pages and completions (#7071)
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>
2026-10-24 19:45:27 +03:00
Ivan e96b5e2b11 docs(app): describe the desktop flags' variables and usage errors (#7071)
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>
2026-10-24 19:45:27 +03:00
Ivan 23548ee92a fix(cli): check the token pair only where a token is read (#7071)
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>
2026-10-24 19:45:26 +03:00
Ivan b4949702e2 docs(cli): describe the mcp and serve environment fallbacks (#7071)
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>
2026-10-24 19:45:26 +03:00
Ivan 2f13a1564c refactor(cli): share ControlArgs and AutomationArgs, typed --bridge (#7071)
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>
2026-10-24 19:45:26 +03:00
ebosido444-ai b07b7fab55 perf(seam): the shrink proxy drops its original-column map, same pixels (−15 % at 24 MP) (#8526)
* 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>
2026-10-24 11:14:00 -04:00
Ayan 47d9266fa1 Make Crystallize 4x and Pointillize 3x faster on 24 MP layers (#6200)
* 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
2026-10-24 11:13:03 -04:00
Khawar Zamman Wani 3466d93204 docs(control): workspace continuations wait for dialogs, prompts and blocking crops; cargo fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:00:39 +05:30
Khawar Zamman Wani e466b9bd53 fix(ui): ui.menu.list greys what the modal gate refuses, like the native menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:12:15 +05:30
Khawar Zamman Wani 4bd6638cc4 fix(ui): Liquify, Content-Aware Fill and Filter Gallery are modal, like Camera Raw
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.
2026-10-24 10:40:51 +05:30
David Mann 2b0deddec2 Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/ui-egui/src/panels.rs
2026-10-24 00:51:37 +02:00
A Hazy Reign Is Fluid 6a36c48670 fix(automation): release idle headless TCP server ports (#3914)
* fix(automation): release idle headless TCP server ports

* fix(automation): retry aborted incoming TCP connections

* test(automation): synchronize idle daemon client acceptance

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:36:14 -04:00
David Mann 9c5a7b907a Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/ui-egui/src/i18n/tr.tsv
#	docs/ui-parity.md
2026-10-24 00:34:11 +02:00
David Mann 8c04b175ec Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/automation/src/rpc.rs
#	crates/automation/src/server.rs
#	crates/gpu/src/health.rs
#	crates/text/src/tests.rs
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/panels.rs
#	docs/control-protocol.md
#	docs/ui-parity.md
2026-10-24 00:23:15 +02:00
Marco Arnulfo 2b83be6c81 Levels, Curves and the other adjustments: ⌥ plus the shortcut opens the dialog at its last settings (#8900) (#8969)
* 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>
2026-10-23 17:55:18 -04:00
JGF 35cb32be20 fix(export): report skipped layers in doc_export_layers (#8491) (#8713)
* fix(export): report skipped layers across automation surfaces

* test(export): use the MCP visible_only parameter spelling

* test(export): restore old adapter for hosted regression proof

* Revert "test(export): restore old adapter for hosted regression proof"

This reverts commit 397a5ffa2a.

* docs(export): qualify workspace-only response guarantees
2026-10-23 17:41:19 -04:00
Gert Pellin - GPE 10a20743ea Soft Light: dark backdrops follow Photoshop's W3C curve (#8804) (#8817)
* 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.
2026-10-23 17:39:40 -04:00
Khawar Zamman Wani 41599c591c Crop default mode: moving the image under the box snaps (#8580) (#8622)
* 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>
2026-10-23 17:39:02 -04:00
aiongg 7aaa59712a Merge main into integration-batch-7 2026-10-23 20:37:05 +00:00
aiongg 4b51647955 Merge remote-tracking branch 'upstream/main' into xtask-ci-gate
# Conflicts:
#	xtask/src/main.rs
2026-10-23 19:44:29 +00:00
aiongg 1ecb61db42 Merge pull request #8964 from aiongg/translation-gaps-pass
Tests: missing translations don't fail; xtask ci lists them
2026-10-23 15:43:33 -04:00
aiongg a75a414310 CI: cargo xtask ci is the gate, and the workflow runs it 2026-10-23 19:29:31 +00:00
aiongg c3aa3f9e51 Tests: missing translations don't fail; xtask ci prints them
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.
2026-10-23 19:07:23 +00:00
aiongg 9acf9a0cf9 CI: the Linux gate runs through the suite's shared gate workflow 2026-10-23 18:32:21 +00:00
aiongg cf0960b520 Merge pull request #8915 from aiongg/ci-speed-2
CI: no debug info for dependencies, nextest in xtask ci, long sweeps spread over days
2026-10-23 13:48:36 -04:00
aiongg 20ad9f1f4e Docs: the final check before a PR runs the full sweep 2026-10-23 16:40:24 +00:00
aiongg b87cfb3c6a Tests: spread the two longest sweeps over the days
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.
2026-10-23 07:20:54 +00:00
aiongg 175c9c1e75 xtask: test with nextest, doctests separately, one wasm check call
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.
2026-10-23 06:00:38 +00:00
Khawar Zamman Wani 4970534e05 feat(engine): Select and Mask tool edits as select.refineEdge params
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>
2026-10-23 10:30:46 +05:30
David Mann 72bafe9d25 Address CodeRabbit review: Type checklist counts match the scorecard (#6230)
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>
2026-10-23 00:28:19 +02:00
David Mann b54ab8ff30 Address CodeRabbit review: bridge-mode doc_replace_smart_object refuses index (#8150)
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>
2026-10-23 00:25:18 +02:00
David Mann 1efa06b715 Integration: merge main (c36e46c38 #8680, #8668, #6705)
# Conflicts:
#	docs/ui-parity.md
2026-10-22 23:47:54 +02:00
David Mann ca73fb4724 Merge PR #6966: perf(canvas): skip occluded checker on CPU textures 2026-10-22 23:40:20 +02:00
David Mann 7ecac105c2 Merge PR #8556: perf(oil-paint): drop the libm floor/round calls from the LIC gathers, same pixels (1.23x) 2026-10-22 23:40:19 +02:00
David Mann 7c9d6ac344 Merge PR #8150: fix(automation): doc.replaceSmartObject replaces Smart Object contents from the read root (#8061) 2026-10-22 23:40:19 +02:00
David Mann 8d23a06eae Merge PR #6094: Fix legacy PSD point-text placement after font resolution 2026-10-22 23:40:19 +02:00
David Mann c436f61d53 Merge PR #7202: fix(atn): import a recorded Save As with its destination (#7102) 2026-10-22 23:40:18 +02:00
David Mann 3fdcabded4 Merge PR #5248: Keep adjustment clipping visible in compact Properties panels 2026-10-22 23:40:15 +02:00
David Mann 49a98690ad Merge PR #8354: Keep the CPU canvas sharp at 100% zoom 2026-10-22 23:40:14 +02:00
David Mann 14d68f972b Merge PR #8035: CLI: reject ignored or conflicting mcp/serve launch options 2026-10-22 23:40:13 +02:00
David Mann 837242536b Merge PR #7840: gpu, docs: fix timing test skip guard and document Windows ARM64 build on RTX Spark 2026-10-22 23:40:12 +02:00
David Mann 494cdb236a Integration: batch tests join the io test binary, regenerate the scorecard
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>
2026-10-22 23:40:12 +02:00
David Mann fff01d8918 Integration: stack #7335 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David Mann 9e7b35b716 Integration: stack #6055 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David Mann c6e628afad Integration: stack #5815 (its merge onto main, conflicts resolved) 2026-10-22 23:40:10 +02:00