Skip to content

fix(desktop): dismiss stuck tooltips and forward tab-switch hotkeys in in-workspace browser - #5494

Merged
Kitenite merged 8 commits into
mainfrom
fix-in-workspace-browser-ui-bugs
Aug 9, 2026
Merged

Kitenite merged 8 commits into
mainfrom
fix-in-workspace-browser-ui-bugs

Conversation

@Kitenite

@Kitenite Kitenite commented Jul 7, 2026 •

Copy link
Copy Markdown
Member

Fixes five UI bugs in the in-workspace browser (plus a favicon fallback picked up during verification). Most stem from the browser being a native Electron <webview> composited on top of the host page, so it captures pointer/keyboard events the host renderer never sees.

1. Popover/tooltip stays stuck over the URL bar

Radix keeps a tooltip open during its "hoverable content" grace period, waiting for more pointer events — but once the cursor crosses onto the native webview the host document stops receiving pointer events, so the close logic never runs and the tooltip hangs over the URL bar.

Fix: disableHoverableContent on the webview-adjacent tooltips. Since #5870 restyled the preset tooltips into HotkeyTooltip, the flag now lives there (its content is always a non-interactive hotkey chip, so there is no downside anywhere it's used), plus the Manage Presets and DevTools tooltips.

2. Tab-switch keyboard shortcuts don't work when the browser is focused

With the guest webview focused, keystrokes route to the guest renderer, so host react-hotkeys-hook never sees them. browser-manager.ts already intercepts via before-input-event but only forwarded Cmd/Ctrl+W and +R.

Fix: the renderer computes the (override/layout-aware) chords for the tab-switch hotkeys (PREV/NEXT_TAB, _ALT variants, JUMP_TO_TAB_1..9) and syncs them to the main process via browser.setForwardableChords. before-input-event suppresses exactly those chords in the guest and forwards them over a browser.onKeyForward subscription; usePersistentWebview rebuilds the KeyboardEvent, re-resolves it through resolveHotkeyFromEvent, and replays it on document only if it resolves to an allowlisted tab-switch hotkey. Chord canonicalization is shared between main and renderer (shared/hotkey-chord.ts, single assembleChord helper) and pinned by a parity unit test.

3. Browser tab favicon fallback

Chromium guesses /favicon.ico for pages that declare no favicon, so faviconUrl can point at a 404 and the tab shows a broken image (and pages with no favicon showed no icon at all). Tabs now fall back to a globe icon when the favicon is missing or fails to load, keyed by page + favicon URL so navigation retries.

4. Opening a link into an existing browser pane never navigates

Clicking a link (e.g. a URL in a terminal) opens the in-workspace browser — but once a browser pane exists, every subsequent link click replaced the pane in the store without the page ever loading the new URL. Root cause: usePersistentWebview captured the pane URL in a ref at first mount; when openPane -> replacePane swaps in a new pane id on the same component instance, the attach effect re-ran with the stale first-mount URL, so the fresh webview loaded the old page (and the persist echo then reverted the pane data too).

Fix: keep the attach-URL ref current on every render. Attach still keys on paneId alone, so tab switches and navigation echoes don't re-attach; only a pane replacement picks up the new URL. Verified live: alternating cmd+clicks between two terminal URLs navigate the pane correctly every time (previously only the first-ever link worked).

5. Shift+click opens links in a new browser tab

The default URL-link tier map now assigns shift -> "Open in new tab" (previously unbound): shift+clicking a URL in a terminal opens it in a new in-app browser tab instead of doing nothing. Existing profiles are migrated on read when their stored map deep-equals the retired default (same pattern as the sidebar file-link migration) — anyone who customized any tier keeps their configuration.

Review-comment cleanups

  • setForwardableChords sync failures now log a warning instead of being swallowed (stale suppression is otherwise undiagnosable).
  • BrowserTabFavicon extracted to its own component module per repo structure guidelines; its key now includes the page URL so a failed favicon retries on navigation even when the favicon URL is unchanged.
  • normalizeToken's alias lookup is case-insensitive, so canonicalizeChord("MetaLeft+KeyK") === eventToChord output (meta+k); pinned by tests.
  • The two bot P1s (missing preventDefault on the forward path, alt-only chords dropped) were against the first commit only — the current code preventDefaults every forwarded chord (verified live: the guest page receives nothing) and has no meta/ctrl early-return.

