Skip to content

feat: add custom zoom slider with continuous scale control (#513)#518

Open
makaradam wants to merge 4 commits intosiddharthvaddem:mainfrom
makaradam:feature/custom-zoom-slider-clean
Open

feat: add custom zoom slider with continuous scale control (#513)#518
makaradam wants to merge 4 commits intosiddharthvaddem:mainfrom
makaradam:feature/custom-zoom-slider-clean

Conversation

@makaradam
Copy link
Copy Markdown

@makaradam makaradam commented May 2, 2026

Closes #513

Adds a continuous zoom slider below the preset buttons (1.25×–5×) that lets users set any scale value between presets.

  • Custom scale stored on ZoomRegion via new customScale?: number field and getZoomScale() helper — falls back to ZOOM_DEPTH_SCALES[depth] for presets, so existing projects are unaffected
  • Overlay indicator resizes correctly for custom scales (previously stuck at the depth-preset size)
  • Preset button activates when slider value matches a preset exactly (e.g. dragging to 1.80 highlights the 1.8× button)
  • Focus drift fixed in both playback (zoomRegionUtils.ts) and export (frameRenderer.ts) — focus clamping was using the depth-based scale instead of the actual custom scale, causing the zoom to land at the wrong position
  • Drag boundary fixed — clicking near canvas edges was incorrectly restricted when using custom scale due to wrong scale being used for clampFocusToScale
  • Slider styled dark with green thumb/fill when a non-preset value is active; reverts to neutral when value matches a preset
  • Timeline label shows custom scale value (e.g. 4.39×) instead of preset label when custom scale is set
  • Export parity — frameRenderer.ts uses the same getZoomScale() helper so exported video matches preview exactly

Test plan

  • Add a zoom event on the timeline, select it, drag the custom slider — overlay indicator should resize in real time
  • Drag slider to a preset value (e.g. 1.50) — that preset button should highlight as active
  • Set a custom scale, drag the focus indicator to an edge — should reach the full canvas without hitting an invisible wall
  • Play back the video — zoom should land exactly where the overlay indicator was positioned
  • Export as MP4 — exported zoom position and scale should match the preview exactly
  • Use a preset button after using custom slider — customScale should clear, preset behavior should be identical to before this PR

Summary by CodeRabbit

  • New Features

    • Added a custom zoom scale slider (1.00×–5.00×) with live adjustment and separate commit action.
    • Timeline, preview overlay and export now display and use the effective custom zoom multiplier (formatted to 2 decimals).
    • Custom zoom scales initialize/align when switching preset depth levels.
  • Localization

    • Added "Custom Zoom" label for the zoom UI.

…vaddem#513)

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>
@makaradam makaradam requested a review from siddharthvaddem as a code owner May 2, 2026 08:35
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e4e3a68-b129-49dc-ad07-072a24c4b00c

📥 Commits

Reviewing files that changed from the base of the PR and between 1133e07 and cc0c1bf.

📒 Files selected for processing (1)
  • src/components/video-editor/VideoEditor.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/video-editor/VideoEditor.tsx

📝 Walkthrough

Walkthrough

Adds optional per-zoom-region custom scale (clamped 1.0–5.0, two-decimal precision), a SettingsPanel Radix slider with live and commit callbacks, persists customScale on region creation/depth changes, and propagates the effective zoom scale via a new getZoomScale helper through timeline labels, playback, overlays, and frame rendering.

Changes

Custom Zoom Scale Feature

Layer / File(s) Summary
Data Shape
src/components/video-editor/types.ts
Adds ZoomRegion.customScale?: number, exports MIN_ZOOM_SCALE, MAX_ZOOM_SCALE, and getZoomScale(region) which returns a clamped custom scale or falls back to depth presets.
Editor model init
src/components/video-editor/VideoEditor.tsx
New zoom regions initialize customScale from depth presets; handleZoomDepthChange updates both depth and customScale.
Live / Commit handlers
src/components/video-editor/VideoEditor.tsx
Adds handleZoomCustomScaleChange (rounds to 2 decimals via updateState) and handleZoomCustomScaleCommit (calls commitState); wires these into SettingsPanel props.
Settings UI
src/components/video-editor/SettingsPanel.tsx
Props extended with selectedZoomCustomScale, onZoomCustomScaleChange, onZoomCustomScaleCommit; adds Radix slider showing effective scale (custom or depth) and updates badge/preset active logic to use effective scale.
Timeline / Labels
src/components/video-editor/timeline/TimelineEditor.tsx, src/components/video-editor/timeline/Item.tsx
TimelineRenderItem includes zoomCustomScale; Item label prefers zoomCustomScale formatted to 2 decimals when present, else uses existing depth label.
Playback / Overlays
src/components/video-editor/VideoPlayback.tsx, src/components/video-editor/videoPlayback/overlayUtils.ts, src/components/video-editor/videoPlayback/zoomRegionUtils.ts
Replaces depth-indexed scale lookups with getZoomScale(region) and uses clampFocusToScale(...) for focus clamping and transition blending.
Frame Renderer
src/lib/exporter/frameRenderer.ts
Removes private clampFocusToStage and switches to getZoomScale() + clampFocusToScale() in animation updates.
Localization
src/i18n/locales/en/settings.json
Adds key "zoom"."customScale": "Custom Zoom".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • siddharthvaddem
  • FabLrc

Poem

a tiny slider finds its place,
scales from one to five with grace,
custom numbers, two decimals neat,
timeline hums, playback keeps the beat,
tiny change, big zoom — nice.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding a custom zoom slider with continuous scale control, which is the primary change across the changeset.
Description check ✅ Passed The description covers the motivation, implementation details, UI behavior, and testing plan comprehensively, though it's missing the 'Type of Change' checklist from the template.
Linked Issues check ✅ Passed The PR fully addresses issue #513 by implementing a continuous zoom slider (1–5×) with 2-decimal precision, persisting custom values per ZoomRegion, and ensuring UI/playback/export consistency.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the custom zoom feature: zoom slider UI, customScale persistence, scale computation helpers, and related focus/clamping fixes in playback and export—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 596-600: The preset-match logic is using strict float equality
(effectiveScale === ZOOM_DEPTH_SCALES[option.depth]) so a slider value that is
numerically equal to a preset but differs by a tiny float error still appears as
"custom"; change to an approximate-equality check (e.g., Math.abs(a - b) <
epsilon) when computing isActive and when comparing selectedZoomCustomScale to
preset scales so values within a small tolerance are treated as the preset;
update places that key off selectedZoomCustomScale (including the slider
change/commit handlers and the effectiveScale calculation in SettingsPanel) to
either clear selectedZoomCustomScale or coerce it to the preset value when it
matches a preset within that tolerance, using symbols: effectiveScale,
selectedZoomCustomScale, selectedZoomDepth, ZOOM_DEPTH_SCALES, and isActive.

In `@src/components/video-editor/types.ts`:
- Around line 237-240: getZoomScale currently returns region.customScale raw,
which can propagate NaN/Infinity or extreme values; update getZoomScale to
validate and sanitize region.customScale: if customScale is a finite number,
clamp it to a safe range (e.g., min 0.01, max 100) and return that, otherwise
fall back to ZOOM_DEPTH_SCALES[region.depth]; reference the getZoomScale
function and the ZoomRegion.customScale/ZOOM_DEPTH_SCALES symbols when making
the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e17e3029-d8f3-43f3-b434-ff2e155f2e8e

📥 Commits

Reviewing files that changed from the base of the PR and between 884021c and df838fe.

📒 Files selected for processing (10)
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/VideoPlayback.tsx
  • src/components/video-editor/timeline/Item.tsx
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/types.ts
  • src/components/video-editor/videoPlayback/overlayUtils.ts
  • src/components/video-editor/videoPlayback/zoomRegionUtils.ts
  • src/i18n/locales/en/settings.json
  • src/lib/exporter/frameRenderer.ts

Comment thread src/components/video-editor/SettingsPanel.tsx
Comment thread src/components/video-editor/types.ts
makaradam added 2 commits May 2, 2026 11:05
- 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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/video-editor/VideoEditor.tsx (1)

774-783: ⚠️ Potential issue | 🟠 Major

Fix focus clamping in handleZoomFocusChange to account for custom scales.

clampFocusToDepth has an unused _depth parameter and doesn't actually clamp based on the effective zoom scale. When region.customScale differs from ZOOM_DEPTH_SCALES[region.depth], the focus drag isn't properly bounded.

Replace with the same pattern used elsewhere in the codebase (VideoPlayback, overlay, export):

focus: clampFocusToScale(focus, getZoomScale(region))

This handles both preset depths and custom scales correctly. You'll need to import getZoomScale from ./types and clampFocusToScale from ./videoPlayback/focusUtils.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/video-editor/VideoEditor.tsx` around lines 774 - 783,
handleZoomFocusChange currently uses clampFocusToDepth which ignores the
region's effective scale (and has an unused _depth param), so focus dragging
isn't bounded when region.customScale differs from ZOOM_DEPTH_SCALES; update the
mapping in handleZoomFocusChange (for zoomRegions and region) to call
clampFocusToScale(focus, getZoomScale(region)) instead of
clampFocusToDepth(...), and add imports for getZoomScale from ./types and
clampFocusToScale from ./videoPlayback/focusUtils; keep the rest of the
updateState/zoomRegions mapping intact so only the focus computation changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 804-816: The handler handleZoomCustomScaleChange currently writes
raw scale values into zoomRegions.customScale without validating; add a guard to
ensure the incoming scale is a finite number (e.g., using Number.isFinite or
Math.isFinite) before rounding and calling updateState, and if it's not finite,
either return early (no state change) or set a safe default (e.g., 1.0) instead;
update the logic around selectedZoomId, updateState, and the customScale
assignment to only persist the rounded finite value.

---

Outside diff comments:
In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 774-783: handleZoomFocusChange currently uses clampFocusToDepth
which ignores the region's effective scale (and has an unused _depth param), so
focus dragging isn't bounded when region.customScale differs from
ZOOM_DEPTH_SCALES; update the mapping in handleZoomFocusChange (for zoomRegions
and region) to call clampFocusToScale(focus, getZoomScale(region)) instead of
clampFocusToDepth(...), and add imports for getZoomScale from ./types and
clampFocusToScale from ./videoPlayback/focusUtils; keep the rest of the
updateState/zoomRegions mapping intact so only the focus computation changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a224048-b744-4c15-8d15-50ee8d6af26f

📥 Commits

Reviewing files that changed from the base of the PR and between df838fe and 1133e07.

📒 Files selected for processing (2)
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/video-editor/types.ts

Comment thread src/components/video-editor/VideoEditor.tsx
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.

[Feature]: Add custom zoom value

1 participant