224 Commits
Author SHA1 Message Date
Trevor BrownandClaude Sonnet 5.5 cbd389a504 Triangle tool: tests for hostile and degenerate boxes and for the triangle_live field (#13350)
The hostile-radii test now tells the command guard's internal error from a graceful one, and a new test sends hostile boxes. The vector tests pin zero-size, sliver and tall thin boxes. The format tests cover both fields present, a field holding another kind, and junk in triangle_live.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
2026-11-12 20:19:03 -05:00
Trevor BrownandClaude Sonnet 5.5 303be52228 Triangle tool: the triangle fills its box, as its own live kind (#13350)
A new live shape kind, LiveShape::Triangle { rect, corner_radius }, draws an isosceles triangle that fills the dragged box (apex at the top centre, then bottom right, bottom left). The engine no longer turns a triangle into an inscribed 3-sided polygon. Triangles saved before this are 3-sided polygons and keep that geometry.

The .pcraft manifest stores a live triangle in a new optional triangle_live field beside live (as arrow_live does), so older builds open the file and show the saved path. PSD export writes no vogk for a triangle, as for polygons.

Arms the new kind forces: live_kind, the axis-aligned transform, params, the corner radius widgets and the Properties Shape section (Corner radius only). Shift keeps today's equilateral triangle, sent as its tight box.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
2026-11-12 19:27:36 -05:00
RickiNano e660b4906b Clear the clippy 1.100 lints that fail the gate on main (#13531) (#13535)
* Clear the clippy 1.100 lints that fail the gate on main (#13531)

Clippy 1.100.0 flags needless_range_loop in cms synth.rs (the gate
failure on main), algo photo_util.rs and an engine multichannel test,
and, on Windows only, needless_bool in format noreplace.rs, where the
cfg(unix) branch compiles away. Each loop now iterates the slice it
indexed, with the same values in the same order.

* Clear the Linux-only clippy 1.100 needless_bool lints (#13531)

paths_match in ui-egui and is_redirect in the app ended with a
cfg(not(windows)) branch that is just `false`, which clippy 1.100
flags on Linux. Each platform's branch now returns the whole
comparison: the same results, in the same order.
2026-11-12 14:56:57 -06:00
Dylan M. Taylor f373ec181c fix(format,engine): limit zstd windows, set bundle modes via the handle, refuse symlinked presets (#11145)
* fix(format): limit zstd windows, set bundle permissions through the handle, refuse symlinked presets

* test(engine): create the symlink test's directory

* address review: preset writes never follow a symlink swapped in after the check

* address review: a zstd window past the limit is a limit error; replacing a link never reads its target's attributes

* test: replacing a link to a FIFO does not block
2026-11-11 16:29:17 -05:00
Dylan M. Taylor b01e6c0270 Save: a replacement in another group does not widen group access (#11232)
* Save: a replacement in another group does not widen group access

* address review: set the group before copying extended attributes; skip the group test when the original's group is not assignable

* address review: copy the permissions (minus group bits) when the group lookup fails
2026-11-11 16:26:21 -05:00
d4f570a6bf Name leftover staging files and keep staging exhaustion distinct (PR #11577 review)
Running out of staging names returned AlreadyExists, which the
PublishNewError docs tell callers to read as "destination taken, pick
another name". It is now a plain error. When the staging cleanup retry
fails too, the error names the leftover staging file. rename_noreplace
documents that it renames files only.

Co-Authored-By: yoriswind <yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:04:27 +01:00
David Mann 5585ad6407 Merge remote-tracking branch 'origin/main' into pr-11577 2026-11-11 17:01:34 +01:00
Dylan M. Taylor 8ef148ace9 Recovery: replace a symlinked sidecar and do not sweep through symlinked bundle folders (#11230)
* Recovery: replace a symlinked sidecar and do not sweep through symlinked bundle folders

* address review: remove a directory symlink at the sidecar path with remove_dir on Windows
2026-11-11 08:38:40 -05:00
Tanish Manku b0f1fae496 Merge pull request #9323 from DuBearte/fix/8595-pcraft-memory-budget
fix(format): a .pcraft load's decode budget follows the machine's memory (#8595)
2026-11-11 00:03:28 -07:00
Tanish Manku 4313eb2dbe Merge pull request #9319 from DuBearte/feat/8351-gradient-map-source
feat: Gradient Map keeps Photoshop's stops, midpoints and smoothness (#8351)
2026-11-11 00:03:04 -07:00
Terra854 453ea8c9ca [Extend XCF import into native PhotoCraft documents 1/6] Persist import conversion data and bounded native filter foundations
Imported documents need durable conversion evidence and native pixel operations before XCF conversion can produce editable PhotoCraft data. This adds bounded document/report types, lossless pattern construction and `.pcraft` persistence, plus native HDR, percentile, noise, convolution, Average, coverage and matting kernels and checked Smart-filter mixing. Runtime color models/depths are retained; source attachments remain inert. Native SVG operations retain parsing, lowering, rendering and parser-tree destruction on one bounded worker, including nested resources; WebAssembly reports a controlled error for depths requiring native isolation.

Native filter corrections cover matte black points and signed HDR, extent-wide Smart-filter coverage, flat-image identity Auto Levels, opaque FillHoles capability, exact unselected channel preservation, coverage-preserving Average selection and bounded pattern expansion. Checked Smart-filter mixing rejects nonfinite native F32 samples, preserves unchanged Normal pixels without regenerating CMYK separations, and recovers representable alpha mixtures after an overflowing difference. HDR blend overflow retries use cold F64 arithmetic while retaining ordinary finite results and integer-depth behavior. Both opaque and alpha-bearing F32 mask recovery retain signed/HDR colors.

Stored pattern declarations remain lossless and inert. Dense PSD/PAT/ASL pattern export now checks dimensions and one shared plane-allocation budget before conversion. PAT/ASL report conversion errors; PSD/PSB report named omission warnings. Matching raw PSD pattern blocks remain verbatim. CMYK offsets retain the established brightness convention.

Command registration, XCF decoding and frontend workflows follow in dependent parts. The contributor entry is the author's existing chosen entry.

<!-- native-foundations-fixes-20261107:start -->
Small noise, convolution and point-operation edits admit their clipped work area on large owners while retaining output/workspace limits and whole-owner FillHoles bounds. HDR toning retains skipped transparent native samples. Normal Smart-filter strength interpolates stored CMYK/Lab/ink planes with premultiplied coverage, preserving unchanged channels. Bitmap Custom Pattern reads compact patterns in bounded row bands and ranks the complete map once. Embedded SVG isolation failures propagate independently of strict mode; ordinary invalid images can still be omitted, and private native parses share the complete-operation worker. The import-report regression now primes the layer blob cache and requires its specific limit diagnostic.
<!-- native-foundations-fixes-20261107:end -->

<!-- adversarial-5810371597:start -->
Import-report accounting uses portable fixed costs. HDR scene means use F64. Bitmap pattern ranking preflights a 256 MiB input/order/output budget, uses fallible allocation and heap-scratch-free sorting. Average uses the supplied image domain and retains one global mean across output tiles. Non-Normal Smart-filter coverage is classified once across the complete mixing extent. A fallible filter dispatcher and engine error conversion preserve checked native diagnostics and cancellation. Smart-filter engine integration remains in part 5 (#11032).
<!-- adversarial-5810371597:end -->

## Six-part series

Each branch is hosted in Terra854/photocraft, and every PR targets storytold/photocraft:main. Merge in the order below. Later drafts currently show cumulative diffs while prerequisites are unmerged; their descriptions link to isolated comparisons. Rebase each part onto current upstream main after its prerequisites merge, including squash-merge ancestry, and rerun affected checks before requesting review.

| Part | Upstream PR | Fork branch | Scope |
| --- | --- | --- | --- |
| 1 | [#11009](https://github.com/storytold/photocraft/pull/11009) | xcf-support-v2-01-import-data | Document/native-format data and pixel foundations |
| 2 | [#11029](https://github.com/storytold/photocraft/pull/11029) | xcf-support-v2-02-decoder | Bounded transport/records, precision, metadata, editable text and paths |
| 3 | [#11030](https://github.com/storytold/photocraft/pull/11030) | xcf-support-v2-03-effects-core | Native effect frames and primary tone/color/spatial/resource/transform adapters |
| 4 | [#11031](https://github.com/storytold/photocraft/pull/11031) | xcf-support-v2-04-effects-graphs | Remaining adapters, bounded graph lowering, coverage installation and source evidence |
| 5 | [#11032](https://github.com/storytold/photocraft/pull/11032) | xcf-support-v2-05-engine | Native commands, import preparation, historical choices, editing and persistence workflows |
| 6 | [#11033](https://github.com/storytold/photocraft/pull/11033) | xcf-support-v2-06-frontends | Desktop/web UI, CLI/MCP, translations, GPU diagnostics and qualification/docs |

The full integration remains preserved on `xcf-support-v2`; original #7098 remains available for reference.

## Validation

<!-- main-catchup-0d08cb7dd:start -->
Updated to upstream `main` at `0d08cb7dd` and restacked all six branches. Part 1 is `640a01f43`; full integration is `24cd17301`. The final six-part tree exactly matches the full integration tree.

The merge preserves both upstream's new tests/crop translations and the XCF historical-open tests/report labels. The automation adapter now preserves import-source protection across multiple granted read roots: relative source names cover every matching candidate so newly shadowing files cannot expose the original to overwrite, while absolute source names remain precise. Existing ordered reads and implicit-save provenance are retained. Read-only import source identity is kept independently of whether its library root grants an implicit Save target. Focused regressions cover source aliases, removed files, denied candidates, changing root precedence, and read-only library imports; the Windows escape fixture also uses a real junction when symlink privileges are unavailable. The new upstream UI import fixture also uses the native import outcome type.

Local validation passed affected host all-target checks, core and native frontend/automation unit and integration suites, forced-Vulkan GPU unit and integration suites, pinned IO corpora, strict all-target Clippy, formatting, layering, panel command dispatch, WASM, adversarial command tests, and the quick CPU performance harness. Offscreen historical-XCF prompt and conversion-report captures were reviewed. The first two PR boundaries also passed independent all-target engine checks. Tests used the pinned craft-fonts input, four Rayon workers, serial tests for large fixtures and opt-level=0 for engine/UI test glue; pixel/format crates retained their repository settings. Quick mode uses small documents and does not enforce full-size budgets. The part 1 push triggered fresh [CI](https://github.com/storytold/photocraft/actions/runs/42885527754) and [SonarCloud](https://github.com/storytold/photocraft/actions/runs/42885527740) runs. After upstream approval, CI passed the Linux gate, Windows unit/integration tests, no-HEIF tests, pinned corpora and web build. SonarCloud's instrumented archive, coverage shards and actual scan remain skipped behind its token guard; its outer green status does not mean coverage ran. Catching up with main therefore retriggered normal tests but did not remove that SonarCloud skip. Older validation below remains evidence for its explicitly named earlier commits.
<!-- main-catchup-0d08cb7dd:end -->

<!-- average-no-extent-4517477902:start -->
Average no-extent follow-up at part 1 `f8ddb626f` (full integration `882def329`), carried through all six branches. The checked Average dispatcher now uses the requested `area` when no owner extent is supplied; an explicit extent retains its existing clipping behavior. Other filters, the Average pixel kernel and resource admission are unchanged.

The new regression fails against the preceding revision and passes with the fix. It covers `apply`, one-pixel explicit tiling, and the cancellable tiled API at 8/16/32-bit, signed/HDR colors, hidden RGB, native alpha preservation, unchanged source data and pixels outside the requested area. Validation passed the algorithm unit/integration suite (935 tests; 27 existing ignored), strict all-target algorithm Clippy, formatting, layering, WASM (24 crates plus HEIF), and independent all-target engine checks of the first two PR boundaries. The broader suites below remain evidence for their named earlier commits and were not rerun for this dispatcher-only follow-up.
<!-- average-no-extent-4517477902:end -->

<!-- review-20261110:start -->
Review follow-up at part 1 `a3144d559` (full integration `b5fd3e211`), carried through all six branches. Average admits long thin regions within its existing pixel/workspace budgets, clamps finite selection coverage, and uses owner extents for off-canvas replay. HDR Toning also adapts over the owner extent. Pattern GPU uploads now use bounded bands; the warning-free `document_to_psd` API returns `Result` on pattern export failure and reuses prepared pattern blocks. Warning-enabled exports retain explicit omission warnings.

Five filter regressions fail against the previous revision and pass with the fixes. Local validation passed 1,597 algorithm/compositor/GPU tests with Vulkan, all 129 GPU integration tests separately on DX12 with FXC (the app uses FXC when no bundled DXC is present), a separate forced DX12 pattern upload/readback test, 1,144 IO unit and 675 pinned IO integration tests, 10 affected engine tests, strict all-target Clippy, formatting, layering, WASM and quick CPU performance checks. The first two PR boundaries passed independent all-target engine checks. This Intel driver's DX12/DXC configuration rejects the unchanged `fs_adjust` pipeline (`0x80070057`); DX12/FXC and Vulkan both pass. The full workspace suite was not rerun; earlier broad evidence below remains tied to its named commits. Quick performance mode does not enforce full-size budgets.

A separate 24 MP signed/HDR RGBA32F public Average-kernel probe retained exact mean RGB and original alpha. Three alternating runs had medians of 1,274.713 ms before and 1,259.885 ms after; this small difference is within run variation and is not a speedup claim.

The Minor/Poor-tradeoff report-deserialization finding remains open and documented. Encoded records are capped at 32 MiB and individual arrays are bounded, but compact targets can still amplify transient decoded memory before aggregate validation rejects the report. Early shared-budget deserialization is not implemented in this follow-up.
<!-- review-20261110:end -->

<!-- main-catchup-7c64653d5:start -->
Updated to upstream `main` at `7c64653d5` and restacked all six branches. Part 1 is `cbf068d5f`; full integration is `206f71012`. The full integration tree exactly matches part 6.

The merge retains main's alpha-preserving Average behavior with bounded checked processing, and uses it consistently for XCF coverage conversion and native replay. Historical XCF normalization and import preparation now run through main's resumable Place flow and Open as Smart Object; regression tests cover source byte order and native Edit Contents. XCF report/filter labels also cover the seven languages main promoted to menu-complete status.

The current tree passed affected host all-target checks, core/IO, GPU, engine and frontend suites, strict all-target Clippy, formatting, layering, panel command dispatch, WASM and the web build, adversarial command tests, pinned IO corpora and the quick CPU performance harness. Offscreen historical-XCF prompt and conversion-report captures were reviewed. The first two PR boundaries also passed independent all-target engine checks. Tests used CI-pinned craft-fonts `b076417b5880bd4154bd22f00695b1d28c6bea83`, four Rayon workers, serial tests for RAM-heavy suites and opt-level=0 for engine/UI test glue; pixel and format crates kept repository settings. Quick performance mode uses smaller documents and does not enforce full-size budgets. Older measurements below remain evidence for their named earlier commits.
<!-- main-catchup-7c64653d5:end -->

<!-- percentile-owner-4502952267:start -->
Percentile owner-bounds follow-up at part 1 `93946db04` (full integration `f7264af14`): nonempty owner bounds now supply edge samples without a dense whole-owner admission check. The unchanged preflight still bounds clipped output, padded halo, workspace and CPU work; dense-image adapter limits remain in place. Two new regressions failed with `InvalidGeometry` before the fix and pass afterward, covering small edits on 81 MP and wider sparse owners, clamped-edge oracles, fractional selections, source preservation, clipped requests, invalid domains, oversized output, work limits and cancellation.

This follow-up passed 903 algorithm tests (887 unit + 16 integration; 27 existing ignored), all-target strict algorithm Clippy, formatting, layering and WASM (24 crates plus the HEIF configuration). Only sampling-bound admission changed; percentile processing and resource-preflight code are byte-for-byte unchanged. The broader qualification below remains tied to its explicitly named earlier commits; the full workspace suite was not rerun for this one-file follow-up.
<!-- percentile-owner-4502952267:end -->

<!-- native-foundations-validation-20261107:start -->
The production fixes at part 1 `c16e274b8` (full integration `6f8349cd5`) passed 7,245 affected algorithm/compositor/document/engine/native-format/IO tests across 46 binaries, with 65 existing ignored tests, plus doctests, affected all-target strict Clippy, layering, WASM, adversarial command tests, formatting and the pinned IO corpus (1,107 unit + 661 integration tests, 26 existing ignored). The final diagnostics regressions also passed independently on the foundation and full integration branches. The first PR passed an independent all-target engine check. Tests used four Rayon/test workers and the existing CI font pin; engine test glue used opt-level=0, and the foundation-only diagnostic probe also used opt-level=0 for its algorithm dependency. The full integration tree exactly matches the final sixth branch.

Paired 24 MP release probes used three timed repetitions per version after warm-ups, alternating order, matching inlining controls, opaque parameters and a current public-API control, with four Rayon workers. Timings are completed operations on this host.

| Probe | Before (ms) | After (ms) | Public API (ms) |
| --- | ---: | ---: | ---: |
| HDR toning | 830.3 | 834.4 | 791.2 |
| Rgb-Normal | 1070.5 | 1069.3 | 1079.7 |
| Cmyk-Normal | 683.0 | 669.7 | 691.5 |
| Rgb-Multiply | 1103.1 | 1303.3 | 1297.5 |

The extent-wide coverage prepass adds 200.2 ms (18.2%) in the alpha-preserving Multiply case. This is the measured cost of applying one consistent coverage rule; the Normal RGB/CMYK controls remain near baseline.

The uniform HDR probe's middle-gray output changed from 0.200004 to 0.180000 (target 0.18); the public control agrees. Mixer probes check native colors and alpha at sampled positions. The quick CPU application harness also completed; its saved cross-machine comparison was skipped, and quick mode does not apply full-size budgets. No budgets or baselines changed.
<!-- native-foundations-validation-20261107:end -->

Earlier validation of full integration `c003e3f50`, before these follow-up fixes, completed the upstream `main` catch-up at `c101cbfb7` on 2026-11-07. The cumulative series passed affected host all-target checks, foundation and IO suites, GPU, engine and frontend suites, strict Clippy, layering, WASM, adversarial command tests, formatting, pinned IO corpora and the quick CPU performance harness. Offscreen historical-XCF prompt and conversion-report captures were reviewed. Local tests used CI-pinned craft-fonts `b076417b5880bd4154bd22f00695b1d28c6bea83`, four Rayon workers and temporary opt-level=0 overrides for engine/UI test glue; pixel and format crates retained repository settings. The full UI library passed 5,330 tests, and the final dialog-dispatch cleanup passed 945 focused dialog tests.

The decoder and engine PR boundaries also passed independent all-target checks. The full `xcf-support-v2` integration tree matches the final sixth branch exactly. SVG stack regressions retain their 512 KiB caller, isolated complete native lifecycle and 100 retained group levels.

The matched performance figures below were measured before this main integration; the HDR recovery arithmetic and bounded tile implementation are preserved.

Forward filter-mask mixing retains its existing four-buffer allocation optimization and arithmetic. Its matched 24 MP RGBA32F probes measured opaque pixels at 392.410→182.842 ms, semitransparent pixels at 383.321→179.916 ms, and signed HDR pixels at 373.696→183.903 ms (five-run medians, 50–53% faster).

Inverse filter-mask recovery now processes at most eight native tiles in parallel, with four reusable scratch buffers per slot and ordered surface publication. Small inputs, one-worker operation and WebAssembly use the same serial tile helper. All F64 and integer pixel arithmetic is unchanged. Bitwise regressions compare full/partial/missing tiles, negative coordinates, differing defaults and hidden/zero-alpha outputs across 30 format combinations; an explicit one-worker/two-worker comparison checks both execution paths.

Matched 24 MP HDR inverse-recovery public-API probes measured 166.972→90.565 ms (opaque, 45.8% faster), and 433.325→130.707 ms (semitransparent, 69.8% faster). Five-run medians use four Rayon workers on this host; all runs assert recovered HDR samples at three positions. The existing F64 precision and HDR saturation bounds are preserved.

Mask recovery retains signed/HDR colors, extreme finite values, subnormals and exact full coverage. The PSD/PSB regression uses a real blur on spatially varying HDR pixels and partial masks; raw exported channels must match the unmasked filter result, and reimported Smart caches must match independently authored masked values.

The prior quick CPU application harness completed. Its saved cross-machine baseline comparison was skipped, so the paired public-API measurements above provide the direct before/after evidence. No performance budgets or baselines changed.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
  * Added image adjustments and filters for averaging, channel noise, convolution, percentile effects, alpha and coverage editing, black matte removal, HDR toning, and automatic tone, contrast, and color correction.
  * Added Smart-filter blending controls for opacity and blend modes.
  * Import-conversion information is preserved when saving and reopening documents.

* **Improvements**
  * Improved handling of deeply nested SVGs and large patterns, with bounded pattern processing and warnings when pattern export is not possible.
  * Improved stability when blending extreme HDR values and applying filters to HDR images.
  * Improved transparency handling for channel-selective filters and added clearer feedback when image processing or export cannot be completed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-11 07:35:40 +01:00
Michael Higgins 1253e79d68 fix(format): treat an unsupported directory flush on Windows as done (#9440)
Fixes #9440
Fixes #9787
Fixes #9535

## Cause
Saving to a mapped SMB drive on Windows publishes the file, then `AtomicFs::sync_dir` fails with `ERROR_INVALID_FUNCTION` (os error 1): the share cannot flush a directory handle. Since #8034 (for #7858) every Windows flush failure fails the save, so the document stays dirty and the save looks failed even though the file is in place. Unix already treats "directory flush unsupported" as done (`dir_synced`, #5420); Windows had no equivalent.

## Fix
`dir_synced`/`dir_sync_unsupported` now exist on all platforms and, on Windows, count `ERROR_INVALID_FUNCTION` (1), `ERROR_NOT_SUPPORTED` (50) and `ErrorKind::Unsupported` as "cannot flush directories". `atomic_write` and the bundle store use it before `publication_error`. `ERROR_ACCESS_DENIED`, I/O errors and everything else still fail the save, so the #7858 protection stays for real failures.

## Tested locally
`cargo fmt --all`; `cargo clippy -p photocraft-format --lib --tests -- -D warnings`; `cargo test -p photocraft-format --lib atomic` (includes new `windows_unsupported_directory_flush_codes_are_classified`). Not replayed on a real SMB share. The full gate was left to CI.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
  * On Windows, saves now succeed when directory flushing reports that the operation is unsupported, including during bundle publication and directory saves. This applies to unsupported-operation errors such as `ERROR_INVALID_FUNCTION` and `ERROR_NOT_SUPPORTED`. Other directory-flush errors, including access denied, continue to cause the save to fail and be reported.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-11 07:31:44 +01:00
Michael Higginsandkayrockscreenprinting 6a676ee1be Save over a flat JPEG: JPEG Options, then in place; transparent ones offered as PNG (#2223)
Fixes #2223. Carries the in-place flat Save of #5865 (closed as superseded by this PR) and revives what #2515 (@kayrockscreenprinting, closed when its author stepped away) added beyond it.

File › Save now writes back over a flat file when that file still holds the document: one plain opaque layer the format can store, and only when the file held a single image. A JPEG (and a TIFF, Targa or DDS) asks its format options first, as Photoshop does; Cancel writes nothing. A JPEG whose lone layer has transparency it can't keep is offered as a PNG. Multi-image files (animations, multi-page TIFFs, multi-size icons, multi-part EXRs) still go through Save As; the source image count is kept through crash recovery.

Co-authored-by: kayrockscreenprinting <257643019+kayrockscreenprinting@users.noreply.github.com>
2026-11-10 20:26:48 +01:00
Khawar Zamman WaniandFredApps e1145b604e Make text-on-path placement editable (#11629)
* Add path text placement ranges and drag gestures

* Preview path placement and track bounded canvas damage

* Preserve legacy path anchors and show overflow markers

* Match path marker hit targets and directional cursors

* Fix path placement lint failures

* Cover path text body movement and position locks

* Keep open path text movable at range endpoints

* Target the type path after attaching text

* Fix path text range rendering and placement lifecycle

* Map vertical path text and preserve drag branch continuity

* Cover path placement lifecycle and preview damage regressions

* Preserve closest branch projection and fix CI regressions

* Normalize path endpoints and bind gestures to their viewport

* Simplify viewport ownership guards for Clippy

* Document path placement invariants and regression cases

* Fix path placement ownership, validation and geometry caching

* Correct path placement regression fixtures

* Ignore transient path geometry cache in UI state equality

* fix: allow path text flips with stale placement ranges

* fix: retain path text handles after shortening curves

* test: account for layer translation in path placement drags

* fix: mark path text range with x and o markers on the path

The beginning marker is an x and the end a small circle, both on the
path, with a plus in the circle while the range hides glyphs. The
cursor's triangle points right over the beginning, left over the end,
and both ways over the text body, which moves the whole range.
Direct Selection leaves an anchor or handle under a marker to path
editing; Path Selection always takes the marker.

* fix: resolve coincident path text markers by lap or drag direction

The x and o markers coincide on a closed path's full lap and on an
empty or end-clamped open range. A full lap now moves the whole text;
otherwise the first motion picks the beginning (backward) or the end
(forward). Marker fixtures press on the path, where the markers now
sit.

* fix: let either end of a full-lap path text range be dragged

Text attached to a closed path fills one lap, so its x and o markers
coincide and every press moved the whole text, leaving no way to
shorten the range. As the text begins just ahead of that point and
ends just behind it, a press ahead along the path now grabs the
beginning and one behind grabs the end; the point itself still moves
the whole text.

* test: check full-lap marker drags do not flip the text

* fix: read and write the path text range in PSD

Type on a path keeps its range in the Txt2 frame as /2 /6 [start end],
in curve-segment units with -1 for the default. Three reference-editor
files in ag-psd's test set fix the mapping: text-path ends a third into
the first segment and draws only "so" of "some text", and
text-path-26/27 move the start on an open path.

Import reads both ends (and the flipped flag) into the layer's start and
end. Export writes an open path whole with its start and end in the
range, instead of cutting the path at the start, and a closed path's
limited end as a range from its start. Path text in a document that
never came from a PSD now gets a Txt2 block, so its path is saved at
all, and the warning that the end limit is lost is gone.

* fix: let both selection tools grab a path text marker on an anchor

Direct Selection gave an anchor under a marker the click, so an open
path's default markers (on its end anchors) and a closed path's
coincident full-lap markers (on its first anchor) could not be moved
with that tool. In the reference editor either selection tool drags a
marker wherever the pointer shows the text bar with a triangle, so the
marker now wins for both tools.

The Direct Selection tests for reshaping a type path (#9114) drag its
last anchor, where the default end marker sits; they now end the
range short of it. To reshape at an anchor under a marker, drag the
marker off it or select the anchor with a marquee and nudge it.

* fix: keep explicit and empty path text ranges through PSD

An explicit end at the path's end stays explicit, so centered and right type keeps its alignment within the span. A closed range whose ends meet reads as empty, not as a full lap.

* ci: re-run checks after a download outage

* ci: re-run checks after a download outage

* fix: preserve legacy path text anchors through PSD

* ci: re-run checks against current main

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 16:28:51 +01:00
0999a27c86 Document PublishNewError fields (PR #11577 review)
The public `published` and `error` fields had no docs, so a caller could
not tell that `published: true` means the destination already holds the
complete bytes and must not be retried as unpublished.

Co-Authored-By: yoriswind <yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 13:49:15 +01:00
David Mann c49159f1c4 Merge remote-tracking branch 'origin/main' into pr-11577 2026-11-08 13:45:32 +01:00
DuBearteandClaude Opus 5.5 72df150914 feat: Gradient Map keeps Photoshop's stops, midpoints and smoothness (#8351)
A Gradient Map read from a PSD keeps Photoshop's own gradient (its
stops, midpoints, Smoothness and interpolation method) next to the
dense stops baked from it for rendering. A save writes that gradient
back, so Photoshop opens the same editable gradient; the editor shows
and edits Photoshop's stops and notes the Smoothness and method, and the
params carry it as "source". Plain stops, a preset or "source": null
replace it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 00:18:57 -05:00
DuBearteandClaude Opus 5.5 7426cb9858 fix(format): a .pcraft load's decode budget follows the machine (#8595)
LoadOptions::default took the format's 16 GiB total-bytes limit on
every machine, so a small bundle of nearly empty, highly compressed
tiles could ask for far more than the machine has. The default is now
that limit lowered to the available RAM plus the scratch disk's room
(decoded tiles spill there as memory fills), never below 1 GiB, and
unchanged where neither is known. It uses photocraft_raster's own
memory and scratch measurements; no new dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:49:12 -05:00
Zinadin Zidan 489d994524 Saving: a leftover temp file is recognised only with a file part a save can write (review)
A save's temporary file keeps at most the first 64 characters of the
document's name, and older versions put one dot in front of them. A name
with a longer part before the process id and counter was still recognised
as a leftover, so the recovery sweep could delete someone else's file a
day later. Only a part a save can write is recognised now, counted in
characters as the save cuts it.
2026-11-07 03:06:17 +06:00
Zinadin Zidan dd639352ed Saving: a leftover temp file is recognised only with a counter a save can write (review)
The recovery sweep deletes files it recognises as a save's temporary file.
A name whose counter is longer than the 64-bit counter a save counts with,
or whose process id or counter starts with a zero, was still recognised,
so the sweep could delete a file no save wrote. Only the numbers a save
writes are recognised now.
2026-11-07 02:01:13 +06:00
Zinadin Zidan fc94ac59e2 Saving: the temporary file is no longer a dot file (#11624)
A save writes a temporary file beside the document and renames it over the
document. Its name started with a dot, and an SMB server that hides dot
files marked it hidden. The mark survived the rename, so the saved document
was hidden in Finder. The name is now <file>.<pid>-<n>.pcsave.tmp. Leftovers
are recognised, and swept from the recovery folder, under both the old and
the new name.
2026-11-07 00:27:13 +06:00
yoriswind 93b0bbc04c Keep exclusive file staging distinct and preserve native Windows paths 2026-11-05 17:46:22 -08:00
yoriswind 9bcb6776ec Separate bounded reads and exclusive file publication 2026-11-05 17:03:20 -08:00
Khawar Zamman Wani e3ecd73569 feat(frame): rework the Frame tool's selection, handles and placement (#8920)
Thanks @wanikhawar — merging the Frame Tool rework.

Summary: Frame Tool selection/handles/placement now distinguish frame vs image (border / click / double-click and Layers thumbnails), with independent transforms, shape variants, Properties/options-bar stroke and fit commands, Convert to Frame, CLI/MCP `layer.frame.*` commands, native `.pcraft` persistence, and PSD export via editable groups + `pcFr` metadata (not Photoshop's native frame type). Tests cover selection, transforms, fitting, locks/hostile geometry, and native/PSD round-trips.
2026-11-05 12:39:02 +01:00
Khawar Zamman WaniandFredApps 316e00a97e Bound Photomerge memory and safely reject out-of-budget jobs (#7851) (#10463)
* Reduce Photomerge input and fill memory

* Bounded working-memory budgeting and safe allocation for Photomerge

* Photomerge memory: estimate warped sizes, no RAM ceiling, shared RAM detection

- Budget is half of physical RAM (at least 1 GiB) with no 8 GiB ceiling.
- The pre-flight estimate uses each image's warped extent and the band
  grid's exact bound (band_bytes_bound), at the levels seam_blend uses.
- Auto-Blend keeps its old batching with no single-band cap.
- multiband returns a Result instead of a blank image on failure.
- Physical-memory detection moves to photocraft_engine::memory; the GPU
  budgets reuse it, so PHOTOCRAFT_RAM_MB means the same in both.
- Document memoryBudget; restore comments; tests for the band cap and the
  budget precedence.

* Photomerge: lower the blend's peak and estimate it by phase

- The band build makes its binary coverage straight from the alpha and
  writes each Laplacian level in place, so a band holds two fewer planes.
- The weight sums are freed before the collapse, which adds each level
  in place and frees it as it goes.
- The estimate is the largest phase (warp, ROIs, bands, collapse, output,
  fill) rather than the sum of all of them, and the bands get what is
  left of the budget after the ROIs and accumulators.

Every output pixel is unchanged: each change drops a plane or does the
same elementwise operation in place.

* Fix Photomerge band budget validation and fallible pyramid allocation

* Keep reference pyramid test helpers infallible

* Photomerge: bound source loading with the memory budget

Resolve the budget before any source is read, and admit each flattened
source before its surface is allocated. The accounting charges the
retained surface, the same-canvas temporary the flatten writes while the
source document is alive, and a file import's encoded bytes, with
saturating math so an overflow cannot wrap under a finite budget. Merge
to HDR Pro passes no budget and keeps its previous behaviour.

Tests cover the exact retained-plus-temporary charge, a refused source,
the within/over-budget command paths, the post-load estimate path, and
the unbudgeted HDR loader.

* Photomerge: bound source reads and imports by the remaining budget

Read each source file with what is left of the budget as its limit, lower the importer's own PSD/TIFF preflight or the flat decoders' pixel allowance to the remainder after the retained sources and encoded bytes, and refuse formats with no pre-decode estimate. Charge the decoded document's actual pixels (every layer) plus the flatten's working and output surfaces before flattening. Merge to HDR Pro keeps its unbudgeted read-and-import path.

* Remove redundant TIFF decode options update

* Clean up Photomerge budget Clippy findings

* Accept early decoder admission in source budget regression

* Photomerge: report unreadable sources as file errors; never reserve past a read limit

- A budgeted Photomerge reported a missing or unreadable source file as
  'not enough memory'. Only a file over the remaining budget, or one memory
  cannot hold, is a memory error now; others are file errors, as without a budget.
- read_all_with reserved a whole read chunk before checking the caller's limit
  for a file that grew after it was statted. It now refuses first and caps the
  reservation at the limit.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 06:23:56 +01:00
FredApps 97ba6d16a9 Merge pull request #11189 from hnoerenberg/fix/windows-replace-recovery-11178
fix(atomic): report where the original survives when the Windows restore fails
2026-11-04 13:42:21 +01:00
Dylan M. Taylor 37a01ca874 format: bound blob inflation by the load's remaining budget; browser budgets (#10984)
LoadFetch::blob_bounded decompressed a new blob up to max_blob_bytes
and only then charged it against max_total_bytes. Successful blobs are
retained, so several distinct, highly compressible blobs each allocated
a full max_blob_bytes before the total was checked. A new blob may now
inflate only to what the total budget has left.

The browser build loaded native documents with the desktop defaults
(1 GiB a blob, 16 GiB in all), far beyond a wasm32 instance's 4 GiB of
linear memory that the editor also uses. On wasm32 the defaults are now
512 MiB a blob and 1 GiB in all.
2026-11-03 20:55:42 -05:00
Hartmut Nörenberg 82ee6ada2e fix(atomic): the vanished-backup error names the path it looked at 2026-11-04 01:13:43 +01:00
Hartmut Nörenberg 30c617cc79 fix(atomic): a vanished backup is not a recovery copy 2026-11-04 00:36:12 +01:00
Hartmut Nörenberg fbe7680328 fix(atomic): report where the original survives when the Windows restore fails 2026-11-03 23:55:50 +01:00
FredApps 05fc07b538 Merge main into fix/save-memory-6607
Resolve against main's save restructure (Services::save_file/SaveCtl, scratch-integrity checks,
ZipWriter::with_entries, exact encoded_len):
- PSD/PSB saves stream the file model through atomic_write_stream_progress; the length used for
  progress and reservations is main's exact encoded_len (the PR's size_bound under-counted lazy
  data); export_with/encode_with share checked_stages, so main's scratch checks (previews
  included) still refuse unreadable scratch.
- Drop ZipWriter::for_entries in favour of main's with_entries.
- TIFF: take_from_psd moves layer data out of the export's own model (never the document);
  body() reserves its exact length, failures are OutOfMemory (not LimitExceeded, which would
  trigger the PSB retry).
- Streaming writer: AlreadyExists from the writer is a failed save, not a name collision.
- Remove dead Vec put_widened_rle; channel_data_len is test-only.
2026-11-03 09:23:16 +01:00
FredApps 8ce599da3a Merge remote-tracking branch 'upstream/main' into pr-5330 2026-11-03 07:12:38 +01:00
dotCooCoo fef15729aa Merge main into feat/pcraft-full-composite
Main now takes the manifest envelope from craft-pcraft-format, which has
no full_composite member. The save writes the member after the envelope
when it writes composite/full.png, and read_full_composite reads it back.
2026-11-02 17:02:14 -08:00
FredApps 44f13e5498 Scope scratch read failures to the document that holds the tile
One unreadable tile used to block editing, saving and exporting every open document
(spill::check_integrity was process-global). Add Tile/Surface/Document::scratch_health
(free while no tile has failed) and Document::for_each_surface (ops' history walk now
delegates to it). edit_with checks the document before and after, jobs the document they
captured, import the result, save/export the document written; check_since is now
generation-only so a failure or recovery during an operation refuses it once and a retry
works. Caches check their own sources and purge once per new failure instead of every
frame. Fault-injection tests use a duplicated handle (the Unix file has no name).
2026-11-02 22:23:42 +01:00
FredApps b0f3c7516b Merge current main into codex/scratch-storage
Resolve conflicts with main's prefs UI split, session/execute.rs move, GPU memory
budget (gpuMemoryMb, 0 = auto, kept from main) and effective_memory_usage_mb history
budget. Port the Memory Usage % and Scratch Disks preferences into prefs_ui, the edit
integrity guard into session/execute.rs, and adapt main's new pixel readers to the
guarded TileBytes API. Cargo.lock regenerated; scorecard regenerated.
2026-11-02 22:16:22 +01:00
.CooCoo 289dd0a874 Format: take the .pcraft envelope from craft-pcraft-format (#10724)
* Format: take the .pcraft envelope from craft-pcraft-format

The manifest envelope, TileRef, Hash, FORMAT_VERSION, EXTENSION, the
entry names, tile_path and blob_path, is_valid_hash, the manifest
nesting and item checks and the migration runner now come from
craft-libs' craft-pcraft-format. photocraft-format re-exports them, so
callers do not change. Manifest is craft_pcraft_format::Manifest<DocM>.

Saved bundles keep the same bytes. A save still checks only the nesting
and the item count of its manifest, not its size, and the manifest
nesting limit is still computed from photocraft_doc::MAX_GROUP_DEPTH.

Tests: a save, load and save again gives the same bytes in every color
mode and depth, with and without previews; MAX_GROUP_DEPTH equals
craft_pcraft_format::MAX_GROUP_DEPTH.

* Format: pin craft-pcraft-format from storytold/craft-libs

* Format: read manifests with craft-pcraft-format's read_manifest_with

Pins craft-pcraft-format to dca870a0, the head of craft-libs #407.
Loading reads the manifest with read_manifest_with, which runs the
limit check, the version check, the migration steps and the envelope
read in one path; the layers are still decoded one at a time after the
rest of the document. A save builds the manifest with Manifest::new and
tile references with TileRef::new, and the bundle's directory names and
the .zst suffix come from the crate's constants. Saved bytes and error
messages are unchanged.

* Format: pin craft-pcraft-format to craft-libs #407's head 04dd1bd

04dd1bd changes only the crate's docs and a test, so photocraft-format
builds the same code.

* Format: pin craft-pcraft-format to craft-libs main at b8f7d93

craft-libs #407 merged as b8f7d93. Its read_manifest also refuses a
thumbnail other than thumb.png and a composite other than
composite/preview.png, the only names photocraft-format writes.
2026-11-02 11:43:46 -05:00
Khawar Zamman Wani 86ddfb72f7 Reuse large buffers so returning freed memory to the OS doesn't slow filters (#10479) (#10514)
* perf: reuse large buffers so a pinned glibc trim threshold costs no speed (#10479)

With MALLOC_TRIM_THRESHOLD_ set (#8743), glibc maps every allocation over
128 KiB fresh, so tiles, compositor bands and filter scratch were page-faulted
in on every call: viewport compose 3.6x slower, Gaussian Blur up to 1.8x,
.pcraft save 2.4x.

- photocraft_raster::scratch: a process-wide, capped pool of large Vec
  buffers (u8/u16/f32/[f32; 4]). A miss drops the oldest buffers worth its
  size down to a 256 MiB floor, so peak memory stays near glibc's defaults.
  The engine releases the pool after any command that closes a document,
  Headless::close does too, and an idle thread releases it after 2 s unused.
- Tiles, compose Buffer/masks/bands, algo Image and the blur paths, engine
  adjustment/filter scratch, GPU tile upload and io import buffers go through
  the pool.
- .pcraft save reuses one zstd FrameCompressor per worker (byte-identical).
- interactive_bench reports minor page faults per run.

36 MP, trim threshold set vs glibc defaults: viewport 5.6 vs 6.1 ms (was
22.7), Blur r4 445 vs 437 ms (was 852), Curves CPU proxy 35.0 vs 35.7 ms
(was 50), flatten 146 vs 147 ms (was 237), save .pcraft 251 vs 230 ms (was
545). RSS after closing a 5400x7200 document: 21 MB with trim set.

* fix: corpus tests build again; return remaining pooled buffers (#10479 review)

- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
  `Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
  use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
  smart-object pixels and patterns give their `interleave` buffers back to
  the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
  and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
  so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
  included; say so.

* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)

* fix: recycle effect hiding masks (#10479 review)

* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)

* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)

* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)

* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)

* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)

* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)

* docs: describe scratch pool lifecycle regression invariants

* test: verify polling releases cancelled document scratch buffers

* perf: recycle remaining transform selection and PSD scratch buffers

* fix: free PSD gather bands before allocating channel planes

* test: assert exact scratch take reuses pooled storage
2026-11-02 09:29:59 +01:00
dotCooCoo 7a2d998ff7 Merge main into feat/pcraft-full-composite 2026-11-01 15:51:56 -08:00
Gabriel Heuser 24be8ea6e2 Add editable arrowheads to Line tool (#6408) (#9259)
* Add configurable arrowheads to line tool

* Add editable arrowheads to Line tool

* Fix arrow scaling validation and PSD import defaults

* Preserve arrow geometry under affine transforms

* Preserve scaled arrowhead dimensions and prevent non-finite geometry

* Localize arrowhead Start and End labels

* Fix arrow transform editability and short line handling

* Fix PSD arrow concavity serialization

* Fix arrow dimension fallback validation

* Refine arrowhead options UI to match Photoshop

* Add opt-in arrowhead defaults to Shape properties

* Fix tool reset syntax after upstream merge

* Validate arrowhead flags and clamp copied defaults

* Preserve editable headless lines under transforms

* Fix arrowhead UI imports and formatting after v0.17 merge

* Format arrowhead properties after v0.18 merge

* Fix Clippy lint errors in arrow geometry and tests

* Fix headless arrow geometry after nonuniform scaling

* Fix proportional arrow scaling in Properties panel

* Fix Rust formatting in Properties arrow scaling tests

* test: validate invalid arrowhead dimensions without mutation

* fix: preserve arrow compatibility with older pcraft versions
2026-11-01 18:46:05 -05:00
dotCooCoo e7829eb449 Merge main into feat/pcraft-full-composite 2026-11-01 15:30:48 -08:00
David MannandClaude Opus 5.5 622707a36f Address CodeRabbit review: four findings on the batch
- Channel targeting routes an allow-list of pixel-editing paint commands, not every
  `paint.*` id: the Mixer Brush's Alt-click load and Symmetry no longer follow a colour
  set or get refused with "Rasterize the layer" on a fill layer. Patch and Content-Aware
  Move stay routed (#9274).
- Native vector save-back checks the preview SVG as strictly as stored previews are
  checked on load and PSD export, so a preview with an unreadable embedded image is
  refused instead of stored and later dropped (#8571).
- .pcraft blobs read their zstd frame with framing headroom, so an incompressible
  preview right at its 16 MiB limit still loads; the limit still bounds the data.
- Levels/Curves recall (Alt+shortcut) decides the combined Lab record from the target
  the dialog captured, as remembering does, so a viewed layer mask keeps its settings
  beside a Lab colour set (#9274).

Each comes with a test that fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:41:36 +01:00
dotCooCoo 69912e6035 Format: the full-size composite stays within 2^26 pixels, and running out of memory leaves it out
A canvas over 2^26 pixels (256 MiB of RGBA8) is saved without composite/full.png. The PNG encoder's buffers are reserved fallibly, so an allocation failure reports an encode error, and a ZIP bundle that cannot be reserved with the composite is written without it.
2026-10-30 06:46:33 -07:00
David Mann b2db24400e Merge main at b1cfff9d3 into integration batch 10
Conflicts resolved as unions of both sides: codecs re-exports (PngRows + TiffBands), smart_cmds source imports
(main's source_surface_from_buffer and cache API with #8571's svg_source_image_in/convert_svg_source; main's
duplicate rendering import dropped), io flat.rs test modules (#8536's banded TIFF tests and main's PNG ones),
menus/invoke.rs (#8571's smart SVG intercept and main's layer.selectUnderCursor), state.rs (#8311's
type_box_overflow and main's type_insertion_style), the canvas_geom test keeps the is_finite guard, docs
status lines keep both change lists, i18n catalogs keep both sides' rows (hi.tsv: main's wording where both
added the same string).
2026-10-30 14:28:02 +01:00
dotCooCoo 49d12c57bf Merge main into feat/pcraft-full-composite
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-30 06:26:41 -07:00
David Mann fea300dd4c Merge origin/main into integration batch 10 2026-10-30 01:47:01 +01:00
FredApps 0cb4ec56d3 Merge current main and complete retained Pen messages in Nordic catalogs 2026-10-30 00:48:24 +01:00
David MannandClaude Opus 5.5 ce33b19090 Tests: a failed autosave outcome test does not wait for a stuck write
Address CodeRabbit review: dropping the Autosaver joins its worker, which waits
for the write in progress, so a timed-out receive could still hold the test
until nextest kills it. The saver is now ManuallyDrop and only dropped on the
passing path. With a worker that sleeps 60 s per write and a 2 s bound, the
test fails after 2.01 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:15:31 +01:00
David MannandClaude Opus 5.5 f39f94aef8 Tests: the autosave outcome test waits up to 5 minutes, not 30 s, for a real write
background_save_outcomes_report_failure_then_recovery failed on main's Windows
integration job: the real (second) autosave write flushes every tile to disk and
took over 30 s on a busy runner (normally 2-3 s; a concurrent autosave test ran
past 60 s too). The bound only exists to turn a worker that never reports into a
failure instead of a hang, so it is now 5 minutes, still under the 10 minutes
after which nextest's ci profile kills a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:53:19 +01:00
FredApps 255ecc81d0 Remove 21 requested PR contributions from the 108-PR integration
Retain 87 PRs. Reconcile overlapping integration fixes and update testing scope.
Removed: #7153 #7742 #5493 #9282 #3455 #9172 #3139 #3967 #4783 #5453 #8347 #1991 #3345 #4042 #4305 #4883 #4890 #5090 #6621 #5870 #5939.

Combined CI pending for this source; historical portable artifact is not applicable.
2026-10-29 18:49:17 +01:00
David Mann afacb057f7 Merge origin/main into pr-8571-fixup
Conflicts: svg.rs (main's bufread inflate and shared render helper, kept
with the PR's bounded raster path), psd_import.rs (the PR's vector
preview moved into main's smart_content), menus/invoke.rs (the SVG
intercept moved into main's split invoke_unguarded), rendering.rs and
the ui-egui import lists (main's explicit imports), Cargo.lock, docs
status lines (unions).
2026-10-29 16:38:24 +01:00