Skip to content

feat(desktop,ui): multi-tab Browser panel — tabbed webviews, popup capture, find/zoom/devtools (CODE-266)#181

Open
AprilNEA wants to merge 5 commits into
masterfrom
xuan/code-266
Open

feat(desktop,ui): multi-tab Browser panel — tabbed webviews, popup capture, find/zoom/devtools (CODE-266)#181
AprilNEA wants to merge 5 commits into
masterfrom
xuan/code-266

Conversation

@AprilNEA

Copy link
Copy Markdown
Member

Summary

Phase A of the in-app browser roadmap (CODE-266): the right panel's Browser section grows from a single-instance, single-URL webview into a multi-tab browser mirroring the Terminal section's tab model, plus popup capture and basic browser features.

  • Multi-tab store model: RightPanelBrowserState{ tabs, activeTabId } (BrowserSectionTab { url, title }), tab ids right-browser-${seq}, add/close/select/navigate actions, entering the section seeds an empty tab. Persistence bumps shell-state v2→v3 (browserTabUrls + activeBrowserTabIndex, blob: URLs dropped per tab, 20-tab restore cap; stale v2 blobs fall back to defaults by design).
  • UI: new SectionBrowserTabStrip (labels follow the page title via page-title-updated, falling back to "Browser N"), SectionPanelRegion renders it for the browser section; BrowserWebviewPane is now prop-driven (tabId/url) — one resident webview per tab in the shell's PanelTabContentStack (visibility-toggled, never unmounted/DOM-moved).
  • Popup capture: main hooks did-attach-webview and sets a guest setWindowOpenHandler — http(s) popups are pushed over a new system-plane IPC channel (browser.openTab) and land in a new in-app tab; everything else is denied. No guest can create an unmanaged BrowserWindow (related: CODE-153).
  • Features: find-in-page bar (Cmd+F, Enter/Shift+Enter stepping, match counter), zoom in/out/reset (Cmd+= / − / 0), DevTools via a new overflow page menu, download-finished toast (will-download → IPC → coss toast). Shortcuts register through the keyboard registry with the pane root as owner, so only the visible tab's bindings fire.
  • E2E: pnpm -F @linkcode/desktop e2e:browser-tabs — isolated daemon + LINKCODE_PROFILE universe, local http fixture server; asserts seeding, title-following, add/close, the real target="_blank" popup path, and tab restoration across an app restart.

Notable findings

  • allowpopups must be present at webview attach time. The previous ref-callback toggleAttribute ran after Electron snapshotted webview params, so guest popups were being silently blocked (the guest window-open handler was never consulted — verified via a main-process probe). It is now a mount-time attribute (allowpopups="", the string form React 19 forwards).
  • Removed the duplicated main-window setWindowOpenHandler in window.ts (second registration was silently replacing the first).
  • addRightBrowserTab takes no parameters — passing a (url?: string) action directly as a DOM onClick handler fed the MouseEvent in as url (TS-legal, runtime-wrong), producing a <webview src="[object Object]">.

Verification

  • pnpm check:ci green (0 errors); pnpm test 1264 passed (new store unit tests: tab round-trip, blob filtering, restore caps/clamps, v2 fallback, browser-tab pure helpers).
  • e2e:browser-tabs full pass, including restart persistence.

Linear: CODE-266

@linear-code

linear-code Bot commented Jul 17, 2026

Copy link
Copy Markdown
CODE-266 feat(desktop,ui): multi-tab Browser panel — tabbed resident webviews, popup capture, find/zoom/devtools

目标

把右面板 Browser section 从单实例单 URL 升级为多标签浏览器(完全镜像 Terminal section 的 tab 模型),收编 guest popup,补齐 find-in-page/缩放/devtools/下载 toast。纯客户端改造,无 wire 变更。规划全文见 in-app browser roadmap(2026-07-17 拍板:多标签、对齐 terminal tab 条;本 issue 是 Phase A,后续 Phase B 的 agent 自动化以此 tab store 为执行面)。

Tasks

Store(apps/desktop/src/renderer/src/shell/store/

  • RightPanelBrowserState { url }{ tabs: BrowserSectionTab[]; activeTabId }BrowserSectionTab extends PanelSectionTab { url: string | null },镜像 FileSectionTab);createRightBrowserTab() → id right-browser-${seq}
  • 持久化 browserTabUrls + activeBrowserTabIndex(镜像 fileTabPaths),逐 tab 沿用 durableBrowserUrlblob:MAX_PERSISTED_RIGHT_BROWSER_TABS = 20版本四处同步 bump v2→v3(storage key / zodPersist version / z.literal / serialize)。
  • 新 action:addBrowserTab / closeBrowserTab(复用 closeSectionTabState)/ setActiveRightBrowserTab / setBrowserTabUrl(tabId, url)openBrowserUrl 改「有活动 tab 复用导航,无则新建」。

