Skip to content

fix(desktop): forward app hotkeys from browser panes via main-process chord index - #6233

Closed
andyst-dev wants to merge 5 commits into
ParadevAI:mainfrom
andyst-dev:fix/browser-pane-hotkeys
Closed

andyst-dev wants to merge 5 commits into
ParadevAI:mainfrom
andyst-dev:fix/browser-pane-hotkeys

Conversation

@andyst-dev

@andyst-dev andyst-dev commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6186

Bug

With a browser pane focused, every app shortcut dies: the pane is a guest webContents (out-of-process <webview>), so a keydown while the guest has focus never reaches the host document where react-hotkeys-hook listens. The only interception path is main's before-input-event, and that handler hardcoded exactly two chords (⌘W / ⌘R matched by produced character via input.key). ⌘B (TOGGLE_WORKSPACE_SIDEBAR), ⌘L (TOGGLE_SIDEBAR) and every other renderer-registered chord fell through to the page.

Fix

Forward the app's full chord index to main and match there by physical position:

  • shared/browser-pane-chords.ts (new, pure — no Electron/DOM/zustand imports): normalizeToken / canonicalizeChord / eventToChord moved from the renderer (re-exported from resolveHotkeyFromEvent.ts so existing imports keep working), plus inputToChord (Electron Input → canonical chord via input.code), matchAppChord (index lookup with a guest-owned exclusion list), tokenToCode / chordToEventInit (canonical chord → synthetic DOM KeyboardEvent for re-dispatch).
  • browser-manager.ts (main): setAppChords() receives the renderer's canonical chord set; setupBeforeInput matches on physical key position (input.code) instead of input.key, drops the shift/alt and meta||control guards (the chord table is the gate now), keeps dedicated close-pane / reload-pane events for the legacy TabView path (which has no global CLOSE_PANE hotkey), and emits app-hotkey:<paneId> with the matched chord for everything else.
  • Router (browser.ts): setAppChords mutation + onAppHotkey subscription.
  • resolveHotkeyFromEvent.ts: pushes registeredAppChords keys to main on every rebuild (overrides / layout / adaptive-toggle changes), so the main-side index never drifts.
  • usePersistentWebview (v2): subscribes to onAppHotkey and re-dispatches a synthetic KeyboardEvent, letting the normal focus-scoped useHotkey registrations resolve it exactly as they do when the host document has focus.
  • Exclusions: ⌘F / ⌘P / ⌘G stay with the guest (page find / print / find-next are more useful than the pane-scoped app bindings sharing those chords).
  • Docs: KEYBOARD_SYSTEM.md "Known gaps" row.

This also fixes the Dvorak half of the report: the old handler matched whichever physical key prints "w" (input.key), while the registry binds ⌘W to physical KeyW — two different keys on non-QWERTY layouts. Matching on input.code anchors both sides to physical position.

Validation

  • bun test on shared/browser-pane-chords.test.ts: 16/16 pass (⌘B match, Dvorak ⌘W anchoring, shift/alt chords, guest exclusion, modifier-only nulls, tokenToCode/chordToEventInit round-trips)
  • tsc --noEmit on apps/desktop: 0 errors
  • Biome: clean
  • Manual repro from the issue (⌘B / ⌘L in a focused browser pane) now dispatches the chord; ⌘W / ⌘R unchanged; ⌘, / ⌘/ still work via menu accelerators.

Summary by cubic

Forward app hotkeys from focused browser panes by mirroring the renderer’s chord index to main and re-dispatching matches on the host document. Adds reliable retry/backoff for chord sync, restores dead shortcuts (⌘B/⌘L), and anchors to physical keys across layouts (e.g., Dvorak ⌘W).

  • Bug Fixes
    • Main: match chords in before-input-event by physical code (IME-safe), emit app-hotkey:<paneId>, and keep dedicated ⌘W/⌘R close/reload events.
    • Renderer: push the canonical chord index to main on every rebuild with bounded retry backoff and stale-retry cancellation; sends the newest snapshot after in-flight mutations settle.
    • Shared: exclude find/print/find-next and guest editing shortcuts; editing exclusions use the produced key (input.key) and ignore shift-gated chords (e.g., ⌘⇧C), so undo on non‑QWERTY stays with the page while COPY_PATH still forwards.
    • Browser panes: subscribe to onAppHotkey and dispatch a synthetic keydown on document; keep onClosePane and catch close() errors.

