Skip to content

feat(desktop): ブラウザタブのズーム倍率表示・操作ボタン#10

Merged
MocA-Love merged 3 commits intomainfrom
feat/desktop-browser-zoom-v2
Mar 28, 2026
Merged

feat(desktop): ブラウザタブのズーム倍率表示・操作ボタン#10
MocA-Love merged 3 commits intomainfrom
feat/desktop-browser-zoom-v2

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Mar 28, 2026

概要

ブラウザタブの操作性を総合的に強化しました。

1. ズーム倍率表示・操作ボタン

  • ツールバーに [-] [100%] [+] ボタンを配置
  • Cmd+/- による既存ズームとリアルタイム同期(300ms ポーリング)
  • ボタンからもキーボードからも同じ webContents.setZoomLevel() を使用
  • パーセント表示クリックで 100% にリセット

2. target="_blank" / Cmd+click を新しいブラウザタブで開く

  • setWindowOpenHandler でキャッチした URL を addBrowserTab() で新タブとして開く
  • グローバル subscription(onAnyNewWindow)を DashboardLayout に配置し、webview が非表示(パーキング中)でもイベントを確実にハンドル
  • Cmd/Ctrl+click は guest ページへの JS 注入で window.open() を呼び、同じ経路で処理

3. URL コピーボタン

  • URL 表示の右端にリンクアイコンボタン(ホバー時のみ表示)
  • クリックで現在の URL をクリップボードにコピー + toast 通知

変更ファイル

ファイル 変更
browser.ts (tRPC) setZoomLevel mutation、onZoomChanged ポーリング subscription、onAnyNewWindow subscription 追加
browser-manager.ts グローバル new-window イベント emit 追加、不要な zoom listener 削除
BrowserPane.tsx ズームボタン UI + subscription
BrowserToolbar.tsx URL コピーボタン追加
usePersistentWebview.ts Cmd+click JS 注入追加、旧 onNewWindow subscription 削除
useBrowserNewWindowHandler.ts 新規 — グローバル new-window ハンドラ
layout.tsx グローバルハンドラのフック呼び出し追加

テスト計画

  • [+] / [-] ボタンでブラウザ内コンテンツがズームされること
  • Cmd+/- でズームした時、ツールバーの % 表示がリアルタイムに更新されること
  • % 表示クリックで 100% にリセットされること
  • target="_blank" リンクが新しいブラウザタブで開くこと
  • Cmd+click でリンクが新しいブラウザタブで開くこと
  • タブが非表示中に target="_blank" をクリックしても正しく新タブで開くこと
  • URL コピーボタンでクリップボードに URL がコピーされること

Summary by CodeRabbit

  • New Features
    • Browser pane shows current zoom and provides Zoom Out / Reset / Zoom In controls (synced with Cmd/Ctrl+/-).
    • Added a URL copy button next to the address display.
  • Bug Fixes
    • Links with target="_blank" or Cmd/Ctrl+click reliably open in new browser tabs.
    • Global link handling improved so new-tab events are processed even when tabs are hidden.
  • Documentation
    • Added fork-specific README entry "ブラウザタブ機能強化" describing these enhancements.

