Repository navigation
Conversation
A Browser Tab's page is its own native WebContents, so the renderer's window keydown listeners never saw keys pressed in it: ⌘K, ⌘1–⌘9 and ⌃⌘+arrow did nothing, and ⌘W fell through to the menu's Close Window. Main now listens to before-input-event on each page and, only while the page holds native focus on screen, takes a small app-owned set (browser/app-shortcuts.ts, a pure tested matcher): - ⌘K, ⌘1–⌘9, ⌃⌘+arrow are replayed into the renderer as a window keydown, so the existing listener and its guards (the ⌘K terminal guard included) decide, unchanged. - ⌘W closes the page's own tab and ⌘L returns focus to its address bar, routed by the opaque tab id to the pane that draws it, never to the split view's focused pane. Everything else, including every editing chord, Escape and Tab, stays the page's. Focus moves back to the app renderer before delivery.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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 |
Review follow-up on #884. - A click into a native page never reaches the renderer, so the split view's focused pane was whichever pane was last clicked in the app and ⌃⌘+arrow from a page moved from the wrong place. A replayed chord now focuses the pane drawing the page first (BrowserPane onFocusPane, wired by Home and the ticket workspace), proven against the real split listener and workspace store. - runBrowserAppShortcut now reports whether the app acted: a replayed chord no listener preventDefault-ed (⌘K under the terminal guard, ⌃⌘+arrow unsplit), a close with no close, or a tab no pane draws. The hook then asks main to give keyboard focus back to the page through the new client-local volli:browser-focus-page command (BrowserTabHost.focusPage, quiet for a gone or off-screen tab). - Drop runBrowserAppShortcut's test-only target parameter.
docs/BOUNDARIES.md rule 5: no new raw IPC. The two raw channels this PR added are now desktop-only entries (DESKTOP_ENTRIES, client-local, host-placed like hosts.*), served from the host's one handler map: - browserShortcuts.subscribe: every app shortcut a focused Browser page hands back (was the volli:browser-app-shortcut push). - browserShortcuts.focusPage: focus back to a page whose shortcut did nothing (was volli:browser-focus-page). The wire types move to @volli/shared; host-core gains a structural BrowserShortcutsPort (hostd has none, so both answer unavailable); session-rpc builds both procedures with zod validators and publishes their schemas; desktop main's new app-shortcut-relay focuses the window then feeds every subscriber, and wires the port. The renderer follows the stream over the Session RPC client and re-follows after a beat if it ends in error. Both raw channels are deleted from contract, placement, descriptors, preload and the browser IPC handlers. Behaviour and the review-round regression tests are unchanged.
hussainph
added a commit
that referenced
this pull request
Oct 18, 2026
…browser-tab-blank-loading-surfa VC-302: theme the native Browser Tab blank/loading plane (stacked on #884 — merge after it)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
A Browser Tab's page is its own native
WebContentsView, so the renderer's windowkeydownlisteners never saw a key pressed inside it. With a page focused, ⌘K, ⌘1–⌘9 and ⌃⌘+arrow did nothing, and ⌘W went through to the menu's Close Window role (A15 / VC-301).Main (
main/browser/app-shortcuts.ts, new, pure, 100% covered and added to the coverage gate):browserKeyRoute(input)sorts each page key intopage(the page keeps it),swallow(the app owns it but has nothing to do) orapp.tab-host.tsadds onebefore-input-eventlistener per page. The listener acts only when the page is on screen and has native focus. CDP key events also reach this hook, so an agent pressing ⌘W into a page nobody is focused on still goes to the page and never closes a tab.index.tswires the new optionaldeliverAppShortcutdep tomain/browser/app-shortcut-relay.ts, which focuses the app renderer and then hands the shortcut to every window subscribed to the desktop-onlybrowserShortcuts.subscribeentry (see Transport below).The app owns this deliberately small set:
keydown. The listener that already owns the chord makes the decision, guards included.use-command-palette-shortcut.tsand its terminal guard are untouched. First, the split-view pane that draws the page is focused, because a click into a native page never reaches the renderer, so the split view's focused pane is otherwise whichever pane was last clicked in the app.Every other key stays with the page. That includes all editing chords (⌘B/C/V/Z/A/P), Escape, Tab/Shift+Tab, and ⌘[ / ⌘], which mean outdent and indent in page editors. Main can't tell whether a page field has focus, so the set holds only chords that a page in a real browser doesn't get to keep either. ⌘K is the ticket's one explicit exception. A held ⌘W/⌘L fires once, and its repeats are swallowed so they can't reach the menu's Close Window.
Renderer:
components/browser/browser-app-shortcuts.ts(new, 100%, gated) holds a registry keyed by tab id, plus the replay.BrowserPaneregisters its own close and Address field while it's visible, and takes a new optionalonCloseprop. Every surface passes the close its tab strip's × already uses:ticket-detailpassescloseTab(tab).browser-previewpasses the sameapi.closecall as its × button.home-surface's browser branch ofhandleCloseis pulled out into acloseBrowser(tabId)callback, unchanged, so both paths share it.BrowserPanealso takesonFocusPane. Home wires it tofocusHomePane(pane)and the ticket workspace tofocusTicketPane(pane).hooks/use-browser-app-shortcuts.tsis mounted once inAppShell. WhenrunBrowserAppShortcutreports that the app did nothing, the hook calls the desktop-onlybrowserShortcuts.focusPageentry, which hands keyboard focus back to the page. "Did nothing" means one of: a replayed chord no listenerpreventDefault-ed (for example ⌘K under the terminal guard, or ⌃⌘+arrow with no split), a pane with no close, or a tab no pane draws. On the main side,BrowserTabHost.focusPagestays quiet for a tab that is gone or off screen.Docs: a Browser Tab section in
reference/keyboard-shortcuts.mdx.Choices for the maintainer to confirm
Transport (docs/BOUNDARIES.md rule 5)
Both crossings are desktop-only tier entries,
client-localand host-placed likehosts.*, served from the host's one handler map. No raw IPC channel is added.browserShortcuts.subscribeBrowserAppShortcutEventvolli:browser-app-shortcutpushbrowserShortcuts.focusPage{ tabId }→nullvolli:browser-focus-pageinvoke@volli/shared(browser-app-shortcut.ts).BrowserShortcutsPortis a structural port. hostd has none, so both entries answer unavailable there.pnpm check:protocol-schema --base origin/mainreports two client-local additions and passes.app-shortcut-relay.tsis gated at 100%, andindex.tswires the port.followBrowserAppShortcutsfollows the stream over the Session RPC client. If the stream ends in error, it follows it again after a second; a shortcut is a press, not state, so nothing is lost.contract.ts,placement.ts,ipc-descriptors.ts, the preload and the browser IPC handlers.Why this shape
Replaying the chord keeps one meaning for each shortcut: the renderer predicates stay the single source.
app-shortcuts.test.tschecks every replayed chord againstisCommandPaletteKeyEvent,projectIndexForKeyEventandsplitShortcutForKeyEvent. Only the two tab-specific actions are named, because only they need to know where the key came from.Known limitations
preventDefault". That is the renderer's own definition of handled.use-project-shortcuts.tscallspreventDefaultfor ⌘N even when there is no Nth project, exactly as it does in the app today, so that case keeps focus in the app rather than returning it to the page. I left that hook alone.Browser overlays (acceptance)
before-input-eventhook sees their keys.<select>popup menu or a JSalert. These take keys outside the WebContents.For the maintainer
docs/BOUNDARIES.mdrule 5, no raw IPC was added (see Transport below).Verification
src/main/browser/,src/main/ipc-descriptors.test.ts,src/ipc/placement.test.ts,src/renderer/src/components/browser/andsrc/renderer/src/hooks/use-split-shortcuts.browser.test.tsx. That is 22 files and 1025 tests, all passing. They include:tab-hostbefore-input tests: keys are routed while the page is focused and on screen; left alone when unfocused (CDP), staged, or with no deliver dep; repeats are swallowed; the page's own tab is named when two are shown;focusPagereturns focus to a shown page and does nothing for a hidden or missing tab.use-split-shortcuts.browser.test.tsx: three panes, with the app's focused pane on the left and the page in the middle. ⌃⌘→ from the page lands on the right pane through the real split listener and workspace store. A control case without the pane focus lands one pane short. Unsplit, the chord is reported as unclaimed.browser-pane.shortcuts.test.tsx: ⌘W closes the pane's own tab and not the other visible pane; ⌘W with no close is reported as not acted on; a replay focuses the pane's own split pane; ⌘L focuses and selects the right Address field; hidden or unmounted panes don't answer.browser-app-shortcuts.test.ts: the acted/ignored results, and the order (pane focus before keydown).desktop-router.test.tscovers the stream and the hand-back over both contract links, plus input bounds.host-handlers.test.tscovers unavailable on hostd and the port pass-through. The fixtures aredesktop-ipc,handler-projectionandsession-handlers.test-support.app-shortcut-relay.test.tschecks focus before delivery, fan-out, unsubscribe, and a listener that throws.browser-app-shortcuts.test.tschecks re-follow on error and the stop.app-shortcuts.ts,app-shortcut-relay.ts,browser-app-shortcuts.ts, session-rpc'sdesktop-router.tsand host-core'shandlers/host-handlers.ts.@volli/shared,@volli/host-core,@volli/session-rpc, apps/desktop and the root config.vp run typecheck(apps/desktop, all four configs) andvp check: clean.volli-drivesends synthetic CDP input to an accessory app that never takes focus, and this fix acts only on native page focus, so the drive can't exercise it. The native repro in the ticket comments (vc322-browser-boundary.mjs --native-window, PR fix(a11y): restore dialog focus and record release blockers (VC-322) #579, not on main) is the right live check.