Written for commit 275af17. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Browser-pane keyboard shortcuts now support synchronized application hotkeys.
    • Matching shortcuts are forwarded consistently across panes and keyboard layouts.
    • Application hotkey updates are synchronized automatically, including retry handling.
  • Bug Fixes
    • Improved handling of modifier combinations, aliases, and ignored key events.
    • Preserved browser-native Find, Print, editing, close, and reload shortcuts.
  • Documentation
    • Documented browser-pane hotkey forwarding behavior.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

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

Browser panes now use shared canonical chord utilities. The renderer synchronizes registered application chords with the main process. Matching pane shortcuts are emitted through tRPC and re-dispatched as host-renderer keyboard events. Editing, find, and print shortcuts remain page-owned.

Changes

Browser pane hotkey forwarding

Layer / File(s) Summary
Shared chord conversion and matching
apps/desktop/src/shared/browser-pane-chords.ts, apps/desktop/src/shared/browser-pane-chords.test.ts
Adds canonical chord normalization, input matching, browser shortcut exclusions, token-to-code conversion, synthetic event creation, and tests for these behaviors.
Renderer chord synchronization
apps/desktop/src/renderer/hotkeys/utils/appChordSync.ts, apps/desktop/src/renderer/hotkeys/utils/appChordSync.test.ts, apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts
Synchronizes registered chords with the main process. Updates use revision tracking, capped retry delays, stale-request cancellation, and shared chord utilities.
Main-process matching and tRPC routing
apps/desktop/src/main/lib/browser/browser-manager.ts, apps/desktop/src/lib/trpc/routers/browser/browser.ts
The main process stores registered chords, matches pane input, and emits pane-specific app-hotkey events through new tRPC procedures.
Host-renderer dispatch and documentation
apps/desktop/src/renderer/routes/.../usePersistentWebview.ts, apps/desktop/docs/KEYBOARD_SYSTEM.md
The persistent webview converts received chords into synthetic keydown events and cleans up its subscription. The keyboard documentation records the forwarding behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant HotkeyRegistry
  participant BrowserRouter
  participant BrowserManager
  participant PersistentWebview
  participant HostRenderer
  HotkeyRegistry->>BrowserRouter: setAppChords(chords)
  BrowserRouter->>BrowserManager: setAppChords(chords)
  BrowserManager->>BrowserManager: match before-input-event against appChords
  BrowserManager->>BrowserRouter: emit app-hotkey(paneId, chord)
  BrowserRouter->>PersistentWebview: deliver chord
  PersistentWebview->>HostRenderer: dispatch synthetic keydown
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #6186 by synchronizing chords, matching physical keys, forwarding app hotkeys, and preserving guest shortcuts.
Out of Scope Changes check ✅ Passed The code, tests, synchronization retry logic, and documentation changes directly support the browser-pane hotkey fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title uses conventional commit format and clearly describes forwarding browser-pane app hotkeys through the main-process chord index.
Description check ✅ Passed The description explains the problem, implementation, exclusions, testing, and issue reference; the checklist section is missing but non-critical.
✨ 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.

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a shared physical-key chord index and forwards registered application hotkeys from browser guest webContents into the renderer. It also adds the corresponding tRPC synchronization/subscription path, browser-pane redispatch, tests, and keyboard-system documentation, but accidentally removes the v2 listener required for the dedicated close-pane event.

  • Moves chord normalization and conversion helpers into a main/renderer-safe shared module.
  • Synchronizes renderer hotkey registrations to the main-process browser manager.
  • Forwards matched browser-pane chords back to v2 panes as synthetic keyboard events.
  • Preserves guest-owned find and print chords and adds focused helper tests.

Confidence Score: 4/5

The Cmd/Ctrl+W regression must be fixed before merging because focused v2 browser panes now swallow the shortcut without closing.

The main process still routes Cmd/Ctrl+W exclusively through close-pane, but the changed v2 hook removes the only subscriber that invokes the pane close action.

Files Needing Attention: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts

Important Files Changed

