Skip to content

Zoom in monitor area under a mouse - #39

Merged
ronak-create merged 8 commits into
ronak-create:mainfrom
PlkMarudny:monitor-zoom
Jul 27, 2026
Merged

Zoom in monitor area under a mouse#39
ronak-create merged 8 commits into
ronak-create:mainfrom
PlkMarudny:monitor-zoom

Conversation

@PlkMarudny

@PlkMarudny PlkMarudny commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Sometimes a closer look is profitable. When hovering a mouse over a monitor, mousewheel action allows to zoom in the area under the cursor. There is also button to reset zoom to 100%.

Type of change

  • Bug fix
  • New feature (UI)
  • Docs
  • Refactor / internal

How was it verified?

  • node --check server.js && node --check app.js && node --check mcp-server.js passes
  • Opened the editor and confirmed the change in preview
  • Confirmed the change in an export (fast or realtime), if it affects rendering
  • Updated CLAUDE.md / README.md if the schema, props, or API changed

Checklist

  • No new runtime dependencies added
  • Preview and export render identically (single compositor)
  • Commits are focused and messages are descriptive

Summary by CodeRabbit

  • New Features
    • Added cursor-centered mouse-wheel zoom for the Program Monitor (with a zoom cap) plus a “100%” button to return to fit-to-stage.
    • Added zoom-mode pan using middle-mouse or Alt+drag, with grab/grabbing feedback.
  • Bug Fixes
    • Improved safe-area overlay locking and refresh during zoom, pan, scrolling, resizing, and guide toggles.
  • Documentation
    • Updated README to describe Program Monitor zoom/pan and the Fit (“100%”) control.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The program monitor now supports cursor-centered zooming, a Fit reset control, and panning with middle mouse or Alt-drag. Four reel timeline backup configurations are also added with media, layered clips, effects, audio, and markers.

Changes

Program Monitor View Controls

Layer / File(s) Summary
Monitor structure and view state
app.js, index.html, style.css
Monitor zoom state, Fit control markup, nested scrolling containers, title styling, and base layout establish the view-control structure.
Zoom, pan, and overlay behavior
app.js
Wheel zoom, Fit reset, native-scroll panning, pointer gesture exclusions, and safe-overlay refresh behavior are implemented.
Monitor presentation and documentation
style.css, README.md
Zoom cursor and preview sizing rules, VU meter spacing adjustments, and the Program Monitor zoom feature description are updated.

Reel Timeline Backup Configurations

Layer / File(s) Summary
Kesarix reel backup
project-kesarix-reel2-backup-1783969307.json
Adds reel metadata, media assets, layered clips, text and adjustment effects, audio, and timeline markers.
Porsche revision 304 backup
project-porsche-backup-rev304.json
Adds visual, text, adjustment, audio, flash-effect, and marker data for the Porsche Blackout Reel.
Porsche Blackout backup
project-porsche-blackout-backup.json
Adds a complete Porsche Blackout Reel configuration with media, layered timeline content, effects, audio, and markers.
Candle Studio reel backup
project.backup-candle-reel.json
Adds media, video and audio clips, grading, text and SVG overlays, transitions, and markers for the Candle Studio Reel.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant monitorScroll
  participant state
  participant preview
  participant safeOverlay
  User->>monitorScroll: Wheel over preview
  monitorScroll->>state: Update viewZoom
  state->>preview: Apply rendered size and scroll offsets
  preview->>safeOverlay: Refresh overlay position
  User->>monitorScroll: Middle mouse or Alt-drag
  monitorScroll->>preview: Update native scroll offsets
  preview->>safeOverlay: Refresh overlay position
  User->>monitorScroll: Click Fit
  monitorScroll->>state: Reset viewZoom to 1
  state->>preview: Restore fit size
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: mouse-driven zooming of the monitor area.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
index.html (1)

65-69: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

"Reset to 100%" label/tooltip mismatches actual "fit stage" behavior.

state.viewZoom = 1 is documented in app.js as "program-monitor display zoom (1 = fit stage)", i.e., it's a fit-to-stage baseline, not a literal native-pixel 100% zoom. Labeling the button "Reset to 100%" (and tooltip "Reset monitor zoom to fit (100%)") conflates these two concepts and may confuse users who expect true 1:1 pixel scale.

