Commit Graph
2108 Commits
Author SHA1 Message Date
Dylan M. TaylorandGitHub d3b0a76c3f lint: drop blank lines after doc comments and attributes; tidy IME else, closure, shell tests (#9576)
- Remove blank lines between doc comments or #[test] attributes and the item (S8855, S8856)
- type_tool: drop the redundant else after continue in the IME event arm (S8862)
- ui_state: pass OsStr::to_os_string instead of an equivalent closure (S1612)
- macos packaging: [[ ]] for the attempt test (S7688); bind validate_staple's argument to a local (S7679)
2026-10-25 22:08:50 -04:00
aiongg da02d0ef2d Merge remote-tracking branch 'upstream/main' into docking-adopt
# Conflicts:
#	Cargo.toml
#	crates/ui-egui/Cargo.toml
2026-10-26 00:12:23 +00:00
aionggandGitHub cece50d513 Merge pull request #9386 from davidmann4/integration-batch-8
Integration batch 8: 37 reviewed community PRs on main
2026-10-25 20:04:41 -04:00
Dylan M. TaylorandGitHub 3877bb1dbe chore: resolve SonarCloud maintainability issues (#9044)
* chore: resolve SonarCloud maintainability issues

* fix: format imports and fix effect_entry argument type

* fix: restore test closure and wildcard import that broke compilation

* refactor: split composite_to_surface to reduce cognitive complexity
2026-10-25 19:38:31 -04:00
0f8521851e Integration: Vanishing Point clone dabs stay on the canvas (#3281)
Clone strokes painted every dab where its points lay, up to 1e7 px off
the canvas: on the layer itself without "newLayer", and on the work
surface with it, so each far dab allocated up to 4096x4096 px of tiles.
A stroke with points [[100,100],[-30,100],[9e6,9e6]] on a 200x200
canvas painted 3 dabs and left pixels left of the canvas. The dabs are
now cut to the canvas before any read or write.

Found by CodeRabbit on #9386.

Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:56:00 +01:00
Dylan M. TaylorandGitHub 669a7f8a94 feat(updates): add startup self-update prompt modal (#9322)
* feat(updates): startup update prompt modal and direct self-updating

Release checks and the install go through craft-update's Updater: the engine's Help ▸ Check for Updates command keeps its status machine, its transport asks craft-update instead of a private GitHub client, and a found release opens the startup prompt. An install holds back while documents are unsaved, and a finished update closes through the guarded close path. craft-update stays out of the wasm build.

Ref: storytold/craft-libs#319

* fix(updates): address review feedback

Wake the UI when a manual check is queued from a menu callback; the update prompt owns the keyboard while it is open (LightCraft); Download opens through the desktop URL hook (FilmCraft); the Help menu dispatches through menus::invoke (FigCraft); print jobs and comment drafts hold an install back (WordCraft); Check for Updates is a registered command (GridCraft); an install holds back while documents are unsaved.
2026-10-25 15:55:45 -04:00
David MannandClaude Opus 5.5 8812fe1e73 Merge main (egui 0.37, #9363)
Conflicts: the batch's wheel reads (#5547 Animated Zoom) keep their extra
argument with main's ids (`Id::unique`, `ui.scope_id()`); crop presets keep
#6620's `apply_choice` with main's accessible name; Bevel & Emboss keeps
#9370's per-mode id with main's name argument; the Stroke field keeps
#7203's gradients with main's name; tone.rs keeps #9139's move of the
Histogram panel to histogram_panel.rs (main only renamed ids there);
translation catalogs as unions.

Not flagged by git, the batch's new code follows egui 0.37 too: dropdowns
take an accessible name (Histogram Channel/Source #9139, tool Mode #5504,
Art History Style #1610, New Snapshot From #3567), MouseWheel events carry
`source`, `WidgetType` is `Role` (ColorButton is ColorWell, #7203),
`Id::new`/`Ui::id` are `Id::unique`/`Ui::scope_id`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 20:53:11 +01:00
5c9192bbe2 Migrate PhotoCraft to egui 0.37 (#9363)
* egui037: name each control for what it sets, and order the eager fonts for the UI

CodeRabbit review follow-ups on the migration: the Artboard X/Y fields, the CAF
dropdowns, the channel Solidity field, the GPU backend/adapter dropdowns, the
corner radius fields and the canvas extension colour all had an accessible name
that named another control (or was shared by four). The eager craft-fonts
registration now puts the UI Japanese face first, as the lazy loader does, so
Japanese UI is not drawn in the manifest's Mincho face and shared Han characters
do not take Japanese forms.

Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* egui037: rustfmt the eager craft-font order

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-25 15:04:35 -04:00
David Mann 6baf1586ba Merge PR #1610: Art History Brush in the Y-group flyout 2026-10-25 19:50:57 +01:00
7e98763436 Integration: Art History Brush source sampling, History source, erased patches, silent no-op, Normal mode only (#1610)
- Each tile still copied a source window of (tile +- halo) clipped to the
  dab bounds, but a rotated read can land up to a dab diameter away: a
  leftward drag [[110,40],[20,40]] at size 40 read empty pixels at x = 64
  (seam), and a large click still copied about the whole dab per tile.
  The stamp now samples the history state directly (no window, no
  StampParams::halo), and bilinear reads at pixel centres, so an
  unrotated dab copies the history pixel instead of a 2x2 blend.
- Tolerance compared colour only, so an erased patch (the Eraser keeps the
  colour, lowers alpha 1 -> 0.6) was skipped at tolerance 25: the
  difference is now the larger of the mean colour change and the alpha
  change.
- The brush had its own history lookup (row 0 unless `state` was passed):
  it now uses the History Brush's source (history.setSource, snapshots,
  redo rows, refusal after a canvas size change).
- A stroke that wrote nothing at tolerance 0 (wholly outside the
  selection, or on Lock Transparency over transparent pixels) failed
  with "no stroke points"; it is now the same quiet no-op as a stroke
  under the tolerance (damage null, no History step).
- A mode other than Normal (agents only: the options bar sends none)
  blended each dab with the already-mixed result at full strength, so
  Multiply with a white source over 0.5 grey fell to 0.33 five pixels
  from a dab's centre instead of staying 0.5. Other modes are refused
  until the stroke is buffered, and the wrong path is removed.
- TOOL-213-20 is partial (8 of 10 styles, Area scales spacing); docs follow.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 19:48:32 +01:00
aiongg 292f869d8a Merge remote-tracking branch 'upstream/egui037' into docking-adopt
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/ui-egui/src/gallery_ui.rs
#	crates/ui-egui/src/layer_props_ui.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panel_docking.rs
#	crates/ui-egui/src/panel_docking_tests.rs
#	crates/ui-egui/src/view_cmds.rs
#	crates/ui-egui/tests/it/batch_export_options.rs
2026-10-25 17:51:11 +00:00
Matt Van Horn 45c303ba5c Art History Brush: count only the sampling margin against coverage
The stamp now reads each dab's bounds plus one pixel, but the coverage
check still inflated the stroke bounds by the full halo, so a single
Loose Curl click of size 1400 or more on a 2600x2600 canvas failed with
"stroke too large". Inflate by one pixel instead, and add a test for
size 2000 Loose Curl and size 2400 Tight Medium clicks.

Also make the documented tolerance default match the engine's (0).
2026-10-25 10:47:09 -07:00
aiongg e3f242af38 Merge remote-tracking branch 'upstream/main' into docking-adopt 2026-10-25 16:25:21 +00:00
David Mann c01dc04b5f Revert "Merge PR #4573: Interface: Animation Effects follows the system setting, or is on or off"
This reverts commit acd7e07252, reversing
changes made to bc0354db5d.
2026-10-25 16:36:09 +01:00
David MannandClaude Opus 5.5 14693eb085 Integration: merge main 528491f9a, resolve conflicts
- crates/engine/src/edit_cmds.rs: copy_mask keeps #3297's filter-mask target and takes main's
  clipboard `source` (#8945).
- crates/cms/src/lutfile.rs: #9183's shaper doc comment next to main's UTF-16 decode_text (#9129).
- contributors/people.toml, docs: unions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:26:06 +01:00
David Mann ff479a2c01 Merge PR #9139: Rebuild the Histogram panel
# Conflicts:
#	crates/ui-egui/src/control.rs
#	docs/scorecard.md
#	docs/ui-parity.md
2026-10-25 16:11:31 +01:00
David Mann ef98aa507a Merge PR #3281: Vanishing Point: tear off planes and clone in perspective (#3280) 2026-10-25 15:57:43 +01:00
David MannandClaude Opus 5.5 5a9a8ff9e8 Integration: History Brush source tests take snapshot 0 as the opening snapshot (#3567)
#3567 makes the document as opened snapshot 0, so {"snapshot": 0} is a valid source now;
main's bad-params test still listed it as an error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:52:03 +01:00
David Mann 7b79f70787 Merge PR #3297: Smart Filters: paint the filter mask directly, with undo (#3290)
Merged with our fix-up pr-3297-fixup (1de1e9eb58) on top of the author's head 053e1e0d03.
Conflicts resolved: channel_cmds.rs routed() keeps the PR's PAINT_IDS list plus the batch's
paint.artHistoryBrush; paint_cmds.rs Paint Bucket params keep the batch's "mode" and the PR's
"filterMask" target; docs/scorecard.md takes the batch's numbers with FILE-215-3 done.
2026-10-25 15:42:51 +01:00
eb4494b915 Integration: VP preview keyed to its document, OK paints the session's layer (#3281)
Vanishing Point is not modal, so the Layers panel and menu commands stay
usable while it is open. The stamp preview kept painting over the document
as it was when the mode opened, and OK painted whichever layer was active
by then: stamp on layer 1, click layer 2, and the canvas still showed
layer 1 while OK cloned onto layer 2 (a filter run meanwhile was hidden).
filter.vanishingPoint now takes an optional `layer` (the UI passes the
session's when another layer was picked, so recorded actions stay
portable) and makes the painted layer active, so Fade and a colour-channel
target follow it. The preview repaints over the document as it is now when
its pixels change (the canvas syncs it before drawing, so no frame goes
without the strokes); a selection or view change keeps it as it is.

display_doc's VP branch is also keyed to the session's document, as main's
Puppet and Perspective Warp branches are (#5115): a document opened in the
same frame no longer shows the other document's strokes.

Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:29:02 +01:00
1de1e9eb58 Integration: keep the filter mask target to commands that edit it (#3297)
Redone on the PR head 053e1e0d0 (the author's main merge); the earlier
fix-up f7af09d0f carried the first six points.

With a smart object's filter mask targeted, every routed command got
"target":"filterMask" and the filter-mask precondition instead of its own:

- Cmd+T and Edit > Transform > Flip/Rotate failed ("can't transform a
  filter mask on its own"); now the smart object transforms, as in
  Photoshop (lone_target -> Ok(None), and transforms get no target).
- Move (layer.translate) re-rendered the whole filter stack on every
  nudge, and failed for contents that can't render.
- Convert for Smart Filters became enabled and nested the smart object;
  Apply Image, Last Filter, Liquify, the lens filters, Vanishing Point,
  the render filters and Desaturate ignore the target.
  `follows_filter_mask` now leaves these out, in the shell, in
  inject_target, in the precondition and in the re-render hook; Paste
  keeps its own precondition.
- With the filter mask shown (Alt-click), clicking the layer, layer-mask
  or vector-mask thumbnail (or ui.set maskTarget/vectorMaskTarget/
  filterMaskTarget:false) left the view on (and ui.set maskTarget:false
  left the filter mask targeted), so strokes kept landing in
  the filter mask; those now end the view (CodeRabbit, panels.rs).
- ui.set filterMaskTarget:true on a layer without smart filters, or with
  a single channel targeted, cleared the mask target and answered ok; it
  is now refused before anything changes (CodeRabbit, control.rs).
- Curves on a targeted filter mask showed colour channels and the smart
  object's histogram; it is now a gray plane with the mask's histogram
  (GraySurface::FilterMask).

New on this head:

- Filters with a parameter schema (Gaussian Blur, High Pass, ...)
  refused "target":"filterMask" ("`target` must be pixels, mask,
  quickMask or {channel}"), so blurring the filter mask, the usual way to
  feather it, failed from the menu, the dialog and agents. The filter
  validation now accepts it (blurring_the_filter_mask_feathers_it).
- Delete with a selection and the filter mask targeted ran Edit > Clear
  on the smart object ("not a pixel layer"); it now fills the filter mask
  with the background colour there, as Photoshop does. With no selection
  Photoshop deletes the layer, which Delete already did
  (delete_with_the_filter_mask_targeted_edits_the_filter_mask).
- docs/control-protocol.md: the ui.set line lost its closing parenthesis
  in the main merge.

Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:21:05 +01:00
David Mann bb39b4a8dd Revert "Merge PR #1199: Add optional local models for subject matting and object selection"
This reverts commit 5f2a7b9903, reversing
changes made to 9ef4c0a487.
2026-10-25 15:19:27 +01:00
Justin JohnandGitHub ab2508d6af fix(atn): preserve unsupported event kinds (#8509)
* fix(atn): preserve unsupported event kinds

* fix(atn): reject invalid event kind metadata
2026-10-25 10:19:19 -04:00
121a2407ff Engine: assets_dir's doc comment back on assets_dir (#9390) (#9391)
has_pixels was inserted between assets_dir's doc line and the function,
so rustdoc attached both lines to has_pixels.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:19:13 -04:00
2c4982f2d2 Export Data Sets as Files keeps outputs in the chosen folder (#9420) (#9422)
* Export Data Sets as Files keeps outputs in the chosen folder (#9420)

{document} was the document name verbatim, so a document named
../esc/aped.psd wrote outside the chosen folder; separators and reserved
characters in it now become _. A data set named after a Windows device
(nul, CON) gets the same _ every other exporter gives it (nul_.png).

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

* Export Data Sets as Files: a document stem of . or .. becomes _ (#9420)

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

* Address CodeRabbit review: data-set names check every folder, an empty stem is _

A device-named document under `{document}/{name}` made a `nul` folder,
since the device check saw the whole path; it now checks each component.
A document named `.` has an empty stem and gave `.png`; it becomes `_`
like `.` and `..`. The test uses a temp dir that cleans itself up.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:19:01 -04:00
00d0deaeac Reveal All: ignore erased, fully transparent pixels (#9379) (#9380)
* Reveal All: ignore erased, fully transparent pixels (#9379)

The Eraser only clears alpha, so an erased pixel keeps its colour bytes
and Surface::content_bounds counted it: Reveal All (and the Crop tool's
beyond-canvas preview) grew the canvas to pixels nothing shows. Count
only pixels with alpha > 0 outside the canvas, as Photoshop's layer
bounds do.

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

* Reveal All: scan only off-canvas pixels, in parallel (#9379)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:18:48 -04:00
28e5efa888 Paste: centre pixels copied from another document (#8945) (#9353)
Paste kept clipboard pixels where they were whenever that spot lay on
the canvas, whichever document they came from: a whole small document
copied into a larger one landed in its top-left corner. The clipboard
now remembers its source document; Paste keeps pixels in place only
there and centres them in any other, as it already did for pixels that
don't fit. Images from other apps have no source and centre too. Paste
in Place is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:18:30 -04:00
284f569264 fix(engine): defined and imported patterns survive a restart (#9317) (#9318)
* fix(engine): defined and imported patterns survive a restart (#9317)

The pattern library lived only in memory: patterns defined with Edit >
Define Pattern or imported from a .pat file were gone after a restart,
while their Patterns panel folders (saved with the preferences) still
named them. Photoshop keeps them.

The preset store now writes the user patterns (the library minus the
built-ins, in order) to patterns.pat, Photoshop's own pattern file
format, whenever their ids, names, sizes or order change, and loads
them when the store attaches. Like actions.json, a file that fails to
load is warned about and never overwritten. The web store accepts the
new record.

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

* Patterns: persist same-id pixel replacements, retry a failed removal

CodeRabbit (discussion_r4369506324, r4369506326):
- an imported .pat whose id is already in the library replaces its
  pixels, which the id/name/size key didn't show, so the old pixels came
  back after a restart; PatternLibrary::pixels_rev is bumped on such a
  replacement and is part of the key;
- a failed patterns.pat removal recorded the empty library as saved and
  was never retried, so the deleted pattern came back; it now stays
  pending, as a failed write does.

Tests: a_same_id_pattern_replacement_survives_a_restart,
a_failed_patterns_removal_is_retried.

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

* Patterns: persist replaced built-ins and ASL replacements

CodeRabbit (discussion_r4369536978, r4369536981):
- a .pat import that replaced a built-in pattern by id was filtered out
  as a built-in and never saved; only built-ins exactly as shipped
  (is_pristine_builtin) are skipped now, and on load a saved built-in
  takes the shipped one's place unless it changed before attachment;
- style.presets.importAsl replaced patterns by id without bumping
  pixels_rev; both imports now go through PatternLibrary::upsert.

Tests: an_imported_builtin_replacement_survives_a_restart,
an_asl_pattern_replacement_bumps_pixels_rev.

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

* .pat: refuse names and counts the reader won't load, on write too

CodeRabbit (review 5641404908, outside-diff, two findings): now that the
preset store writes the user patterns, a file its own loader rejects or
truncates loses patterns at the next start.
- a name of 65,536+ UTF-16 units (with its NUL) was written although the
  reader refuses it; write_pattern refuses it now;
- more than 100,000 patterns were written, and the reader kept the first
  100,000 without a word; both now refuse counts over MAX_PAT_PATTERNS.

Tests: a_name_the_reader_refuses_is_refused_on_write,
more_patterns_than_a_pat_file_holds_are_refused.

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

* Patterns: an early same-id import with other pixels is saved

CodeRabbit (review 5641454449, outside-diff, preset_store.rs:1191-1202):
a pattern imported before the store finished loading, with the id, name
and size of a saved one but other pixels, was appended (no pixels_rev
bump), won the merge, and then matched the loaded file's key, so it was
never written and a restart brought the old pixels back. The merge now
bumps pixels_rev when the copy it keeps differs from the saved one.

Test: an_early_same_id_import_wins_and_is_saved.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:18:23 -04:00
6b122c7477 Crop and Straighten Photos processes only the selection (#9297) (#9298)
* Crop and Straighten Photos processes only the selection (#9297)

Like Photoshop, File > Automate > Crop and Straighten Photos only searches
the selected area when there is a selection; before, it always split the
whole scan. The found photos are warped from the full canvas, so a photo
reaching past a tight marquee keeps its real pixels.

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

* Address CodeRabbit review: mask unselected pixels before photo detection (#9297)

Pixels inside the selection's bounding box but outside the selection (holes,
gaps between regions) now read as background, so a photo there is not split
off.

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

* Address CodeRabbit review: refuse a selection with no pixel at least half selected (#9297)

A faint feathered selection left every pixel unmasked, so photos outside
it could still be split off. The command now reports that no pixels are
more than 50% selected.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:18:11 -04:00
e456c9b677 feat: New Document can start as an artboard (#9086) (#9258)
file.new takes "artboard": true: the document is one artboard over the
canvas, its background the chosen Background Contents, holding an empty
Layer 1 and no Background layer. The New Document dialog has an
Artboards box under Orientation that sets it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:18:00 -04:00
100ff983f5 fix(engine): turn a shape's gradient stroke with the canvas (#9239) (#9240)
Image Rotation, rotated Crop, canvas flips and Image Size mapped a shape
layer's gradient fill through map_fill but never its stroke paint, so a
gradient stroke kept its old angle and offset while the fill turned.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:17:44 -04:00
41c8a9e215 Image Rotation › Arbitrary turns unlinked layer masks (#9201) (#9203)
* Image Rotation › Arbitrary turns unlinked layer masks (#9201)

Free Transform's helper moves a pixel mask only when it is linked, so
rotating the whole image (and a turned crop) left unlinked masks of
non-Background layers in place. Warp them too, recursively into groups,
as every other canvas operation does.

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

* Tests: the unlinked mask also turns the right way at 90° (#9201)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:17:29 -04:00
softwaregames1GitHubClaudecoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1e5b5f7617 Package: never overwrite one link's copy with another, list a missing link once (#9185)
* Package: never overwrite one link's copy with another, list a missing link once

Copies whose names collided were renamed to "<count>-<name>" once, without
checking that name too: links a/x.png, b/2-x.png and c/x.png packaged the
third over the second's copy, so two layers showed the same image. The name
now steps until no earlier copy has it, ignoring case as macOS and Windows
folders do. A missing link placed on several layers was listed once per layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPBRXWMhtDUsQDdZKmt9eZ

* Contributors: credit softwaregames1 by their GitHub profile name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPBRXWMhtDUsQDdZKmt9eZ

* Package: never overwrite a file already in Links

A second package into the same folder picked the same Links/<name> and
replaced the earlier copy. A name taken by a file already there is now
stepped like one taken by another link in this run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPBRXWMhtDUsQDdZKmt9eZ

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-10-25 10:17:17 -04:00
David Mann 8c89d4ce41 Integration: merge main (#3281) 2026-10-25 14:52:56 +01:00
248972cfa3 Integration: clip VP clone copy-back to the canvas, paste in the stamp preview (#3281)
A newLayer clone copied back the union of every dab's box: one point far
off the canvas (e.g. [9e6, 9e6], accepted by the params) made it read a
region of petabytes and abort. The command and the stamp preview now copy
back and report damage only inside the canvas.

With an image on the clipboard (Paste is on by default), OK pastes first
and the strokes paint over and sample the paste, but the preview painted
the strokes on the unpasted layer and drew the paste on top. The preview
now pastes first too (shared paste_onto), repaints when the paste moves,
and the overlay is only drawn when no current preview shows the paste.
While the paste is dragged the baked preview is hidden so only one copy
follows the pointer; a plane drag freezes preview and paste until release.

Deleting the last plane now drops the stroke preview.

Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:52:51 +01:00
David Mann fe07776735 Merge PR #8179: Crop: Content-Aware Fill keeps the pixels a non-deleting crop hid (#8060) 2026-10-25 14:47:33 +01:00
4a17c64139 Integration: a straight Content-Aware crop fills over hidden pixels (#8179)
Photoshop keeps the pixels an earlier Delete Cropped Pixels off crop hid
only under a turned frame. A straight crop pulled back out over them fills
the whole area past the old canvas from the canvas, with Delete Cropped
Pixels off or on (checked: 64x48 green with a red left strip, crop to x=16
keeping pixels, then a Content-Aware crop to x=-24 w=72: all green). The
PR took the snapshot for straight crops too, so the hidden red came back.
The straight path is main's again; the turned path keeps the snapshot.

extending_crop_keeps_the_hidden_pixels becomes
extending_crop_fills_over_the_hidden_pixels (transparent and white
Background, Delete off and on); it fails with the straight snapshot back.

Co-authored-by: Michael Higgins <55243596+Higgs32584@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:35:33 +01:00
David Mann 14946918cd Integration: merge main (#8179) 2026-10-25 14:33:03 +01:00
David Mann d2f9aeb88d Merge PR #7203: feature: gradient paint to the shape tools, any shape can have a gradient stroke. 2026-10-25 14:29:48 +01:00
David Mann d576113233 Merge PR #3627: Mixer Brush: render mixed paint during the stroke and commit the live result
# Conflicts:
#	crates/engine/src/float_cmds.rs
2026-10-25 14:29:47 +01:00
David Mann b28073389c Merge PR #3316: Add intermediate UI scale presets between 100% and 125%
# Conflicts:
#	contributors/people.toml
#	docs/gaps.md
#	docs/scorecard.md
#	docs/ui-parity.md
2026-10-25 14:27:10 +01:00
David MannandClaude Opus 5.5 5f2a7b9903 Merge PR #1199: Add optional local models for subject matting and object selection
Merged with our fix-up d5139d177 (pr-1199-fixup), which merged main as of 8f09d122d.
Conflicts with main since then and with batch 8:
- photocraft-cli: main split the CLI into clap modules (#7071); the model wiring
  (local_headless, configure_cli_models, batch's shared backend, droplet) is re-applied there.
- Cargo.lock regenerated: crates/ml pins ureq =3.4.2, so the app's ureq goes 3.5.0 -> 3.4.2.
- cutout_cmds: main's subject area (#8235) kept for the classical path beside the model path.
- file_cmds batch: main's per-input closure takes `s` later, so the backend and
  integrations prefs are read before the closure instead of inside it.
- prefs_ui: model-choice filter with #4573's pref_choice_label; Integrations stays visible
  (#4097) with the model choices only where models run; the translation-coverage test main
  deleted (c3aa3f9e5) stays deleted; old section_fields test calls take PreferenceFieldOptions.
- Semantic clashes git did not flag: #4097's Integrations test literals in
  apps/photocraft take ..Default::default() for the new model fields; main's and #4573's
  section_fields test calls pass PreferenceFieldOptions.
- engine lib.rs, prefs.rs, smartselect tests, snapshot example, jobs_ui_tests, people.toml,
  docs and i18n catalogs merged as unions.


Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:23:25 +01:00
05b9477635 Integration: Mixer Brush tips above 1024 px, Alt with a held tool, long strokes (#3627)
A UI Mixer Brush drag with a tip of 1025-5000 px was dropped on press: the live
preview refuses tips above 1024 px, and the canvas cleared the drag on any begin
error, though the status said the stroke would render on release (main painted
it). The drag is now kept when only the preview size refuses it
(`LiveMixer::defers_to_release`), and the release renders it; a stroke that
can't paint at all (a locked layer) is still refused on press.

Alt sampling tested `app.ui.tool`, so a held temporary tool (macOS Cmd = Move)
with Alt loaded paint; it now uses `active_tool()`, like `alt_eyedropper`.

The 65,536-sample and 250,000-dab caps also applied to the release replay and
the `paint.mixerBrush` command, so a long stroke (or Stroke Path with Simulate
Pressure, up to 200,000 samples) failed entirely; main had no cap. They now
bound the live preview only, which falls back to the replay.

Also: `cargo fmt` on the merged `LiveCommit` literal, and `as_chunks` in a test
for clippy's `chunks_exact_to_as_chunks`.

Co-authored-by: durtyrobbin <76844303+durtyrobbin@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:49:48 +01:00
e13a984042 Integration: refuse non-array stop lists, keep CMYK/Lab stops, stroke width takes the gradient (#7203)
- parse_fill took a present non-array "stops" / "opacityStops" / "midpoints"
  as absent: {"gradient":{"stops":"red"}} silently became black to white. It is
  an error now (CodeRabbit's finding); the hostile-values test also checks that a
  rejected shape.create leaves the document and history alone.
- The swatches sent every gradient stop as sRGB RGBA, so in a CMYK or Lab
  document ticking Reverse or changing the angle turned every stop into an RGB
  colour (written back to PSD as RGBC). Stops now go out as stored Colors, which
  shape.create / shape.edit accept, finite with alpha in 0..1.
- Options bar, strokeless shape selected, stroke gradient chosen: the width
  field and the stroke type added a solid background-colour stroke. They take
  the stroke gradient now, as the Fill checkbox and the next shape do.

Co-authored-by: Denis <179751626+deniskhud@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:37:56 +01:00
David MannandClaude Opus 5.5 33e09e56fe Merge PR #3567: History panel: New Snapshot dialog and the first snapshot (#2966)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 13:37:08 +01:00
David MannandClaude Opus 5.5 8c15e55cbf Integration: merge main, resolve conflicts (#3567)
panels.rs: the PR's snapshot rows next to main's History Brush source
and scroll-to-current; ui-parity.md: main's status line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 13:36:40 +01:00
096ad5b194 Integration: merge main, resolve conflicts (#7203)
Main moved the shape options bar and paint swatch out of vector_ui.rs into
shape_options.rs and shape_paint_ui.rs (#5452). Carry the PR's gradient paint
over: PaintChange::Gradient, paint_param and the swatch's Gradient tab live in
shape_paint_ui.rs, the tool gradients and set_tool_paint in shape_options.rs,
and shape_gradient_ui.rs is its own module. Both sides' new tests are kept.

Co-authored-by: Denis <179751626+deniskhud@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:26:26 +01:00
David Mann 7eec69f8f5 Merge PR #5128: Camera Raw: Calibration panel, Auto, raw sidecars and live Camera Raw Defaults (FILE-215-11)
# Conflicts:
#	crates/engine/src/prefs.rs
#	docs/gaps.md
#	docs/scorecard.md
2026-10-25 13:07:14 +01:00
David MannandClaude Opus 5.5 2415c6979f Integration: merge main, resolve conflicts (#3627)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:00:08 +01:00
David Mann 6d0ab91536 Merge PR #4097: Integrations: wire Allow Agent Control and Control Port (#204)
# Conflicts:
#	docs/scorecard.md
2026-10-25 12:10:21 +01:00