Regression safety

  • Existing Cmd/Ctrl+W/+R interception is untouched (re-verified live).
  • Non-registered keys fall through: plain typing and in-page shortcuts in the guest are unchanged (verified live).
  • A balancing keyup is dispatched so react-hotkeys-hook's pressed-key set never gets a stuck key.

Verification (live E2E on the dev app via CDP + OS-level keystrokes)

  • Forwarding chain end-to-end: with the guest webview holding OS focus, a real ctrl+Tab was intercepted in main (guest page received nothing), forwarded, replayed on the host document (observed as an untrusted keydown), and the workspace switched tabs. ⌘W with guest focus closed just the pane.
  • Tooltip A/B: with main's HotkeyTooltip, hover a preset button → move toward the webview → tooltip sticks open indefinitely (bug reproduced); with this branch it closes as the pointer leaves the trigger.
  • Link navigation: real cmd+clicks on URLs rendered in a terminal, alternating between two links — every click navigates the browser pane (previously only the first).
  • Favicon: about:blank → globe; example.com (404 favicon) → globe; github.com → real favicon.
  • Chord parity: bun test src/shared/hotkey-chord.test.ts pins eventToChord ≡ chordFromInput for all forwarded chords plus alias-case canonicalization.
  • Lint clean; the 5 local typecheck errors are pre-existing and reproduce identically on origin/main in the same environment (CI Typecheck green).

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 7, 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

The PR adds shared keyboard-chord conversion, configurable browser-pane hotkey forwarding through tRPC, renderer-side replay for selected hotkeys, favicon fallback rendering, non-hoverable tooltip content, and URL-specific link defaults.

Changes

Key forwarding pipeline

