- Implement native bridge for Windows cursor capture via PowerShell/C#
- Add cursor-free capture using getDisplayMedia with setDisplayMediaRequestHandler
- Update video player and exporters to support native cursor telemetry
- Enable system audio capture on Windows via WASAPI loopback
- Add interpolation for smoother cursor movement in playback and export
- Improve cursor scaling and visibility handling in editor and playback
The right-side trim handle could be dragged past the end of the
timeline because clampSpanToBounds did not cap the computed end
value at totalMs. This adds Math.min(…, totalMs) so the handle
snaps to the timeline edge.
Fixes#393
- Clamp and NaN-guard customScale in getZoomScale (defensive sanitization)
- Set customScale on preset button click so slider stays green
- Set customScale on new zoom region creation so slider lights up immediately
Adds a Radix UI slider below the zoom preset buttons allowing any scale
between 1.0x and 5.0x. When the slider value matches a preset exactly,
that preset button also shows as active.
- Add `customScale?: number` to `ZoomRegion` and `getZoomScale()` helper
that returns customScale when set, falling back to ZOOM_DEPTH_SCALES[depth]
- Overlay indicator, playback renderer, and frame exporter all use
getZoomScale() so preview, playback, and export are consistent
- Fix focus clamping in zoomRegionUtils and frameRenderer to use actual
scale instead of depth-based preset scale, preventing zoom drift with
custom values
- Fix drag boundary in VideoPlayback to use clampFocusToScale with the
actual scale so the full canvas is clickable at high custom zoom levels
- Timeline item label shows custom scale value when set
- Slider styled dark with green thumb/fill when a custom (non-preset) value is active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new 'No Webcam' option to the webcam layout preset dropdown in the editor. When selected, the webcam feed is completely hidden from both the preview and the exported video, allowing users who recorded with a webcam to exclude it from the final output.
- Add 'no-webcam' to WebcamLayoutPreset type union and preset map
- Handle 'no-webcam' in computeCompositeLayout (returns webcamRect: null)
- Add 'no-webcam' case in project persistence normalization
- Add 'No Webcam' option to the layout preset dropdown in SettingsPanel
- Add 'noWebcam' i18n translation key (en)
Resolved conflicts in src/App.tsx and src/components/launch/LaunchWindow.tsx:
- App.tsx: kept main's split useEffect for loadAllCustomFonts; placed PR's
HUD-overlay style block inside the original [windowType] effect.
- LaunchWindow.tsx: kept main's systemLocaleSuggestion modal in place of the
earlier inline language switcher; preserved PR's root-div className change
that fixes the Windows horizontal-scrollbar bug.
./openscreen.png resolves correctly both in dev (Vite serves public/)
and in production (loadFile sets base to dist/, where public assets land
inside the asar). getAssetPath points to extraResources, which is the
wrong location for bundled dist assets.
- Replace anonymous Error in resolveImageWallpaperUrl with typed
UnsafeImagePrefixError, mirroring UnsafeAssetPathError so cause
chains stay discriminable.
- Replace `(err as BackgroundLoadError).cause` casts in wallpaper
tests with instanceof narrowing (no `as` per project rules).
- Remove unused `WALLPAPER_PATHS` re-export from projectPersistence;
consumers import directly from @/lib/wallpaper (SSOT).
Reviewer audit found two real risks in the prior amendment:
1. LEGACY_FILE_WALLPAPER_RE was too permissive. Any file:// URL
containing /wallpapers/wallpaperN.jpg would match — including a user's
own file at /home/me/wallpapers/wallpaper1.jpg that happened to share
the name pattern. Silent data-loss potential: user's photo replaced
with a bundled asset. In-app upload flow uses data: URIs today so it
can't actually produce such a value, but the regex should be tight
on intent. Now requires a known install-layout segment:
resources/[assets/]wallpapers/ (packaged) or public/wallpapers/ (dev).
2. No upper bound on \d+. A corrupted or future-schema project with
wallpaper99.jpg was silently rewritten to /wallpapers/wallpaper99.jpg
which 404s. Now validates against WALLPAPER_PATHS; out-of-set
bundled-looking values fall back to DEFAULT_WALLPAPER.
Also applied R2.2 defensive guard: resolveImageWallpaperUrl's catch
block now checks instanceof BackgroundLoadError and rethrows unchanged
instead of wrapping a second time. Current getAssetPath cannot throw
BackgroundLoadError so this is a future-proof against refactors.
Tests: 56 pass (up from 54). Added coverage for "user file outside
install dir stays untouched" and "bundled-looking but out-of-set falls
back to default".
R1 — Persisted wallpaper is now always the canonical /wallpapers/wallpaperN.jpg
form, never the resolved file:// URL. Swatch clicks pass WALLPAPER_PATHS[i]
(the relative path) to onWallpaperChange; the resolved URL stays in
wallpaperPreviewUrls for rendering only. This prevents machine-specific paths
from being written into project JSON and avoids break-on-upgrade /
break-on-share regressions. Legacy projects carrying resolved file:// URLs are
rewritten by a new normalizer in normalizeProjectEditor:
file://…(/assets)?/wallpapers/wallpaperN.jpg → /wallpapers/wallpaperN.jpg.
R2 — resolveImageWallpaperUrl now catches anything getAssetPath throws
(UnsafeAssetPathError, AssetBaseUnavailableError) and rewraps as
BackgroundLoadError with the original as cause. Callers (videoExporter retry
loop, gifExporter catch, VideoEditor toast) only need one instanceof check and
users always see the translated errors.exportBackgroundLoadFailed toast.
R3 — src/vite-env.d.ts no longer duplicates Window.electronAPI. The interface
had drifted — renderer declaration was missing readBinaryFile, getPlatform,
revealInFolder, getShortcuts, saveShortcuts, hudOverlay*, countdown overlay
methods that electron-env.d.ts already declares. Removed the duplicate and
kept the triple-slash reference so the authoritative declaration is the one
in electron/electron-env.d.ts.
N1 — GRADIENT_RE accepts optional "repeating-" prefix so
repeating-linear/radial/conic-gradient values classify as gradients instead
of falling through to color.
N2 — displayBasename returns "(unknown)" sentinel for URLs without a
meaningful basename (file:///, bare /) instead of leaking the original string.
N3 — electron-builder.json5 extraResources block gets an inline comment
pointing at preload.ts:assetBaseDir so the bidirectional coupling is
discoverable from either file.
Tests: 54 unit tests pass (up from 35). New coverage for repeating
gradients, displayBasename sentinels, BackgroundLoadError cause wrapping,
legacy file:// wallpaper normalization (5 cases).