Skip to content

fix: three post-rebuild regressions — asar asset paths, tilt feather at radius 0, blank-MIME uploads#170

Merged
EtienneLescot merged 3 commits into
feat/ai-editionfrom
claude/post-rebuild-regressions-7883c2
Jul 26, 2026
Merged

fix: three post-rebuild regressions — asar asset paths, tilt feather at radius 0, blank-MIME uploads#170
EtienneLescot merged 3 commits into
feat/ai-editionfrom
claude/post-rebuild-regressions-7883c2

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Three regressions reported after rebuilding from release/1.8.0, with the worry that the 2026-07-26 history reorg (317 → 126 commits) had dropped features.

It hadn't. poc-d3d/src/*.rs — the whole native renderer — is byte-identical between backup/ai-edition-pre-reorg and release/1.8.0; the zoom/tilt/corner fixes all survived. The cursor, background and zoom sources differ only by benign refactors (a shared clamp, reusing the gradient parser), assets are intact, and the freshly built compositor_view.node hashes identical to the one in the installer. Two unrelated real bugs, neither caused by the reorg.

1. Cursor themes and bundled backgrounds — a packaging bug

The native compositor is a Rust addon calling image::open on raw OS paths. resolveSceneAssetPaths rooted those at VITE_PUBLIC, which is <root>/public in dev but <resources>/app.asar/dist when packaged. Vite copies public/ into dist, so the files are there — inside the archive, where only Electron's patched fs can read. Both features failed silently on the native side's fallbacks: default cursor art, flat background colour.

This is why it looked new: the app is now run as a packaged installer rather than npm run dev. The bug was latent all along and is invisible in dev.

The clue that cracked it: a custom uploaded background worked the whole time, because it travels as a data: URL the addon decodes in memory (decode_data_uri), never as a path. Feature works with a data URL but not a file path ⇒ path resolution, not rendering.

Fix follows the layout contract already documented in electron-builder.json5 and already used by ASSET_BASE_DIR in electron/windows.ts: extraResources copies both trees to <resources>/{wallpapers,cursors} as real files. Candidate bases are probed for existence rather than branching on app.isPackaged, mirroring ffmpegSharedBinCandidates right below it, which keeps --dir staging builds and tests working.

Verified against the real installed build — every path the old code produced is missing, every path the fix produces exists:

wallpapers/wallpaper1.jpg          OLD: MISSING <res>\app.asar\dist\...  NEW: <res>\wallpapers\wallpaper1.jpg
cursors/hello-kitty.../arrow.png   OLD: MISSING                          NEW: <res>\cursors\...\arrow.png

This resolver also feeds exportMulti, so bundled wallpapers and themed cursors were missing from rendered exports too, not only the preview.

2. 3D zoom read as truncated at Roundness 0

The tilt path (mode 8) decides coverage with a binary r.z < 0.5 reject, and the 1.5px SDF feather that turns that staircase into an edge sat behind if (radius_px > 0.0). At the slider's default of 0 the plane kept hard aliased edges — exactly what the branch's own comment describes: "des arêtes de couteau qui coupent le contenu en pleine phrase, et ça se lit comme une troncature plutôt que comme une inclinaison." Hence "truncated, but not above some minimum corner radius".

sd_round_rect degenerates to a plain box SDF at r = 0, so the guard bought nothing. Dropped, clamped with max(radius_px, 0.0). The mode 12 shadow already applies its radius unguarded for the same reason.

Geometry was never at fault — every_preset_stays_inside_the_original_rect already passes, so nothing was being clipped by the frame.

3. Custom uploads silently dropped on a blank MIME type

handleFileSelected gated on file.type.startsWith("image/"), so a file the browser reports no MIME type for (Windows does this for some files and locales) was dropped with no toast, no message. That case had been fixed once in "Allow PNG custom background uploads", but isSupportedBackgroundImageType was never imported by the upload path — only by its own test — so the reorg removed it as dead code, correctly, and the repo lost the fallback while keeping the weaker check that needed it.