Layer / File(s) Summary
Shared chord handling
apps/desktop/src/shared/hotkey-chord.ts, apps/desktop/src/shared/hotkey-chord.test.ts, apps/desktop/src/renderer/hotkeys/utils/*
Shared utilities normalize and compare keyboard chords across DOM and Electron inputs. Tests verify canonicalization and conversion parity.
Browser-manager key forwarding
apps/desktop/src/main/lib/browser/browser-manager.ts
BrowserManager stores forwardable chords and emits pane-specific ForwardedKey events for matching browser inputs.
tRPC forwarding procedures
apps/desktop/src/lib/trpc/routers/browser/browser.ts
The browser router configures forwardable chords and exposes the onKeyForward subscription.
Renderer hotkey replay and pane attachment
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
The webview hook uses the current pane URL, synchronizes configured chords, filters forwarded events, dispatches keydown and keyup, and cleans up subscriptions.

Browser tab presentation

Layer / File(s) Summary
Favicon rendering and fallback
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/components/BrowserTabFavicon/*, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx
Browser tabs render favicons with URL-keyed load-failure handling and a globe fallback.

Tooltip behavior

Layer / File(s) Summary
Non-hoverable tooltip content
apps/desktop/src/renderer/hotkeys/components/HotkeyTooltip/HotkeyTooltip.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx
Hotkey, settings, and DevTools tooltips disable hoverable content while retaining their delays.

URL-link preferences

Layer / File(s) Summary
URL-link default mapping
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
URL links use dedicated defaults for new-tab, pane, and external actions.

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

Sequence Diagram(s)

sequenceDiagram
  participant BrowserPane
  participant BrowserRouter
  participant BrowserManager
  participant Document
  BrowserPane->>BrowserRouter: Configure forwardable chords
  BrowserRouter->>BrowserManager: setForwardableChords(chords)
  BrowserManager->>BrowserRouter: Emit key-forward:paneId
  BrowserRouter->>BrowserPane: Deliver ForwardedKey
  BrowserPane->>Document: Dispatch keydown and keyup
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main desktop browser changes: tooltip dismissal and forwarding tab-switch hotkeys.
Description check ✅ Passed The description is detailed and includes changes, rationale, testing, and regression coverage, but it omits the required checklist section.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-in-workspace-browser-ui-bugs

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.

@ghost

ghost commented Jul 7, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Move hotkey chord logic to shared library
2 Implement key forwarding in the main process
3 Expose key forwarding via tRPC
4 Replay forwarded hotkeys in the renderer
5 Fix stuck tooltips near browser webview
Open in Stage

Chapters generated by Stage for commit 9a3cb48 on Jul 7, 2026 9:34pm UTC.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes browser-pane tooltip and keyboard handling around native webview focus.

  • Adds a pane-scoped key-forwarding subscription from main to renderer.
  • Replays allowlisted tab-switch hotkeys into the host document.
  • Disables hoverable tooltip content on static tooltips next to the webview.

Confidence Score: 4/5

The webview hotkey forwarding path has two contained issues that should be fixed before merging.

  • Some configured tab-switch chords can still be filtered out before they reach the renderer.
  • Forwarded chords can also run inside the focused guest page because the original event is not suppressed.
  • The tooltip changes are limited to static content and look safe.

apps/desktop/src/main/lib/browser/browser-manager.ts

Important Files Changed

Filename Overview
apps/desktop/src/lib/trpc/routers/browser/browser.ts Adds a pane-scoped onKeyForward subscription and cleans it up like the existing browser event subscriptions.
apps/desktop/src/main/lib/browser/browser-manager.ts Extends webview before-input-event handling, but the forwarding branch can drop some configured tab-switch chords and can leave forwarded chords active in the guest page.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts Subscribes to forwarded webview keys, resolves them through the central hotkey registry, and replays allowlisted tab-switch events.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/components/V2PresetBarItem/V2PresetBarItem.tsx Disables hoverable content for a static preset tooltip label.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx Disables hoverable content for the static DevTools tooltip.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/components/BrowserToolbar/BrowserToolbar.tsx Disables hoverable content for static back, forward, and reload tooltips.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant Guest as Focused webview
  participant Main as before-input-event
  participant TRPC as browser.onKeyForward
  participant Host as Host renderer
  Guest->>Main: keyDown chord
  Main->>TRPC: emit forwarded key
  TRPC->>Host: deliver ForwardedKey
  Host->>Host: resolveHotkeyFromEvent
  alt Allowlisted tab hotkey
    Host->>Host: dispatch synthetic keydown and keyup
  else Not allowlisted
    Host->>Host: ignore
  end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant Guest as Focused webview
  participant Main as before-input-event
  participant TRPC as browser.onKeyForward
  participant Host as Host renderer
  Guest->>Main: keyDown chord
  Main->>TRPC: emit forwarded key
  TRPC->>Host: deliver ForwardedKey
  Host->>Host: resolveHotkeyFromEvent
  alt Allowlisted tab hotkey
    Host->>Host: dispatch synthetic keydown and keyup
  else Not allowlisted
    Host->>Host: ignore
  end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/main/lib/browser/browser-manager.ts:264
**Alt-Only Bindings Are Dropped**

When a user configures a tab-switch binding that uses `Alt` without `Cmd` or `Ctrl`, this guard returns before the renderer can resolve the chord against the allowlist. The webview-focused shortcut then stays broken even though `resolveHotkeyFromEvent` would be able to accept that configured tab-switch hotkey.

### Issue 2 of 2
apps/desktop/src/main/lib/browser/browser-manager.ts:280-288
**Forwarded Chords Still Reach Guest**

The new forwarding path emits accepted tab-switch chords without stopping the original key event, unlike the close and reload branches above. A focused page can receive the same `Ctrl`/`Cmd` tab-switch chord while the host also replays it, so one keypress can both switch the workspace tab and trigger a guest-page shortcut.

Reviews (1): Last reviewed commit: "fix(desktop): dismiss stuck tooltips and..." | Re-trigger Greptile

event.preventDefault();
this.emit(`reload-pane:${paneId}`);
return;
if (!input.shift && !input.alt) {

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 Alt-Only Bindings Are Dropped

When a user configures a tab-switch binding that uses Alt without Cmd or Ctrl, this guard returns before the renderer can resolve the chord against the allowlist. The webview-focused shortcut then stays broken even though resolveHotkeyFromEvent would be able to accept that configured tab-switch hotkey.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/lib/browser/browser-manager.ts
Line: 264

Comment:
**Alt-Only Bindings Are Dropped**

When a user configures a tab-switch binding that uses `Alt` without `Cmd` or `Ctrl`, this guard returns before the renderer can resolve the chord against the allowlist. The webview-focused shortcut then stays broken even though `resolveHotkeyFromEvent` would be able to accept that configured tab-switch hotkey.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +280 to 288
code: input.code,
meta: input.meta,
control: input.control,
alt: input.alt,
shift: input.shift,
} satisfies ForwardedKey);
};

wc.on("before-input-event", handler);

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 Forwarded Chords Still Reach Guest

The new forwarding path emits accepted tab-switch chords without stopping the original key event, unlike the close and reload branches above. A focused page can receive the same Ctrl/Cmd tab-switch chord while the host also replays it, so one keypress can both switch the workspace tab and trigger a guest-page shortcut.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/lib/browser/browser-manager.ts
Line: 280-288

Comment:
**Forwarded Chords Still Reach Guest**

The new forwarding path emits accepted tab-switch chords without stopping the original key event, unlike the close and reload branches above. A focused page can receive the same `Ctrl`/`Cmd` tab-switch chord while the host also replays it, so one keypress can both switch the workspace tab and trigger a guest-page shortcut.

How can I resolve this? If you propose a fix, please make it concise.

@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.

3 issues found across 6 files

Architecture diagram
sequenceDiagram
    participant Guest as Guest Webview
    participant Main as Electron Main Process (browser-manager)
    participant Sub as browser.onKeyForward (tRPC)
    participant Renderer as Renderer (usePersistentWebview)
    participant Doc as Host Document
    participant Hotkey as react-hotkeys-hook

    Note over Guest,Hotkey: NEW: Keyboard forwarding for tab switch hotkeys

    Note over Guest: User presses key in guest webview
    Guest->>Main: before-input-event (keyDown)

    alt Not meta/control
        Main->>Main: return (ignored, guest handles normally)
    else Meta/control + (W or R) WITHOUT shift/alt
        Main->>Main: preventDefault
        Main->>Main: emit close-pane / reload-pane (existing behavior)
    else Other meta/control chords (including with shift/alt)
        Main->>Main: emit key-forward event with ForwardedKey
        Main->>Sub: ForwardedKey (paneId)
        Sub->>Renderer: push ForwardedKey via subscription
        Renderer->>Renderer: build KeyboardEventInit, create KeyboardEvent
        Renderer->>Renderer: resolveHotkeyFromEvent(event)
        alt HotkeyId in FORWARDABLE_HOTKEYS (tab-switch)
            Renderer->>Doc: dispatch keydown event
            Renderer->>Doc: dispatch keyup event (balance)
            Doc->>Hotkey: react-hotkeys-hook processes chord
            Hotkey->>Hotkey: Execute tab switch (e.g., NEXT_TAB)
        else Non-tab-switch hotkey or no match
            Renderer->>Renderer: ignore (do nothing)
        end
    end

    Note over Guest,Hotkey: Tooltip fix: disableHoverableContent on four adjacent tooltips\n(static property, no runtime flow change)
Loading

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

Re-trigger cubic

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@Kitenite

Kitenite commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Thanks — addressed the key-forwarding findings in 9a3cb48.

Both P1s (guest double-delivery + alt-only bindings) and the P2 (override-awareness) share one root cause: the old forward path forwarded all meta/ctrl chords and never preventDefault'd, and the main process had no idea which chords are actually replayable. Simply adding event.preventDefault() (as suggested) would have been wrong — it would break Cmd+C/Cmd+V/Cmd+F etc. inside the browser, since those were being forwarded too.

Fix: the renderer now registers its override/layout-aware forwardable chord set with the main process, and before-input-event only acts on chords in that set:

  • Guest double-delivery — matched chords are now event.preventDefault()'d, so the guest page no longer also receives Ctrl+Tab etc.
  • Alt-only bindings — the meta || control guard is gone from the forward path; membership in the registered set is the only filter, so an alt-only remap is honored.
  • Override-awareness — the set is built from getDispatchChord(id) (reflects overrides + keyboard layout) and re-pushed whenever the overrides / layout / preferences stores change, mirroring resolveHotkeyFromEvent's index rebuild. preventDefault also removes the double-fire the P2 worried about.
  • No clipboard/find regression — non-tab-switch chords are never in the set, so they fall through to the guest untouched.

The chord canonicalization is now a single source of truth: pure helpers moved to shared/hotkey-chord.ts, imported by both the renderer hotkey resolver and the main-process forwarder, so chordFromInput (main) and canonicalizeChord(getDispatchChord(...)) (renderer) can't drift. Verified they produce identical canonical strings for every default tab-switch binding (mac + win), and all 122 hotkey unit tests still pass.

@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.

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/main/lib/browser/browser-manager.ts">

<violation number="1" location="apps/desktop/src/main/lib/browser/browser-manager.ts:287">
P2: AltGr typing in the focused webview can be swallowed and replayed as a Ctrl+Alt tab-switch override, so non-US layout characters like AltGr+E may switch tabs instead of entering text. This new main-process gate uses `chordFromInput(input)`, which does not preserve the renderer hotkey system’s AltGraph suppression.</violation>
</file>

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

Re-trigger cubic

}
}

const chord = chordFromInput(input);

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.

P2: AltGr typing in the focused webview can be swallowed and replayed as a Ctrl+Alt tab-switch override, so non-US layout characters like AltGr+E may switch tabs instead of entering text. This new main-process gate uses chordFromInput(input), which does not preserve the renderer hotkey system’s AltGraph suppression.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/browser/browser-manager.ts, line 287:

<comment>AltGr typing in the focused webview can be swallowed and replayed as a Ctrl+Alt tab-switch override, so non-US layout characters like AltGr+E may switch tabs instead of entering text. This new main-process gate uses `chordFromInput(input)`, which does not preserve the renderer hotkey system’s AltGraph suppression.</comment>

<file context>
@@ -275,6 +284,9 @@ class BrowserManager extends EventEmitter {
 				}
 			}
 
+			const chord = chordFromInput(input);
+			if (!chord || !this.forwardableChords.has(chord)) return;
+			event.preventDefault();
</file context>

@Kitenite
Kitenite force-pushed the fix-in-workspace-browser-ui-bugs branch from 9a3cb48 to 5e2b242 Compare August 9, 2026 19:12
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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

🤖 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/BrowserPane.tsx:
- Around line 25-40: Move the BrowserTabFavicon component into a dedicated
BrowserTabFavicon/BrowserTabFavicon.tsx module, preserving its existing fallback
and image-error behavior, and export it through BrowserTabFavicon/index.ts.
Remove the inline definition from BrowserPane.tsx and update its import to use
the barrel export.
- Around line 42-47: Update renderBrowserTabIcon so BrowserTabFavicon is keyed
by both browser.data.url and faviconUrl, preserving the nullable favicon
fallback while ensuring navigation remounts the component even when the favicon
URL is unchanged.

In `@apps/desktop/src/shared/hotkey-chord.ts`:
- Around line 45-46: Update canonicalizeChord so each raw token is passed to
normalizeToken before lowercasing, allowing physical aliases such as MetaLeft
and ControlLeft to resolve consistently with eventToChord. Add coverage
verifying both aliases canonicalize to the same modifier names used by keyboard
events.
🪄 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: 981485b9-a415-4daa-9e9c-ec5b49d31f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 3c219a5 and 5e2b242.

📒 Files selected for processing (10)
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/main/lib/browser/browser-manager.ts
  • apps/desktop/src/renderer/hotkeys/components/HotkeyTooltip/HotkeyTooltip.tsx
  • apps/desktop/src/renderer/hotkeys/utils/binding.ts
  • apps/desktop/src/renderer/hotkeys/utils/resolveHotkeyFromEvent.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
  • apps/desktop/src/shared/hotkey-chord.test.ts
  • apps/desktop/src/shared/hotkey-chord.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/renderer/hotkeys/utils/binding.ts
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts

Comment thread apps/desktop/src/shared/hotkey-chord.ts Resolved

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts (1)

170-193: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Retry or report setForwardableChords failures.

The mutation currently calls .catch(() => {}), and BrowserManager.forwardableChords starts empty if the initial sync fails. When the main process keeps an empty or stale chord set, tab-switch keys do not suppress/forward, or host replay falls back to guest-only behavior. Add bounded retry for transient failures and surface persistent failures instead of silencing them.

🤖 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/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
around lines 170 - 193, Update the forwardable-chord synchronization in the
useEffect around setForwardableChords to retry transient mutation failures with
a bounded limit and appropriate delay, while preventing overlapping retries and
cleaning up pending work on unmount. After retries are exhausted, report the
failure through the existing renderer error-reporting mechanism instead of
swallowing it, while preserving the current subscription and chord computation
behavior.
🤖 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.

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts:
- Around line 170-193: Update the forwardable-chord synchronization in the
useEffect around setForwardableChords to retry transient mutation failures with
a bounded limit and appropriate delay, while preventing overlapping retries and
cleaning up pending work on unmount. After retries are exhausted, report the
failure through the existing renderer error-reporting mechanism instead of
swallowing it, while preserving the current subscription and chord computation
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 21ce9b04-7404-4ca8-b7a4-bd26c30f8074

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2b242 and 8840489.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.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.

3 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx:34">
P3: A failed favicon still cannot retry after hash/history-API navigation because this key reads pane data that is never persisted for `did-navigate-in-page`. Persist that navigation URL (or key from state updated by it) so the favicon component remounts on every intended navigation.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts:304">
P1: Existing users keep the old unbound Shift URL tier, so Shift-click still no-ops after upgrading; migrate the prior complete URL default in `healV2UserPreferences`, as done for legacy sidebar file links.</violation>
</file>

<file name="apps/desktop/src/shared/hotkey-chord.ts">

<violation number="1" location="apps/desktop/src/shared/hotkey-chord.ts:56">
P3: This comment now describes behavior the preceding case-insensitive alias map explicitly eliminates; remove or correct it so future changes are not guided by a false constraint.</violation>
</file>

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

Re-trigger cubic

metaShift: "external",
};

const DEFAULT_URL_LINKS: LinkTierMap = {

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: Existing users keep the old unbound Shift URL tier, so Shift-click still no-ops after upgrading; migrate the prior complete URL default in healV2UserPreferences, as done for legacy sidebar file links.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, line 304:

<comment>Existing users keep the old unbound Shift URL tier, so Shift-click still no-ops after upgrading; migrate the prior complete URL default in `healV2UserPreferences`, as done for legacy sidebar file links.</comment>

<file context>
@@ -301,6 +301,13 @@ const DEFAULT_LINK_TIER_MAP: LinkTierMap = {
 	metaShift: "external",
 };
 
+const DEFAULT_URL_LINKS: LinkTierMap = {
+	plain: null,
+	shift: "newTab",
</file context>

return (
<img src={browser.data.faviconUrl} alt="" className="size-3.5 shrink-0" />
<BrowserTabFavicon
key={`${browser.data.url}|${faviconUrl ?? "none"}`}

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.

P3: A failed favicon still cannot retry after hash/history-API navigation because this key reads pane data that is never persisted for did-navigate-in-page. Persist that navigation URL (or key from state updated by it) so the favicon component remounts on every intended navigation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx, line 34:

<comment>A failed favicon still cannot retry after hash/history-API navigation because this key reads pane data that is never persisted for `did-navigate-in-page`. Persist that navigation URL (or key from state updated by it) so the favicon component remounts on every intended navigation.</comment>

<file context>
@@ -22,29 +23,18 @@ function getSingleBrowserPane(
+	// even when the favicon URL itself is unchanged.
+	return (
+		<BrowserTabFavicon
+			key={`${browser.data.url}|${faviconUrl ?? "none"}`}
+			src={faviconUrl}
+		/>
</file context>

* aliases: `meta+alt+up` ≡ `alt+meta+arrowup` ≡ `control+alt+arrowup`.
*/
export function canonicalizeChord(chord: string): string {
// normalizeToken lowercases after its alias lookup; lowercasing here first

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.

P3: This comment now describes behavior the preceding case-insensitive alias map explicitly eliminates; remove or correct it so future changes are not guided by a false constraint.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/shared/hotkey-chord.ts, line 56:

<comment>This comment now describes behavior the preceding case-insensitive alias map explicitly eliminates; remove or correct it so future changes are not guided by a false constraint.</comment>

<file context>
@@ -43,7 +53,9 @@ export function isIgnorableKey(normalized: string): boolean {
  */
 export function canonicalizeChord(chord: string): string {
-	const parts = chord.toLowerCase().split("+").map(normalizeToken);
+	// normalizeToken lowercases after its alias lookup; lowercasing here first
+	// would break case-sensitive physical-code aliases like `MetaLeft` → meta.
+	const parts = chord.split("+").map(normalizeToken);
</file context>

@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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts:456">
P3: Reuse DEFAULT_LINK_TIER_MAP as the legacy urlLinks sentinel couples the urlLinks migration to the current fileLinks default: the same constant backs both the fileLinks default and the "retired" urlLinks default detection. If fileLinks' default is ever updated (new tier, changed action), this heal would silently re-baseline stored urlLinks maps it should treat as arbitrary customizations. Introduce a dedicated LEGACY_URL_LINKS constant mirroring the existing LEGACY_SIDEBAR_FILE_LINKS pattern and compare against that instead.</violation>
</file>

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

Re-trigger cubic

const shouldMigrateLegacyUrlLinks =
r.urlLinks &&
isCompleteLinkTierMap(r.urlLinks) &&
isSameLinkTierMap(r.urlLinks, DEFAULT_LINK_TIER_MAP);

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.

P3: Reuse DEFAULT_LINK_TIER_MAP as the legacy urlLinks sentinel couples the urlLinks migration to the current fileLinks default: the same constant backs both the fileLinks default and the "retired" urlLinks default detection. If fileLinks' default is ever updated (new tier, changed action), this heal would silently re-baseline stored urlLinks maps it should treat as arbitrary customizations. Introduce a dedicated LEGACY_URL_LINKS constant mirroring the existing LEGACY_SIDEBAR_FILE_LINKS pattern and compare against that instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, line 456:

<comment>Reuse DEFAULT_LINK_TIER_MAP as the legacy urlLinks sentinel couples the urlLinks migration to the current fileLinks default: the same constant backs both the fileLinks default and the "retired" urlLinks default detection. If fileLinks' default is ever updated (new tier, changed action), this heal would silently re-baseline stored urlLinks maps it should treat as arbitrary customizations. Introduce a dedicated LEGACY_URL_LINKS constant mirroring the existing LEGACY_SIDEBAR_FILE_LINKS pattern and compare against that instead.</comment>

<file context>
@@ -448,11 +448,19 @@ export function healV2UserPreferences(raw: unknown): V2UserPreferencesRow {
+	const shouldMigrateLegacyUrlLinks =
+		r.urlLinks &&
+		isCompleteLinkTierMap(r.urlLinks) &&
+		isSameLinkTierMap(r.urlLinks, DEFAULT_LINK_TIER_MAP);
 	return {
 		...DEFAULT_V2_USER_PREFERENCES,
</file context>

@Kitenite
Kitenite merged commit 7b7627c into main Aug 9, 2026
12 checks passed
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

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.

1 participant