Skip to content

Zoom in monitor area under a mouse#39

Open
PlkMarudny wants to merge 2 commits into
ronak-create:mainfrom
PlkMarudny:monitor-zoom
Open

Zoom in monitor area under a mouse#39
PlkMarudny wants to merge 2 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 zoom controls for the Program Monitor, including cursor-centered wheel zooming and a one-click reset to 100%.
    • Added panning while zoomed using middle-mouse or Alt-drag gestures.
    • Updated monitor cursor feedback during panning.
    • Safe-area overlays now remain synchronized as the preview view changes.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The program monitor now supports cursor-centered zooming, a 100% reset control, and panning with middle mouse or Alt-drag. Zoom and pan state are stored in the editor state, applied to the preview canvas, and reflected through monitor cursor styling.

Changes

Program Monitor View Controls

Layer / File(s) Summary
View state and monitor controls
app.js, index.html
Monitor view zoom and pan state are added, the reset button is wired into DOM references, and Alt or middle-button pointer events bypass canvas editing gestures.
Zoom and pan interactions
app.js, style.css
Wheel zoom, 100% reset, pointer-capture panning, preview transforms, title-button styling, and zoomed/panning cursor states are implemented.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MonitorStage
  participant EditorState
  participant PreviewCanvas
  User->>MonitorStage: Wheel over preview
  MonitorStage->>EditorState: Update viewZoom and viewPan
  EditorState->>PreviewCanvas: Apply CSS transform
  User->>MonitorStage: Middle mouse or Alt-drag
  MonitorStage->>EditorState: Update viewPan during pointer movement
  EditorState->>PreviewCanvas: Apply translated transform
  User->>MonitorStage: Click reset to 100%
  MonitorStage->>EditorState: Reset zoom and pan
  EditorState->>PreviewCanvas: Apply identity transform
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 clearly reflects the main change: mouse-based zooming in 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

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