✏️ Suggested wording
-          <button class="btn tiny hidden" id="btnZoom100" title="Reset monitor zoom to fit (100%)">...Reset to 100%</button>
+          <button class="btn tiny hidden" id="btnZoom100" title="Reset monitor zoom to fit">...Reset zoom</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@index.html` around lines 65 - 69, Update the Program Monitor reset button
identified by btnZoom100 so its visible label and title describe restoring the
fit-to-stage baseline rather than “100%” or native-pixel zoom; keep the existing
reset behavior unchanged.
app.js (2)

4430-4457: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No bounds on state.viewPan.

Neither the wheel-zoom pan adjustment nor the drag-pan pointermove handler clamps state.viewPan, so a user can drag the zoomed canvas entirely out of the visible stage. Recovery only via the "Reset to 100%" button, which is a usable fallback but still a rough edge.

🔧 Suggested clamp using existing bounding-rect data
 function applyMonitorView() {
   const z = state.viewZoom, pan = state.viewPan;
   const zoomed = z > 1.001;
   if (!zoomed) {
     state.viewZoom = 1;
     state.viewPan = { x: 0, y: 0 };
     els.preview.style.transform = "";
   } else {
+    const stage = els.monitorStage.getBoundingClientRect();
+    const maxX = (stage.width * (z - 1)) / 2;
+    const maxY = (stage.height * (z - 1)) / 2;
+    pan.x = clamp(pan.x, -maxX, maxX);
+    pan.y = clamp(pan.y, -maxY, maxY);
     els.preview.style.transform = `translate(${pan.x}px, ${pan.y}px) scale(${z})`;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 4430 - 4457, Clamp state.viewPan to valid bounds after
both the wheel-zoom adjustment and drag-pan updates so the zoomed canvas cannot
be moved entirely outside the visible stage. Reuse the existing monitor stage
and content bounding-rect data where available, and apply the clamp before
applyMonitorView() in the pointermove path and before storing the zoom-adjusted
pan.

4398-4457: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

updateSafeOverlay() forces layout on every wheel tick and pan pointermove.

applyMonitorView() calls updateSafeOverlay() (two getBoundingClientRect() reads) right after mutating els.preview.style.transform, and this runs on every wheel event and every pointermove while panning (when state.guides is on). That's a write-then-read cycle repeated at high frequency, forcing synchronous layout each time and risking visible jank during zoom/pan.

⚡ Suggested throttling via requestAnimationFrame
+let viewRafPending = false;
+function scheduleApplyMonitorView() {
+  if (viewRafPending) return;
+  viewRafPending = true;
+  requestAnimationFrame(() => { viewRafPending = false; applyMonitorView(); });
+}

Then call scheduleApplyMonitorView() from the wheel and pointermove handlers instead of applyMonitorView() directly (transform mutation itself can stay immediate if desired; the goal is to coalesce the updateSafeOverlay reflow to once per frame).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 4398 - 4457, Throttle monitor view updates with
requestAnimationFrame: add a scheduling helper around applyMonitorView that
coalesces repeated calls into one frame, and use it from the wheel and
pointermove handlers instead of calling applyMonitorView directly. Preserve
immediate reset behavior for resetMonitorView and ensure updateSafeOverlay runs
at most once per animation frame during zooming and panning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app.js`:
- Around line 4430-4457: Clamp state.viewPan to valid bounds after both the
wheel-zoom adjustment and drag-pan updates so the zoomed canvas cannot be moved
entirely outside the visible stage. Reuse the existing monitor stage and content
bounding-rect data where available, and apply the clamp before
applyMonitorView() in the pointermove path and before storing the zoom-adjusted
pan.
- Around line 4398-4457: Throttle monitor view updates with
requestAnimationFrame: add a scheduling helper around applyMonitorView that
coalesces repeated calls into one frame, and use it from the wheel and
pointermove handlers instead of calling applyMonitorView directly. Preserve
immediate reset behavior for resetMonitorView and ensure updateSafeOverlay runs
at most once per animation frame during zooming and panning.

