feat(desktop,ui): multi-tab Browser panel — tabbed webviews, popup capture, find/zoom/devtools (CODE-266)#181
feat(desktop,ui): multi-tab Browser panel — tabbed webviews, popup capture, find/zoom/devtools (CODE-266)#181AprilNEA wants to merge 5 commits into
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".
| useKeyboardShortcut({ | ||
| actionId: 'browser.find', | ||
| shortcut: { code: 'KeyF', modifiers: ['primary'] }, |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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.
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