Skip to content

feat: add zoom slider and trackpad panning#985

Open
AkshitBitsian356u wants to merge 7 commits intoCircuitVerse:mainfrom
AkshitBitsian356u:main
Open

feat: add zoom slider and trackpad panning#985
AkshitBitsian356u wants to merge 7 commits intoCircuitVerse:mainfrom
AkshitBitsian356u:main

Conversation

@AkshitBitsian356u
Copy link

@AkshitBitsian356u AkshitBitsian356u commented Feb 26, 2026

Screenshots/Video of the UI changes -

2026-03-01.23-56-41.mp4

Closes #987

Changes :

  • Added zoom slider and trackpad panning support for
    intuitive canvas navigation.
  • Replaced zoom slider with +/- buttons in QuickButton
    components for cleaner zoom UX.
  • Improved canvas pan/zoom robustness and event handling
    to prevent conflicting interactions.
  • Normalized zoom delta calculations and validated slider
    inputs for consistent zoom behavior.
  • Fixed horizontal/vertical panning direction in
    applyCanvasPan and added inline documentation for
    wheel/trackpad event handling.
  • Added findDimensions call for minimap setup and
    refactored zoom scale constants in applyZoomChange.
  • Added devicePixelRatio constant (DPR) for consistent
    scaling across devices, fixing blank simulator screen.

Code Understanding and AI Usage

Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?

  • No, I wrote all the code myself
  • Yes, I used AI assistance (continue below)

If you used AI assistance:

  • I have reviewed every single line of the AI-generated or AI-suggested code
  • I can explain the purpose and logic of each function/component I added
  • I have tested edge cases and understand how the code handles them
  • I have modified the AI output to follow this project's coding standards and conventions

Explain your implementation approach:

  • The main goal of this change is to make moving around the circuit canvas more natural by using panning gestures instead of the older “hand swapping” interaction.
  • I updated the interaction layer so that panning now works through pointer/wheel events (for example, trackpad two‑finger drag) that translate into changes in the canvas offset.
  • The panning logic reuses the existing camera/viewport state, so zoom level and panning stay consistent across different input methods.
  • I verified that the existing zoom slider behaviour remains unchanged and that zooming and panning can be combined smoothly without conflicting events.
  • I used AI assistance to explore alternative ways of handling the gesture events and to think through potential edge cases (fast scrolling, panning at high zoom, and when the canvas is near its bounds). All final decisions and code changes were made by me, and I tested the interactions directly in the simulator.

Checklist before requesting a review

  • I have added proper PR title and linked to the issue (or marked it as N/A)
  • I have performed a self-review of my code
  • I can explain the purpose of every function, class, and logic block I added
  • I understand why my changes work and have tested them thoroughly on the simulator
  • I have considered potential edge cases and how my code handles them (e.g., high zoom, fast panning)
  • If it is a core feature, I have added thorough tests
  • My code follows the project's style guidelines and conventions

Note: Please check Allow edits from maintainers if you would like us to assist in the PR.

Summary by CodeRabbit

  • New Features

    • Slider replaced by simplified discrete zoom controls (± buttons) with accessible titles.
  • Improvements

    • Expanded keyboard zoom shortcuts (Cmd/Ctrl + +/= and -).
    • Mouse wheel/trackpad now pans the canvas instead of zooming; minimap briefly appears during pan.
    • Visual, hover and focus updates for zoom controls and improved accessibility.
    • Added stable pan/zoom helpers to ensure consistent behavior across inputs.

Copilot AI review requested due to automatic review settings February 26, 2026 22:38
@netlify
Copy link

netlify bot commented Feb 26, 2026

Deploy Preview for circuitverse ready!

Name Link
🔨 Latest commit 4459d89
🔍 Latest deploy log https://app.netlify.com/projects/circuitverse/deploys/69a483e8d48b970008302f95
😎 Deploy Preview https://deploy-preview-985--circuitverse.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 44 (🔴 down 2 from production)
Accessibility: 66 (no change from production)
Best Practices: 92 (no change from production)
SEO: 82 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

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