In `@index.html`:
- Around line 65-69: Update the Program Monitor reset button identified by
btnZoom100 so its visible label and title describe restoring the fit-to-stage
baseline rather than “100%” or native-pixel zoom; keep the existing reset
behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3637d4de-0f6f-4d48-ada1-f85ff1551fba

📥 Commits

Reviewing files that changed from the base of the PR and between d584430 and a12b424.

📒 Files selected for processing (3)
  • app.js
  • index.html
  • style.css

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.js`:
- Around line 4456-4474: The monitor update paths currently bypass the
coalescing helper. Replace direct applyMonitorView() calls in frequent monitor
zoom/pan update handlers and add scheduleMonitorView() after the scroll
handler’s updateSafeOverlay() call, while preserving resetMonitorView()’s
immediate applyMonitorView() behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d9f153c-4c59-480a-a4a0-c0f6c0d37c9c

📥 Commits

Reviewing files that changed from the base of the PR and between a12b424 and 0d81466.

📒 Files selected for processing (4)
  • README.md
  • app.js
  • index.html
  • style.css

Comment thread app.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
app.js (2)

1221-1221: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Remove duplicate module-scope declarations.

Each repeated let/const causes app.js to fail parsing before any monitor code can run.

  • app.js#L1221-L1221: retain one let replaceFileInput = null, replaceFileTargetId = null;.
  • app.js#L1880-L1880: retain one let rulerWorker = null, rulerWorkerTried = false;.
  • app.js#L2933-L2933: retain one const METER_SEG_W = 8, METER_SEG_H = 12, METER_SEG_GAP = 2;.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` at line 1221, Remove the duplicate module-scope declarations in
app.js: retain only one declaration of replaceFileInput and replaceFileTargetId
at lines 1221-1221, one declaration of rulerWorker and rulerWorkerTried at lines
1880-1880, and one declaration of METER_SEG_W, METER_SEG_H, and METER_SEG_GAP at
lines 2933-2933; eliminate any repeated let or const declarations while
preserving the retained variables and initial values.

2878-2888: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Isolate channels beyond left/right.

connectChannelIsolated() only isolates left and right, so clips with audioChannel >= 2 are routed through the full source mix instead of only their assigned channel. Split and route every non-negative channel index in connectChannelIsolated() before the otherwise-connected final merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 2878 - 2888, Update connectChannelIsolated() to isolate
every non-negative channel index, not only channels 0 and 1. Create a splitter
sized for the requested channel, route that channel into the graph, and connect
the graph output to the matching merger input before returning the isolated
output; retain the direct src-to-g path only for negative channel values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app.js`:
- Line 1221: Remove the duplicate module-scope declarations in app.js: retain
only one declaration of replaceFileInput and replaceFileTargetId at lines
1221-1221, one declaration of rulerWorker and rulerWorkerTried at lines
1880-1880, and one declaration of METER_SEG_W, METER_SEG_H, and METER_SEG_GAP at
lines 2933-2933; eliminate any repeated let or const declarations while
preserving the retained variables and initial values.
- Around line 2878-2888: Update connectChannelIsolated() to isolate every
non-negative channel index, not only channels 0 and 1. Create a splitter sized
for the requested channel, route that channel into the graph, and connect the
graph output to the matching merger input before returning the isolated output;
retain the direct src-to-g path only for negative channel values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f55f7db-6157-4a35-8eee-fad689392f1c

📥 Commits

Reviewing files that changed from the base of the PR and between fbcd69a and 1cd39bb.

📒 Files selected for processing (8)
  • README.md
  • app.js
  • index.html
  • project-kesarix-reel2-backup-1783969307.json
  • project-porsche-backup-rev304.json
  • project-porsche-blackout-backup.json
  • project.backup-candle-reel.json
  • style.css
🚧 Files skipped from review as they are similar to previous changes (3)
  • index.html
  • README.md
  • style.css

@ronak-create
ronak-create merged commit 9ebcc09 into ronak-create:main Jul 27, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 28, 2026
11 tasks
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.

2 participants