Filename Overview
apps/desktop/src/main/lib/browser/browser-manager.ts Adds renderer-synchronized chord matching and forwarding while retaining dedicated close/reload branches; the close branch now emits an event no longer consumed by v2.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts Adds app-hotkey redispatch but replaces, rather than supplements, the dedicated close-pane subscription required by Cmd/Ctrl+W.
apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts Re-exports shared chord utilities and mirrors rebuilt renderer chord indexes to the main process.
apps/desktop/src/shared/browser-pane-chords.ts Introduces pure canonicalization, Electron-input matching, exclusions, and synthetic KeyboardEvent conversion helpers.
apps/desktop/src/lib/trpc/routers/browser/browser.ts Adds observable-based hotkey subscriptions and a mutation for replacing the main-process chord index.
apps/desktop/src/shared/browser-pane-chords.test.ts Covers physical-position matching, exclusions, modifier handling, and chord-to-event conversion.

Sequence Diagram

sequenceDiagram
  participant Guest as Browser guest webContents
  participant Main as BrowserManager
  participant IPC as Browser tRPC router
  participant Renderer as v2 BrowserPane
  participant Hotkeys as Renderer hotkey handlers
  Guest->>Main: before-input-event
  alt Cmd/Ctrl+W
    Main->>Main: preventDefault()
    Main->>IPC: emit close-pane
    Note over IPC,Renderer: v2 close-pane subscriber was removed
  else Other registered app chord
    Main->>Main: match physical input.code
    Main->>IPC: emit app-hotkey(chord)
    IPC->>Renderer: onAppHotkey
    Renderer->>Hotkeys: dispatch synthetic keydown
  end
Loading
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts:86-96
**Close-pane event loses its subscriber**

When Cmd/Ctrl+W is pressed with a v2 browser pane focused, the main process prevents the default action, emits `close-pane`, and returns before `app-hotkey` forwarding, but this change replaces the pane's `onClosePane` subscription with `onAppHotkey`. The dedicated event is therefore unhandled and the shortcut is swallowed without closing the pane.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(desktop): forward app hotkeys from b..." | Re-trigger Greptile