Navbar zoom controls were replaced with discrete increment/decrement buttons in desktop and mobile QuickButton components instead of a range slider. Wheel/trackpad events now perform canvas panning using cross‑browser delta extraction (extractScrollDelta) and applyCanvasPan, showing the minimap briefly during pans; wheel no longer changes zoom. Keyboard zoom accepts Cmd/Ctrl plus +/= to zoom in and - to zoom out. A unified applyZoomChange was added, and helper functions setZoomFromSlider and getZoomSliderValue were introduced. A comment in engine.js was revised.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: zoom slider UI is now accompanied by +/- buttons and trackpad panning is added to the event listeners, matching the core objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds trackpad panning support to the circuit simulator canvas and refactors the zoom controls from a jQuery-based implementation to a Vue-based implementation. The changes modify how users navigate the canvas by replacing scroll-to-zoom with scroll-to-pan behavior, while keeping zoom controls accessible via keyboard shortcuts and UI slider buttons.

Changes:

  • Changed mouse wheel/trackpad scroll events from zooming to panning the canvas
  • Replaced jQuery-based zoom slider implementation with Vue-based reactive zoom controls in QuickButton and QuickButtonMobile components
  • Added keyboard shortcut enhancements for zoom (supporting both keyCode and e.key checks)

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/simulator/src/listeners.js Replaced MouseScroll (zoom) with handleCanvasPan (pan), removed jQuery zoom slider listeners, added setZoomFromSlider/getZoomSliderValue exports, removed desktop app Tauri event listeners
src/simulator/src/engine.js Updated comment to clarify existing drag-to-pan behavior
src/simulator/src/embedListeners.js Enhanced keyboard zoom shortcuts to support e.key in addition to keyCode
src/components/Navbar/QuickButton/QuickButton.vue Replaced jQuery zoom slider with Vue reactive implementation, added zoom conversion logic and state management
src/components/Navbar/QuickButton/QuickButtonMobile.vue Replaced jQuery zoom slider with Vue reactive implementation (similar to desktop but with mobile-specific styling)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const INTERNAL_ZOOM_MIN = 0
const INTERNAL_ZOOM_MAX = 200

const displayZoomPercent = ref(100)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The initial zoom value is hardcoded to 100 instead of using DISPLAY_ZOOM_DEFAULT (which is 50). This creates an inconsistency with QuickButtonMobile.vue (which correctly uses DISPLAY_ZOOM_DEFAULT) and means the desktop zoom slider will start at 100% while mobile starts at 50%, creating different user experiences. Change this line to use DISPLAY_ZOOM_DEFAULT for consistency.

Suggested change
const displayZoomPercent = ref(100)
const displayZoomPercent = ref(DISPLAY_ZOOM_DEFAULT)

Copilot uses AI. Check for mistakes.
Comment on lines 343 to 347
.zoom-button-decrement:hover,
.zoom-button-increment:hover {
opacity: 0.7;
}

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The hover styles for zoom buttons are duplicated. Lines 290-292 and 343-346 define identical hover styles for the same elements. The second block (lines 343-346) should be removed to eliminate redundant CSS.

Suggested change
.zoom-button-decrement:hover,
.zoom-button-increment:hover {
opacity: 0.7;
}

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +199
import { ref, onMounted } from 'vue'
import Hamburger2 from '../Hamburger/Hamburger2.vue'
import navbarData from '#/assets/constants/Navbar/NAVBAR_DATA.json'
import { useSimulatorMobileStore } from '#/store/simulatorMobileStore'
import { saveOnline, saveOffline, deleteSelectedItem, createSaveAsImgPrompt, zoomToFit, undoit, redoit, view, decrement, increment } from './QuickButton';
import navbarData from '../../../assets/constants/Navbar/NAVBAR_DATA.json'
import { useSimulatorMobileStore } from '../../../store/simulatorMobileStore'
import { saveOnline, saveOffline, deleteSelectedItem, createSaveAsImgPrompt, zoomToFit, undoit, redoit, view } from './QuickButton'
// @ts-ignore - simulator functions are not typed
import { setZoomFromSlider, getZoomSliderValue } from '../../../simulator/src/listeners'

const simulatorMobileStore = useSimulatorMobileStore()

// Display zoom constants (user-facing percentage values)
const DISPLAY_ZOOM_MIN = 1
const DISPLAY_ZOOM_MAX = 100
const DISPLAY_ZOOM_DEFAULT = 50
const DISPLAY_ZOOM_STEP = 5

// Internal zoom constants (simulator scale range)
const INTERNAL_ZOOM_MIN = 0
const INTERNAL_ZOOM_MAX = 200

// Current zoom level displayed to user (1-100%)
const displayZoomPercent = ref(DISPLAY_ZOOM_DEFAULT)

