Migration to InputManager#615
Conversation
📝 WalkthroughWalkthroughThis PR systematically migrates keyboard event handling across the application from direct ChangesInputManager keyboard shortcut migration
Gizmo state transition and menu integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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)
main/accessibility.js (1)
175-201:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
_watcherevent listeners are never removed — accumulates leaked listeners on everytoggle(true)call.Each call to
toggle(true)overwritesthis._watcherwith a new function and callsdocument.addEventListenertwice (forfocusinandpointerdowncapture), but the previous function reference is discarded withoutremoveEventListener. Sincetoggle(false)does no cleanup either, the old listeners remain attached permanently.This is called from at least three sites:
Ctrl+G(line 211),toggleGizmo()ingizmos.js, andactivateArea()(line 104). Every gizmo button click adds two more permanent document-level listeners that fire on every subsequent user interaction.🔒 Proposed fix — clean up stale watcher on both `true` and `false` paths
toggle(show) { if (!this.overlay) return; if (show) { this.renderBadges(); + if (this._watcher) { + document.removeEventListener("focusin", this._watcher); + document.removeEventListener("pointerdown", this._watcher, { capture: true }); + } this._watcher = () => { const ctx = ContextManager.getCurrentContext(); if (ctx !== "GIZMO" && ctx !== "NAVIGATION") this.toggle(false); }; document.addEventListener("focusin", this._watcher); document.addEventListener("pointerdown", this._watcher, { capture: true, }); // Focus 1st button if nothing in gizmos is already focused, // but if another gizmo is active, leave focus there const alreadyFocused = document.activeElement?.closest("#gizmoButtons"); if (!alreadyFocused) { const btn = document.querySelector(".gizmo-button.active") || document.getElementById("showShapesButton"); if (btn && !btn.disabled && btn.offsetParent !== null) btn.focus(); } + } else { + if (this._watcher) { + document.removeEventListener("focusin", this._watcher); + document.removeEventListener("pointerdown", this._watcher, { capture: true }); + this._watcher = null; + } } this.overlay.classList.toggle("hidden", !show); },🤖 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 `@main/accessibility.js` around lines 175 - 201, The toggle(show) method is adding document-level listeners via this._watcher every time show is true but never removes them, leaking handlers; update toggle and related cleanup so any existing watcher is removed before adding a new one and also removed when hiding: check for this._watcher and call document.removeEventListener("focusin", this._watcher) and document.removeEventListener("pointerdown", this._watcher, {capture:true}) before assigning a new watcher, and ensure when toggling false you remove the watchers and clear this._watcher; keep the existing behavior around renderBadges(), focus logic and overlay.classList.toggle but add this watcher removal logic to prevent accumulation.
♻️ Duplicate comments (1)
ui/gizmos.js (1)
1299-1303:⚠️ Potential issue | 🟠 Major
GizmoMenuManager.toggle(true)here compounds the listener leak inaccessibility.js.Every gizmo button click invokes
toggleGizmo→GizmoMenuManager.toggle(true), which adds a newfocusin/pointerdown_watcherpair ondocumentwithout removing the previous one (root cause is inGizmoMenuManager.toggle). The fix below (inaccessibility.js) eliminates the leak for this call site too.🤖 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 `@ui/gizmos.js` around lines 1299 - 1303, GizmoMenuManager.toggle(true) here keeps adding duplicate document listeners causing a leak; make toggle idempotent by modifying GizmoMenuManager.toggle so it checks for existing watcher state before adding listeners (e.g., if enabling and a watcher already exists, do nothing; if disabling remove the watcher and clear its reference). Update the toggle logic used by exitGizmoState/resetAttachedMeshIfMeshAttached callers so calling GizmoMenuManager.toggle(true) won’t register additional focusin/pointerdown watchers when one is already active.
🤖 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 `@main/main.js`:
- Around line 607-619: The exportCode call-site arguments are
inconsistent—InputManager.on("*", "Mod+KeyS", (e) => exportCode(workspace))
passes workspace but exportCodeButton.addEventListener("click", exportCode) and
other calls use no args; standardize by always passing the current workspace:
update exportCodeButton.addEventListener("click", ...) and any no-arg
exportCode() invocations to call exportCode(workspace) so all callers
consistently supply the workspace (refer to exportCode,
exportCodeButton.addEventListener, and the InputManager.on handler).
---
Outside diff comments:
In `@main/accessibility.js`:
- Around line 175-201: The toggle(show) method is adding document-level
listeners via this._watcher every time show is true but never removes them,
leaking handlers; update toggle and related cleanup so any existing watcher is
removed before adding a new one and also removed when hiding: check for
this._watcher and call document.removeEventListener("focusin", this._watcher)
and document.removeEventListener("pointerdown", this._watcher, {capture:true})
before assigning a new watcher, and ensure when toggling false you remove the
watchers and clear this._watcher; keep the existing behavior around
renderBadges(), focus logic and overlay.classList.toggle but add this watcher
removal logic to prevent accumulation.
---
Duplicate comments:
In `@ui/gizmos.js`:
- Around line 1299-1303: GizmoMenuManager.toggle(true) here keeps adding
duplicate document listeners causing a leak; make toggle idempotent by modifying
GizmoMenuManager.toggle so it checks for existing watcher state before adding
listeners (e.g., if enabling and a watcher already exists, do nothing; if
disabling remove the watcher and clear its reference). Update the toggle logic
used by exitGizmoState/resetAttachedMeshIfMeshAttached callers so calling
GizmoMenuManager.toggle(true) won’t register additional focusin/pointerdown
watchers when one is already active.
🪄 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
Run ID: c6a27dc4-83e6-4194-84bc-301ac692f5f5
📒 Files selected for processing (6)
main/accessibility.jsmain/context.jsmain/inputmanager.jsmain/main.jsui/colourpicker.jsui/gizmos.js
Summary
InputManagerto make things clearer.Ctrl + G(in any context except overlay or typing) and persists until the context is switched away from gizmo/navigation - i.e. primarily if you do something in the editor.Some keyboard input listeners have NOT been migrated on purpose, because they are limited to a specific context (e.g. when the keyboard shortcuts overlay specifically is active) or because they are extremely complicated!
AI usage
Claude Sonnet 4.6 used throughout as a co-author, carefully checked by a human.
Questions
Summary by CodeRabbit