Skip to content

Add browser panel with right panel tabs - #1516

Closed
kubk wants to merge 1 commit into
pingdotgg:mainfrom
kubk:browser-panel
Closed

Add browser panel with right panel tabs#1516
kubk wants to merge 1 commit into
pingdotgg:mainfrom
kubk:browser-panel

Conversation

@kubk

@kubk kubk commented Mar 29, 2026

Copy link
Copy Markdown

Note

Medium Risk
Introduces a new persisted per-project browser state and integrates it into routing/keyboard shortcuts for the right panel; regressions could affect diff panel toggling behavior and stored UI state across threads/projects.

Overview
Adds a new Browser right-panel tab alongside the existing diff viewer, including a tab switcher (RightPanelTabs) that lazy-loads either the diff panel or a sandboxed iframe-based BrowserPanel.

Introduces a persisted Zustand store (browserPanelStore) to manage per-project browser tabs (add/close/switch/navigate) and wires new navigation/search param support (rpt) so the right panel can open to either diff or browser.

Updates chat UX to support browser.toggle (header button + keybinding) and routes terminal URL clicks into the in-app browser when available, falling back to opening externally otherwise.

Written by Cursor Bugbot for commit d1f7c1e. This will update automatically on new commits. Configure here.

Note

Add in-app browser panel with tabbed right panel to chat view

  • Adds a per-project in-app browser with multi-tab support, persisted via a new Zustand store in browserPanelStore.ts.
  • Introduces RightPanelTabs.tsx to switch between Diff and Browser views in the right panel, with lazy loading for both.
  • Adds a browser.toggle keyboard shortcut and a globe icon toggle button in ChatHeader.tsx.
  • Encodes the active right panel tab in the URL via the rpt query param in diffRouteSearch.ts.
  • Terminal link clicks in ThreadTerminalDrawer.tsx now open URLs in the in-app browser when the handler is supplied, instead of always using the external browser.
📊 Macroscope summarized d1f7c1e. 9 files reviewed, 3 issues evaluated, 0 issues filtered, 2 comments posted

🗂️ Filtered Issues

@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: afc06aee-b73c-4405-a724-fd35cc095c18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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 and usage tips.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 29, 2026
@kubk kubk closed this Mar 29, 2026
Comment on lines +175 to 181
const closePanel = useCallback(() => {
void navigate({
to: "/$threadId",
params: { threadId },
search: { diff: undefined },
});
}, [navigate, threadId]);

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.

🟠 High routes/_chat.$threadId.tsx:175

The closePanel callback replaces all search params with { diff: undefined }, which drops any non-diff-related params that may exist on the route. This is inconsistent with openPanel and changeTab, which both use the function form search: (previous) => ... to preserve existing params. Consider using search: (previous) => stripDiffSearchParams(previous) to maintain consistency and prevent data loss.

Suggested change
const closePanel = useCallback(() => {
void navigate({
to: "/$threadId",
params: { threadId },
search: { diff: undefined },
});
}, [navigate, threadId]);
const closePanel = useCallback(() => {
void navigate({
to: "/$threadId",
params: { threadId },
search: (previous) => stripDiffSearchParams(previous),
});
}, [navigate, threadId]);
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/routes/_chat.$threadId.tsx around lines 175-181:

The `closePanel` callback replaces all search params with `{ diff: undefined }`, which drops any non-diff-related params that may exist on the route. This is inconsistent with `openPanel` and `changeTab`, which both use the function form `search: (previous) => ...` to preserve existing params. Consider using `search: (previous) => stripDiffSearchParams(previous)` to maintain consistency and prevent data loss.

Evidence trail:
apps/web/src/routes/_chat.$threadId.tsx lines 175-180 (closePanel uses direct object `search: { diff: undefined }`), lines 182-191 (openPanel uses function form `search: (previous) => ...`), lines 193-206 (changeTab uses function form `search: (previous) => ...`). Commit: REVIEWED_COMMIT

value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={handleSubmit}

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.

🟡 Medium components/BrowserPanel.tsx:129

The onBlur={handleSubmit} handler calls onNavigate on every blur, even when the URL hasn't changed. This resets the tab title to the URL via navigateTab, overwriting any page title that was set by setTabTitle. Consider only calling onNavigate when the input value actually differs from the current URL.

-        onBlur={handleSubmit}
+        onBlur={() => {
+          if (inputValue !== url && inputValue.trim().length > 0) {
+            handleSubmit();
+          }
+        }}
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/BrowserPanel.tsx around line 129:

The `onBlur={handleSubmit}` handler calls `onNavigate` on every blur, even when the URL hasn't changed. This resets the tab `title` to the URL via `navigateTab`, overwriting any page title that was set by `setTabTitle`. Consider only calling `onNavigate` when the input value actually differs from the current URL.

Evidence trail:
apps/web/src/components/BrowserPanel.tsx lines 79-86 (`handleSubmit` function that always calls `onNavigate` without checking if URL changed), line 129 (`onBlur={handleSubmit}`), lines 160-167 (`handleNavigate` calls `navigateTab`); apps/web/src/browserPanelStore.ts lines 122-128 (`navigateTab` sets `title: url`, overwriting any existing title)

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

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

);
});
if (onOpenUrlInBrowser) {
onOpenUrlInBrowser(match.text);

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.

Stale closure captures onOpenUrlInBrowser in terminal link provider

High Severity

The onOpenUrlInBrowser prop is captured directly in the link provider closure registered inside a useEffect that only re-runs when [cwd, runtimeEnv, terminalId, threadId] change. Unlike onSessionExited and onAddTerminalContext, which use refs (onSessionExitedRef, onAddTerminalContextRef) kept in sync via separate useEffect hooks, onOpenUrlInBrowser has no ref. After initial mount, clicking a URL link in the terminal will invoke a stale callback, potentially using an outdated project ID or navigation state.

Additional Locations (1)
Fix in Cursor Fix in Web

<XIcon className="size-2.5" />
</button>
</button>
);

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.

Nested button elements create invalid HTML

Medium Severity

BrowserTabButton nests a close <button> (line 50) inside the outer tab <button> (line 38). Nesting interactive elements like <button> inside another <button> is invalid HTML. Browsers handle this inconsistently — some may prevent the inner button's click from firing, or always trigger the outer button's onClick regardless of stopPropagation, leading to unreliable close-tab behavior.

Fix in Cursor Fix in Web

import type { ProjectId } from "@t3tools/contracts";
import {
ArrowLeftIcon,
ArrowRightIcon,

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.

Unused imports ArrowLeftIcon and ArrowRightIcon

Low Severity

ArrowLeftIcon and ArrowRightIcon are imported from lucide-react but never used anywhere in the BrowserPanel component.

Fix in Cursor Fix in Web

diffToggleShortcutLabel={diffPanelShortcutLabel}
gitCwd={gitCwd}
diffOpen={diffOpen}
browserOpen={browserOpen}

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.

Diff toggle button shows pressed when browser tab active

Medium Severity

The diff toggle button's pressed state is bound to diffOpen, which is true whenever the right panel is open regardless of which tab is active. Meanwhile browserOpen is correctly computed as diffOpen && rightPanelTab === "browser". When the browser tab is active, both the diff and browser toggle buttons appear pressed simultaneously, which is visually incorrect and confusing. The diffOpen prop passed to ChatHeader needs to reflect only whether the diff tab is actually active, not just whether the panel is open.

Additional Locations (1)
Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant