feat(desktop,ui): multi-tab Browser panel — tabbed webviews, popup capture, find/zoom/devtools (CODE-266) - #181
Conversation
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 为执行面)。 TasksStore(
UI(
Webview 宿主
Popup 收编
功能补齐
明确不做
验收
|
Greptile SummaryThis PR adds multi-tab browsing to the desktop right panel. The main changes are:
Confidence Score: 4/5Mostly 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
What T-Rex did
Important Files Changed
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
%%{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
Reviews (3): Last reviewed commit: "fix(desktop): route focused browser shor..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 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".
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.
RightPanelBrowserState→{ tabs, activeTabId }(BrowserSectionTab { url, title }), tab idsright-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).SectionBrowserTabStrip(labels follow the page title viapage-title-updated, falling back to "Browser N"),SectionPanelRegionrenders it for the browser section;BrowserWebviewPaneis now prop-driven (tabId/url) — one resident webview per tab in the shell'sPanelTabContentStack(visibility-toggled, never unmounted/DOM-moved).did-attach-webviewand sets a guestsetWindowOpenHandler— 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).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.pnpm -F @linkcode/desktop e2e:browser-tabs— isolated daemon +LINKCODE_PROFILEuniverse, local http fixture server; asserts seeding, title-following, add/close, the realtarget="_blank"popup path, and tab restoration across an app restart.Notable findings
allowpopupsmust be present at webview attach time. The previous ref-callbacktoggleAttributeran 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).setWindowOpenHandlerinwindow.ts(second registration was silently replacing the first).addRightBrowserTabtakes no parameters — passing a(url?: string)action directly as a DOMonClickhandler fed the MouseEvent in asurl(TS-legal, runtime-wrong), producing a<webview src="[object Object]">.Verification
pnpm check:cigreen (0 errors);pnpm test1264 passed (new store unit tests: tab round-trip, blob filtering, restore caps/clamps, v2 fallback, browser-tab pure helpers).e2e:browser-tabsfull pass, including restart persistence.Linear: CODE-266