Adversarial review surfaced four defects and four drive-bys. All applied:
B1 (security, MEDIUM) — Path traversal via encodeRelativeAssetPath.
encodeURIComponent passed "." and ".." through unchanged; percent-encoded
"%2e%2e" got decoded by the URL constructor. Either form escaped the
asset root: new URL("../../etc/passwd", "file:///opt/Openscreen/resources/")
→ file:///opt/etc/passwd. Reject both at src/lib/assetPath.ts via a new
UnsafeAssetPathError thrown when a decoded segment equals "." or "..".
B2 (correctness) — classifyWallpaper returned { kind: "image" } for
conic-gradient(...), rgb(...), hsl(...), oklch(...), empty string,
and named colors like "red". Old frameRenderer's bare fillStyle = value
handled these; new code would throw BackgroundLoadError with misleading
message. Classification now anchors on regexes, accepts all CSS color
functions and all three gradient types, treats unknown strings as
fallthrough color (old behavior), and normalizes "" to "#000000".
B3 (SSOT) — DEFAULT_WALLPAPER, projectPersistence.WALLPAPER_PATHS, and
SettingsPanel.WALLPAPER_RELATIVE independently hardcoded the same
/wallpapers/wallpaperN.jpg pattern. Three drift sites collapse into one:
WALLPAPER_PATHS lives in src/lib/wallpaper.ts, DEFAULT_WALLPAPER derives
from WALLPAPER_PATHS[0], projectPersistence re-exports from the canonical
module, SettingsPanel imports it directly.
B4 (privacy) — BackgroundLoadError.message and the translated toast
surfaced full file paths like file:///home/<user>/…/wallpaper.jpg —
leaks the user's home directory in copy-pasted bug reports. Added a
displayUrl getter that returns just the basename (or "data:…" for data
URIs), wired into the toast. Full URL remains in console.error and
error.url for debugging.
N1 — resolveImageWallpaperUrl now rejects image paths that don't live
under /wallpapers/ (throws BackgroundLoadError). Narrows the blast
radius of the returned <resourcesPath>/ base so the renderer can only
request files within the wallpapers directory, regardless of what the
project JSON claims.
N2 — videoExporter retry loop no longer calls cleanup() twice in the
BackgroundLoadError branch; the finally handles it.
N3 — Browser tests assert BackgroundLoadError.url contains the failing
path. Guards the {{url}} i18n interpolation contract.
N4 — VideoPlayback wallpaper resolve effect now catches resolver
throws (UnsafeAssetPathError, BackgroundLoadError from /wallpapers/
prefix enforcement). Prevents the new strict-rejection logic from
silently leaving the preview without a background.
Tests: 35 unit tests pass (up from 20); new coverage for all color
functions, all gradient types, empty string, named color fallback,
whitespace trimming, /wallpapers/ prefix enforcement, traversal
rejection, percent-encoded traversal rejection, displayUrl basename
and data-URI abbreviation.
Three independent defects plus one SSOT violation caused reported symptom
of image wallpapers rendering solid black in exported MP4/GIF while
appearing correctly in the editor preview.
Bug A — Dev-mode IPC handler returned <appPath>/public/assets/, but
wallpapers live at public/wallpapers/. No assets/ subdirectory exists in
source.
Bug B — FrameRenderer.setupBackground bypassed getAssetPath and did
window.location.origin + wallpaper, producing file:///wallpapers/*.jpg
404s in packaged Electron.
Bug C — setupBackground silently caught any background-load error and
filled black. Masked Bug B from the export pipeline; why the bug shipped.
Smell D — Asset layout asymmetric: public/wallpapers/ (dev) vs
resources/assets/wallpapers/ (packaged). assets/ subdirectory had no
other consumers.
Fixes:
- Unify asset layout. electron-builder extraResources now copies to
resources/wallpapers/ (no assets/). Main handler returns
<resourcesPath>/ packaged and <appPath>/public/ unpackaged. Same
convention in both modes: /wallpapers/x.jpg maps to <base>/wallpapers/x.jpg.
Nix package.nix mirror updated.
- New src/lib/wallpaper.ts module owns the wallpaper contract:
DEFAULT_WALLPAPER, classifyWallpaper (color/gradient/image), and
resolveImageWallpaperUrl (pure URL resolver, wraps getAssetPath).
BackgroundLoadError typed error for short-circuit detection.
- FrameRenderer.setupBackground uses the new helpers. Silent black
fallback removed; rethrows as BackgroundLoadError. Export pipeline
(VideoExporter + GifExporter) short-circuits encoder-retry loop on
BackgroundLoadError. VideoEditor catch site dispatches to translated
exportBackgroundLoadFailed toast.
- VideoPlayback editor preview consolidated onto the same helpers.
Three default-wallpaper path literals (useEditorHistory,
projectPersistence, VideoPlayback) collapsed onto DEFAULT_WALLPAPER.
- i18n: new errors.exportBackgroundLoadFailed key added to all seven
locales (en, zh-CN, zh-TW, es, fr, tr, ko-KR).
- Tests: 20 unit tests for wallpaper module (classifyWallpaper +
resolveImageWallpaperUrl branches + BackgroundLoadError).
videoExporter.browser.test.ts and gifExporter.browser.test.ts extended
with image-wallpaper happy path and BackgroundLoadError failure path.
Migration note: packaged users upgrading in place may retain an empty
resources/assets/ directory from the prior layout. Unreferenced at
runtime; cosmetic only. DMG/AppImage fresh installs get the new layout
directly.
- Remove trailing comma in SUPPORTED_LOCALES that caused Locale type to
include undefined, fixing all downstream type errors
- Remove unused webcamSizePreset from useMemo dependency array
- Use parsed.toString() instead of raw url in shell.openExternal per
Electron security best practice
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Added corner-shape: squircle; to SourceSelector.module.css for more visually appealing rounded corners.
Customized windows source selector scrollbar to be more subtle but carry the product colour.
Removed box-shadow on SourceSelector because electron doesn't round corners of the shadow, thereby leaving a square border shadow conflicting with the rounded corners of the SourceSelector.
- derive available locales from locale folders with required namespace validation
- exclude incomplete locales and report missing namespace files
- align system-language suggestion and selectors with discovered locales
- improve launch HUD language menu interaction, scrolling, and viewport clipping
- make i18n-check discover locale folders automatically