/**
* Clamp zoom percentage to valid display range
*/
const clampZoomPercent = (value: number): number => {
return Math.max(DISPLAY_ZOOM_MIN, Math.min(DISPLAY_ZOOM_MAX, value))
}

/**
* Convert internal zoom value (0-200) to display percentage (1-100%)
*/
const convertInternalToDisplayZoom = (internalValue: number): number => {
return Math.round((internalValue / INTERNAL_ZOOM_MAX) * 100)
}

/**
* Convert display percentage (1-100%) to internal zoom value (0-200)
*/
const convertDisplayToInternalZoom = (displayPercent: number): number => {
return (displayPercent / 100) * INTERNAL_ZOOM_MAX
}

/**
* Initialize zoom slider with current simulator zoom level
*/
const initializeZoomSlider = () => {
try {
const currentInternalZoom = getZoomSliderValue(INTERNAL_ZOOM_MIN, INTERNAL_ZOOM_MAX)
const currentDisplayZoom = convertInternalToDisplayZoom(currentInternalZoom)
displayZoomPercent.value = clampZoomPercent(currentDisplayZoom)
} catch (error) {
console.warn('Could not initialize zoom slider:', error)
displayZoomPercent.value = DISPLAY_ZOOM_DEFAULT
}
}

/**
* Update simulator zoom based on current display percentage
*/
const updateSimulatorZoom = () => {
try {
const internalZoomValue = convertDisplayToInternalZoom(displayZoomPercent.value)
setZoomFromSlider(internalZoomValue, INTERNAL_ZOOM_MIN, INTERNAL_ZOOM_MAX)
} catch (error) {
console.warn('Could not apply zoom:', error)
}
}

/**
* Increase zoom level by one step
*/
const incrementZoom = () => {
displayZoomPercent.value = clampZoomPercent(displayZoomPercent.value + DISPLAY_ZOOM_STEP)
updateSimulatorZoom()
}

/**
* Decrease zoom level by one step
*/
const decrementZoom = () => {
displayZoomPercent.value = clampZoomPercent(displayZoomPercent.value - DISPLAY_ZOOM_STEP)
updateSimulatorZoom()
}

/**
* Handle slider input event - updates zoom in real-time as user drags
*/
const onZoomSliderInput = () => {
updateSimulatorZoom()
}

onMounted(initializeZoomSlider)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

There is significant code duplication between QuickButton.vue and QuickButtonMobile.vue. The zoom slider logic (constants, conversion functions, increment/decrement/update functions) is nearly identical in both files. Consider extracting this shared logic into a composable or shared utility file to improve maintainability and reduce the risk of inconsistencies.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. The zoom controls and logic are currently duplicated between the desktop and mobile quick button components so they stay in sync. I’d be happy to extract this into a shared composable/component in a follow‑up PR if you’d like.

Comment on lines +832 to +904
/**
* Set zoom level from a slider value
* Converts a slider value (typically 0-200) to the internal zoom scale (0.5-4.0 * DPR)
* and applies it centered on the viewport.
*
* @param {number} sliderValue - The value from a zoom slider
* @param {number} minSliderValue - Minimum slider value (default: 0)
* @param {number} maxSliderValue - Maximum slider value (default: 10)
* @param {number} minZoom - Minimum zoom scale (default: 0.5 * DPR)
* @param {number} maxZoom - Maximum zoom scale (default: 4 * DPR)
*
* Example: setZoomFromSlider(100, 0, 200) // Sets zoom to middle of range
*/
export function setZoomFromSlider(
sliderValue,
minSliderValue = 0,
maxSliderValue = 10,
minZoom = 0.5 * DPR,
maxZoom = 4 * DPR
) {
// Normalize slider value to 0-1 range
const normalizedValue = (sliderValue - minSliderValue) / (maxSliderValue - minSliderValue)

// Map to zoom scale range
const targetScale = minZoom + normalizedValue * (maxZoom - minZoom)

// Clamp to valid zoom range
const clampedScale = Math.max(minZoom, Math.min(maxZoom, targetScale))

// Calculate delta from current scale
const scaleDelta = clampedScale - globalScope.scale

// Apply zoom centered on viewport (method = 3)
changeScale(scaleDelta, 'zoomButton', 'zoomButton', 3)

// Update display
gridUpdateSet(true)
updateCanvasSet(true)
scheduleUpdate()
}