Electron 既存の Cmd+/- ズームと同じ webContents.setZoomLevel() を使用。
zoom-changed イベントの subscription でキーボード操作とボタン操作を
リアルタイム同期。ツールバーに [-] [%] [+] ボタンを配置。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Added browser zoom controls and subscriptions, a global new-window handler that routes target="_blank"/Cmd+click opens into workspace tabs, a URL copy button, and new tRPC procedures/events to coordinate zoom and new-window events across renderer and main processes.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added a fork-specific entry ("ブラウザタブ機能強化") describing zoom display/controls, copy-URL button, Cmd/Ctrl+click and target="_blank" behavior, and the global link/new-window handler.
tRPC Router
apps/desktop/src/lib/trpc/routers/browser/browser.ts
Added setZoomLevel mutation, onZoomChanged subscription (pane-scoped polling), and onAnyNewWindow subscription (global new-window events emitting { paneId, url }).
Main Process: Browser Manager
apps/desktop/src/main/lib/browser/browser-manager.ts
Emit an unscoped "new-window" event with { paneId, url } from the webContents open handler for non-about:blank URLs (in addition to existing pane-scoped event).
Renderer: Browser Pane UI
apps/desktop/src/renderer/screens/.../BrowserPane/BrowserPane.tsx
Introduced local zoom state, subscribed to onZoomChanged, added setZoomLevel mutation use, and toolbar zoom controls (zoom out, reset, zoom in) with clamping and percent display.
Renderer: Toolbar
apps/desktop/src/renderer/.../BrowserToolbar/BrowserToolbar.tsx
Added clipboard copy button for current URL (uses navigator.clipboard + toast), refactored URL display wrapper layout and imports for toast/link icon.
Renderer: New-window Handling Hook
apps/desktop/src/renderer/hooks/useBrowserNewWindowHandler.ts
New exported hook subscribing to onAnyNewWindow; resolves pane→tab via useTabsStore and calls state.addBrowserTab(tab.workspaceId, url) to open links in the correct workspace/tab.
Renderer: Global Layout
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
Mounted useBrowserNewWindowHandler at dashboard layout level so the new-window handler remains active even when BrowserPane components are unmounted.
Renderer: Webview Hook
apps/desktop/src/renderer/.../usePersistentWebview/usePersistentWebview.ts
Removed previous subscription-based new-window handling; injects guest-page listener on dom-ready to intercept Cmd/Ctrl+click on anchors and call window.open(..., '_blank'), guarded to avoid duplicate installs.

Sequence Diagram

sequenceDiagram
    participant User
    participant Webview as WebContents (guest)
    participant BrowserManager as Main BrowserManager
    participant tRPC as tRPC Router/Subscriptions
    participant GlobalHandler as Dashboard Hook / Tabs Store

    User->>Webview: Cmd/Ctrl+Click or anchor target="_blank"
    Webview-->>BrowserManager: window.open -> webContents.window-open handler (main)
    BrowserManager->>tRPC: emit unscoped "new-window" {paneId, url}
    tRPC-->>GlobalHandler: onAnyNewWindow subscription event
    GlobalHandler->>TabsStore: resolve paneId -> tab, call addBrowserTab(workspaceId, url)
    TabsStore-->>UI: new browser tab opened in workspace
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I'm a rabbit with a tiny scroll, 🐇
Zooming in and out to reach my goal.
Links hop to new tabs with a click so neat,
Copying URLs, a quick little treat.
Hop, tap, and snip — the browsing is sweet! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding browser tab zoom level display and operation buttons to the desktop application.
Description check ✅ Passed The description provides a comprehensive overview with clear sections covering all major changes, implementation details, modified files, and a test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-browser-zoom-v2

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

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/main/lib/browser/browser-manager.ts (1)

32-63: ⚠️ Potential issue | 🟠 Major

Make register() safe to call more than once for the same pane.

The cleanup guard only runs when webContentsId changes. A second register(paneId, sameId) call adds another console/context-menu/zoom handler, then overwrites the stored cleanup closure, so unregister() can no longer remove the older listeners.

