163 Commits
Author SHA1 Message Date
David Mann 3369f427ba Merge main into #7377 (Fujifilm compressed RAF)
Only the docs status lines conflicted: main's text, with the compressed RAF entry kept first.
2026-11-09 14:26:32 +01:00
6afd603e53 Merge main into #7377 (Fujifilm compressed RAF)
Only the doc status lines, histories and raw tables conflicted. The
status lines take main's with the #7377 entry kept first; histories keep
both sides. The RAF table row keeps this PR's compressed support next to
main's new Hasselblad 3FR row and its sidecar status, and points at the
moved test file tests/it/raf.rs. The Camera RAW row keeps main's 3FR and
Nikon text and no longer lists compressed RAF as open.

Co-Authored-By: Yorgos S. <smyrgeorge@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:35:13 +01:00
David Mann d95faebaa3 Merge remote-tracking branch 'origin/main' into pr-9077
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-08 03:04:43 +01:00
David Mann b8b539c845 Merge origin/main into PR #9058 (raw: Hasselblad 3FR/FFF) 2026-11-07 19:37:23 +01:00
7f09b2013d Merge current main into #9077 (Average on Smart Objects)
Resolves the status-line conflicts in docs/gaps.md and
docs/target-app-parity.md (both sides kept) and the module list in
crates/engine/tests/it/main.rs.

Co-Authored-By: arjen <76012639+ArjenS-LA@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:48:12 +01:00
dotCooCoo e207210745 Merge main into feat/equalize-entire-image 2026-11-07 08:24:11 -08:00
David Mann f98218cbe8 Merge pull request #10529 from luben93/raw/nef-lossy-after-split-v2
raw: decode Nikon "lossy after split" NEFs
2026-11-07 16:06:23 +01:00
b9323e117e Merge main into #7377 (Fujifilm compressed RAF)
Main moved crates/raw/src/testgen.rs into testgen/ and the integration
tests into tests/it/, and its preview fallback now also opens a larger
JPEG XL preview. The compressed RAF test generator moves to
testgen/raf.rs; the JPEG fallback and the damaged-data fallback both go
through preview_image (Adobe RGB tag from the EXIF) under the caller's
decode limits. Doc status lines and histories keep both sides; G14 and
the Camera Raw row no longer list compressed RAF as missing.