listen('discussion-forum', () => {
logixFunction.showDiscussionForum();
});
/**
* Get the current zoom level as a slider value
* Inverse of setZoomFromSlider - converts internal scale to slider value
* Useful for initializing or syncing a zoom slider UI
*
* @param {number} minSliderValue - Minimum slider value (default: 0)
* @param {number} maxSliderValue - Maximum slider value (default: 10)
* @param {number} minZoom - Minimum zoom scale (default: 0.5 * DPR)
* @param {number} maxZoom - Maximum zoom scale (default: 4 * DPR)
* @returns {number} The slider value corresponding to current zoom level
*
* Example: const sliderValue = getZoomSliderValue(0, 200) // Returns 0-200
*/
export function getZoomSliderValue(
minSliderValue = 0,
maxSliderValue = 10,
minZoom = 0.5 * DPR,
maxZoom = 4 * DPR
) {
const currentScale = globalScope.scale

// Clamp current scale to valid range
const clampedScale = Math.max(minZoom, Math.min(maxZoom, currentScale))

// Normalize scale to 0-1 range
const normalizedScale = (clampedScale - minZoom) / (maxZoom - minZoom)

// Map to slider value range
const sliderValue = minSliderValue + normalizedScale * (maxSliderValue - minSliderValue)

return sliderValue
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Critical: The desktop app listeners (listen('new-project'), listen('save-online'), etc.) have been removed, but the Tauri backend (src-tauri/src/lib.rs) still emits these events when menu items are clicked. This will break all desktop app menu functionality. These listeners must be restored or the Tauri event emissions need to be handled elsewhere.

Copilot uses AI. Check for mistakes.
if (
(simulationArea.controlDown &&
(e.keyCode == 187 || e.keyCode == 171)) ||
(e.keyCode == 187 || e.keyCode == 171 || e.key == '+' || e.key == '=')) ||
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The keyboard shortcut handling now checks both keyCode and e.key values. However, when Cmd/Ctrl is pressed with '+', the e.key value is typically '+' (not '='). Similarly, with Shift+'-', the key is ''. Consider whether checking for '=' and '' is necessary, as these would require different key combinations (Cmd/Ctrl + Shift + = and Cmd/Ctrl + Shift + -). This might create confusing behavior where Shift changes zoom behavior unexpectedly.

Copilot uses AI. Check for mistakes.
// zoom out (-)
if (simulationArea.controlDown && (e.keyCode == 189 || e.Keycode == 173)) {
// zoom out: Cmd/Ctrl + '-'
if (simulationArea.controlDown && (e.keyCode == 189 || e.Keycode == 173 || e.key == '-' || e.key == '_')) {
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Same issue as in listeners.js: checking for e.key == '' will match Cmd/Ctrl + Shift + '-', which may not be the intended behavior. Consider removing the check for '' to avoid unexpected zoom behavior when Shift is pressed.

Suggested change
if (simulationArea.controlDown && (e.keyCode == 189 || e.Keycode == 173 || e.key == '-' || e.key == '_')) {
if (simulationArea.controlDown && (e.keyCode == 189 || e.Keycode == 173 || e.key == '-')) {

Copilot uses AI. Check for mistakes.
Comment on lines +630 to +632
// Invert delta: positive scroll should move content down/right
globalScope.ox -= deltaX
globalScope.oy -= deltaY
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The pan direction logic subtracts deltaX and deltaY from the origin (globalScope.ox -= deltaX). According to the comment on line 630, "positive scroll should move content down/right". However, with the current implementation, positive deltaY (scrolling down) will decrease oy, which typically moves content UP on most canvas implementations. Verify that the pan direction matches user expectations - users typically expect the content to move in the same direction as their scroll/swipe gesture (natural scrolling).

Suggested change
// Invert delta: positive scroll should move content down/right
globalScope.ox -= deltaX
globalScope.oy -= deltaY
// Positive scroll moves content down/right
globalScope.ox += deltaX
globalScope.oy += deltaY

Copilot uses AI. Check for mistakes.
Comment on lines +781 to +782
// zoomSliderListeners() disabled - slider removed to prevent accidental zoom from swipes/scroll
// Zoom is now controlled by +/- buttons only (which call ZoomIn/ZoomOut directly)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The comment states that the zoom slider has been removed, but this is misleading. The slider is still present in the Vue components (QuickButton.vue and QuickButtonMobile.vue) with new implementation. The comment should be updated to reflect that the old jQuery-based slider listeners were removed and replaced with Vue-based implementation.

Suggested change
// zoomSliderListeners() disabled - slider removed to prevent accidental zoom from swipes/scroll
// Zoom is now controlled by +/- buttons only (which call ZoomIn/ZoomOut directly)
// zoomSliderListeners() (jQuery-based) disabled here - zoom slider handling has moved to Vue
// Vue components (QuickButton.vue / QuickButtonMobile.vue) now manage the zoom slider and +/- buttons

Copilot uses AI. Check for mistakes.
Copy link
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: 3

🧹 Nitpick comments (3)
src/simulator/src/listeners.js (2)

781-783: Refresh this comment to match current behavior.

The note says slider-based zoom was removed, but this PR adds slider helpers and slider-driven zoom usage. Keeping this stale will mislead future changes.

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

In `@src/simulator/src/listeners.js` around lines 781 - 783, Update the stale
comment near the embed check in listeners.js: replace the note that says the
slider was removed and zoom is controlled only by ZoomIn/ZoomOut with a concise
description that the code now includes slider helpers and supports slider-driven
zoom (mentioning the zoomSliderListeners helper and the ZoomIn/ZoomOut
controls), and clarify when the slider logic is enabled/disabled relative to the
embed flag so future readers won't be misled.

600-620: Add deltaMode normalization to ensure consistent pan speeds across browsers and devices.

The extractScrollDelta() function currently extracts deltaX and deltaY without checking the deltaMode property. When deltaMode is 1 (lines) or 2 (pages), these values need pixel normalization. Without this, pan speed will be inconsistent across different browsers and input devices.

Proposed fix
 function extractScrollDelta(event) {
     let deltaX = 0
     let deltaY = 0
@@
     if (event.deltaX !== undefined && event.deltaY !== undefined) {
         // Modern browsers: event.deltaX, event.deltaY
         deltaX = event.deltaX
         deltaY = event.deltaY
+        // Normalize to pixel units when needed
+        if (event.deltaMode === 1) {
+            // lines -> pixels (approx)
+            deltaX *= 16
+            deltaY *= 16
+        } else if (event.deltaMode === 2) {
+            // pages -> pixels (approx viewport)
+            deltaX *= window.innerWidth
+            deltaY *= window.innerHeight
+        }
     } else if (event.wheelDeltaX !== undefined && event.wheelDeltaY !== undefined) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/simulator/src/listeners.js` around lines 600 - 620, The
extractScrollDelta function must normalize for event.deltaMode so pan speeds are
consistent: after computing deltaX/deltaY from deltaX/deltaY, wheelDelta*,
wheelDelta or detail, check event.deltaMode and, when ===1 (lines) multiply
deltas by a reasonable line height constant (e.g., ~16) and when ===2 (pages)
multiply by viewport height (e.g., window.innerHeight) to convert to pixels;
ensure this normalization uses the already-computed deltaX/deltaY values and is
applied before returning from extractScrollDelta.
src/components/Navbar/QuickButton/QuickButton.vue (1)

99-153: Extract shared zoom mapping logic into a composable.

This conversion/clamp/init/update block is duplicated with src/components/Navbar/QuickButton/QuickButtonMobile.vue, which increases drift risk.

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

In `@src/components/Navbar/QuickButton/QuickButton.vue` around lines 99 - 153, The
zoom conversion/clamping/initialization/update logic is duplicated and should be
extracted into a reusable composable; create a composable (e.g.,
useZoomComposable) that exports the constants DISPLAY_ZOOM_MIN/MAX/DEFAULT/STEP
and INTERNAL_ZOOM_MIN/MAX, the reactive displayZoomPercent ref, and the
functions clampZoomPercent, convertInternalToDisplayZoom,
convertDisplayToInternalZoom, initializeZoomSlider and updateSimulatorZoom
(which internally call getZoomSliderValue and setZoomFromSlider as before), then
replace the duplicated implementations in QuickButton.vue and
QuickButtonMobile.vue by importing and using the composable so both components
share the same logic and behavior.
🤖 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/Navbar/QuickButton/QuickButton.vue`:
- Around line 121-156: The zoom slider is only synced once in onMounted via
initializeZoomSlider, so displayZoomPercent can drift when zoom changes
elsewhere; fix by adding a reactive sync that updates displayZoomPercent
whenever the authoritative zoom changes (e.g., add a watcher or event listener
that calls initializeZoomSlider or updateSimulatorZoom when the simulator's
zoom/state changes), reference initializeZoomSlider, updateSimulatorZoom and
onMounted to locate the logic, and ensure you register the listener/watcher
during mount and remove it onBeforeUnmount to avoid leaks.

In `@src/simulator/src/embedListeners.js`:
- Around line 182-187: The keyboard shortcut checks for zoom use incorrect
`KeyboardEvent` property casing—replace the undefined `e.KeyCode` and
`e.Keycode` with the correct `e.keyCode` in the zoom-in and zoom-out
conditionals so the control+plus and control+minus shortcuts properly detect key
codes; update the conditions around the ZoomIn() and ZoomOut() calls in
embedListeners (the lines checking `simulationArea.controlDown`) to use
`e.keyCode` consistently.

In `@src/simulator/src/listeners.js`:
- Around line 659-665: The legacy-only listeners for canvas panning miss modern
browsers; add a standard "wheel" event listener on the element with id
"simulationArea" so handleCanvasPan is invoked for modern wheel events (it
already supports deltaX/deltaY via extractScrollDelta); update the registration
near the existing calls that addEventListener('mousewheel', handleCanvasPan) and
addEventListener('DOMMouseScroll', handleCanvasPan) to also call
addEventListener('wheel', handleCanvasPan) so panning works reliably across
browsers.

---

Nitpick comments:
In `@src/components/Navbar/QuickButton/QuickButton.vue`:
- Around line 99-153: The zoom conversion/clamping/initialization/update logic
is duplicated and should be extracted into a reusable composable; create a
composable (e.g., useZoomComposable) that exports the constants
DISPLAY_ZOOM_MIN/MAX/DEFAULT/STEP and INTERNAL_ZOOM_MIN/MAX, the reactive
displayZoomPercent ref, and the functions clampZoomPercent,
convertInternalToDisplayZoom, convertDisplayToInternalZoom, initializeZoomSlider
and updateSimulatorZoom (which internally call getZoomSliderValue and
setZoomFromSlider as before), then replace the duplicated implementations in
QuickButton.vue and QuickButtonMobile.vue by importing and using the composable
so both components share the same logic and behavior.

In `@src/simulator/src/listeners.js`:
- Around line 781-783: Update the stale comment near the embed check in
listeners.js: replace the note that says the slider was removed and zoom is
controlled only by ZoomIn/ZoomOut with a concise description that the code now
includes slider helpers and supports slider-driven zoom (mentioning the
zoomSliderListeners helper and the ZoomIn/ZoomOut controls), and clarify when
the slider logic is enabled/disabled relative to the embed flag so future
readers won't be misled.
- Around line 600-620: The extractScrollDelta function must normalize for
event.deltaMode so pan speeds are consistent: after computing deltaX/deltaY from
deltaX/deltaY, wheelDelta*, wheelDelta or detail, check event.deltaMode and,
when ===1 (lines) multiply deltas by a reasonable line height constant (e.g.,
~16) and when ===2 (pages) multiply by viewport height (e.g.,
window.innerHeight) to convert to pixels; ensure this normalization uses the
already-computed deltaX/deltaY values and is applied before returning from
extractScrollDelta.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5adad52 and fd14070.

📒 Files selected for processing (5)
  • src/components/Navbar/QuickButton/QuickButton.vue
  • src/components/Navbar/QuickButton/QuickButtonMobile.vue
  • src/simulator/src/embedListeners.js
  • src/simulator/src/engine.js
  • src/simulator/src/listeners.js

Comment on lines 121 to 156
const initializeZoomSlider = () => {
try {
const currentInternalZoom = getZoomSliderValue(INTERNAL_ZOOM_MIN, INTERNAL_ZOOM_MAX)
const currentDisplayZoom = convertInternalToDisplayZoom(currentInternalZoom)
displayZoomPercent.value = clampZoomPercent(currentDisplayZoom)
} catch (error) {
console.warn('Could not initialize zoom slider:', error)
displayZoomPercent.value = DISPLAY_ZOOM_DEFAULT
}
}

const updateSimulatorZoom = () => {
try {
const internalZoomValue = convertDisplayToInternalZoom(displayZoomPercent.value)
setZoomFromSlider(internalZoomValue, INTERNAL_ZOOM_MIN, INTERNAL_ZOOM_MAX)
} catch (error) {
console.warn('Could not apply zoom:', error)
}
}

const incrementZoom = () => {
displayZoomPercent.value = clampZoomPercent(displayZoomPercent.value + DISPLAY_ZOOM_STEP)
updateSimulatorZoom()
}

const decrementZoom = () => {
displayZoomPercent.value = clampZoomPercent(displayZoomPercent.value - DISPLAY_ZOOM_STEP)
updateSimulatorZoom()
}

const onZoomSliderInput = () => {
updateSimulatorZoom()
}

onMounted(initializeZoomSlider)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Zoom display can drift because sync runs only once.

The slider value is initialized in onMounted, but zoom can later change via keyboard shortcuts/pinch/other controls, leaving displayZoomPercent stale.

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

In `@src/components/Navbar/QuickButton/QuickButton.vue` around lines 121 - 156,
The zoom slider is only synced once in onMounted via initializeZoomSlider, so
displayZoomPercent can drift when zoom changes elsewhere; fix by adding a
reactive sync that updates displayZoomPercent whenever the authoritative zoom
changes (e.g., add a watcher or event listener that calls initializeZoomSlider
or updateSimulatorZoom when the simulator's zoom/state changes), reference
initializeZoomSlider, updateSimulatorZoom and onMounted to locate the logic, and
ensure you register the listener/watcher during mount and remove it
onBeforeUnmount to avoid leaks.

Copy link
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

♻️ Duplicate comments (1)
src/simulator/src/listeners.js (1)

663-669: ⚠️ Potential issue | 🟠 Major

Register the standard wheel listener for reliable panning.

Line 665-Line 669 only bind legacy events. On browsers that emit only wheel, canvas pan won’t trigger.

Proposed fix
-    document
-        .getElementById('simulationArea')
-        .addEventListener('mousewheel', handleCanvasPan)
-    document
-        .getElementById('simulationArea')
-        .addEventListener('DOMMouseScroll', handleCanvasPan)
+    const simulationAreaElement = document.getElementById('simulationArea')
+    simulationAreaElement.addEventListener('wheel', handleCanvasPan, { passive: false })
+    simulationAreaElement.addEventListener('mousewheel', handleCanvasPan, { passive: false })
+    simulationAreaElement.addEventListener('DOMMouseScroll', handleCanvasPan, { passive: false })
#!/bin/bash
# Verify which wheel-related listeners are currently registered in main simulator listeners.
rg -n "addEventListener\\(['\"](wheel|mousewheel|DOMMouseScroll)['\"]" src/simulator/src/listeners.js -C2
🧹 Nitpick comments (4)
src/components/Navbar/QuickButton/QuickButton.vue (2)

75-76: Set explicit type="button" on the new zoom controls.

Without explicit type, these default to submit in form contexts.

Proposed fix
-            <button class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button>
-            <button class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>
+            <button type="button" class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button>
+            <button type="button" class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>

223-269: Remove stale slider-specific styles to keep CSS aligned with the template.

The slider UI is no longer present, but .zoom-slider / .zoom-label rules remain.

src/components/Navbar/QuickButton/QuickButtonMobile.vue (2)

86-87: Set explicit type="button" for mobile zoom buttons.

This avoids accidental form submission behavior in embedded/form contexts.

Proposed fix
-          <button class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button>
-          <button class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>
+          <button type="button" class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button>
+          <button type="button" class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>

176-222: Clean up leftover slider CSS in mobile quick controls.

.zoom-slider and .zoom-label styles are no longer used after the button-only zoom UI switch.


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd14070 and 6522940.

📒 Files selected for processing (4)
  • src/components/Navbar/QuickButton/QuickButton.vue
  • src/components/Navbar/QuickButton/QuickButtonMobile.vue
  • src/simulator/src/embedListeners.js
  • src/simulator/src/listeners.js

Copy link
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


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6522940 and 4ef87f0.

📒 Files selected for processing (1)
  • src/simulator/src/listeners.js

Copy link
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


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef87f0 and c13a478.

📒 Files selected for processing (1)
  • src/simulator/src/listeners.js

Copy link
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


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c13a478 and 929641c.

📒 Files selected for processing (1)
  • src/simulator/src/listeners.js

@AkshitBitsian356u AkshitBitsian356u force-pushed the main branch 2 times, most recently from c13a478 to 4ef87f0 Compare March 1, 2026 17:14
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 : Canvas panning feels unnatural - trackpad gestures and zoom/pan interaction needs improvement

2 participants