Commit Graph
1193 Commits
Author SHA1 Message Date
Brandon Thomas 29384a0404 Merge PR #1898: LR-MERGE-HEADLESS: merge dialogs remember the last settings per kind 2026-10-18 09:20:21 -04:00
Brandon Thomas 11308a5c8a Merge PR #2035: Reject out-of-range start in photo.rename and photo.renamePreview 2026-10-18 09:18:17 -04:00
Brandon Thomas 9bf08f6ce6 Merge PR #2083: Reject signed hex digits in #rrggbb colour parsing 2026-10-18 09:18:16 -04:00
Claude 23f00822eb #1927: Auto Sync fits each photo's own crop under Constrain Crop
The crop fit ran before the Auto Sync delta, so a Geometry edit carried
the active photo's fitted crop rect into the delta and onto every other
selected photo, replacing (and could enlarge) their own crops. The delta
is now taken from the edit as asked, and each target fits its own crop.
Review on #1990.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
2026-10-18 12:37:56 +00:00
Carlos VillanuevaandClaude Opus 5.5 ad43a976f3 Lightroom import: one warning per skip reason; skip incomplete original paths too
A catalog with many orphaned images produced one warning line each (82 in
the reported catalog). Skipped images are now listed in one warning per
reason, showing the first ten ids. An image whose original path is empty is
skipped the same way instead of failing the whole import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhZLBHaSnSko1eMByPibfh
2026-10-18 12:33:18 +00:00
Carlos VillanuevaandClaude Opus 5.5 9c812b88a9 Lightroom import: skip images whose file record is missing instead of failing
A catalog can keep Adobe_images rows whose AgLibraryFile, AgLibraryFolder
or AgLibraryRootFolder record is gone; Lightroom ignores them, but the import
stopped at the first one with "image N: missing file record". Such images,
and their virtual copies, are now skipped with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhZLBHaSnSko1eMByPibfh
2026-10-18 12:19:10 +00:00
Davit MatchakhelidzeandClaude Opus 5.5 07b2f55022 fonts: Georgian UI text from craft-fonts' Noto Sans Georgian on the web
Native already appends every non-CJK craft face via ui_fallback; the wasm build
only embedded BIZ UDPGothic and Noto Sans Thai, so add Noto Sans Georgian to
that filter. Check its glyphs next to Thai and Arabic, and bump craft-fonts-ref
to a71c20e (storytold/craft-fonts#27).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-18 16:14:56 +04:00
Marco Arnulfo eabcae0fdc LR-EXP-BITDEPTH: judge a preset's bitDepth against the format the export writes
A preset without `format` lost its `bitDepth` before the call's `path` was merged in,
because it was judged against JPEG: `app.export {preset, path: a.png}` with a 16-bit
preset wrote an 8-bit PNG. Session::export_params now merges first and drops a preset's
depth only if the format written (with the caller's fallback) lacks it; a depth the call
passes is still validated. export.savePreset judges the depth at once only when the preset
has a `format`.
2026-10-18 13:45:08 +02:00
Marco Arnulfo 302faf547b LR-EXP-BITDEPTH: check bitDepth against the format a path without a known extension gets
app.export with a path like `output` or `output.xyz` and no `format` skipped the depth
check: the desktop app then wrote JPEG and the headless backend PNG, both at 8 bits.
ExportOptions::export_format resolves the format written (with the caller's fallback for
such a path), from_params_or validates against it, and both callers use it instead of
their own override.
2026-10-18 13:27:07 +02:00
NetterSilverandClaude Haiku 5.5 0d2c77c258 Fix Assisted Culling bursts that cross midnight at month end (#2095)
Found while testing crates/engine/src/cmd/cull.rs:17-20 (secs, used by the burst grouping at cull.rs:45)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-17 18:38:33 -06:00
skylar taylor-barrick 2218d97e89 Merge pull request #2199 from skylartaylor/open-original-media-gate
Open Original: only file types LightCraft reads reach the system opener
2026-10-17 17:07:18 -06:00
FredApps d515f9affe Merge main into file-backed Lightroom catalog import (#798)
Preserve the file-backed reader and current upstream safeguards.
Resolve documentation conflicts without integrating unmerged work.
2026-10-18 00:49:15 +02:00
skylar taylor-barrickandClaude e6587c3f82 chore: clear the rustc 1.96 clippy lints that fail cargo xtask ci
Unrelated to the #2135 security fix in the commit before this one: both lines
predate this branch and are rustc/clippy 1.96 lint churn. `cargo clippy
--workspace --all-targets` only warns about them, but `cargo xtask ci` runs it
with `-D warnings` (xtask/src/main.rs), so the gate fails before any test runs.

- `clippy::nonminimal_bool` in `slide_overlay::shadow` (reported twice, two
  overlapping suggestions on the one expression): `!(A && B) || !C || !D` is
  `!(A && B && C && D)` by De Morgan, which is what the line now says. The
  operands are pure float comparisons and `is_finite` guards with no side
  effects, so this is an exact rewrite — a NaN or infinite `mx0`/`my0` still
  makes `shadow` return without drawing, as before. (Clippy's second suggestion
  drops the `my0.is_finite()` term and is *not* equivalent; it is not used.)
- `clippy::drop_non_drop` on `drop(write)` in
  `same_folder_as_original_refuses_photos_without_a_folder`. The line is not
  dead: `write` is a closure holding a mutable borrow of `written`, and the
  `drop` ended that borrow so the next assertion could read it. The closure now
  lives in a block of its own instead, which ends the borrow at the brace
  rather than relying on `drop` of a non-`Drop` type. Every assertion of the
  test is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-17 10:33:47 -06:00
skylar taylor-barrickandClaude d54673bb95 #2135: only file types LightCraft reads reach the system opener
Photo ▸ Edit In ▸ Open Original in Other Editor handed a photo's stored path
straight to `open` / `xdg-open` / `explorer.exe`, with `Path::is_file()` as the
only check, and Lightroom catalog import kept original paths exactly as the
catalog had them. A crafted `.lrcat` could therefore put `payload.exe`,
`start.lnk`, `run.bat`, `page.hta`, `Evil.app` or `go.command` into the library
and have it run on one user click.

- `lightcraft_engine::open_path`: one gate for every path that leaves
  LightCraft for the OS. The extension must be one LightCraft reads
  (`import::EXTENSIONS`, which already covers the Edit In copy formats
  `tif`/`tiff`/`psd`), it must be a regular file (so a `.app` bundle and any
  other folder are out), what a link resolves to must be a read type too, and
  what is handed over is the absolute path. A refusal is the sentence shown to
  the user.
- Open Original and Edit In go through it; a refused photo is listed in
  `skipped` with the reason, and the menu item is greyed out for a record that
  can never be opened (extension only, so no disk or share is touched while a
  menu is drawn).
- `lightroom_catalog::refuse_non_media` drops such images where the catalog is
  read and again where its photos become records, with a warning each, so a
  refused entry never becomes an openable library record. A virtual copy of a
  refused original goes with it.

Tests: a catalog naming a program (and a virtual copy of it) imports neither
while the catalog's real photos do; a library record naming a program is never
handed to the stub launcher, which records no invocation, and a program
selected beside a photo leaves only the photo, by absolute path. Both fail
before this change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-17 10:18:47 -06:00
NetterSilverandClaude Haiku 5.5 fafa70724b Keep face index records aligned after a torn or failed append
Fixes #2129

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:33:33 +02:00
NetterSilverandClaude Haiku 5.5 414764ef77 Stop export lanes when a panic unwinds the batch thread
Fixes #2132

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 11:25:12 +00:00
Alex Chambers 005881ce94 Issue #2098: reject malformed mask and spot targets 2026-10-17 07:42:38 +02:00
Alex Chambers de44200a8d Issue #2097: reject fractional command indices 2026-10-17 07:40:34 +02:00
NetterSilverandClaude Haiku 5.5 80e439f6dc Reject signed hex digits in #rrggbb colour parsing
Found while testing crates/slideshow/src/settings.rs:296-300 (parse_color); same pattern in crates/engine/src/slideshow.rs:117-122 (fadeColor)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 00:45:25 +00:00
helgehelge123andClaude Opus 5.5 53f57b9404 LR-MASK-OBJECTS: SAM 3 falls back to the CPU when the GPU can't run it (#1974)
On an Intel Mac with an AMD Radeon Pro 5300M the Metal compiler crashes
on candle's mlx_gemm kernel, candle-metal-kernels panics on the compile
error, and the worker reported "the AI model failed unexpectedly" and
loaded the model on Metal again for every next job, so AI masks never
worked there.

The worker now retries a job that fails on the GPU (a panic or a candle
error) once on the CPU, after freeing the model. When that works, the
model stays on the CPU for the session and the job's outcome carries a
notice that segment_poll shows once; when the CPU fails too, the GPU
was not the cause and the next job uses it again. Missing or damaged
model files, render and remote-worker errors are not retried. Only
builds with a GPU path (macOS) retry; LIGHTCRAFT_GPU=0 and
LIGHTCRAFT_GPU_BACKEND=off now keep SAM 3 on the CPU from the start
(lightcraft_gpu::env_off_reason), like AI Denoise and the face models.

lightcraft-segment: Sam3::load_cpu, GPU_BUILD; text_logits returns the
crate's Error so Describe failures are classified like clicks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:55:32 +02:00
aiongg 7b934ca2b2 Sidecars: write XMP only beside an existing local original 2026-10-16 17:57:30 +00:00
aiongg 346681561a Lightroom import: follow catalog paths only on this computer or the catalog's share 2026-10-16 17:44:52 +00:00
NetterSilverandClaude Haiku 5.5 312dd04830 Reject out-of-range start in photo.rename and photo.renamePreview
Found while testing crates/engine/src/rename.rs:919 (start parsed at crates/engine/src/cmd/manage.rs:12)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 16:21:24 +02:00
Matthew James SivakandClaude Opus 5.5 0d30c99e87 LR-VIEW-CLIPPING: merge main into #937 (⌥-drag previews)
Conflict only in docs/parity-checklist.md: the generated summary is
refreshed with `cargo xtask parity --write`, and the Last updated line
keeps main's format with this PR's change note (today's date) instead of
the duplicated note the branch carried. ⌥-drag preview still wins over
AllMasks and the selected mask in view_overlay; overlay kinds 6–9 unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:52:08 +02:00
ubergenericandClaude Opus 5.5 7db0a64d7e Detail: Fit is as sharp as the photo (display fit, whole-pixel draw, Lanczos decode)
At Fit the loupe was softer than a good downscale of the same file. Four
resamples stacked up between the source and the screen:

- the render edge is rounded up to a 64 px step, so the texture was drawn up
  to ~6 % smaller than its size and the GPU shrank it with 2-tap bilinear
  sampling;
- the fitted texture sat on a fractional pixel position, so even at the right
  size every screen pixel was averaged with its neighbour (#1831 fixed the
  same for 100 %);
- the render box truncated its short side (2560 x 1706 for a 2560 x 1707
  source), a slightly anisotropic resample that blurred one axis only;
- the codec's decode-time shrink at 2:1 and more, and SourceRef::Fit, used a
  box filter, which halves the contrast of detail near the output's limit.

RenderJob::display_fit shrinks a full-quality render that is larger than its
drawn size to exactly that size with Lanczos-3 on the worker (after the caches
have the full render; drafts untouched; part of the key). The Detail view sets
it for the After and Before renders at rest, and snap_fit_rect draws a texture
that is its drawn size on whole physical pixels. The render boxes round their
short side, and the 2:1-and-more shrinks use Lanczos-3.

Refs #1472, #1042.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:28:12 +11:00
Matthew James SivakandClaude Opus 5.5 62e82479d2 LR-PROF-CAMERACOLOR: fit a DNG's tone with a look table and a 1024 px preview
A DNG whose profile has a LookTable but no ProfileToneCurve (Adobe Standard in
Lightroom-written DNGs) was not tone-fitted and rendered with the near-linear
default tone: flat and ~0.8 EV darker than Lightroom. A look table defines colour,
not tone, and the fit already applies the tables to its proxy. The preview may now
be 10% of the raw's long edge (and 384 px), so Lightroom's 1024 px previews
qualify. Two lossy ILCE-7CM2 DNGs now match Lightroom's mean linear RGB within 3%.
Render cache 65, look version 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:46:01 +02:00
LochLogicandClaude Opus 5.5 2e0906b71a Tests: make the Windows-only failures pass (paths with /, MCP sockets vs child processes)
On Windows 11, `cargo test --workspace` on main fails 11 tests:

- tests_export::same_folder_as_original_exports_next_to_each_photo built its
  expected paths with Path::join (`\`), but export joins the subfolder and
  file name with `/` (join_path) on every platform. Expect what export writes.
- tests_folder_drop (8 tests) looked rows up as
  `source:libfolder:C:\...`, while the Folders section names them with `/`
  (`source:libfolder:C:/...`), so no row was found. Look them up the same way.
- mcp backend::tests::{valid_replies_and_errors_are_preserved,
  malformed_replies_are_rejected_without_resending} failed only when the whole
  crate ran: the shutdown and one-shot-process tests start a copy of the test
  binary, the child can inherit a socket a fake server just accepted, and when
  the child exits Windows closes its copy abortively, resetting the test's
  connection. A test-only lock keeps socket tests and child-process tests from
  overlapping (passes 6 of 6 whole-crate runs, failed 4 of 4 before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:11:32 -06:00
Marco Arnulfo e14db89462 LR-EXP-BITDEPTH: app.export refuses a bitDepth the format doesn't have (part of #1952) 2026-10-16 02:21:07 +02:00
Claude 54c6c43b07 #1927: Constrain Crop tightens the stored crop
Constrain Crop only applied at render time, and the Crop tool shows the
uncropped view with the stored crop, so in the Crop panel (where the
checkbox is) ticking it or moving a Geometry slider changed nothing.
Session::set_develop now shrinks the stored crop about its centre until it
fits the warped photo when Constrain Crop is turned on, or when an edit
changes the warp (Geometry, Upright, lens corrections, section eyes) while
it is on. A crop edit itself is kept as drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbegtYaPuwqZ81pGfELsFK
2026-10-16 00:01:00 +00:00
FredApps 08e3893731 LR-798: refresh file-backed catalog against main 2026-10-15 22:44:43 +02:00
FredApps 9bec9db541 LR-1670: refresh opaque RGB export against main 2026-10-15 22:24:56 +02:00
Claude 6938be39e4 Engine tests: drop the drop() of a closure (clippy drop_non_drop)
Clippy 1.97 rejects `drop(write)` in the same-folder export test: the
closure has no Drop, so its borrow of `written` already ends at its last
use and the call did nothing. Removed, with a note on why the following
read of `written` compiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2NDVXA8jQQWeJb4YVTb6b
2026-10-15 19:17:57 +00:00
Claude b5dc500c54 Hasselblad 3FR / FFF: uncompressed RGB embedded previews
These files carry no JPEG: their preview is an uncompressed 8-bit RGB
IFD (1217 x 913 in the H4D-40 FFF, 218 x 218 in the CFV 3FR). The
largest such image is handed on by embedded_preview as a small baseline
TIFF, which lightcraft_codecs::decode reads, so 3FR / FFF show a preview
in the grid and loupe before they are developed. No Orientation tag is
written: the raw file's applies, as for JPEG previews without one. The
FFF's 16-bit copy (with a TransferFunction of unknown meaning) is not
used.

decode_raw_preview applied a container-declared colour space by forcing
the JPEG decoder on any owned preview; it now checks for JPEG bytes, so
a wrapped TIFF (or a JPEG XL preview) goes to its own decoder.

Checked on the CC0 H4D-40 FFF and CFV 3FR through
embedded_preview_srgb: right colours, the 3FR upright through its
Rotate90. Tests cover the wrap, the choice among RGB images, a short
strip and truncated files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2NDVXA8jQQWeJb4YVTb6b
2026-10-15 18:58:05 +00:00
fanyzp 5bf0d45ba8 Do not read a sibling's shared stem sidecar on import (#1905)
With stem naming, `IMG_0001.xmp` is the sidecar of every file of that stem and, in a
folder Lightroom has worked on, it is the raw's. On import the packet was looked up
with the library naming alone, so a TIFF or JPEG arriving beside a raw of the same
name read the raw's packet and took its rating, label, keywords and `crs:` develop
settings (the case radialmonster reported in #92; the read side of a catalogued photo
is #1523).

`ImportStems` ranks the files an import is adding as if they were catalogued already,
in the order `StemOwners` uses for writing: the library's files and the arriving ones
together, a raw first, then the file name in lower case. A file that does not own the
stem sidecar reads only its own `<file>.xmp`, never the sibling's packet. A file that
is not catalogued yet has no id, and the id it draws when the import commits is above
every id in the library, so it loses a tie against a catalogued file; between
arriving files the order they arrive in stands for it.

Folder synchronize and the Lightroom catalog import go through the same function, so
a file arriving there is ranked the same way.
2026-10-16 02:20:40 +08:00
Marco Arnulfo 79d726f856 LRC-PRINT-PAGE: Page Options print page numbers, page info and crop marks (gap 15)
The Print dialog's Page section gets Lightroom's Page Options for Single Image
and Contact Sheet: the page number at the bottom right, the print settings line
(sharpening, profile, resolution) at the bottom left, and crop marks at the
corners of each photo. They are `print.set {page: {pageNumbers, pageInfo,
cropMarks}}`, saved with templates, drawn in the preview and composed into the
PDF; marks and text turn white on a dark page.
2026-10-15 15:38:03 +02:00
Matthew James SivakandClaude Opus 5.5 b9b80aee8a LR-BEHAV-BG-TASKS: Reload from Disk reads its files in the background
photo.reload decoded every targeted file on the UI thread, so a selection
of raws froze the window until all were read. The command is now split into
plan / probe / apply (engine `cmd::reload`); the app intercepts photo.reload
and probes the files on a worker thread with a cancellable activity row
(kind `reload`, counted progress), applying the result as one undo step.
`wait: true` keeps the old in-place behaviour for agents; CLI/MCP unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:35:11 +02:00
Matthew James SivakandClaude Opus 5.5 a43cb68593 LR-MERGE-HEADLESS: merge dialogs remember the last settings per kind
The HDR / Panorama / HDR Panorama dialogs now open with the options of the
last merge of that kind instead of the defaults, and those options are kept
in the library's prefs.json (Session::last_merge) so they survive a restart.
"With Last Settings" and HDR for Each Bracket Set read the same store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:04:14 +02:00
Matthew James SivakandClaude Opus 5.5 9a0f076105 LR-VIEW-CLIPPING: ⌥-drag previews for Blacks, Whites, Exposure, Sharpening and NR (#937)
Holding ⌥ (Option) while dragging a slider shows a diagnostic view in the loupe, as in
Lightroom; releasing ⌥ or the slider brings the photo back and nothing is saved:

- Blacks: shadow clipping (white; black where every channel is 0, the colour of the
  unclipped channels where only some are).
- Whites / Exposure: highlight clipping (black; white where every channel is 255, the
  clipped channels' colour where only some are).
- Sharpening Masking: the weight sharpening gets at each pixel, white on black.
- Sharpening Amount / Radius / Detail, luminance NR: the image in grey.

New `Overlay` variants (ShadowClip, HighlightClip, SharpenMask, Grey) are drawn by the shared
`visualize::apply`, so CPU and GPU agree; the sharpening mask needs the detail planes and
renders on the CPU (like HdrRange). The mask uses the same weight function as the render
(`finish::sharpen_weight`). With a display profile the clipping views are tested in sRGB, like
the histogram, and diagnostic views keep the photo's histogram instead of the view's.

Tracker: LR-VIEW-CLIPPING ✅ → 🟡 (J only frames the photo, it doesn't paint clipped pixels)
and LR-EDIT-DETAIL-SHARPEN ✅ → 🟡 (Radius and Detail don't reach the render).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:41:44 +02:00
Brandon Thomas 5e9f7f1a65 Merge PR #1027 (rebased)
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
#	docs/parity-checklist.md
#	docs/target-app-parity.md
2026-10-15 03:40:37 -04:00
Brandon Thomas 9ddb8d1824 Merge PR #1663 (rebased)
# Conflicts:
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
2026-10-15 03:40:17 -04:00
Brandon Thomas 8291f08ab7 Merge PR #1602 (rebased) 2026-10-15 03:40:15 -04:00
Brandon ThomasandClaude Opus 5.5 1d7699056e Merge PR #1027
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:36:18 -04:00
Brandon ThomasandClaude Opus 5.5 2179473536 Merge PR #1663
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:24:51 -04:00
Brandon ThomasandClaude Opus 5.5 00dced3dae Batch 39: #1829's straight brush lines carry pen pressure as the author merged them with #1033 (the whole masks.rs from the PR head)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:07:35 -04:00
Brandon ThomasandClaude Opus 5.5 4212907656 Merge PR #1602
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:02:56 -04:00
Brandon Thomas fd80e4eb2f Merge PR #1829 (updated head) 2026-10-15 03:01:12 -04:00
Brandon Thomas 809259a37d Merge PR #1829 (rebased)
# Conflicts:
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-15 02:32:15 -04:00
Michael Anderson fc6719d1db Merge remote-tracking branch 'upstream/main' into feat/brush-shift-click
# Conflicts:
#	docs/whats-new.md
2026-10-15 01:07:50 -05:00
Brandon ThomasandClaude Opus 5.5 a39e85da72 Merge PR #1829
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:00:02 -04:00
Brandon ThomasandClaude Opus 5.5 e44e7157fc Batch 38: rustfmt and parity summary after the merges
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:58:56 -04:00