Co-Authored-By: Yorgos S. <smyrgeorge@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:43:08 +01:00
Gotya-OFFCIAL e7241d2e26 raw: decode Hasselblad 3FR/FFF (X2D II 100C, uncompressed)
The X2D II 100C's 3FR files develop at full sensor resolution instead of
falling back to the camera's embedded JPEG preview. A 3FR is a TIFF whose
IFD0 holds the camera's JPEG and whose CFA image sits in a SubIFD with
DNG-style tags but no DNGVersion and no CFA-pattern tag (neither the
TIFF/EP pair nor the EXIF one), which is why the generic TIFF/EP path
reported "CFA pattern not recorded". The new decoder reuses the shared
strip reader, takes BlackLevel, WhiteLevel, DefaultCropOrigin/Size and
AsShotNeutral from the file, and takes the Bayer phase from a measured
per-body table (the X2D II 100C: RGGB at the data origin, measured
against the camera's own embedded JPEG; unmeasured bodies warn).
Lossless-JPEG 3FR stays preview-only, like uncompressed-only ORF and RAF
before it.

Clean-room: the TIFF 6.0 and DNG specifications, the sample file's bytes
and its embedded JPEG; no code or tables from dcraw, LibRaw, rawspeed,
rawler or darktable.

Adds round-trip and hostile tests through a mirrored synthetic builder
(tests/it/hasselblad.rs, testgen::Hasselblad3frSpec) and updates the
support matrix (file-format-parity, gaps G14, target-app-parity).
2026-11-06 12:05:10 +08:00
ArjenS-LA a38fc6ff5b Refresh Average Smart Filter stack and verify selection and channel values 2026-11-05 15:53:24 -08:00
ArjenS-LA 19b5ef0fb3 Merge current main and strengthen Average selection regression 2026-11-05 15:52:30 -08:00
midasdf eafaf5cc5d Merge remote-tracking branch 'origin/main' into jp/print-paper-sizes
# Conflicts:
#	docs/target-app-parity.md
2026-11-06 08:51:40 +09:00
dotCooCoo b7e5c92195 Merge main into feat/equalize-entire-image 2026-11-05 15:00:22 -08: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
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
midasdf 59a75c0bc8 Merge remote-tracking branch 'origin/main' into jp/print-paper-sizes 2026-11-05 23:43:36 +09:00
dotCooCoo 7eea3c1bb3 Merge main into feat/equalize-entire-image 2026-11-05 05:29:50 -08:00
Khawar Zamman WaniandFredApps a38e544faf Make Select and Mask refinement and previews tile-sparse (#10732)
* Make Select and Mask refinement and previews tile-sparse

* Preserve finite mask extent when copying sparse pixels

* Keep solid preview coverage shared during conversion

* Preserve alpha sharing and document sparse refinement limits

* Refine mixed tiles in bounded parallel batches

* Skip guide sampling for constant refinement halos

* Balance sparse refinement batches using measured queue size

* Preserve default alpha and cancel sparse refinement cooperatively

* Address review follow-ups: cancellation after completed flush, stroke assertions, and docstrings

* Fix merge: bind tile bytes to avoid temporary borrow in matting

---------

Co-authored-by: FredApps <fredapps@users.noreply.github.com>
2026-11-05 14:22:03 +01:00
dotCooCoo f120908767 Merge main into feat/equalize-entire-image 2026-11-05 04:48:33 -08:00
Khawar Zamman Wani 0350b5de16 Select sky beneath an obstructed top border (#9693)
* Select sky beneath an obstructed top border

* sky: enforce adjacent-pixel RGB constraint in obstructed fallback seeds
2026-11-05 13:16:37 +01: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
midasdf b60b58af6c Merge remote-tracking branch 'origin/main' into pr
# Conflicts:
#	docs/target-app-parity.md
2026-11-05 18:00:44 +09:00
dotCooCoo a55ef6d585 Merge main into feat/equalize-entire-image 2026-11-04 23:04:09 -08:00
d7d138b1e5 Art History Brush: Opacity on the options bar (#10263) (#10278)
* Art History Brush: Opacity on the options bar (#10263)

The Art History Brush's own brush opacity is shown in its options bar
(0-100%) and sent to paint.artHistoryBrush. It is kept per tool, saved
across launches, set by the 1-0 keys and restored to 100% by Reset Tool,
as the Clone Stamp's is. Tests cover the UI wiring, per-tool persistence,
Reset Tool and stroke output at 8/16/32-bit.

* Update crates/ui-egui/src/panels/options_bar.rs

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

* Art History Brush starts at 100% opacity on first use

A tool picked for the first time takes the outgoing brush; the Art
History Brush now takes it at full opacity, as the issue's default asks,
so a 30% Brush no longer makes the first Art History stroke 30%. Other
tools keep inheriting opacity.

* Art History Brush: document Opacity in the command contract and carry it in tool presets

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 07:15:57 +01:00
midasdf 5a00095b06 Merge remote-tracking branch 'origin/main' into pr
# Conflicts:
#	docs/target-app-parity.md
2026-11-04 05:45:54 +09:00
dotCooCoo 4a7342ab5d Merge main into feat/equalize-entire-image 2026-11-03 12:02:52 -08:00
ArjenS-LA 93a8d93104 Merge refreshed transparency prerequisite into Average Smart Filters 2026-11-02 22:52:23 -08:00
ArjenS-LA 051c2a8287 Merge current main and retain Average transparency regressions 2026-11-02 22:51:37 -08:00
David Mann 4f6a2c50f0 Merge #4579: the author's latest commits
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-03 01:05:27 +01:00
Fabio Lissi a2c4ac0b0c Match Batch Esc flow and show read-only input preview 2026-11-02 17:36:55 -06:00
David Mann 2915849b07 Merge #4514: Record actions in a document-independent form (#219) [depends on #4496]
# Conflicts:
#	crates/engine/src/cmd/type_spell_cmds.rs
#	crates/engine/src/jobs.rs
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-03 00:36:40 +01:00
Fabio Lissi 00f75988b9 Match measured Photoshop Batch naming and error logs 2026-11-02 11:30:58 -06:00
Fabio Lissi 50360ac4ef Record portable nudges, document switches, and type positions 2026-11-02 11:30:37 -06:00
Fabio Lissi 4eba6bd2e9 Merge current main and complete Batch automation options 2026-11-01 23:04:19 -06:00
Fabio Lissi 21c0444559 Merge current main and preserve portable action recording 2026-11-01 23:01:23 -06:00
dotCooCoo 67bdb204da Merge main into feat/equalize-entire-image 2026-11-01 18:25:41 -08:00
midasdf 52d4292624 Merge remote-tracking branch 'origin/main' into pr
# Conflicts:
#	docs/target-app-parity.md
2026-11-01 23:42:30 +09:00
midasdf d1a02c7f2f Merge remote-tracking branch 'origin/main' into pr
# Conflicts:
#	docs/target-app-parity.md
2026-11-01 22:50:04 +09:00
aiongg 713197635d Merge origin/main into prefs/gpu-adapter-typed-read 2026-11-01 13:16:28 +00:00
lucas perssonandClaude Opus 5.5 b15e914431 [ai] raw: decode Nikon "lossy after split" NEFs
Some lossy type 2 NEFs store a split row at byte 562 of maker note 0x0096.
They were refused ("lossy after split") and opened as the camera's preview.
The 11 such CC0 raw.pixls.us files come from the D3400, D5, D5000, D5200,
D5500, D5600, D7000, Nikon 1 J5 and Z 6_2.

Decode them following the rule LightCraft derived by black-box analysis of
those 11 files (storytold/lightcraft#633), the same route the Huffman tables
took. Rows above the split are Huffman coded as before. From the split row on,
every pixel is one MSB-first word of bits - 4 bits: a prefix code for the
difference category followed by the top bits of the T.81 additional bits,
dequantised to the centre of the kept bin (fixed_words, ported from
LightCraft's fixed_word_table), with the same predictor and an unclamped
running value. The section ends the strip: whole rows (up to 4 more than the
image), then 0-7 one-bits.

A split file that fails to decode for any reason (a section that does not end
the strip, the all-ones word, truncation, size limits, a split row past the
image, a curve long enough to cover byte 562) stays unsupported, so it keeps
the embedded-preview fallback it had before. Apart from the all-ones word and
the padding, the content of the fixed-rate section is not validated: a damaged
word inside a well-formed section decodes without an error, as in LightCraft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:10 +01:00
David Mann 9ec0f51dc1 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/ui-egui/src/i18n/nb.tsv
2026-10-31 13:55:06 +01:00
aiongg 6d737c219e docs: tidy agent, contributor and release docs 2026-10-31 04:17:45 +00:00
dotCooCoo 1001972059 Merge main into feat/equalize-entire-image 2026-10-30 12:30:15 -07:00
David Mann f5ae6d5276 Merge main into pr-9274-fixup
# Conflicts:
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/dialogs/adjust_editors/curves.rs
#	docs/gaps.md
#	docs/target-app-parity.md
#	docs/ui-parity.md
2026-10-30 13:16:39 +01:00
midasdf d461820117 Merge origin/main into jp/print-paper-sizes
docs/target-app-parity.md status line: main's newest (2026-10-27) plus the Japanese paper-size clause at its previous position; both tables kept main's rows.
2026-10-30 12:01:48 +09:00
FredApps 29bf828ec2 Merge upstream main into reviewed performance integration
Preserve both UI parity status entries; retain all reviewed performance changes.
2026-10-29 23:35:19 +01:00
dotCooCoo 9b30424431 Merge commit 'refs/check/bf-9604-main' into bf-9604
# Conflicts:
#	crates/engine/src/atn_cmds.rs
#	crates/engine/src/cmd/extra_cmds.rs
#	crates/ui-egui/src/dialogs.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/menus.rs
#	docs/target-app-parity.md
2026-10-28 15:01:46 -07:00
RickiNanoandClaude Opus 5.5 599b85a7cd Merge main into prefs/gpu-adapter-typed-read
Integration batch 9 (#9775) raised the preference count to 181 and
added its own 2026-10-27 status lines and history rows to gaps.md and
ui-parity.md. Both sides are kept: the scorecard regenerates to 11 of
181 do nothing, the branch's entries say 181 and are dated 2026-10-27,
above main's 12-of-181 rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:30:17 +01:00
RickiNanoandClaude Opus 5.5 3aff459ad8 Merge main into prefs/gpu-adapter-typed-read
Picks up the export speed work (#9877) and the rest of main. Main's
prefs audit now counts 180 preferences, so the regenerated scorecard
reads 11 of 180 do nothing, and the branch's doc entries say 180 to
match; the 2026-10-25 history rows keep their 178.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:27:58 +01:00
RickiNanoandClaude Opus 5.5 5558e4a2d0 Merge main into prefs/gpu-adapter-typed-read
Main split crates/engine/src/prefs.rs into prefs/ and moved the
gpu_startup tests to gpu_startup/tests.rs: read_section now sits beside
read_lenient in prefs/store.rs, and the malformed-sibling assertions
moved with the tests. Doc status lines and revision rows keep both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:32:23 +01:00