Commit Graph
2185 Commits
Author SHA1 Message Date
FredApps 8d2ee0787f Merge scratch query updates into memory admission policy 2026-11-06 17:29:23 +01:00
FredApps 07580bfb34 Merge commit '898979da306fe9af29e2ec60341951f098225cb5' into review/g-11652-update 2026-11-06 17:28:13 +01:00
FredApps 898979da30 Merge main into scratch folder capacity queries 2026-11-06 17:27:38 +01:00
FredApps 8ab527b1e6 Merge pull request #11474 from wanikhawar/claude/selective-colours-isolation-329d87
Keep each colour's values in the Selective Color dialog
2026-11-06 17:26:42 +01:00
FredApps c8e4376ec6 Merge pull request #11513 from wanikhawar/claude/investigate-issue-e27e90
Windows: count scratch room when opening large files; refusal names the build's limit
2026-11-06 17:26:22 +01:00
David Mann 3768e84b67 Merge pull request #9480 from davidmann4/fix/remove-enable-animations
Interface: remove the Enable animations preference (#9479)
2026-11-06 17:25:38 +01:00
David Mann 16348db38b Merge pull request #9138 from davidmann4/fix/9137-control-click-coord-range
ui.click / ui.move reject coordinates beyond the screen range (#9137)
2026-11-06 17:24:27 +01:00
Khawar Zamman Wani 146d49f5a2 PSD write recipes: keep every exported candidate and pin the base file (#11607)
* PSD write recipes: keep every exported candidate and pin the base file

A case that failed after export dropped out of manifest.json, losing the
file a reference-editor check most needs. The CLI also reopened the corpus
path after the hash was taken.

- Keep the candidate and its hash once export succeeds.
- Run the CLI on a copy of the hashed bytes, so it never opens the corpus.
- Compare numbers by value, so 17 equals 17.0.
- tracking-split also checks runs 0 and 2 and all three run lengths.

Part of #6130.

* PSD write recipes: compare large integers exactly, reject case-only id clashes

Converting to f64 made 9007199254740993 equal 9007199254740992. Ids that
differ only by letter case name one output directory on a case-insensitive
filesystem, so they are now rejected as duplicates.

* PSD write recipes: test that the CLI receives the hashed bytes

* PSD write recipes: reject the contour library commands

Their path param can read or write any file, including the pinned base and
the corpus, and they edit no document.
2026-11-06 17:23:40 +01:00
Khawar Zamman Wani cd2250cb25 Scratch folder queries: address second review
A stuck close no longer holds up the files queued behind it (up to four
closer threads), and a query identifies the current file by the configured
folder. Tests don't read the room and the status across a late answer, and
the growth-check step proves its query is stalled.
2026-11-06 20:48:08 +05:30
Khawar Zamman Wani e01c8719a0 Scratch folder queries: address review
The whole folder change now waits 2 s at most, old files close on one
background thread, the cap counts query threads until they finish, and
writes during a query aren't counted twice. Capacity is refused if a sweep
paused spilling meanwhile, a query opens a file unless this folder has one,
and on macOS an unrelated dead mount only costs the purgeable figure.
2026-11-06 19:44:03 +05:30
Khawar Zamman Wani 6c720c3765 Merge remote-tracking branch 'origin/main' into claude/stoic-yalow-d695ab 2026-11-06 18:38:44 +05:30
Khawar Zamman Wani 2c68c07dbf A scratch folder that stops answering no longer freezes the app
Opening and measuring the folder ran under the config lock, which the UI
takes every frame, and could run on the UI thread itself. They now run on
a short-lived thread, one per folder and question and 16 overall; callers
wait at most 2 s, and a late answer applies if the folder is still current.
No answer counts as unmeasured, or pauses spilling if nothing is open yet.
2026-11-06 17:45:34 +05:30
Khawar Zamman Wani 1ce1bced95 PSD admission: a little free memory no longer refuses every file
With 1 GiB or less of free RAM, or 512 MiB or less of commit headroom, the
hard limit was exactly 0, so even a small file was refused with a message that
didn't say memory was low.

The reserve now shrinks with the measurement: up to a quarter of free RAM opens
automatically, the rest goes to approval, and an approved import always leaves
at least 256 MiB free. Limits are unchanged for automatic opening above 4/3 GiB
free and for approval from 4 GiB free. A refusal set by measured memory now
gives the free amount and says to close other apps and try again, and the
approval prompt warns when little memory would be left.

An unreadable cgroup still measures Some(0) and stays refused.
2026-11-06 17:03:50 +05:30
Khawar Zamman WaniandClaude ed49cd9401 Scratch free space is asked of the folder itself, so network folders report their own numbers
Windows network folders reported unknown because sysinfo doesn't list them, and an unlisted Linux
NFS or CIFS mount used the numbers of its listed parent. GetDiskFreeSpaceEx (Windows) and statvfs
(Unix) on the directory fix both. macOS keeps sysinfo's figure, which counts purgeable space, when
the listed disk is this volume. A mount that reports no space is flagged in Preferences, in the
hard-limit refusal and in spill::stats, with a hint to set a Scratch Size Limit or use a local disk.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-06 14:58:27 +05:30
Khawar Zamman Wani 02c5ea8e68 Merge commit '349a03b6b18b0bcee64090f6faa9fc321960fc34' into claude/brave-wu-261567 2026-11-06 14:54:08 +05:30
David Mann ec5eb1e2d0 Merge commit 'refs/fixprs/main' into HEAD 2026-11-06 10:21:59 +01:00
David Mann 519d29c536 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/scorecard.md
2026-11-06 10:10:32 +01:00
Khawar Zamman Wani 04be226762 Merge remote-tracking branch 'origin/main' into claude/selective-colours-isolation-329d87
# Conflicts:
#	docs/ui-parity.md
2026-11-06 14:39:03 +05:30
Khawar Zamman Wani 349a03b6b1 Refusal text states the lower limit, links the download and shows readable sizes
The 32-bit message ended mid-phrase. It now says the cap applies or less if
less memory is free, and separates the cause from the next step. Windows
scratch tests check the returned numbers belong to the temp directory's volume.
2026-11-06 14:01:03 +05:30
Khawar Zamman Wani 1eb3218a4b Merge remote-tracking branch 'origin/main' into claude/investigate-issue-e27e90 2026-11-06 12:37:29 +05:30
Khawar Zamman Wani 3bc3bec1d5 Merge remote-tracking branch 'origin/main' into claude/selective-colours-isolation-329d87
# Conflicts:
#	docs/ui-parity.md
2026-11-06 08:27:58 +05:30
David Mann 4c6d5e259a Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/ui-parity.md
2026-11-06 02:32:59 +01:00
Zinadin ZidanandClaude Opus 5.5 3d60ad01f9 Channels: the panel menu has the channel commands, New Spot Channel among them (#11451) (#11557)
* Channels: the panel menu has the channel commands, New Spot Channel among them (#11451)

The Channels panel's menu had no channel items, so New Spot Channel... and
the rest could only be reached from a row's right-click menu. It now lists
New Channel..., Duplicate Channel, Delete Channel, New Spot Channel...,
Merge Spot Channel, Channel Options..., Split Channels and Merge
Channels..., each acting on the targeted channel and greyed where it can't:
Duplicate takes a single colour, alpha or spot channel, the composite of a
one-channel image, the Quick Mask or a targeted layer mask. Channel
Options... in Quick Mask mode now opens the targeted alpha or spot
channel's options, not Quick Mask Options. The panel menus run their
commands through the menu bar's enabled rules, so the Layers panel menu
greys its dialog items the same way too. The labels are in every catalog.

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

* Channels: New Channel... and Merge Channels... ask first, from the panel and row menus (#11451)

The panel menu's Merge Channels... ran channel.merge with no parameters, and
the engine merged the first grayscale documents into RGB and closed them with
no choice; New Channel... added a channel straight away. Both now open a
dialog when called without parameters, as New Spot Channel... and Channel
Options... already did: New Channel... reuses the Channel Options dialog
(name, colour indicates, colour and opacity), and a new Merge Channels
dialog picks the mode and the open grayscale document for each channel. The
channel rows' menu had the same two items and goes through the same path.
With parameters (scripts, the New button) both still run at once.

The Merge Spot Channel test now checks pixels, so a delete couldn't pass it.

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

* Channels: Merge Channels waits for three grayscale documents and the dialog offers only modes they fill (#11451)

With two grayscale documents open, Merge Channels... was enabled and its
dialog repeated a document for the third channel; OK then failed in the
engine and the dialog closed. The engine's rule now needs three documents
of one size (no mode merges fewer), so the menus grey the item below that.
The dialog offers only the modes the open documents can fill (RGB and Lab
from three, CMYK from four), greys OK with the reason while the picks
repeat, keeps itself open when OK is refused, and comes back with the
engine's message if the merge itself fails.

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

* Channels: the Merge Channels dialog keeps its picks by document id (#11451)

The dialog kept the picked source documents by tab index, and background
work can still run while it is open, so a document moved or closed in the
meantime could leave another one at a picked index; OK would then merge,
and close, the wrong document. The picks are now document ids, resolved to
the current tabs when OK is pressed: a closed pick, or one that is no
longer a grayscale document of the same size, is refused with the reason
and the dialog stays open.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:20:08 +01:00
David Mann cc361c306d Merge commit '9bef3b2dc8b84c1b6cefc8ce0dc2b01d089cd9e8' into HEAD
# Conflicts:
#	crates/engine/Cargo.toml
#	crates/engine/src/prefs.rs
#	docs/ui-parity.md
2026-11-06 02:16:44 +01:00
David Mann dabf3bab97 Merge commit '9bef3b2dc8b84c1b6cefc8ce0dc2b01d089cd9e8' into HEAD
# Conflicts:
#	docs/scorecard.md
2026-11-06 01:54:36 +01:00
David MannandClaude Opus 5.5 bb28a5210c Batch 18: regenerate parity-checklist and scorecard (660 menu items, #11497)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 00:53:47 +01:00
David Mann 1d053f5c2c Merge PR #11495: Actions: play recorded Stack Modes and Video Frame events 2026-11-06 00:28:47 +01:00
David Mann f96e77e794 Merge PR #11553: Actions: replay recorded Perspective Warp steps 2026-11-06 00:28:38 +01:00
David Mann a77d7d6b53 Merge PR #11503: Layer > Smart Objects says Reveal in Explorer, Embed Linked and Embed All Linked, in the reference order, without the Warp rows (#11401) 2026-11-06 00:28:25 +01:00
e4e2607c85 Preview spot inks over the RGB/CMYK composite as an overprint (#10276)
* PSD: a flat file's named extra channels are not its transparency

A PSD without layer records had its first extra channel read as the merged
transparency, so the first spot or alpha channel of a flattened document
vanished and its ink punched a transparent hole in the image (psd-tools
cmyk-alpha-spot.psd, cmyk-spot.psd). When the alpha channel names (1045, or
1006 when 1045 is missing) name every extra channel and the first is not
"Transparency", there is none. The import also reads channel names from
1006 when 1045 is missing, and a spot whose DisplayInfo colour space is
unknown takes its ink from the Alternate Spot Colors (1067).

* channel_map: read 1067 records with as_chunks

* Preview spot inks over the RGB/CMYK composite as an overprint

Visible spot channels of RGB and CMYK documents print over the canvas
composite with the Multichannel ink formula, in Channels panel order,
instead of a tint; alpha channels and Quick Mask still tint over them.
Spot channels open visible, and the PSD thumbnail (1036) prints them.
flatten, export and the merged image stay free of spot inks. Ink
colours go through the document's CMYK profile, and into an RGB
document's own profile.

Refs #10249

* channel_view: test solidity over a gray picture

* Thumbnail: matte only inked pixels; test the merged planes directly

Review feedback on #10276: pixels without spot ink keep their
transparency in the thumbnail buffer, and the thumbnail test checks the
saved merged process planes instead of a re-imported flatten.

* Thumbnail spot inks: sample big boxes on a 4x4 grid

Review feedback on #10276: reducing each spot channel no longer scans the
whole document; a thumbnail pixel averages at most 4x4 samples, so a huge
document costs about h x 4 row reads per spot.

* Spot overlay: composite a proxy for the first draw of big documents

The overprint needs the composite under the inks. Rendering the full-resolution
composite to sample every factor-th pixel cost ~1 s for a 24 MP CMYK document.
A proxy document picks the same pixels (nearest-neighbour downsample) for about
1/factor² of the work. Documents with layer effects, vector masks or feathered
masks keep the full-resolution path.

* Proxy: downsample rows in parallel; the overlay proxy leaves out unused channels

The spot overlay's proxy shrank the spot channels, selection and Quick Mask that an
RGB or CMYK composite never reads: about two thirds of its cost. downsample now
reads its rows in parallel, 64 at a time.

* Spot overlay proxy: keep the full-resolution composite under uncached pattern fills

A pattern fill renders its tiles at their own size on a proxy, so the proxy would
sample other pattern pixels. Unless the fill's matching cache is shrunk with the
proxy, use the full-resolution composite.

* Spot overlay proxy: hidden layers don't decide the composite path

Hidden layers and the contents of hidden groups are not composited, so a hidden
feathered mask or pattern fill no longer forces the full-resolution composite
(the same rule as proxy_faithful).

* Update crates/compose/src/multichannel.rs

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>

* Spot overlay: only inked pixels replace the canvas; inks through the display transform

Where every visible spot is 0 the overlay stays transparent, so the normal
canvas (display LUT, proofing, 32-bit preview, full resolution) shows; an
empty spot renders no composite. Printed pixels go through the canvas
display transform (sRGB encoding of linear composites, monitor profile,
Proof Colors), keyed into the texture. Ink colours come from one InkSpace
per render: the document's profiles are parsed once, not per spot per
damage rect.

* test(compose): cover hidden spot preview and document approximation

* fix(ui-egui): preserve solo spot channel display when process colours are hidden

Clicking a spot channel row in the Channels panel targets the channel alone
and hides all process colour channels (`vis == 0`). Overprint inks were
previously extracted from `overlays` before checking `vis == 0`, leaving
`overlays` empty and causing the solo channel view to fall back to an active
layer mask (or return `None` if the layer had no mask).

Keep the spot channel in `overlays` so `vis == 0` can select it as the solo
grayscale picture, separating overprint inks only when process colours are
visible (`vis > 0`).

Adds a regression test targeting an existing spot channel in RGB and CMYK on
an unmasked layer and editing it.

* channel_view: document the spot overprint preview as an approximation

* test(ui-egui): solo spot channel wins over an unrelated layer mask

* channel_view: sRGB spot inks, lazy display transform, inked-area composite

Review fixes for the spot overprint preview:

- RGB spot colours are sRGB, like the CMYK, Lab and gray ones: InkSpace now
  converts them into an RGB document's own profile too. In a linear (EXR/HDR),
  Adobe RGB or ProPhoto document an RGB ink showed the wrong colour (brightened
  in a linear one once the canvas sRGB-encoded it).
- The display transform is looked up only when the view is redrawn and inks
  are shown, not every frame before the cache check; display_transform_for
  borrows the proof state (proof_ref) instead of cloning a default CMYK profile.
- Visible spot channels without ink, with nothing else shown, draw no texture:
  render returns None and ensure remembers it, instead of uploading an
  all-transparent document-sized texture. A damage re-render that finds nothing
  clears its rect, so erased ink never lingers.
- Printing a few inked pixels composites only their bounding box when that is
  cheaper than the whole (proxy) composite, and a cached composite is borrowed
  instead of copied.

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

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:34:12 +01:00
e55e2f3577 Place decodes and fits in the background: dropping a large file no longer freezes the window (#10147)
* Place decodes, fits and renders in a background job

File > Place Embedded / Linked started with Session::start, and
Session::start_place_bytes for bytes with no path, now run the decode,
flatten, resize and SVG render on a worker (prepare_place) and insert the
layer when the job is applied (apply_place). Inline callers (execute, the
CLI, headless MCP, actions) behave and return exactly as before. Part of
#10031.

* Dropped and placed files no longer freeze the window on desktop

With background jobs on, File > Place and files dropped on the canvas start
the place as a job (JobsUi.place). Free Transform begins when the layer has
landed, a failed or cancelled place is reported and the drop queue goes on,
and a drop that arrives meanwhile waits for it. The place shows its progress
in the status bar instead of the modal dialog, so the view stays usable.
Part of #10031.

* Tests: compare place job results without the layer id

Layer ids come from a counter shared by every session, so a job and an inline place in two sessions never get the same one.

* Dropped files stay with the document they were dropped on

Each queued canvas drop now carries its document's id. A file is placed
only while that document is active: files dropped on a document in
another tab wait until it is back in view, and are discarded with a
message when it is closed. Web drops, read by the shell after the drop,
go to the document the drop landed on.

Before, the next queued file went into whichever document was active
when the previous place finished, failed or was cancelled, in the
background and inline paths alike.

* Remove the unused file_open import

* fix: address background placement review feedback

* fix: preserve drop targets and interrupt placement pixel stages

* fix: qualify placement inbox document identity

* test: wait for background placement in app drop regression

* fix: retain queued drops while external document jobs run

* test: clone rendered pixels from drop-managed buffers

* test: cover band rows channels and queued-drop resumption

* Place decodes and fits in the background: dropping a large file no longer freezes the window

Placing a file decoded, flattened and resized on the UI thread; a large PNG froze the
window for seconds (#10031). `place_bytes` is split into a session read, `prepare_place`
(decode, flatten, fit, translate, SVG render at placement scale) and `apply_place`, and
runs through `jobs::run`, so File › Place and canvas drops run on a worker with progress
and cancel. The window follows the job: status-bar progress, Free Transform when the layer
lands, and drops queued per target document.

Review fixes on the way:
- a path and byte drop queue entry records its target DocId, so files dropped on one
  document never land in another after a tab switch, and closed documents discard theirs;
- the closed-document notice stays up for a frame instead of being cleared by the next
  queued placement;
- the queue is bounded (32 files / 256 MiB) so drops waiting on another tab cannot grow it
  without limit;
- flatten and resize stop at the job's interrupt, and a place skips the modal progress
  dialog only when the status bar actually shows its progress;
- the placement's `doc` is kept only for SVG input, through the importer's `is_svg_input`;
- the interrupted-composite test uses an opaque fixture that varies by row and channel, and
  checks the composite against the layer's stored pixels.

* Place reads its file in the job, and refuses a huge canvas as before

file.placeEmbedded and file.placeLinked read the whole file in the
command before the job started, so a path dropped on the canvas or
picked in File > Place still blocked the window for the read. The place
now carries the path and reads it in prepare_place: on the job's worker
in the background, on the calling thread inline (wasm, background jobs
off), with the same read_file limit and error text. Whether the source
is a vector one is decided once it has been read.

The job's flatten check returned Other("the document canvas is too large
to flatten"), where the place used to give flattened_checked's
BadParams for file.place naming the canvas size. Both now share
check_flattenable, so the old error type and message are back.

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

* Path drops aren't capped, and a discarded drop wakes the queue

The 32-file cap on the drop queue counted path drops, which hold no
file contents while they wait, so dropping 40 files on the canvas
placed 32 and refused 8 where main placed them all. The count cap now
applies to browser drops only (each holds its whole file); the byte
budget still bounds those.

When place_next_dropped discarded a closed document's files it returned
without asking for another frame, so another document's queued files
waited for the next input event. It now requests a repaint when files
remain, keeping the one frame that shows the notice.

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:49:52 +01:00
5c157e8400 Build the GPU compositor off the UI thread (#9969)
* Prepare GPU compositor in background

* Add compositor startup performance scenarios

* Generate scorecard in scratch CI

* Fix pipeline build type inference

* Address compositor startup review and document measurements

* Run startup performance checks on scratch CI

* Remove scratch performance job

* Count failed GPU attempts in frame timing

* Make shader fingerprint test portable

* Address review: run first-document scenario as a guarded group; share COMPOSITOR_BUILDING constant

* Handle compositor startup benchmark failures

* Fix frame timing after upstream merge

* Keep GPU startup failures on CPU fallback

* Record the failed GPU attempt of a deferred redraw, and bound --reps

A redraw deferred after a failed GPU composite carries that attempt's time to its end, where it is
recorded once beside the CPU time. Perf::record clears the attempt so a direct CPU refresh doesn't
inherit the last one's. perf_scenarios caps --reps.

* Refresh cached canvases after compositor build

* Expose canvas cache key to GPU completion handler

* ci: drop the GPU compositor probe job

AGENTS.md: feature and fix PRs don't add or change CI, and app workflows
only call the shared craft-common-actions workflows. The probe example
stays as a manual tool (cargo run -p photocraft-gpu --example
compositor_build_probe).

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:49:12 +01:00
6326117c53 Web build: big operations that won't fit fail with an error instead of trapping, and a trap says so (#10129)
* Raster: ask the allocator whether a big operation fits; PSD save reserves its whole output

New photocraft_raster::headroom: ensure(need) tries to reserve the need plus a margin and frees it
at once (web only; native always passes), below(bytes) tells whether less than that can be
allocated, and simulate_available fakes the answer for tests and diagnostics.

PsdFile::to_bytes reserved only the pixel data, so embedded smart-object data and other tagged
blocks grew the output by doubling, which on the web is one unrecoverable huge realloc. It now
reserves the exact encoded length.

* Big operations that won't fit in memory fail with an error instead of trapping

Place, Image Size, a smart-object re-render and Save now ask the allocator whether their estimated
need can be allocated before they start (photocraft_engine::memory_gate, estimates per operation,
photocraft_io::estimate_export_bytes for saves). A refusal changes nothing and says what the
operation needed and what to do (save, Edit > Purge > All, close documents).

A standing notice warns when less than 512 MiB can be allocated, with hysteresis, so the user can
save before anything traps. Notices can now be sticky, which keeps them past the auto-hide delay.

* Web build: a trap says so instead of freezing the page, and ?free-mb simulates low memory

index.html holds a hidden, static 'PhotoCraft stopped' message with a Reload link. web.rs reveals
it when the module traps (the window error event, or an unhandled rejection from a spawned
future), tells running out of memory from an internal error by the panic message a chained panic
hook kept, and skips the Leave site prompt once stopped. ?free-mb=N makes the memory gates and the
low-memory warning see only N MiB, for checking them in a browser.

* Docs: web build memory handling in the gaps list and the parity assessment

* Engine: use is_multiple_of in the megabyte formatter

* Cargo.lock: the web shell depends on photocraft-raster

* Web boot page: carry the stopped message too, as the test that keeps the two pages equal requires

* Save check counts one copy of the document, and runs once per save

The encoded file's buffer is reserved fallibly, so a save it doesn't fit
in fails with an error instead of trapping; counting it as well refused
saves that would have fitted. On the background-save path the check ran
in write_document and again in start_save, which other callers reach
directly; it now runs only in start_save there.

* Review fixes: warning check allocates nothing, PSD merged image counted, automation saves checked

The low-memory warning's periodic check reads the size of linear memory
instead of reserving a block: wasm memory never shrinks, so a trial every
few seconds raised the high-water mark by up to 768 MiB for nothing. The
operation checks keep their trial reservation.

PSD and PSB saves always build a canvas-sized merged image (a placeholder
without maximum compatibility) and hold it raw and encoded; the save
estimate now counts it. The synchronous automation save checks memory
before encoding, as File > Save does, and its test asserts the memory
error.

The trap listeners go in before the app is built, so a trap while
building it shows the stopped message too, and a memory access out of
bounds reads as unexpected rather than as running out of memory.

* Cover layered PSD preflight limitation

* Fix layered PSD test iteration

* Docs: layered PSD preflight limitation in the gaps list

* Web OOM gate: gate direct exports, truthful advice for unbounded PSD sources (#10024)

* Web OOM gate: estimate layered PSDs per layer instead of refusing every real PSD

The PSD check admitted a file only when its color-mode data, image
resources and layer section were all empty. Every Photoshop file has
image resources (at least the resolution), so on the web nearly every
real PSD was refused for Place, PSD smart objects could not render,
and Image Size was refused for documents holding one, whatever the
free memory (u64::MAX never fits).

The color-mode data and image resources hold no pixels, so they are
now only skipped. A layered file reads its layer count from the layer
info (signed, absolute value) and adds a canvas-sized surface and mask
per layer to the flat estimate. Files that are not 8-bit RGB or
grayscale, keep their layers in a Layr tagged block, or whose sections
can't be read stay unbounded and refused, as before.

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

* Web stopped page: match trap messages ignoring case

Safari reports the allocation-failure abort as "Unreachable code
should not be executed" and Firefox says "out of memory", which the
case-sensitive matching missed: the trap was not recognised, or was
shown as an unexpected stop instead of running out of memory.

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

* Export estimate: count the merged image OpenRaster writes

The ORA writer keeps the layers but also flattens the document for
mergedimage.png, a 16 B/px composite of the canvas, which the
estimate left out.

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:04:59 +01:00
3317aede3b Fix Paint Bucket target with Gradient-style crosshair (#7994)
* Mark Paint Bucket fill target with a lower-right crosshair

* Unify Paint Bucket artwork and crosshair in one cursor bitmap

* Match Paint Bucket target crosshair to Gradient cursor

* Keep Paint Bucket cursor errors concise and fix Windows Clippy

* Simplify blend preview condition for strict Clippy

* Fix deprecated IDs in Linux bucket cursor test

* Paint Bucket: a cursor error clears when the cursor recovers

The failure branch wrote its error to the status bar, but nothing cleared
it after a later scale worked, and the same failure could not report again.
The open dialog stays until it is closed: it reports what happened.

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

---------

Co-authored-by: envy-ai <bart.kelsey@osumc.edu>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:27:28 +01:00
Trevor BrownandClaude Fable 5.1 b9cf495f57 Embed All Linked: tests check each object keeps its own linked file; regenerate the scorecard (#11401)
The Embed All Linked test now gives every fixture a different fill and asserts each embedded source carries its own linked file name and bytes, and leaves the already embedded object alone. docs/scorecard.md is regenerated with cargo xtask scorecard after the parity checklist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 14:00:46 -05:00
Khawar Zamman Wani eecc821ee8 Scratch room uses what the user can write; a failed volume query is unknown
sysinfo reports a Windows volume's free bytes, which a per-user disk quota can
exceed, so admission could promise room the user can't write. free_space now asks
GetDiskFreeSpaceEx for the bytes available to the caller.

sysinfo also keeps a volume whose space query failed, with both numbers zero, which
read as an empty disk and would pause spilling. A zero total now counts as unknown.

Docs: describe network scratch per platform and the size limit's import allowance.
2026-11-06 00:21:07 +05:30
Khawar Zamman Wani af2914fd17 Refusal wording holds when memory sets the 32-bit limit; keep non-UTF-8 scratch paths
On a 32-bit build, free RAM or commit headroom can set the limit instead of the
cap, and then 'install the 64-bit build' was wrong. The message now mentions the
cap and says the 64-bit build may open the file.

The verbatim prefix is now removed through path components, so a scratch path with
a non-UTF-8 name still finds its disk. The UNC rewrite is gone: sysinfo lists only
fixed and removable volumes, so a network path can never match. That limitation is
now documented.
2026-11-05 23:22:13 +05:30
Trevor BrownandClaude Fable 5.1 14d9de4f86 Merge upstream/main into fix/smart-objects-menu-11401
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 12:42:11 -05:00
Trevor BrownandClaude Fable 5.1 712da4f05e Layer > Smart Objects says Reveal in Explorer, Embed Linked and Embed All Linked, in the reference order, without the Warp rows (#11401)
Windows now reads "Reveal in Explorer", Convert to Embedded is "Embed Linked", and a new Embed All Linked command (layer.smartObjects.embedAllLinked) embeds every linked smart object of the document in one history step, refusing the whole step with an error that names a missing file. The three Warp rows leave the submenu; their commands stay registered for scripts and the Edit menu.

The menu parity floor drops from 660 to 658 because three live catalog rows were removed and one added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 12:20:09 -05:00
yoriswind 8a126888f3 Merge upstream main after integration batch 17 2026-11-05 09:09:02 -08:00
David Mann 405b56c3c2 Merge pull request #11320 from davidmann4/integration-batch-17
Integration batch 17: 14 epic PRs
2026-11-05 18:02:17 +01:00
Khawar Zamman Wani 83f492f445 Windows scratch room is measured; hard memory refusal names the build's limit
fs::canonicalize returns verbatim paths on Windows, whose prefix never equals a
disk's plain mount point, so free_space always returned None there. That removed
the scratch allowance from import admission (unless a size limit was set) and
turned off the low-disk check.

The PsdHardBudget message said 'on this machine' even when the limit is the fixed
cap of the 32-bit desktop or web build. It now says so and names the build to use.
2026-11-05 22:07:47 +05:30
yoriswind 3feae0e0b6 Actions: map recorded Perspective Warp planes 2026-11-05 08:36:06 -08:00
Luis daa9a01955 Release: refresh contributor credits for each release (#8737) (#11426)
About > Contributors still showed a snapshot from 2026-10-10, because
nothing regenerated contributors/contributors.json before a release.
Opt in to the shared refresh from craft-common-actions: a read-only
credits job regenerates the snapshot for the release commit, and the
shared release workflow installs it before every source build. The
snapshot is not committed back.
2026-11-05 17:23:30 +01:00
David Mann 37f9d3b7d4 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/codecs/src/codecs/png.rs
2026-11-05 17:18:28 +01:00
Khawar Zamman Wani f54d605e2c Add recipe-driven checks for PSD exports (#10307)
* Add recipe-driven PSD export checks

* Exercise PSD recipe runner with the headless CLI

* Make PSD oracle tests isolate temporary paths

* Keep inspect output unchanged; PSD write runner as an xtask library

- info --detail adds text runs, anti-aliasing and full layer-effect settings;
  the control channel and MCP inspect output is unchanged.
- The CLI test uses xtask's new library target instead of #[path] includes.
- The runner records revision and dirty state as unknown outside a git checkout.

* psd-write-oracle: name the layer in each check, document versioning, add recipe tests

* Clean up PSD oracle test fixtures on failure with tempfile::TempDir and add docstrings
2026-11-05 17:11:56 +01:00
yoriswind 05d3c0bf27 Support recorded Smart Object Stack Modes and video frame edits 2026-11-05 06:24:06 -08:00
a3ea9a4277 Guides: integrate edit-session previews and interaction-check scenarios (#9190)
* feat(ui): integrate edit-session guide drafts and interaction-check scenarios

* fix(ui): repaint guide previews after automation field changes

* fix(guides): retain drafts after command refusal

* Restore the upstream Git-source check and keep guide replay local

* Document pending source approval and shared replay workflow

* Clarify that shared CI support for guide replays is pending

* Vendor the guide edit dependency for source review

Keep the edit-session revision and runtime behavior. Track its source, licenses, checksums and lifecycle tests in-tree in response to the source review in #9190. Apply the workspace lint policy to the standalone crate.

* Move edit-session into ui-egui as a module (drop vendor/)

The edit-session code now lives in crates/ui-egui/src/edit_session.rs,
with its tests in edit_session/tests.rs, following the project's module
layout. This removes vendor/edit-session, the path dependency, the
workspace member, the NOTICE entry and the xtask layers entry.

---------

Co-authored-by: Anton Pepperok <perchneko.anton@gmail.com>
Co-authored-by: Perchenko Anton <perchenkoanton@iMac-Perchenko.local>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 14:50:22 +01:00
Khawar Zamman Wani ac92cafdfd Keep each colour's values in the Selective Color dialog (#11463)
Image > Adjustments > Selective Color used the generic schema form: one
set of sliders shared by all nine colours, with OK applying them only to
the colour selected at that moment. Switching colour looked like the
values had spread, and edits to other colours were lost.

Make it a dedicated adjustment kind with one editor shared by the dialog
and the Properties panel. Each colour now keeps its own row, and OK and
Preview apply all nine. The older single-colour params from actions and
smart filters fold into the rows.
2026-11-05 19:15:59 +05:30
David Mann a4a10166df Merge remote-tracking branch 'origin/main' into integration-batch-17
# Conflicts:
#	docs/gaps.md
2026-11-05 14:33:32 +01:00