Restored at the upload path. accept is now derived from the same two lists the validation reads, since those had already drifted once (the inline IMAGE_ACCEPT was a copy of the deleted module's constant). An explicit non-image type is still refused, so notes.txt renamed to notes.png does not get through. Rejections and read failures now raise a toast, localized across all 13 locales.

Verification

  • 1122 tests / 97 files pass, tsc --noEmit clean, biome clean.
  • 67/67 native Rust tests pass, including the HLSL compile test that covers the shader edit.
  • New: 5 service tests for the asset resolution, and 9 upload tests — the four recovered cases (incl. 生成画像1.png with a blank MIME type) plus two component tests that render BackgroundPane, fire a real change, and assert the toast actually reaches the user. All would fail against the previous code.

Fixes 1 and 2 need a rebuild to observe (npm run build:win) — one lives in dist-electron, the other in the addon.

Notes for review

  • The sonner toast in RightPanes.tsx is the pane's first, but the established idiom in this shell (ExportDialog, LeftPanel).
  • I did not touch the legacy video-editor upload path; nothing there referenced the deleted module either, so whether a second uploader needs the same treatment is a separate question I'd rather confirm than assume.
  • Mid-diagnosis I floated a theory that the live preview ignored the scene's padding; the "custom upload does display" observation killed it (padding is baked into the app-resolved screen rect). No code shipped from it, noted only so the same dead end isn't retried.

… wallpapers load

The native compositor is a Rust addon that opens asset paths with `image::open`.
`resolveSceneAssetPaths` handed it paths rooted at `VITE_PUBLIC`, which is
`<root>/public` in dev but `<resources>/app.asar/dist` in a packaged build: vite
copies `public/` into `dist`, so the files are in there, but inside the archive.
Only Electron's patched `fs` reads through an asar, so every bundled wallpaper
and every themed cursor sprite failed to load in an installed app — silently, on
the native side's colour/default-art fallbacks. Picking a cursor theme looked
like it did nothing, and a bundled image background came out a flat colour.

A CUSTOM uploaded background kept working the whole time, which is what pinned
it down: it travels as a `data:` URL the addon decodes in memory, never a path.

`extraResources` already copies both trees to `<resources>/{wallpapers,cursors}`
as real files — electron-builder.json5's own "Asset layout contract" comment
names exactly this, and `ASSET_BASE_DIR` in electron/windows.ts already resolves
them that way for the renderer. So probe the bases in order and take the one
that actually holds the file, the same candidate-list idiom
`ffmpegSharedBinCandidates` uses right below. Existence-probing rather than
branching on `app.isPackaged` keeps `--dir` staging builds working too.

This resolver also feeds `exportMulti`, so the same assets were missing from
rendered exports in installed builds, not just from the preview.
…stops reading as truncated

The tilt path (mode 8) decides coverage with a binary `r.z < 0.5` reject, and the
1.5px SDF feather that turns that staircase into an edge sat behind
`if (radius_px > 0.0)`. With the Roundness slider at 0 — its default — the plane
kept hard aliased edges, which is precisely the failure this branch's own comment
describes: "des arêtes de couteau qui coupent le contenu en pleine phrase, et ça
se lit comme une troncature plutôt que comme une inclinaison". Hence the report
that the 3D zoom looks cut off, but only below some minimum corner radius.

`sd_round_rect` degenerates to a plain box SDF at r = 0, so the guard bought
nothing: drop it and clamp with `max(radius_px, 0.0)`. The mode 12 shadow already
applies its radius unguarded for the same reason.

The geometry was never the problem — `every_preset_stays_inside_the_original_rect`
already holds, so nothing was being clipped by the frame.
…en a file is refused

`handleFileSelected` gated custom wallpaper uploads on
`file.type.startsWith("image/")`, so a file the browser reports no MIME type for
— which Windows does for some files and some locales — was dropped on the floor,
with no toast, no message, nothing. The user picked a PNG and the pane simply did
not react.

That exact case had been fixed once, in "Allow PNG custom background uploads":
`isSupportedBackgroundImageType` fell back to the file extension when `type` was
blank, and its test named a real offender (`生成画像1.png`). The module was never
imported by the upload path though — only by its own test — so the 2026-07-26
reorg removed it as dead code, correctly, and the repo lost the fallback while
keeping the weaker check that needed it.

So restore the fallback where the upload actually happens, and derive the
`accept` filter from the same two lists the validation uses, since those had
already drifted apart once (the accept string was an inline copy of the deleted
module's constant). An explicit non-image type is still refused — only a blank
one earns the extension fallback, so `notes.txt` renamed to `notes.png` does not
get through.

Rejections and unreadable files now raise a toast, localized across all 13
locales. Tests are the recovered cases plus a component test asserting the toast
actually reaches the user, which is the half that was really missing.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 90954120-c0a0-4bf7-a237-6c5c39c229f1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/post-rebuild-regressions-7883c2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot
EtienneLescot merged commit f7a020d into feat/ai-edition Jul 26, 2026
8 checks passed
@EtienneLescot
EtienneLescot deleted the claude/post-rebuild-regressions-7883c2 branch July 26, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant