Skip to content

v1.5.6 - Definitive Sidebar Toggle Fix

Choose a tag to compare

@doublegate doublegate released this 08 Jan 04:02
· 1 commit to master since this release

Summary

Critical bug fix release that definitively resolves the Ctrl+I sidebar toggle functionality after 5 failed attempts (v1.5.1-v1.5.5). The root cause was finally identified by comparing v1.4.0 (working) with v1.5.x (broken).

Root Cause Analysis

Why v1.4.0 Worked

  • globalShortcut.register("Control+I") was COMMENTED OUT in main.ts
  • Sidebar toggle was handled entirely by keyboard event listener in the renderer
  • Keyboard events propagate normally when window has focus

Why v1.5.1-v1.5.5 Failed

Version Approach Why It Failed
v1.5.1 onSidebarToggle(callback) contextBridge callback proxy unreliable
v1.5.2 Technical debt release Sidebar still broken
v1.5.3 CustomEvent in preload Context isolation blocked it (preload's window !== page's window)
v1.5.4 Stored callback pattern Same contextBridge proxy issue
v1.5.5 Simplified callback Same underlying contextBridge problem

The Pattern That Failed:

Main Process → IPC → Preload → contextBridge callback → Page
                              ↑
                     Proxy fails to reliably invoke React state setter

The Solution

Use webContents.executeJavaScript() to dispatch a CustomEvent directly into the renderer context.

The Pattern That Works:

Main Process (globalShortcut detects Ctrl+I)
    ↓
webContents.executeJavaScript() dispatches CustomEvent directly into renderer
    ↓
document.addEventListener('geforce-sidebar-toggle') in overlay
    ↓
React state setter called in the same JavaScript context
    ↓
Sidebar toggles successfully

Why This Works

  1. executeJavaScript() runs code directly in the renderer's main world (same context as overlay)
  2. CustomEvent is a native DOM mechanism that doesn't require IPC callback proxying
  3. The event listener receives events reliably because it's in the same JavaScript context
  4. No contextBridge proxy issues - the event dispatch and listener are both in page context

Implementation Details

main.ts - registerShortcuts()

const success = globalShortcut.register("Control+I", () => {
  // Dispatch CustomEvent directly in renderer - bypasses IPC callback issues
  mainWindow.webContents.executeJavaScript(`
    (function() {
      document.dispatchEvent(new CustomEvent('geforce-sidebar-toggle'));
    })();
  `);
});

index.tsx - useEffect()

// PRIMARY: Listen for CustomEvent dispatched by main process
const customEventHandler = () => {
  setVisible((v) => !v);
};
document.addEventListener("geforce-sidebar-toggle", customEventHandler);

// FALLBACK: Keyboard handler for when globalShortcut fails
const keyboardHandler = (e: KeyboardEvent) => {
  if (e.ctrlKey && e.key === "i") {
    e.preventDefault();
    setVisible((v) => !v);
  }
};
window.addEventListener("keydown", keyboardHandler);

Testing Instructions

  1. Launch GeForce Infinity
  2. Press Ctrl+I - sidebar should toggle open/closed
  3. Press Ctrl+I again - sidebar should toggle again
  4. Verify toggle works regardless of where focus is in the window
  5. Check DevTools console for debug logging confirming CustomEvent dispatch

Files Changed

  • src/electron/main.ts - executeJavaScript + CustomEvent dispatch in registerShortcuts()
  • src/overlay/index.tsx - CustomEvent listener + fallback keyboard handler with cleanup
  • CHANGELOG.md - v1.5.6 release documentation
  • README.md - Updated latest release section
  • package.json - Version bump to 1.5.6
  • VERSION - Version bump to 1.5.6

Lessons Learned

  1. contextBridge callback proxying is unreliable for IPC-triggered state changes
  2. executeJavaScript bypasses context isolation for simple event dispatch
  3. CustomEvent is reliable when dispatched and listened in the same context
  4. Always compare with working version when debugging regressions
  5. Document previous failed attempts to avoid repeating them

Generated with Claude Code