100 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 0b3cab2eba CMYK: Dissolve and post-adjustment rounding match the RGB compositor (#9033)
The CMYK proptests failed on main now and then (the_split_route_agrees_with_the_exact_path
after #8663). Three gaps, each found by the proptests at 5000 cases:

- cmyk.rs: a clipped Dissolve layer blended like Normal; it now shows each pixel fully or not
  at all by the compositor's noise, as the RGB compositor does since #3052.
- cmyk.rs: a Dissolve adjustment layer mixed by its opacity; opacity and mask are now the chance
  a pixel takes the adjustment, as on the RGB compositor (#3050).
- native.rs: the display/Flatten ink route didn't round to the document's levels after an
  adjustment layer, where the RGB compositor and cmyk.rs do; coverage in a pass-through group
  then differed by up to half a level.

The test reference model had the same two Dissolve gaps (ref_clip passed no position, so
Dissolve fell back to Normal), which is why random_documents_match_the_reference never caught
the first one. Three named regression tests cover the minimal cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 03:34:07 +02:00
David MannandClaude Opus 5.5 c238a9de44 Integration: main's Brightness/Contrast export test builds the shared Arc raw bytes (#5815)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 01:07:12 +02:00
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
aaaa990314 Crop: Delete Cropped Pixels keeps type and smart objects whole (#7116) (#7117)
* Crop: Delete Cropped Pixels keeps type and smart objects whole (#7116)

crop_doc cropped every surface for_each_surface visits, including the
rendered caches of type layers and smart objects. Those move by shifting
their stored pixels, so the cut stayed and showed as soon as they were
moved. Like Photoshop, Delete Cropped Pixels now deletes pixel-layer data
(and fill caches) only; shapes are re-rendered as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: Delete Cropped Pixels crops stored video frames again (#4956)

The pixel-layer-only crop path skipped a video layer's stored frames and
original footage, so scrubbing brought the cropped pixels back. Crop them
with the displayed content, as before, and test a shrinking crop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Address CodeRabbit review: the turned crop keeps type and smart objects whole before rotating

The pass that drops what a turned crop deletes before the rotation still cut
every cached surface. The rotation re-renders type and smart objects, but a
smart object whose source is unavailable keeps its cache, so it stayed clipped.
That pass now uses the same pixel-layer-only rule as the crop itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:36:28 -04:00
6e52641508 Auto-Align: a locked layer becomes the reference instead of being moved (#7080) (#7081)
* Auto-Align: a locked layer becomes the reference instead of being moved (#7080)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: Auto-Align moves the Background as a normal layer

With the Background selected and a Cylindrical or Spherical result (every
layer is reprojected), or another layer as the reference, the Background's
own position lock made Auto-Align refuse. Like Free Transform and Photoshop,
the Background's built-in position lock no longer pins it: it becomes a
normal layer ("Layer 0") when it has to move. Lock All, Lock Pixels and a
user's Lock Position on any other layer still keep it as the reference.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Address CodeRabbit review: the Auto-Align params doc names the locked layer as the default reference

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:35:08 -04:00
f719f0bfac Perspective Crop: the swap button swaps W and H (#7059) (#7060)
* Perspective Crop: the swap button swaps W and H (#7059)

The W/H swap button called crop_size::swap, which only acts in the Crop tool's W x H x Resolution mode; Perspective Crop never sets that mode, so the button did nothing there. The button now always swaps the fields; X keeps the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: the test clicks Perspective Crop's ⇄ button

The test now renders the real options bar with the Perspective Crop tool,
clicks ⇄ through the egui_kittest harness and checks that W and H swapped,
instead of calling `swap_fields` directly. It fails if the button goes back
to calling `swap`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 18:34:16 -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 MannandClaude Opus 5.5 f38004e721 Address CodeRabbit review: an opacity-only style preset added with ⇧ sets the opacity (#4049)
`style.presets.new` with `includeBlending: false` and an explicit `opacity` stores only that
opacity, but `apply` with `add: true` skipped the blending block unless the preset had a blend
mode, so the layer kept its opacity. Test: an_opacity_only_style_added_sets_just_the_opacity
(also checks the layer's blend mode and Fill stay).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 00:23:15 +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
David Mann 27caf2823b Merge remote-tracking branch 'origin/main' into integration-batch-7
# Conflicts:
#	crates/engine/Cargo.toml
2026-10-23 23:13:46 +02:00
David MannandClaude Opus 5.5 7c4af5bdf1 Address CodeRabbit review: only this call's failed start opens the updates window (#5485)
b1b6bd248 opened the window whenever the status was Failed after an
error, which includes a Failed left from an earlier check, so a later call
refused for bad params opened it too. It now opens only when this call's
start failed: the status changed to Failed, or the error is the Failed
message (the same start failure again). The test covers bad params after a
failed check, bad params from Idle, and a repeated start failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:48:03 +02:00
David MannandClaude Opus 5.5 7d13c854a3 Address CodeRabbit review: the held Type press keeps the path pointer until it drags a box (#6230)
81bcf7f3d hid the path pointer for any drag, but every Type press starts
one, so pressing on a path showed the I-beam although a click or flat drag
still makes path type on release. While the button is held the pointer is
now what the release makes: nothing once the drag passes the box threshold,
else the path click at the press. pointer_up and the hover share the
threshold (makes_box). The test holds a press on a shape, drags flat (area
type on release) and drags a real box from inside the shape (no pointer).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:46:28 +02:00
David MannandClaude Opus 5.5 b1b6bd2481 Address CodeRabbit review: a manual update check that fails to start opens its window (#5485)
When the checker cannot start, help.checkForUpdates sets Status::Failed and
returns the error, and `?` skipped opening the window, so Help > Check for
Updates showed neither the failure nor Check Again. The window now opens
for a failed check (the command error is still returned); bad params open
nothing. Covered by a test with a checker that fails to start.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:31:11 +02:00
David MannandClaude Opus 5.5 81bcf7f3df Address CodeRabbit review: no path pointer while a text box is dragged out (#6230)
Dragging out a paragraph box with the Type tool sets app.drag and no type
edit is open yet, so the hover lookup showed the area-type or path-type
pointer over a path although the release makes a box. hover_path_target now
returns nothing during a drag or an open type edit (the edit check moved in
from canvas.rs), and a test drives a drag over a shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:30:36 +02:00
David MannandClaude Opus 5.5 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 MannandClaude Opus 5.5 08728a5677 Address CodeRabbit review: the manual PSD fixture command names the it test target (#6658)
psd_recovery_generated is a module of the io crate's single `it` test
binary, so `--test psd_recovery_generated` names no target. The comment now
runs `--test it psd_recovery_generated::write_psds_for_manual_gui_test`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:28:19 +02:00
David MannandClaude Opus 5.5 c88691c0b8 Address CodeRabbit review: CMYK ink paths convert colours through the document profile (#3356)
Two places turned an RGB or Lab colour into ink with the built-in coated
CMYK instead of the document's embedded profile: the exact ink path's
tiles (render_tile runs on rayon workers, which never inherit a caller's
with_cmyk_space scope), e.g. a solid fill picked in RGB, and the split
path's stand-ins (fill and effect colours, converted on the calling thread
outside any scope). Both now enter cmyk_space(doc). A test with a synthetic
uncoated profile checks a solid fill and a Color Overlay get the profile's
inks; each half fails on its own without its fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:26:54 +02:00
David MannandClaude Opus 5.5 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 MannandClaude Opus 5.5 4604cfc4bb Address CodeRabbit review: doc.replaceSmartObject restores the active document (#8150)
`index` targets one call (#7025): replace_smart_object now runs through
on_doc like place and the data-set methods, so the previously active
document is active again on success, on error and on the trusted-local path.
The index test covers a successful and a failing indexed replace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 00:24:13 +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 4db1674b01 Merge PR #8556: perf(oil-paint): drop the libm floor/round calls from the LIC gathers, same pixels (1.23x) (author's own rounding fix since review; the batch keeps our equivalent fix-up 249eae743) 2026-10-22 23:42:34 +02:00
David Mann 104fea5c1b Merge PR #6055: Color panel: Photoshop's panel-menu modes, the same in every theme (#6000) (author's update since review: main merge; batch resolution kept) 2026-10-22 23:41:06 +02:00
David Mann f4f305ea64 Merge PR #5815: perf(doc): share the XMP packet, Duotone PSD data and unsupported adjustment bytes between snapshots (author's update since review: main merge; batch resolution kept) 2026-10-22 23:41:04 +02:00
David Mann 71284b908c Merge PR #8547: perf(gpu): layer pages that just miss the budget borrow the effect-map share, P60/P61 10× faster on integrated GPUs (author's update since review: main merge; batch resolution kept) 2026-10-22 23:41:01 +02:00
David MannandClaude Opus 5.5 ae8809c029 Integration: Levels gamma just above 1 no longer turns black into NaN (#5143)
levels_gamma's shadow toe: for gamma ~1.003..1.03 the join point u1 is a
tiny positive f32 whose u1^2.2 underflows to 0, so b = inf and the toe
gives inf*0 = NaN at u = 0 (any 8/16-bit Levels channel at black). The
toe vanishes as gamma -> 1, so the power curve is its limit: use it when
b is not finite. Found by cmyk random_documents_match_the_reference
(proptest, unseeded; the shrunk case is kept in proptest-regressions);
the bug is on main since #5143, not from this batch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:40:21 +02:00
David MannandClaude Opus 5.5 07bb7c2522 Integration: enable Generator in the Image Assets artboard tests (#8254)
#5598 made File > Generate > Image Assets require Preferences > Plug-ins >
Generator, so #8254's two artboard tests got Disabled. Enable it first,
as #5598's own test does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:40:21 +02:00
David Mann 450b832a4c Merge PR #6254: Selection: ⇧ mid-drag locks a selection-tool move to the axis (#6231) 2026-10-22 23:40:21 +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 adace9a86f Integration: #8254 second fix-up (an empty artboard asset is refused, as in Photoshop's Generator) 2026-10-22 23:40:20 +02:00
David Mann 3623a8b6c8 Merge PR #7021: Eyedropper: a large sampling ring that reads on any colour (#6973) 2026-10-22 23:40:20 +02:00
David Mann d4a5be2980 Integration: #5878 third fix-up (a deliberate change in this instance beats another instance's migration) 2026-10-22 23:40:20 +02:00
David Mann 2bfc24d9f8 Merge PR #8180: fix(actions): strict .atn export keeps Apply Image's flat source fields (#7197) 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 159728aaa0 Merge PR #8118: GPU compositor: build its pipelines in parallel; DX12 startup 13.2 s -> 7.3 s (part of #6419) 2026-10-22 23:40:19 +02:00
David Mann a79d8cb9f6 Merge PR #5937: fix(compose): prevent outside stroke from bleeding into opaque character interior (#5861) 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 18124238f7 Merge PR #8502: fix(actions): accept Photoshop stringID blend modes (#8454) 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 09eef59e90 Integration: #5878 second fix-up (two instances keep each other's Shift tool switch choice) 2026-10-22 23:40:18 +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 ee85da2ffd Merge PR #8527: fix(atn): export Black & White adjustment layers with Photoshop's BanW class (#8481) 2026-10-22 23:40:17 +02:00
David Mann 9ee6c407b6 Merge PR #8254: Image Assets: optionally keep a transparent artboard's full size 2026-10-22 23:40:16 +02:00
David Mann a23c29b53a Merge PR #5878: Preferences: old settings files take Shift-to-switch-tools on (#5647) 2026-10-22 23:40:16 +02:00
David Mann 79ab013961 Merge PR #8496: fix(ui-egui): expand layer visibility eye hitbox and render sunken well (#8467) 2026-10-22 23:40:16 +02:00
David Mann b1e4c9b1f9 Merge PR #6041: Raw: charge the retained tile intermediates together (#4959) 2026-10-22 23:40:15 +02:00
David Mann 24051914fd Merge PR #6352: fix(engine): honor lock all on layer mask commands 2026-10-22 23:40:15 +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 5301a128b8 Merge PR #8509: fix(atn): preserve unsupported event kinds 2026-10-22 23:40:13 +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 7f68d4e06a Merge PR #5370: fix(gpu): catch validation errors in watched compositor work 2026-10-22 23:40:13 +02:00
David Mann 83b1850539 Merge PR #7455: photocraft-cli run: refuse an --out that is the input file unless --in-place (#7437) 2026-10-22 23:40:13 +02:00
David Mann 7be48940b3 Merge PR #8518: fix(engine): select files correctly in Windows Explorer 2026-10-22 23:40:13 +02:00
David Mann 0fcdd660cb Merge PR #7849: Menu bar: a held Alt's auto-repeat doesn't re-arm the Alt tap 2026-10-22 23:40:12 +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 6e4d89d13d Merge PR #8473: test(panic_hunt): give the panic-counting self-test its own deadline 2026-10-22 23:40:12 +02:00
David MannandClaude Opus 5.5 106776f598 Integration: fix the batch's merge seams (#5815, #6658)
- io tests/it/psd_structure.rs: main's Duotone test compares the raw
  block as a slice now that #5815 shares it as Arc<[u8]>.
- ru and fr catalogs: main and #6658 both added "Continue" with the same
  translation; the second copy goes (bundled_catalogs_are_consistent).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:40:12 +02:00
David MannandClaude Opus 5.5 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
8a2d35fcd2 Integration: an unchanged dialog keeps a baked overlay's exact ends (#7335)
After merging main, the Layer Style dialog always sends From/To as 8-bit
hex (#7072). On a baked Gradient Overlay without a source record (PSD or
.asl import) the custom and no-record paths wrote those hex ends back,
so OK without a change rounded the curve's ends (a Smooth bake's white
end 0.99999994 became 1.0, Lab ends became RGB). Like edit_endpoint for
the recorded source, a hex end that only repeats the current end is now
left out. baked_overlay_is_unchanged_until_method_selection_then_edits_
the_whole_curve fails without this.

Co-authored-by: oRE-o <42003350+oRE-o@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:40:12 +02:00
f895f844e7 Integration: a hidden Work Path is no area type target (#6230)
After merging main, the Type tool's path target fell back to the Work
Path over its whole inside, while main's #6551 only draws an unselected
Work Path for path tools. Ctrl-click Load as Selection (or deselecting
the Work Path) and a Type tool click inside it started area type in a
path nobody could see. Now the Work Path's inside counts only while it
is selected in Paths (drawn), its outline still takes type on a path
with main's 6 px band, and a missed shape path falls back to the Work
Path as on main. an_unselected_work_path_is_not_an_area_target fails
without this.

Co-authored-by: SillySilk <31193727+SillySilk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:40:12 +02:00
David Mann a7b833caf6 Integration: stack #8529 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David Mann fff01d8918 Integration: stack #7335 (its merge onto main, conflicts resolved) 2026-10-22 23:40:11 +02:00
David Mann 6cb8673e8f Integration: stack #6658 (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
David Mann 8748dcbf16 Integration: stack #5598 (its merge onto main, conflicts resolved) 2026-10-22 23:40:09 +02:00
David Mann ddf1bba02f Integration: stack #5485 (its merge onto main, conflicts resolved) 2026-10-22 23:40:09 +02:00
David Mann 7546c9d60d Integration: stack #4221 (its merge onto main, conflicts resolved) 2026-10-22 23:40:09 +02:00
David MannandClaude Opus 5.5 e0fed98f23 Merge PR #8529: perf(bench): run bench_filters2 from cargo xtask perf, four heavy filters now tracked (P64-P67)
docs/scorecard.md: conflict in generated file, kept ours (b7-base); regenerated later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:39:17 +02:00
David MannandClaude Opus 5.5 6c7e920d5a Merge PR #7335: Gradient Overlay: add Method selector and retain editable source stops
- crates/ui-egui/src/layer_style.rs: values_of keeps the PR's method/smoothness keys and source-stop ends, and main's (#7072) unconditional From/To and geometry fields.
- crates/compose/src/cmyk_tests.rs (no textual conflict): add the PR's new `interpolation: None` field to main's CMYK test GradientOverlay literal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:39:14 +02:00
David MannandClaude Opus 5.5 6aa27edb06 Merge PR #6658: fix(ui): confirm opening damaged PSD/PSB documents
crates/io/src/lib.rs: kept main's ImportOptions; import_for_open/_with and import_stages now take &ImportOptions (plus the PR's damaged flag); import_with_options_and_interrupt delegates to import_for_open_with.
crates/engine/src/jobs.rs: open job decodes via import_for_open_with(&opts) so the damage flag reaches the held-open path; main's raw_defaults and docId kept.
crates/ui-egui/src/lib.rs: open_bytes_from uses main's import_options + raw_defaults and the PR's PSD damage check via import_for_open(&opts).
Also: moved crates/io/tests/psd_recovery_generated.rs to tests/it/ (mod added, support path fixed) and adapted PR tests to ImportOptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:39:09 +02:00
David MannandClaude Opus 5.5 945a10dc7c Merge PR #6055: Color panel: Photoshop's panel-menu modes, the same in every theme (#6000)
Conflicts:
- crates/ui-egui/src/i18n/{cs,de,el,es,fr,hu,id,it,ja,ko,nl,pl,pt-br,ru,sr,tr,uk,vi,zh-hans,zh-hant}.tsv: kept every key of both sides (main's block, then the PR's Color panel block); no duplicate keys.
- crates/ui-egui/src/panels.rs: took the PR's removal of color_picker/hsb_sliders/rgb_sliders (moved to color_panel_ui.rs); kept main's color_field_height doc comment; main's #5842 slider recolour tests ported to color_panel_tests.rs for the new HSB/RGB slider modes; main's black-HSB test dropped as the PR's hsb_sliders_take_values_on_black covers it.
- crates/ui-egui/src/color_panel_ui.rs (no textual conflict, adapted): set_color now goes through apply_color::foreground_changed with the gesture (was type_tool::foreground_changed), so every mode (sliders, ramp, alerts, wheel) recolours selected type/shapes tool-gated in one undo step per gesture; panel() calls apply_color::track_focus.
- crates/ui-egui/src/color_panel_tests.rs (adapted): new every_slider_mode_recolours_the_selected_shape test.
- docs/gaps.md, docs/ui-parity.md: union of both sides' status lines, rows and revision history; the PR's done row replaces the old "missing" Color panel row.
- docs/scorecard.md: generated, took b7-base's version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:39:04 +02:00
David MannandClaude Opus 5.5 cab3ef6191 Merge PR #5815: perf(doc): share the XMP packet, Duotone PSD data and unsupported adjustment bytes between snapshots
- crates/engine/src/tests.rs: kept both tests (main's restore_layer_selection test and the PR's history_states_share_large_document_payloads).
- crates/io/src/psd_import.rs: kept main's parsed Duotone inks (duotone_map) with the PR's Arc<[u8]> psd_raw; also main's unknown-adjustment-key import (#6575) now builds Arc<[u8]> raw.
- crates/io/tests/adjust_warnings.rs: moved to tests/it/adjust_warnings.rs on main; PR's .into() edits applied there, plus #6322's Color Lookup test (raw.as_slice().into()).
- docs/architecture.md: union; status line names both changes, both revision-history rows kept.
- Adapted main-new sites to the PR's types: psd_export.rs (Duotone write-back via as_deref/to_vec, Unsupported block compare by slice), inspect.rs and ui-egui unsupported_marker_tests.rs (raw .into()), io tests/it/psd_unknown_adjustment.rs (slice compare).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:38:56 +02:00
David MannandClaude Opus 5.5 ced9aa6947 Merge PR #5598: Plug-ins: wire Enable Generator (Image Assets needs it)
docs/scorecard.md: conflict in generated file, kept ours (b7-base); regenerated later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:38:50 +02:00
David MannandClaude Opus 5.5 f56147655d Merge PR #5485: Add opt-out startup release checks and manual update status
Conflicts resolved:
- crates/ui-egui/src/i18n/hu.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/ru.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/i18n/sr.tsv: kept base's added sections, then the PR's update-check keys
- crates/ui-egui/src/i18n/tr.tsv: kept base's Bevel & Emboss keys, then the PR's update-check keys
- crates/ui-egui/src/menus.rs: kept base's saved-workspace entries, then the PR's Help › Check for Updates reordering
- docs/gaps.md: base status line with the PR's G17 clause prepended; PR revision row above base's rows
- docs/target-app-parity.md: base status line (updated 2026-10-22) with the PR's clause prepended; PR revision row above base's rows
- docs/scorecard.md: generated, took b7-base's version

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:38:46 +02:00
David Mann e7c53231a5 Merge PR #8547: perf(gpu): layer pages that just miss the budget borrow the effect-map share, P60/P61 10× faster on integrated GPUs 2026-10-22 23:38:40 +02:00
David MannandClaude Opus 5.5 08f951b909 Merge PR #4221: Options bar: the same tool options in every theme (#4178)
Conflicts:
- crates/ui-egui/src/panels.rs: took b7-base's file (keeps align_bar in the Move bar and all other main changes) and re-applied the PR's change: dropped the six `if t.pro` guards (Marquee, Lasso, Paint Bucket, Perspective Crop, Crop, Move) and deleted the eight non-Pro hint/fallback arms (incl. the Studio Move arm, now covered by the Pro arm which already has Auto-Select, Show Transform Controls and align_bar); rustfmt reindent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:38:40 +02:00
David Mann 4d6e2b08c5 Merge PR #8465: perf(camera-raw): exposure with all scopes under its 10 ms budget (P36), same output 2026-10-22 23:38:39 +02:00
8ee9917e5d Integration: move eyedropper_ring tests into tests/it (#7021)
Main now builds each crate's integration tests as one binary (crates/ui-egui/tests/it). The PR's
tests/eyedropper_ring.rs moves to tests/it/eyedropper_ring.rs behind `mod eyedropper_ring;`,
and both wgpu tests take photocraft_testkit::gpu_lock() like their neighbours.
ring_reads_over_a_flat_area_of_its_own_colour still fails with main's canvas.rs and passes with the PR's.

Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:03:30 +02:00
David MannandClaude Opus 5.5 ad68fa36e0 Integration: merge main (#7021)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:03:07 +02:00
dc180cd891 Integration: write a shift tool switch change made before the first save (#5878)
When the file on disk is already version 2 (another instance saved the
migrated "on"), the save base took this instance's current value, so
turning the switch off here before the first save matched the base and
was never written; the file kept "on". The base now holds the migrated
value (on), so only a change made here since loading is written.

Test: turning_the_shift_tool_switch_off_is_saved_over_another_instances_migration
(fails with the previous base: true kept instead of false).

Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:57:43 +02:00
4efebbe2bf Integration: lock a Shift selection drag to multiples of 45° (#6254)
Photoshop constrains a Shift-mid-drag selection move to multiples of 45°
("Move a selection border"), and the Move tool's drag of selected pixels
already uses TRANSFORM_DIRECTIONS (8). selection_drag_to used
MOVE_DIRECTIONS (4), so a near-diagonal Shift drag (delta 13,11) snapped
to the horizontal axis (13,0) instead of 45° (12,12).

Test: shift_constrains_a_selection_drag_to_an_axis gains a diagonal case
(fails with 4 directions); the floating-drag test moves to (34,22) so it
stays an axis case with 8 directions.

Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:56:36 +02:00
d921fedcca Integration: export dialog-recorded Apply Image channels, refuse numeric selectors (#8180)
The Apply Image dialog sends Channels-panel names in sourceChannel ("RGB" by
default, "Red", "Alpha 1"), but the export matched only lowercase keys, so a
dialog-recorded Apply Image failed strict .atn export. Channel names now map
case-insensitively (RGB/CMYK/Lab to the composite), an alpha channel is written
as a Chnl name reference and read back on import, and a numeric layer or channel
selector is refused instead of becoming the merged composite.

Tests: apply_image_dialog_channel_names_survive_a_strict_export (the dialog's
exact params) and apply_image_numeric_source_selectors_are_refused; both fail
without the fix.

Co-authored-by: VishnuByrraju <112673081+VishnuByrraju@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:51:51 +02:00
06530cd1a1 Integration: skip CPU canvas pixel tests only without an adapter (#6966)
The two pixel/checker tests wrapped all of harness() in catch_unwind, so a
failing fixture command or a panic in the first 12 frames was reported as
"no offscreen graphics adapter" and the test passed without checking
anything (CodeRabbit thread on line 107). harness() now runs the fixture
commands first, catches only the wgpu renderer creation (the one step that
panics without an adapter) and builds and steps the harness outside it.

fixture_failures_are_not_skipped_as_a_missing_adapter feeds a 0x0 document:
with the old catch-all it was swallowed and the test fails; now it panics.

Co-authored-by: hogawish <60434858+hogawish@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:45:09 +02:00
3175a3c4cb Integration: keep failed compositor pipelines in the map (#8118)
The parallel build skipped a non-map pipeline whose creation raised an
error, so with new_with_format (errors logged, compositor still returned)
its key was missing from kit.pipelines and the first draw panicked on the
`self.kit.pipelines[&(kernel, acc)]` index. As before the PR, a failed
non-map pipeline now keeps its invalid handle (draws report GPU errors);
only a failed effect-map pipeline is skipped (all maps go to the CPU).

Test: a_compositor_with_failed_pipelines_draws_without_panicking builds
new_with_format(Rgb9e5Ufloat) on a device with an uncaptured-error
handler and renders: it panicked at the index without the fix. The PR's
test moves into tests/it (one test binary) and takes GPU_LOCK.

Co-authored-by: 00xJS <56056764+00xJS@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:42:40 +02:00
2e29033b50 Integration: no Outside stroke share under opaque pixels on the GPU too (#5937)
The PR gives an Outside stroke no share beneath fully covered pixels on the
CPU, but no test failed without it: under a Normal layer the opaque pixel hides
the stroke anyway. It shows with Blend Interior Effects as Group on a Multiply
layer: main multiplied the red interior with the blue stroke beneath it (black).
New test outside_stroke_under_multiply_layer_leaves_interior_to_backdrop
(raster and filled shape) fails on main's effects.rs, passes with the PR.

The GPU compositor mirrored the old math (outline_share 1 at full coverage, and
1 under an opaque pixel layer); compose.wgsl now matches the CPU. Blend Interior
Effects as Group itself renders on the CPU, so on the GPU this is consistency;
a parity case pins non-Normal layers with an Outside stroke.

Co-authored-by: seotarek <30214297+seotarek@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:41:01 +02:00
David Mann aa87afc886 Integration: merge main (#8180) 2026-10-22 21:39:51 +02:00
1bfae77c16 Integration: test stringID blend modes through .atn import (#8502)
The PR's test only called blend_label/blend_ps. This adds one through the
import entry point: a Set Lyr step with Md enm("BlnM","softLight") and a
Make layer step with Usng Md "luminosity", written with atn::write and
imported, must give layer.setProps blend "Soft Light" and layer.new.layer
mode "Luminosity" with no unsupported steps. Without the PR's table rows
the Set step is unsupported (and the Make falls back to Normal).

Co-authored-by: justinjohn0306 <34035011+justinjohn0306@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:39:28 +02:00
6bb15d2278 Integration: keep another instance's shift tool switch choice (#5878)
Two instances migrate the same version 1 file (useShiftKeyForToolSwitch
false -> true), so both save bases hold false. When B turns the switch off
and saves version 2, A's next unrelated save wrote its migrated true over
that choice. write_prefs now drops the migration delta from the base when
the file on disk is already version 2 or later.

Test: another_instances_shift_tool_switch_choice_beats_this_ones_migration
(fails without the fix: true written over false).

Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:39:04 +02:00
95c3af68c3 Integration: prove doc.replaceSmartObject replaces and undoes (#8150)
The rpc test only checked the returned layer id, so a no-op that echoed
{"layer": id} passed, its undo check passed on doc.place's own step, and
the bad-layer case asserted on an empty reason. It now checks the smart
object's file name and rendered centre pixel (42 -> 200 after the
replace, back to 42 on undo, 200 on redo), that every refusal changes
nothing, and that layer 999999 fails with "no such layer".

The bridge row in docs/control-protocol.md claimed doc_replace_smart_object
maps onto layer.smartObjects.replaceContents; the desktop refuses that
command (ambient path), like doc_place's file.placeEmbedded. The row now
says so and points to the headless / workspace server method.

Co-authored-by: VishnuByrraju <112673081+VishnuByrraju@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:36:42 +02:00
David Mann b7eabd4990 Integration: merge main (#6254) 2026-10-22 21:36:35 +02:00
249eae743f Integration: make Oil Paint's round_i32 exact for every f32 (#8556)
round_i32 added 0.5 in f32, which rounds up at the binade crossing:
0.49999997 (0.5 - 2^-25) + 0.5 becomes 1.0, so round_i32 gave 1 (and -1 for
the negative) where f32::round gives 0. The sum is now taken in f64, where
it is exact for every f32 the i32 cast does not saturate; still no roundf call.

rounding_matches_the_standard_library now covers +-0.49999997, the ties and
8388607.5, and a new test compares every f32 bit pattern in [-4096, 4096]
against f32::round (0.6 s). Both fail on the previous head. The code comment
and docs/oil-paint-performance.md no longer claim the f32 sum is exact.

Co-authored-by: ebosido444-ai <316167330+ebosido444-ai@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:31:24 +02:00
fd8a2856d4 Integration: line the layout's ink box up with the TySh ink box (#6094)
refine_legacy_point_origin moved the layout's logical box (line x0,
baseline - ascent) onto the TySh boundingBox, but Photoshop writes
boundingBox as the tight ink box (Arial 120 pt "..." has bounds top
-102.98, boundingBox top -11). Legacy point text whose ink starts below
the ascent line ("...", "_", lowercase) opened up to ~116 pt too low.

Match like with like: use the layout's unrounded text-space ink bounds
(new render::ink_bounds). The PR's tests built boundingBox from the
logical box, so they now write bounds = layout box and boundingBox =
ink box and pin the exact anchor; legacy_origin_lines_up_ink_with_the_
ink_bounding_box covers "Name", "..." and "_" and fails without this.

Co-authored-by: bogusyogi <102761952+bogusyogi@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:29:29 +02:00
David Mann 1e3323f64c Integration: merge main (#6966) 2026-10-22 21:28:41 +02:00
David Mann 87905d448f Integration: merge main (#8118) 2026-10-22 21:25:47 +02:00
David Mann f9efad49d6 Integration: merge main (#5937) 2026-10-22 21:16:38 +02:00
cc498e537e Integration: keep the save base complete after the prefs migration (#5878)
The save base after loading kept only the keys the file had, so a key an
older file lacks was written as this instance's default over another
instance's change (two instances on one v1 file: B sets the theme, A saves
anything, the theme reverts). A version 3 file was also written back as
version 2. The base is now what was loaded, with only the version and
tools.useShiftKeyForToolSwitch put back as the file had them (a newer
version is left alone), the way repair_invalid patches keys.

Tests: an_old_sparse_file_keeps_another_instances_change,
a_newer_files_version_is_kept_on_save (both fail with the previous base).

Co-authored-by: Higgs32584 <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:15:53 +02:00
David MannandClaude Opus 5.5 018d3605b1 Integration: merge main (#6094)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:13:36 +02:00
77caa32765 Integration: keep a hidden layer's eye box empty (#8496)
The PR widened the eye hit area to the whole column (#8467) but also painted
a 12x12 sunken well in a hidden layer's eye box. The maintainer decided on
#1662 that a hidden layer's eye box stays empty, as in Photoshop (and the
hidden fx-row eye still does), so the well goes; the full-column hit area and
its test stay. New test a_hidden_layers_eye_box_paints_nothing checks, in all
five themes, that nothing is drawn inside a hidden layer's eye column while a
visible layer's eye is; it fails with the well. Also drops the trailing blank
line at EOF of layer_row_ui/tests.rs (cargo fmt).

Co-authored-by: kobaltgit <159009675+kobaltgit@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:10:34 +02:00
f40366f1a8 Integration: keep only the segment of a lossless-JPEG tile, test the sum through decode (#6041)
A compression-7 frame may declare up to four times its segment's samples
(ljpeg::decode is capped at 4x the tile), and read_plane returned the whole
frame, so each retained tile held up to 4x what retained_segment_bytes
charges it: tiles declared 256x256 holding 512x512 frames pass a 1 MiB
budget while keeping 2 MiB. The lossless-JPEG arm moves into
lossless_jpeg(), which now truncates to the segment's rows (all the scatter
reads) and releases the spare capacity.

The PR's test only called the helper; deleting the call in read_plane kept
it green. decode_charges_the_retained_tiles_together drives decode on an
18x18 CFA in four padded 16x16 LJ92 tiles: 1500 B -> LimitExceeded, 2048 B
-> Ok. lossless_jpeg_keeps_only_the_segment pins the truncation. The doc
comment now says the output plane is alive alongside the sum.

Co-authored-by: Muhammad Saad <188063027+saad-mughal435@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:09:26 +02:00