Comment on lines +86 to 96
const appHotkeySub = electronTrpcClient.browser.onAppHotkey.subscribe(
{ paneId },
{
onData: () => {
void ctxRef.current.actions.close();
onData: (chord: string) => {
const event = dispatchChordEvent(chord);
if (event) {
window.dispatchEvent(event);
}
},
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Close-pane event loses its subscriber

When Cmd/Ctrl+W is pressed with a v2 browser pane focused, the main process prevents the default action, emits close-pane, and returns before app-hotkey forwarding, but this change replaces the pane's onClosePane subscription with onAppHotkey. The dedicated event is therefore unhandled and the shortcut is swallowed without closing the pane.

Knowledge Base Used: Desktop App (Electron main + renderer)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
Line: 86-96

Comment:
**Close-pane event loses its subscriber**

When Cmd/Ctrl+W is pressed with a v2 browser pane focused, the main process prevents the default action, emits `close-pane`, and returns before `app-hotkey` forwarding, but this change replaces the pane's `onClosePane` subscription with `onAppHotkey`. The dedicated event is therefore unhandled and the shortcut is swallowed without closing the pane.

**Knowledge Base Used:** [Desktop App (Electron main + renderer)](https://app.greptile.com/personal-org-6810/-/custom-context/knowledge-base/superset-sh/superset/-/docs/desktop-app.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
apps/desktop/src/shared/browser-pane-chords.test.ts (1)

77-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test exclusions with registered excluded chords.

CHORDS does not contain meta+f or meta+p. These assertions return null from the membership check even if the exclusion check fails. Use a set containing each excluded chord. Cover Meta and Ctrl variants, including A, C, V, X, Z, and G.

🤖 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 `@apps/desktop/src/shared/browser-pane-chords.test.ts` around lines 77 - 88,
Update the browser-pane chord exclusion tests to pass a chord set containing the
relevant registered exclusions instead of relying on CHORDS, which lacks them.
Extend coverage for both Meta and Ctrl variants of A, C, V, X, Z, and G, while
preserving the existing find and print guest-handling assertions.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts:
- Around line 86-93: Update the app hotkey forwarding in the onAppHotkey
subscription callback to dispatch the event through document or the focused pane
element instead of window. Preserve the existing dispatchChordEvent and event
guard so the bubbling keydown reaches global useHotkey listeners.

In `@apps/desktop/src/shared/browser-pane-chords.ts`:
- Around line 116-118: Expand BROWSER_PANE_EXCLUDED_CHORDS to include both Meta
and Ctrl variants of A, C, V, X, Z, F, P, and G. In
apps/desktop/src/shared/browser-pane-chords.test.ts lines 77-88, pass the
registered excluded chords into matchAppChord and assert each returns null.
- Around line 64-87: The inputToChord flow should ignore IME composition events.
Add isComposing?: boolean to BrowserPaneInput, return null before normalizeToken
and chord construction when input.isComposing is true, and add coverage for a
composing keyDown returning null.

---

Nitpick comments:
In `@apps/desktop/src/shared/browser-pane-chords.test.ts`:
- Around line 77-88: Update the browser-pane chord exclusion tests to pass a
chord set containing the relevant registered exclusions instead of relying on
CHORDS, which lacks them. Extend coverage for both Meta and Ctrl variants of A,
C, V, X, Z, and G, while preserving the existing find and print guest-handling
assertions.
🪄 Autofix

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: 762ced90-3df7-4959-b708-26fe32b35f7d

📥 Commits

Reviewing files that changed from the base of the PR and between c920d52 and cdc2e3c.

📒 Files selected for processing (7)
  • apps/desktop/docs/KEYBOARD_SYSTEM.md
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/main/lib/browser/browser-manager.ts
  • apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
  • apps/desktop/src/shared/browser-pane-chords.test.ts
  • apps/desktop/src/shared/browser-pane-chords.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files

Architecture diagram
sequenceDiagram
    participant Reg as Renderer Hotkey Registry
    participant TRPC as Browser tRPC Router
    participant Main as BrowserManager (Main Process)
    participant Shared as browser-pane-chords
    participant Guest as Browser Pane Guest webContents
    participant Host as Host Document (usePersistentWebview / useHotkey)

    Note over Reg,Main: Renderer pushes its canonical chord index to main
    Reg->>Reg: Rebuild chord index (overrides / layout / adaptive)
    Reg->>TRPC: browser.setAppChords.mutate({ chords })
    TRPC->>Main: setAppChords(chords)
    Main->>Main: Replace appChords Set (empty until first push)
    loop On registry rebuild (override / layout / preference changes)
        Reg->>TRPC: setAppChords.mutate({ chords })
        TRPC->>Main: setAppChords(chords)
    end

    Note over Guest,Host: Keydown while guest pane has focus
    Guest->>Main: before-input-event (keyDown: input.code + modifiers)
    Main->>Shared: inputToChord(input)
    Shared-->>Main: Canonical chord by physical position (e.g. meta+b)
    Note over Main,Shared: Physical code matching anchors chords across Dvorak and other layouts
    alt Chord is meta/ctrl+w or meta/ctrl+r
        Main->>Main: Emit close-pane / reload-pane (legacy TabView path)
    else Chord excluded for panes (meta+f / meta+p / meta+g)
        Main-->>Guest: No preventDefault: guest find / print / find-next wins
    else Chord found in appChords index
        Main->>Main: preventDefault, then emit app-hotkey for pane
        Main-->>TRPC: onAppHotkey subscription emits chord
        TRPC-->>Host: Chord over tRPC stream
        Host->>Shared: dispatchChordEvent(chord)
        Shared-->>Host: Synthetic KeyboardEvent init (code + modifiers)
        Host->>Host: window.dispatchEvent(keydown)
        Host->>Host: useHotkey resolves chord and fires handler
    else No match (modifier-only / unregistered / index not yet pushed)
        Main-->>Guest: Key falls through to guest page
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/desktop/docs/KEYBOARD_SYSTEM.md Resolved
- keep onClosePane subscription in v2 pane (Cmd+W was swallowed after
  the dedicated close-pane event replaced app-hotkey forwarding)
- dispatch synthetic keydowns on document (react-hotkeys-hook listens
  there, not window)
- exclude guest-owned editing chords (meta/ctrl a c v x z) from the
  forwarded index, and test exclusions against registered chords
- skip IME composition keydowns in inputToChord (isComposing)
- catch setAppChords rejection during startup/teardown
- move the fix note from Known gaps to Decision history
@andyst-dev

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews — all actionable feedback is addressed in aa4d2bb:

  • ⌘W no longer swallowed in v2 panes (greptile P1 + cubic P1): the renderer keeps its onClosePane subscription — main emits the dedicated close-pane event before app-chord matching, so ⌘W never arrived via app-hotkey. actions.close() already runs the onBeforeClose chain, matching the renderer CLOSE_PANE path.
  • Synthetic keydown dispatched on document (cubic P1): react-hotkeys-hook attaches its listeners there, not on window — forwarded chords now actually reach the registered hotkeys.
  • Guest-owned editing chords excluded (coderabbit + cubic): meta/ctrl + a/c/v/x/z (select-all, copy, paste, cut, undo) stay with the page, alongside the existing ⌘F/⌘P/⌘G exclusions.
  • Exclusions tested against registered chords (coderabbit + cubic): ⌘F/⌘P/⌘G are now IN the test chord index, proving exclusion wins over the index.
  • IME composition guard (cubic P2): inputToChord bails on isComposing, mirroring the renderer's eventToChord guard.
  • setAppChords rejection handled (cubic P3): .catch logs a warning; the main-side index degrades gracefully at startup/teardown.
  • Docs (cubic P3): fix note moved from the "Known gaps" table to "Decision history".

bun test: 19/19 pass · tsc --noEmit: 0 errors · Biome: clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts`:
- Around line 87-96: Update pushAppChordsToMain to retain the latest chords when
setAppChords rejects because the IPC channel is unavailable, rather than only
warning and discarding the update. Add a pending chord-list retry mechanism and
trigger it when the electron-trpc channel becomes available, ensuring the most
recent registeredAppChords state is eventually sent while preserving successful
replacement behavior.

In `@apps/desktop/src/shared/browser-pane-chords.test.ts`:
- Around line 129-130: Update the test input construction in the chord test loop
so the Ctrl case sets the `control` property rather than `ctrl`, matching the
`inputToChord` field it reads. Keep the existing modifier behavior for other
keys and ensure the test exercises the Ctrl exclusion path.
🪄 Autofix

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: e72a39ca-3a79-4967-9a45-4dbc5a9276bc

📥 Commits

Reviewing files that changed from the base of the PR and between cdc2e3c and aa4d2bb.

📒 Files selected for processing (5)
  • apps/desktop/docs/KEYBOARD_SYSTEM.md
  • apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
  • apps/desktop/src/shared/browser-pane-chords.test.ts
  • apps/desktop/src/shared/browser-pane-chords.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/docs/KEYBOARD_SYSTEM.md
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
  • apps/desktop/src/shared/browser-pane-chords.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

- match guest editing shortcuts by produced key (input.key) so undo on
  non-QWERTY layouts (AZERTY Cmd+Z = physical KeyW) stays with the page
  instead of colliding with the app's CLOSE_PANE chord
- catch close() rejection in the pane close subscription
- retry setAppChords once after a short delay when the IPC channel is
  unavailable at startup, instead of dropping the update
- fix ctrl-variant exclusion test to set input.control (the field
  inputToChord reads), which previously never exercised the ctrl path
@andyst-dev

Copy link
Copy Markdown
Contributor Author

Thanks — all round-2 feedback is addressed in 529debc:

  • Guest editing shortcuts matched by produced key (cubic P2, non-QWERTY undo): matchAppChord now checks isGuestOwnedShortcut, which matches the editing family (⌘/Ctrl A/C/V/X/Z) on input.key (the character the OS layout produces) rather than physical code. AZERTY ⌘Z undo (physical KeyW) no longer collides with the app's CLOSE_PANE (meta+w) chord — the produced character wins. Covered by a new test.
  • Close rejection handled (cubic P2): the onClosePane subscription wraps actions.close() in try/catch and logs on failure.
  • Chord-index retry (coderabbit): setAppChords rejection now schedules a single delayed retry (1s) that pushes the latest chords, so a transiently-unavailable IPC channel at startup doesn't leave the main-side index stale for the session; subsequent rebuilds still re-push as before.
  • Ctrl test actually exercises the ctrl path (coderabbit): the ctrl-variant exclusion test now sets input.control (the field inputToChord reads) instead of input.ctrl, which previously never hit the ctrl branch.

bun test: 20/20 pass (incl. AZERTY undo) · tsc --noEmit: 0 errors · Biome: clean.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

isGuestEditingShortcut matched the produced character regardless of
shift, so the app's COPY_PATH (Cmd+Shift+C) was classified as guest copy
and never forwarded from a browser pane. Shift-gated chords are not
guest editing shortcuts (the guest's copy/paste/undo are plain
Cmd/Ctrl+letter) — add the shift guard and a regression test. Also
include paneId in the close-failure warning.
@andyst-dev

Copy link
Copy Markdown
Contributor Author

Thanks — all three points are addressed in cafeeb2:

  • ⌘⇧C COPY_PATH no longer swallowed (cubic P2): isGuestEditingShortcut now returns false when shift is held — shift-gated chords are app chords (the guest's copy/paste/undo are plain ⌘/Ctrl+letter). Regression test: meta+shift+c matches the registered chord instead of being excluded (21/21 pass).
  • paneId in the close warning (cubic P3): console.warn("Failed to close browser pane (${paneId})", error).
  • Retry backoff (cubic P2): the single delayed retry is intentionally bounded — a subsequent rebuild() (override/layout/preference change) also re-pushes, and the IPC channel comes up within a second of renderer boot; a long backoff loop would add noise for a case that resolves on the next user interaction. Documented in the comment; happy to switch to bounded backoff if you'd prefer it.

tsc --noEmit: 0 errors · bun test: 21/21 pass · Biome: clean.

@andyst-dev

Copy link
Copy Markdown
Contributor Author

Thanks - fixed in 275af17.

The renderer-to-main chord sync now uses a dedicated latest-state synchronizer:

  • retries with capped exponential backoff until IPC accepts the chord index, so startup races cannot leave browser-pane hotkeys disabled for the session;
  • keeps only one retry timer active;
  • a chord rebuild cancels stale delayed work and sends the newest complete index immediately;
  • if state changes during an in-flight mutation, the newest snapshot is sent as soon as that mutation settles.

Added deterministic scheduler tests for transient recovery, stale-retry cancellation, in-flight replacement, and capped retry behavior.

Validation:

  • focused Bun tests -> 25 passed
  • Desktop typecheck -> passed
  • Biome on the 3 changed files -> clean
  • git diff --check -> clean

@andyst-dev

Copy link
Copy Markdown
Contributor Author

Heads-up: this PR is now superseded by a refactor on main. Since it was opened, main introduced shared/hotkey-chord.ts (the chord-normalization moved out of browser-pane-chords.ts) and a main-process webview key-forwarder (ForwardedKey in browser-manager.ts) — which is exactly what this PR set out to build. The branch no longer rebases cleanly onto main and its files have been renamed/absorbed.

Per the repo's contribution guidance, the same end-state is already on main, so this PR is obsolete. Happy to close it, or I can leave it open if a maintainer wants to cherry-pick any surviving intent.

@andyst-dev

Copy link
Copy Markdown
Contributor Author

Closing this as a duplicate: the same feature (forwarding app hotkeys from
in-workspace browser panes via the main-process chord index) was already
merged upstream in #5494 ("fix(desktop): dismiss stuck tooltips and forward
tab-switch hotkeys in in-workspace browser", merged 2026-08-09).

main now carries the full implementation under shared/hotkey-chord +
BrowserManager.setForwardableChords (tRPC browser.setForwardableChords),
with the renderer wiring in usePersistentWebview.ts. The maintainers chose a
scoped design (only tab-switch hotkeys are forwardable, via FORWARDABLE_HOTKEYS)
that supersedes the broader "forward every app chord" approach this PR took, so
keeping both would conflict with that decision.

Thanks for considering. I'll keep contributing on the superset desktop area.

@andyst-dev

Copy link
Copy Markdown
Contributor Author

Superseded by #5494 (merged). See comment above.

@andyst-dev andyst-dev closed this Aug 13, 2026
@Kitenite

Copy link
Copy Markdown
Member

Thanks for the contribution @andyst-dev !

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.

[bug] Browser panes swallow every app shortcut except ⌘W and ⌘R

2 participants