Commit Graph
2027 Commits
Author SHA1 Message Date
David Mann cb323e9aea Merge PR #10965: Group action playback into one history state 2026-11-04 14:51:18 +01:00
David Mann d7e2bbe4a4 Merge PR #11077: Layers panel: filter by layer attribute (#10881)
# Conflicts:
#	crates/ui-egui/src/panels/layer_filter_ui.rs
#	docs/control-protocol.md
2026-11-04 14:51:17 +01:00
David Mann aac944a315 Merge PR #11223: Actions: map supported classic Gradient recordings
# Conflicts:
#	crates/engine/tests/it/main.rs
2026-11-04 14:51:00 +01:00
David Mann fd736f7ddc Merge PR #10986: Actions: map recorded Content-Aware Fill settings 2026-11-04 14:50:49 +01:00
David Mann a7f34dfa9f Merge PR #11222: Actions: support recorded Type Create Work Path 2026-11-04 14:50:47 +01:00
David Mann 3ea385cef3 Merge PR #11017: Layers filter: fill layers match no Kind button, and the Mode list has no Pass Through (#10878) 2026-11-04 14:50:46 +01:00
David Mann 2c68cc6b95 Merge pull request #11203 from davidmann4/integration-batch-15
Integration batch 15: Crop, Free Transform and Actions epic PRs
2026-11-04 14:18:54 +01:00
Tareq HalabyandFredApps 2472b4a46b PSD: keep stylistic sets in Txt2 for PhotoCraft-native documents (#10313)
* PSD: keep stylistic sets in Txt2 for PhotoCraft-native documents.

needs_txt2 now covers Txt2-only OpenType flags (ss01–ss20 and non-default TXT2_FEATURES). Fresh documents get a framed point-text object with full style runs so Set 1/5 round-trip on save/reopen (#10308).

* Txt2: seed fill colour and font list on PhotoCraft-native blocks.

Fresh style runs get a /53 paint dict, and an empty font list gets a CoolTypeFont entry so index 0 resolves (CodeRabbit on #10313).

* Txt2: tag SimplePaint and ensure numbered /0 on legacy blocks.

CodeRabbit: write /99 /SimplePaint on fresh /53 dicts, and create the numbered resource dictionary before pad_frames when a legacy block lacks it.

* Txt2: treat a missing stylistic-set mask as 0 when comparing styles

A style run without key 85 used to agree with any ssNN mask, so enabling
ss01 could leave the file's runs in place on save. Align with import's
default of 0. Also restore the G8 gaps note after rebasing onto main.

* style(io): rustfmt psd_kerning stylistic-sets test

* Txt2: keep Photoshop /0 stable when seeding fonts and frames

Only seed a CoolTypeFont list and inject /0/8 when the block is
PhotoCraft-native or already had those keys / needs new frames, so
unedited Photoshop Txt2 saves stay byte-identical (#2374).

* style(text): rustfmt oversized-template assert

* PSD: keep the Txt2 scaffolding to PhotoCraft-native blocks

A file's own Txt2 block (any Photoshop era) is written as before: no
numbered /0 or /0/8 frame list is injected, a layer without a template
object gets its stub, a run without /53 isn't given one, and no
minimal style base is used. Only a block PhotoCraft creates itself
(prev.is_none()) gets fresh point-text objects, minimal runs, a seeded
font list and a frame list.

Fresh objects now write one /5 paragraph run per paragraph (UTF-16
lengths, checked conversion), as edited objects are refitted.

Tests: Photoshop blocks with an empty or missing /0 get no native
scaffolding; the oversized-template test is back to main's assertion;
fresh paragraph runs; the PSD round trip checks ss01 and ss05 on every
run.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-04 13:28:06 +01:00
skylar taylor-barrick 68b6dbfa79 Merge pull request #10750 from RickiNano/perf/bench-cmd-refresh-split
perf(bench): split command and refresh time; refresh like the app; stop rows timing setup
2026-11-04 12:39:54 +01:00
Tareq HalabyandFredApps 4d0ecd746f Toolbar: Alt/Option-click cycles the flyout tool slot (#10301)
* Toolbar: Alt/Option-click cycles the flyout tool slot.

Matches Photoshop: plain click keeps the shown tool; Alt-click advances through the slot without opening the menu (including tools Shift+letter skips).

* Toolbar: don't fit-on-screen on rapid Alt-clicks through Hand.

Alt-click only cycles the flyout slot; exclude Alt from the Hand/Zoom double-click handlers and cover inactive-slot cycling.

* Toolbar: ignore Hand/Zoom double-click after an Alt-click.

egui pairs a plain click with a prior Alt-click as a double-click; remember the previous click's Alt so fit-on-screen and actual-pixels only run for two plain clicks.

* Toolbar: read click modifiers from the slot's pointer release.

Match DesignCraft/EffectCraft: bind release mods to the tool rect, treat any modifier as blocking Hand/Zoom double-click, and harden the Alt-cycle test by clearing i.modifiers before release.

* Toolbar: cover 1/2/3-tool Alt-click cycles and hidden-tool slots.

Addresses ReviewerA on #10301: next_slot_tool wrap cases for short flyouts, and visible_sections so Edit › Toolbar hidden tools shrink the Alt-click cycle.

* style(ui): rustfmt after rebase onto main

* Toolbar: next_slot_tool never panics on an empty slot

Index with checked arithmetic and get() (house rule: no panics on
input-derived indices); an empty slot returns the current tool. Test
added.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-04 12:29:11 +01:00
Psychotoxical c71677a159 Merge remote-tracking branch 'origin/main' into feat/layer-filter-attribute
# Conflicts:
#	crates/automation/src/server.rs
#	crates/ui-egui/src/control/tests.rs
#	crates/ui-egui/src/panels/layer_filter_ui.rs
#	crates/ui-egui/src/state.rs
#	docs/control-protocol.md
2026-11-04 09:49:44 +01:00
Hamza e85b360248 Merge remote-tracking branch 'origin/main' into fix/layer-filter-kind-and-mode
# Conflicts:
#	crates/ui-egui/src/control/tests.rs
#	crates/ui-egui/src/panels/layer_filter_ui.rs
#	docs/control-protocol.md
2026-11-04 12:48:11 +05:00
yoriswind 2974772936 Map supported classic Gradient action recordings 2026-11-03 19:32:28 -08:00
yoriswind 16b24885e6 Support recorded Type Create Work Path actions 2026-11-03 19:28:31 -08:00
yoriswind 1bcf7a3c6a Merge batch 14 main before action-history review fixes 2026-11-03 17:27:25 -08:00
yoriswind 1f9f37ae8e Merge batch 14 main into Content-Aware Fill recordings 2026-11-03 17:24:10 -08:00
David Mann 59b49ca14f Merge PR #9979: feat(ui): live preview in Color Pickers for dialog colour fields and the Crop shield 2026-11-04 01:51:22 +01:00
David Mann 2c4666aa6e Merge PR #10814: Edit › Transform › Distort: side handles move their edge, Perspective's side handles skew (#10085) 2026-11-04 01:51:10 +01:00
David Mann 545d447c10 Merge PR #10998: Actions: map recorded Scale Effects and Create Layers 2026-11-04 00:51:01 +01:00
David Mann e06d1cbdc1 Merge PR #11067: Layers panel: filter by layer effect (#10880) 2026-11-03 22:55:47 +01:00
Psychotoxical 94a6b68a04 Merge remote-tracking branch 'origin/main' into feat/layer-filter-attribute
# Conflicts:
#	crates/automation/src/server.rs
#	crates/ui-egui/src/panels/layer_filter_ui.rs
#	crates/ui-egui/src/state.rs
#	docs/control-protocol.md
2026-11-03 22:09:02 +01:00
Psychotoxical 66b9f2f68b Merge remote-tracking branch 'origin/main' into feat/layer-filter-effect
# Conflicts:
#	crates/automation/src/server.rs
#	crates/ui-egui/src/panels/layer_filter_ui.rs
#	crates/ui-egui/src/state.rs
#	docs/control-protocol.md
2026-11-03 21:55:06 +01:00
yoriswind 3028355333 Merge main and retain action history across Batch and Spelling 2026-11-03 12:42:57 -08:00
yoriswind 1b6bc4136c Merge main into recorded Content-Aware Fill mapping 2026-11-03 12:14:13 -08:00
yoriswind 27f86af55b Merge main into recorded layer style mappings 2026-11-03 12:12:41 -08:00
yoriswind 7763e2578f Merge main into recorded Shear mapping 2026-11-03 12:11:27 -08:00
ljkxandClaude Opus 5.5 a0986a3977 Merge origin/main into feat/color-picker-live-preview
Resolve docs/ui-parity.md: keep main's status line and prepend this PR's entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:41:33 +01:00
ljkxandClaude Opus 5.5 9df16201aa Merge origin/main into feat/distort-edge-handles
Resolve the parity-doc conflict: keep main's status line and entries, re-add this PR's entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:39:56 +01:00
David MannandClaude Opus 5.5 72c2585177 Merge origin/main (287352bf92) into integration-batch-13
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>
2026-11-03 19:35:44 +01:00
FredApps 287352bf92 Merge pull request #5333 from vistaero/codex/streamed-psb-save
Integrate streamed PSB saves with the shared save service
2026-11-03 18:41:16 +01:00
yoriswind 0edb29a18c Merge upstream main into Shear action mapping 2026-11-03 09:32:21 -08:00
David Mann 3d36330d94 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/scorecard.md
2026-11-03 18:08:29 +01:00
FredAppsandClaude Opus 5.5 d07684a379 fix(ui): Overscroll off keeps an image smaller than the window centred (#10488) (#11085)
* fix(ui): Overscroll off keeps an image smaller than the window centred (#10488)

With Tools › Overscroll off, an image smaller than the window stayed
wherever zooming around the pointer left it, inside the window, so
zooming out drifted it off centre. #1718 measured that on Photoshop 25.4,
but two reports of Photoshop itself (#10488, #4799) say it stays centred
with Overscroll off. The owner chose the centred behaviour:
scrollbars::clamp_axis now centres an axis on which the image is no
larger than the view; a larger image still can't leave its edges, and
Overscroll on keeps the free view.

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

* test(ui): Overscroll off centres a fitting rotated image (#10488)

CodeRabbit on #11085: the rotated clamp path was only tested from an
already-centred view. A 600 px square at 45 degrees, started off centre
in a 900-point view, now ends with its screen bounds centred.

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

* style(ui): keep the rotated centring test within rustfmt's width

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:55:50 +01:00
d21c403e5f Type: add OpenType Numerals submenu (figure styles) (#11051)
* 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>
2026-11-03 17:55:35 +01:00
ljkxandClaude Opus 5.5 0a379e2465 Merge origin/main into feat/color-picker-live-preview
Resolve docs/ui-parity.md: keep main's status line and prepend this PR's entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:46:51 +01:00
David Mann 559d3fe0f4 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/engine/src/cmd/file_cmds.rs
#	crates/ui-egui/src/app/files.rs
#	crates/ui-egui/src/dialogs/tiff_options_ui.rs
#	docs/file-format-parity.md
#	docs/gaps.md
#	docs/ui-parity.md
2026-11-03 17:11:53 +01:00
FredApps 26d151d061 Jobs: a save past its commit point can't be reported as cancelled
jobs.cancel, cancel_all_jobs and closing a document (cancel_jobs_on) cancelled
any running job, so they could report a save as cancelled while its worker
went on to replace the file. The point of no return was tracked only by the
desktop shell's own Cancel/Esc path.

The commit point now lives in the engine's JobCtx: commit() and a cancel race
on one compare-and-swap, so exactly one wins.
- Cancelled before commit: commit() fails, the save writes nothing and the
  job ends Cancelled.
- Committed: every cancel path leaves the job running and says so
  (Session::try_cancel_job -> CancelRefused::Committing; cancel_all_jobs and
  cancel_jobs_on return a CancelReport). The job then reports its real
  outcome, applied even if its document was closed meanwhile.
- jobs.cancel (engine and desktop control) replies {cancelled: n} as before,
  plus notCancelled: [{job, reason: "committing"}] for a save left running.
  This is not an error: the job is fine and will report how it ended.
  Single-job print refusals are unchanged.
- The shell's SaveCtl::commit and Cancel/Esc use the engine gate, so the
  status bar, control and scripting agree.

Tests: cancel before commit (target untouched, Cancelled), cancel after
commit (refused, Done, file written), cancel_all and document close with
mixed committed/open jobs, a commit/cancel race, and desktop jobs.cancel
before and during the write with real files.
2026-11-03 16:09:07 +01:00
Psychotoxical 30c8f8d5f0 Layers panel: filter by layer attribute (#10881)
Attribute comes after Mode in the filter-type dropdown, with Photoshop's 18
entries: Visible, Locked, Empty, Linked, Clipped, Layer Mask, Vector Mask,
Layer Effects and Advanced Blending, and their opposites. Advanced Blending
covers all eight options (excluded channels, knockout, the four switches and
Blend If); Fill opacity alone doesn't count. ui.set {layerFilter} takes the
attribute by camelCase id.
2026-11-03 15:47:45 +01:00
ljkxandClaude Opus 5.5 c2acddae8c Transform: Distort side handles move their edge, Perspective's skew (#10085)
Edit › Transform › Distort ignored its mid-edge handles: mode_allows
rejected Hit::Edge in Distort, so a press there armed no gesture. As in
Photoshop, a Distort side handle now moves its edge freely: both of that
edge's corners move by the drag offset and the opposite edge stays put,
stopping before the box folds flat (keep_convex), as a Distort corner
does. Like the corner, it ignores Shift and Alt. The reference point
keeps its place in the box. Preview and commit share the session quad,
and the commit is one history step.

Perspective mode's side handles now skew along their edge, as in
Photoshop, by reusing the Skew-mode edge arm. Before, they scaled.

The handle pointer follows: the distort pointer on Distort side handles
and the skew pointer on Perspective side handles. Free Transform's plain
side handles (scale) and its Cmd-drag (free edge move) are unchanged.

Tests cover each edge's exact corner offsets, modifier independence, the
collapse stop, a real pointer drag committed in one step with the pixels
inside the shown quad, and Perspective edge slides. docs/ui-parity.md is
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:39:32 +01:00
Yuki-init e3a84b628d TIFF Options: compression, image pyramid and Save Transparency (#8827) (#10932)
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.
2026-11-03 15:39:15 +01:00
Hartmut NörenbergandClaude Opus 5.5 2fff1f8688 fix(ui): Ctrl+Alt + wheel zooms the canvas like Alt + wheel (#10488) (#10517)
* fix(ui): Ctrl+Alt + wheel zooms the canvas like Alt + wheel (#10488)

Ctrl+Alt+wheel over the canvas did nothing useful because two conditions in
wheel_nav.rs excluded it from the Alt gesture:

    let latest_alt = wheel.last().map(|(_, _, m, _)| m.alt && !m.command && !m.ctrl);

and, in the raw-event accumulator, every branch was filtered with
`!m.command && !m.ctrl`. Ctrl+Alt+wheel therefore never set the tracked `alt`
and never contributed a notch to `raw`: with Preferences > General > Zoom with
Scroll Wheel ON it produced nothing at all (raw stayed zero, so the zoom read an
eased ~0), and with it OFF it fell through to classify's `_` arm and merely
scrolled vertically, because egui keeps vertical_scroll_modifier = ALT and does
not fold the delta onto x when both scroll patterns match. The combination is a
dead/incorrect gesture today, so no working behaviour is being replaced.

The new `alt_gesture` helper forgives `command` only when `ctrl` is set as well.
winit mirrors a held Ctrl on Windows (and Linux) into BOTH ctrl and command, so
a bare `!command` test can never match what a Windows reporter typed; macOS
Cmd+Alt (command set, ctrl not) is left out of the zoom gesture (egui 0.37 does not fold it sideways either: with Alt held both scroll patterns match, so its delta stays vertical). `latest_shift` stays strict, so
Ctrl+Alt+Shift+wheel zooms continuously instead of snapping to the ladder, and
the Shift-only and plain branches are untouched, so plain Ctrl+wheel still
scrolls sideways (#635). `legacy_pinch_to_zoom` is untouched and cannot
double-count a fractional Ctrl+Alt pinch step: it matches `modifiers.ctrl`
whatever else is held, but it *replaces* the wheel event with `Event::Zoom`, so
`wheel_events` never sees that step a second time.

Deliberately out of scope: the other half of the report, the centred-zoom /
overscroll complaint. The per-frame view clamp at canvas.rs:1737-1741 runs only
when Preferences > Tools > Overscroll is off (it is on by default), so with the
defaults nothing pulls the view centre back, and what the reporter is seeing is
more likely the deliberate wheel_anchor nearest-image-point clamp
(view_xform.rs:239, #3720) or Zoom Clicked Point to Center. That needs its own
reproduction and issue, and is noted as still open in the dev log.

Tests, fail-first: each_modifier_maps_like_photoshop gained Windows Ctrl+Alt
rows (preference off and on, instant and eased, a fractional pinch step counted
once, and a macOS Cmd+Alt guard row) while keeping its Ctrl-only sideways-scroll
rows; new full-app kittest test
input_tests::ctrl_alt_wheel_zooms_the_canvas_like_alt_wheel zooms five notches
around the pointer and holds the document point under it. Both failed before the
production change and pass after.

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

* test(ui): Ctrl + Alt + Shift + wheel is dead today, not a zoom (#10488)

CodeRabbit 4432102938 asked for an assertion on the Ctrl + Alt + Shift combination instead of
only a comment, and for the reason: the filter rejects the wheel event. Both are right. With ctrl
held, latest_shift (wheel_nav.rs:381) is false because it excludes ctrl and command, so the alt
branch of the filter (wheel_nav.rs:413-422) runs and its "!m.shift" drops the notch out of raw;
shift stays false, so the snap branch never takes it either.

The new row in each_modifier_maps_like_photoshop first asserted what my own comment claimed - a
continuous zoom, with Zoom with Scroll Wheel off and Animated Zoom off - and failed exactly that
way: "Ctrl + Alt + Shift + wheel zooms 10%, at once: [0.0 0.0] 1 snaps 0". Pan zero, zoom 1.0,
snaps 0, and the same numbers with Animated Zoom on, while plain Alt + Shift still snaps. The
gesture is dead, and only the ctrl half of it kills it.

So the three places that asserted a continuous zoom are corrected here instead of left to rot: the
module doc (wheel_nav.rs:8-11), the comment above the filter (wheel_nav.rs:407-413) and the parity
row (docs/ui-parity.md:193). They now state that the gesture is dead, point at the test that pins
it, and name the open question. No behaviour change: the repo has no measured Photoshop evidence
for this combination (the issue names only Ctrl + Alt), so the choice - (a) accept Shift in the alt
branch when Ctrl is held, making it zoom like Alt + wheel, or (b) treat it as exactly Alt + Shift
and snap - goes to a maintainer rather than being invented. Option (b) has a measured cost worth
knowing before anyone picks it: plain Ctrl + Shift + wheel pans sideways today ([40,0], zoom 1.0,
snaps 0, and no test covers it), and under (b) that becomes a no-op with Zoom with Scroll Wheel on.

Gates: cargo test -p photocraft-ui-egui --lib -- wheel = 102 passed, 0 failed (with the promised
assertion first: 101 passed, 1 failed); cargo clippy -p photocraft-ui-egui --all-targets
-- -D warnings clean; cargo fmt -p photocraft-ui-egui -- --check clean.

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

* test(ui): pin Ctrl + Alt + Shift + wheel with Zoom with Scroll Wheel on (#10488)

Review 4433950859: both Ctrl + Alt + Shift + wheel assertions ran with Zoom with
Scroll Wheel off, while the test comment, the wheel_nav.rs filter comment and the
ui-parity.md Wheel row stated the no-action result unqualified. Measured, not
copied: a temporary probe test ran one Ctrl + Alt + Shift notch (Line unit,
gesture_with, 60 frames summed) in all four preference combinations and printed
(pan, zoom, snaps) through an assert that was made to fail:

    zww=off anim=off ([0.0 0.0], 1.0, 0) | zww=off anim=on ([0.0 0.0], 1.0, 0)
    zww=on  anim=off ([0.0 40.0], 1.0, 0) | zww=on  anim=on ([0.0 40.0], 1.0, 0)

    test result: FAILED. 102 passed; 1 failed; 0 ignored; 0 measured; 4526 filtered out; finished in 2.63s

The suggestion's premise holds: the dead-gesture claim is true only with Zoom
with Scroll Wheel off. With the preference on, alt_gesture keeps the notch out of
the branch's raw, but `alt != zoom_with_wheel` is false so the zoom arms are
skipped, and egui's own smoothed scroll delta (which the raw filter never gates)
pans the notch through the ordinary scroll path: one whole notch, 40 points
vertical, no zoom, no snap, with Animated Zoom off AND on (it only spreads the
same 40 points over later frames).

Probe deleted; the qualified version landed in all three artefacts:
- wheel_nav/tests.rs: the two dead assertions now say "with Zoom with Scroll
  Wheel off", two new assertions pin the preference-on pan (near [0.0 40.0],
  zoom 1.0, snaps 0, both Animated Zoom modes), and the comment and the
  "not even a scroll" sentence carry the preference condition and the numbers.
- wheel_nav.rs classification comment: the dead-gesture sentence is qualified
  the same way.
- docs/ui-parity.md Wheel row: same qualification plus the measured numbers.

Final gates: cargo test -p photocraft-ui-egui --lib -- wheel ->
test result: ok. 102 passed; 0 failed (also re-run after cargo fmt re-wrapped
one over-long assert, same result); clippy -D warnings clean; fmt --check exit
0; cargo xtask layers: OK, 31 crates, no layering violations.

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

* fix(ui): keep Cmd+Ctrl+Alt + wheel off the Alt zoom path, pin AltGr (#10488)

alt_gesture now also requires !mac_cmd when forgiving command, so macOS
Cmd+Ctrl+Alt (command, ctrl, mac_cmd, alt) is not the zoom gesture. Tests pin
the macOS and Windows/Linux modifier shapes and that AltGr reaches egui as
neither ctrl nor alt.

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

* fix(ui): keep Ctrl+Alt+Shift + wheel as before, correct the AltGr note, pin the Alt key fallback (#10488)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:36:39 +01:00
Psychotoxical f72165ad42 Layers panel: filter by layer effect (#10880)
Effect comes after Name in the filter-type dropdown. A second dropdown picks
Bevel & Emboss, Stroke, Inner Shadow, Inner Glow, Satin, Overlay, Outer Glow or
Drop Shadow, and the panel lists the layers with that effect turned on.
Overlay covers Color, Gradient and Pattern Overlay. ui.set {layerFilter}
takes the effect by id.
2026-11-03 15:17:12 +01:00
David MannandClaude Opus 5.5 a0418a2e29 Address CodeRabbit review: Batch failures, late Esc, CLI format, bridge result
- 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>
2026-11-03 14:50:52 +01:00
David Mann 7329c700bb Merge remote-tracking branch 'origin/main' into integration-batch-13
# Conflicts:
#	apps/photocraft-cli/src/args.rs
#	crates/engine/src/jobs.rs
#	crates/engine/src/jobs/tests.rs
2026-11-03 13:52:30 +01:00
FredApps 66e9325379 Merge remote-tracking branch 'upstream/main' into pr-5332 2026-11-03 11:16:12 +01:00
Hamza fcf57cf2b4 Layers filter: fill layers match no Kind button, and the Mode list has no Pass Through 2026-11-03 14:55:32 +05:00
FredApps 5f70428dfc Merge storytold/main (d0bff086d) into PR #10897 branch
Resolve conflicts with #5331 (EXR streaming): the block-by-block EXR import now
reads from the same identity-verified handle as the rest of the open path, and
docs/scratch-disks.md keeps both the EXR streaming pointer and the RAM/scratch
admission notes.
2026-11-03 10:33:10 +01:00
FredApps 57443b6a30 Merge remote-tracking branch 'upstream/main' into pr-5332
# Conflicts:
#	crates/codecs/src/exr_stream.rs
#	crates/codecs/tests/it/exr_stream.rs
#	crates/io/src/exr_native.rs
#	crates/io/src/lib.rs
#	crates/io/tests/it/streaming_exr.rs
#	docs/roadmap.md
#	docs/scorecard.md
#	docs/scratch-disks.md
#	docs/streaming-io.md
#	scorecard/reliability.toml
2026-11-03 10:26:54 +01:00
FredApps 0ec30fcf7c Merge storytold/main into codex/gpu-viewport-streaming
Main moved the GPU canvas into canvas/gpu_canvas/* and gained its own
budget, LayerFit and background-redraw machinery. Conflicted files take
main's version; the viewport streaming is re-integrated on top of main's
budget in the following commit.
2026-11-03 09:59:21 +01:00
FredApps 3695186329 Consolidate RAM detection in raster::memory::system; scratch-aware PSD/TIFF admission
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.
2026-11-03 09:48:22 +01:00