UI(packages/ui/src/shell/panels/

  • 新建 section-browser-tabs.tsx(克隆 section-terminal-tabs.tsx),label 用页面 title(page-title-updated,回落「浏览器 N」)。
  • section-panel.tsxSectionPanelState 加 browser tabs;activeSection === 'browser' 渲染子 tab 条;回调经 DesktopRightPanelRegiondesktop-shell.tsx 穿透(与 terminal 平行)。

Webview 宿主

  • BrowserWebviewPane 改 prop 驱动 { tabId, url, onNavigate, onTitleChange }renderRightPanelContents 把单个 browser-resident 换成 browser.tabs.map(...)(常驻 stack,visibility 切换,永不 unmount)。
  • 分区决策:所有 tab 继续共享 persist:linkcode-browser

Popup 收编

  • 主进程挂 did-attach-webview → guest setWindowOpenHandler:http(s) popup 经新 system-plane IPC(browser.openTab(url))落新 in-app tab,其余 deny。与 CODE-153(related)的方向差异:多标签时代 popup 语义升级为落新 tab,而非一律禁用。

功能补齐

  • find-in-page(findInPage,Cmd+F 经 packages/ui/src/keyboard 注册)、缩放(Cmd+=/−/0)、devtools(overflow 菜单)、下载完成 toast;i18n zh-cn 先行。

明确不做

  • bottom panel 的 browser 类型维持 stub;webview(浏览器版)客户端仍无 in-app browser;webview suspended 节流留观察。

验收

  • 3 个 tab 各自导航,切 section 往返无重载(滚动位置保留);popup 落新 tab;重启还原 tab 列表(blob: 滤除,旧 v2 blob 静默回落);store 纯逻辑单测进根 vitest;playwright E2E(LINKCODE_PROFILE 隔离,aria-label 定位)覆盖加/关/导航。

Review in Linear

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds multi-tab browsing to the desktop right panel. The main changes are:

  • Per-tab browser state with v3 shell persistence.
  • Resident webviews for each browser tab.
  • Popup capture into new in-app tabs.
  • Find, zoom, DevTools, and download toast support.
  • Browser tab UI and E2E coverage.

Confidence Score: 4/5

Mostly safe, with one browser tab state bug to fix before merging.

The IPC, persistence, and UI wiring are consistent overall. Closing the final browser tab leaves the active Browser section without a mounted pane.

apps/desktop/src/renderer/src/shell/store/store.ts

T-Rex T-Rex Logs

What T-Rex did

  • I attempted a reseed after close, but the run was blocked because the maximum steps for this agent were reached before a real executable reproduction could be run.
  • I inspected the narrow store and the rendering paths needed for a reproduction, including that closeRightBrowserTab delegates to closeSectionTabState and that renderRightPanelContents builds BrowserWebviewPane items by iterating rightPanel.browser.tabs.
  • I validated evidence that the browser tabs preflight log exists and that the desktop package exposes an e2e:browser-tabs capability.
  • I documented the E2E failure reasons, including the xvfb path failure due to a missing xauth command and the Playwright process failing to launch, and noted that no E2E screenshots, videos, or traces were generated.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
apps/desktop/e2e/browser-tabs.e2e.mts Adds browser-tab E2E coverage for seeding, navigation/title updates, popup capture, closing, shortcuts, and restart restore.
apps/desktop/src/main/window.ts Adds browser webview popup rerouting, shortcut forwarding, and download-finished notifications via system IPC.
apps/desktop/src/renderer/src/shell/browser/browser-webview-pane.tsx Converts the browser pane to per-tab props and adds title sync, find-in-page, zoom, DevTools, popup-compatible webview attributes, and shortcut handling.
apps/desktop/src/renderer/src/shell/store/model.ts Migrates browser state to tab arrays with v3 persistence and helper functions for seed, open, update, and serialization.
apps/desktop/src/renderer/src/shell/store/store.ts Adds browser tab store actions; closing the last browser tab can leave the active Browser section with no pane mounted.
packages/presentation/ui/src/shell/browser/browser-pane.tsx Extends shared browser chrome with find controls, zoom/devtools menu, and failure/empty states for per-tab panes.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Guest as Browser webview guest
participant Main as Electron main window.ts
participant IPC as SystemBridge browser IPC
participant Store as Desktop shell store
participant Pane as BrowserWebviewPane

Guest->>Main: "window.open / target=_blank URL"
Main->>Main: isHttpUrl(url) and deny unmanaged window
Main->>IPC: BROWSER_OPEN_TAB_CHANNEL(url)
IPC->>Store: openBrowserTab(url)
Store->>Pane: mount resident tab webview
Pane->>Store: page-title-updated / did-navigate syncs title + url
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 Browser webview guest
participant Main as Electron main window.ts
participant IPC as SystemBridge browser IPC
participant Store as Desktop shell store
participant Pane as BrowserWebviewPane

Guest->>Main: "window.open / target=_blank URL"
Main->>Main: isHttpUrl(url) and deny unmanaged window
Main->>IPC: BROWSER_OPEN_TAB_CHANNEL(url)
IPC->>Store: openBrowserTab(url)
Store->>Pane: mount resident tab webview
Pane->>Store: page-title-updated / did-navigate syncs title + url
Loading

Reviews (3): Last reviewed commit: "fix(desktop): route focused browser shor..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a512697a3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +145 to +147
useKeyboardShortcut({
actionId: 'browser.find',
shortcut: { code: 'KeyF', modifiers: ['primary'] },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route browser shortcuts from the focused webview

When focus is inside the loaded page, keydown events are delivered to the guest <webview> WebContents and do not bubble to this renderer shortcut registry, so Cmd/Ctrl+F only opens the find bar when focus is in the panel chrome/address bar rather than during normal browsing. The same pattern affects the zoom shortcuts registered below; these need to be wired from the guest WebContents (for example via before-input-event) so they work while the page has focus.

Useful? React with 👍 / 👎.

...current,
rightPanel: {
...current.rightPanel,
browser: closeSectionTabState(current.rightPanel.browser, id),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Reseed after close
Closing the last browser tab leaves rightPanel.browser.tabs empty while the Browser section remains active and open. renderRightPanelContents only mounts panes by iterating rightPanel.browser.tabs, so the browser content area loses its address bar and webview until the user switches sections or presses New tab.

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