♻️ Suggested fix
  register(paneId: string, webContentsId: number): void {
  	// Clean up previous listeners if re-registering with a new webContentsId
  	const prevId = this.paneWebContentsIds.get(paneId);
- 	if (prevId != null && prevId !== webContentsId) {
+ 	if (prevId != null) {
  		for (const map of [
  			this.consoleListeners,
  			this.contextMenuListeners,
  			this.zoomListeners,
  		]) {
  			const cleanup = map.get(paneId);
  			if (cleanup) {
  				cleanup();
  				map.delete(paneId);
  			}
  		}
  	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/browser/browser-manager.ts` around lines 32 - 63,
The register method currently only removes old listeners when the webContentsId
changes, which allows duplicate handlers if register(paneId, sameId) is called;
modify register (in browser-manager.ts) to always run the existing cleanup for
the given paneId before installing new listeners: for each of
this.consoleListeners, this.contextMenuListeners, this.zoomListeners check
map.get(paneId) and if present call cleanup() and map.delete(paneId) regardless
of prevId equality, then set this.paneWebContentsIds.set(paneId, webContentsId)
and proceed to create new handlers (setupConsoleCapture, setupContextMenu,
setupZoomListener) so that unregister() can reliably remove the latest
listeners.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/browser/browser.ts`:
- Around line 148-163: The onZoomChanged subscription currently only emits
future zoom changes, so new subscribers see the default zoom (100%) until the
next event; modify the subscription in onZoomChanged to immediately emit the
current zoom by calling browserManager.getWebContents(input.paneId) and, if
present, emit.next({ zoomLevel: wc.getZoomLevel() }) before registering the
handler, and still register the handler that emits on subsequent zoom-changed
events; also ensure this is paired with a registration-time emit in
browserManager.register() if subscriptions may be created before registration so
the initial state is available.

In `@apps/desktop/src/main/lib/browser/browser-manager.ts`:
- Around line 236-240: The current setupZoomListener uses the "zoom-changed"
event which only fires for wheel/pinch and is pre-change; replace it with a
post-change check that captures keyboard and programmatic zoom: in
setupZoomListener(paneId, wc) store a lastZoom value (per pane), remove or stop
using the "zoom-changed" handler, and add a wc.on('resize', ...) listener that
calls wc.getZoomLevel(), compares to lastZoom, updates lastZoom, and emits
`zoom-changed:${paneId}` only when the level changed; also ensure you remove the
'resize' listener when the webContents is destroyed to avoid leaks.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx`:
- Around line 48-49: The optimistic update to the zoom UI is applied before
confirming the server mutation result, so when browser.applyZoom rejects (the
server returns { success: false } from browser.ts) the toolbar stays showing an
incorrect percent; modify the mutation usage around
electronTrpc.browser.setZoomLevel.useMutation() (and the applyZoom caller) to
perform the optimistic UI update only while storing the previous zoomLevel and
then rollback on mutation failure—either by using the mutation's
onError/onSettled callbacks to restore the prior zoomLevel when the response is
unsuccessful or by awaiting the mutation and only committing the new zoomLevel
if the returned payload.success is true; update the setZoomLevel usage and the
applyZoom handler (references: setZoomLevel, applyZoom, zoomLevel state)
accordingly, and ensure the same fix is applied to the other optimistic update
site noted (lines ~83-90).
- Around line 129-150: The zoom buttons currently only show symbols/text and
rely on tooltips, so update each button in BrowserPane.tsx (the buttons that
call applyZoom and use zoomLevel/zoomPercent/ZOOM_STEP/ZOOM_MIN/ZOOM_MAX) to
include explicit aria-label attributes (e.g., aria-label="Zoom out",
aria-label="Reset zoom to 100%", aria-label="Zoom in") for the zoom-out,
zoom-reset, and zoom-in controls; ensure the labels describe the action and, if
helpful, include target percent (e.g., "Zoom to 100%") and apply the same
aria-label changes to the other group of buttons around the code referenced at
the second location (the controls at lines 158-165).

In `@README.md`:
- Line 43: The changelog row for "ブラウザタブのズーム表示・操作" contains a future added-date
"2026-03-29"; update that date to the correct current or past date (e.g.,
"2026-03-28") in README.md so the "追加日" is not in the future, ensuring the table
cell with the date string is replaced accordingly.

---

Outside diff comments:
In `@apps/desktop/src/main/lib/browser/browser-manager.ts`:
- Around line 32-63: The register method currently only removes old listeners
when the webContentsId changes, which allows duplicate handlers if
register(paneId, sameId) is called; modify register (in browser-manager.ts) to
always run the existing cleanup for the given paneId before installing new
listeners: for each of this.consoleListeners, this.contextMenuListeners,
this.zoomListeners check map.get(paneId) and if present call cleanup() and
map.delete(paneId) regardless of prevId equality, then set
this.paneWebContentsIds.set(paneId, webContentsId) and proceed to create new
handlers (setupConsoleCapture, setupContextMenu, setupZoomListener) so that
unregister() can reliably remove the latest listeners.
🪄 Autofix (Beta)

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

Run ID: a7753df4-c8d9-449e-9a50-9d6caf1155fc

📥 Commits

Reviewing files that changed from the base of the PR and between e0bce47 and 48ab79e.

📒 Files selected for processing (4)
  • README.md
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/main/lib/browser/browser-manager.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx

Comment thread apps/desktop/src/lib/trpc/routers/browser/browser.ts
Comment thread apps/desktop/src/main/lib/browser/browser-manager.ts Outdated
Comment thread README.md Outdated
- ズーム: Cmd+/- のポーリング同期(300ms)で倍率表示がリアルタイム連動
- 新タブ: target="_blank" / Cmd+click を新しいブラウザタブで開くように変更
  グローバル subscription で非表示タブからのイベントも正しく処理
- URLコピー: ツールバーにリンクボタン追加、クリックでクリップボードにコピー
@MocA-Love MocA-Love self-assigned this Mar 28, 2026
@MocA-Love
Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserToolbar/BrowserToolbar.tsx (1)

217-220: Consider handling clipboard write failures.

navigator.clipboard.writeText() returns a Promise that can reject if the clipboard is unavailable or permissions are denied. Users would see no feedback in such cases.

♻️ Suggested improvement
 onClick={() => {
-	navigator.clipboard.writeText(currentUrl);
-	toast.success("URL copied");
+	navigator.clipboard.writeText(currentUrl).then(
+		() => toast.success("URL copied"),
+		() => toast.error("Failed to copy URL"),
+	);
 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserToolbar/BrowserToolbar.tsx`
around lines 217 - 220, The onClick handler using
navigator.clipboard.writeText(currentUrl) doesn't handle rejections or the
absence of the clipboard API; make the handler async (or handle the returned
Promise) in the BrowserToolbar component so you await
navigator.clipboard?.writeText(currentUrl) (or call .then/.catch), and on
success call toast.success("URL copied") but on failure call toast.error with a
helpful message (e.g., "Failed to copy URL" plus error.message) and handle the
case where navigator.clipboard is undefined by immediately showing an error
toast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserToolbar/BrowserToolbar.tsx`:
- Around line 217-220: The onClick handler using
navigator.clipboard.writeText(currentUrl) doesn't handle rejections or the
absence of the clipboard API; make the handler async (or handle the returned
Promise) in the BrowserToolbar component so you await
navigator.clipboard?.writeText(currentUrl) (or call .then/.catch), and on
success call toast.success("URL copied") but on failure call toast.error with a
helpful message (e.g., "Failed to copy URL" plus error.message) and handle the
case where navigator.clipboard is undefined by immediately showing an error
toast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 615273c1-6b35-4117-b301-817a57a92fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 48ab79e and afa9d21.

📒 Files selected for processing (7)
  • README.md
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/main/lib/browser/browser-manager.ts
  • apps/desktop/src/renderer/hooks/useBrowserNewWindowHandler.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserToolbar/BrowserToolbar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/hooks/usePersistentWebview/usePersistentWebview.ts
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • apps/desktop/src/main/lib/browser/browser-manager.ts

@MocA-Love MocA-Love merged commit eff3348 into main Mar 28, 2026
1 check passed
@MocA-Love MocA-Love deleted the feat/desktop-browser-zoom-v2 branch March 29, 2026 05:05
MocA-Love added a commit that referenced this pull request Apr 24, 2026
upstream 取り込み PR #10: delete dialog checkbox (superset-sh#3681) + hook 補完
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