fix(tab-bar): prevent detaching settings tabs - #18428
Conversation
Signed-off-by: Pleasurecruise <3196812536@qq.com>
|
Code review complete — no findings. Reviewed the three-layer detach guard (capability table, drag handler, provider guard) and all touched APIs: the |
|
Claude finished @kangfenmao's task in 46s —— View job PR 作用分析@kangfenmao 这个 PR 的目标是禁止设置(Settings)标签页被分离/拖出为独立窗口,从而避免出现多个拥有各自窗口本地状态的 Settings 界面。 背景设置页面的导航在主窗口中是单例设计,而分离出去的子窗口则是多实例的。如果允许 Settings 标签页被分离,就会出现多个不一致的 Settings 界面,造成状态错乱。 改动机制:三层防御PR 通过三个层面同时封堵 Settings 标签页的分离路径: 1. 能力层(capability)—
2. 工具栏按钮 —
3. Provider 守卫(纵深防御)—
附带的重构
测试
总结普通标签页的分离行为完全不变;只有 Settings 标签页在工具栏按钮、右键菜单、拖拽手势三条路径以及直接 IPC 调用上都被禁用。这是一个聚焦、低风险的改动。 |
There was a problem hiding this comment.
This review was translated automatically.
This is a product behavior change. The settings interface is allowed to be a separate independent window, which makes it convenient to quickly modify settings and verify.
Original Content
这个是一个产品行为变更,设置界面是允许单独独立窗口的,这样方便快速修改设置和验证
Signed-off-by: Pleasurecruise <3196812536@qq.com>
|
@kangfenmao Adjusted according to your feedback. Settings is still allowed to be dragged out as an independent window, but at most one Settings page is kept within the app: after dragging out, when opening Settings from the sidebar, menu, or protocol entry, it will navigate to and focus the existing independent Settings window, and will not create a second Settings page in the main window; only after the independent window is closed or re-attached will it resume opening in the main window. The multi-window behavior of regular tabs is not affected. Corresponding regression tests have been added; the relevant targeted tests passed 141/141, and |
404-Page-Found
left a comment
There was a problem hiding this comment.
Code Review: fix(tab-bar): prevent detaching settings tabs
Overall: The approach is sound — centralizing settings-window singleton logic in SubWindowService with defense-in-depth at multiple layers is the right architecture. The tests are well-written and cover the key scenarios. I have a few findings ranging from a correctness concern to minor observations.
Findings
1. closed handler changes behavior for ALL sub-windows, not just Settings
The closed handler was changed from:
this.tabIdToWindowId.delete(tabId)
this.windowState.delete(tabId)to iterating all tabIdToWindowId entries and deleting every one that maps to the destroyed windowId.
This is necessary for the Settings singleton (multiple tabIds → one windowId), but it changes cleanup behavior for every sub-window, not just Settings. In practice this is likely harmless (each non-Settings sub-window normally has exactly one tabId mapping), but it widens the blast radius of the change. Consider scoping the iteration to only run when the window is a Settings path, or accept the generalization explicitly.
2. windowState is not cleaned up for non-active tabIds on close
The closed handler cleans up tabIdToWindowId entries but does not clean up the corresponding windowState entries for all removed tabIds — it only cleans the specific tabId it captured. For the Settings singleton, this means stale state can accumulate in windowState for old tabIds that mapped to the closed window. This is a pre-existing issue (the old code had the same gap for a single tabId), but the PR's multi-tabId-per-window pattern makes it more visible.
3. PR description says "cannot be detached" but the detach affordance is still shown
The PR description states "Settings tabs remain focused tabs in the main window and cannot be detached." However, getTabCapabilities in AppShellTabBar.tsx does not suppress the detach affordance for settings paths — canDetach is purely !!detachTab. The PR implements defense-in-depth at the SubWindowService level (reuse the existing window rather than creating a second one), but the user still sees the detach button/menu/drag gesture. They just get silently rerouted. If the goal is to prevent the action (not just absorb it), the capability layer should be updated too. If the current "absorb silently" behavior is intentional, the PR description should say "cannot be detached into a second window" to be precise.
4. Stale state copy in the singleton reuse path
In createWindow (line ~165 of the new code), when a second settings createWindow is intercepted:
const existingState = existingTabId && this.windowState.get(existingTabId)
this.tabIdToWindowId.set(tabId, this.settingsWindowId)
if (existingState) this.windowState.set(tabId, existingState)The old tabId's windowState (which may contain stale URL/title from the first settings page) is copied to the new tabId. Since openSettingsWindow immediately sends IPC to navigate the existing window, this copied state is likely never read for the new tabId — but it exists as a latent inconsistency. If it's truly dead, consider omitting the copy to avoid confusion.
Positive observations
- Test coverage is good. The new tests cover singleton reuse, window-close-then-reopen, the
mainWindowNavigationdelegation path, and theSubWindowAppShellIPC listener. The existing tests are properly updated. - The renderer-side
openSettingsTabsimplification is clean. Removing the DOM-event-first path in favor of direct IPC makes the routing unambiguous — all settings navigation always goes through the main process singleton check. - The
openSettingsWindowmethod is well-guarded. It checks for null/destroyed windows and clearssettingsWindowIdbefore returningfalse, enabling clean fallback. - The
SubWindowAppShellIPC listener is correctly scoped — it only navigates the active tab, matching the single-tab-per-sub-window pattern for settings.
There was a problem hiding this comment.
Cherry Review · 阻塞
1 blocker · 1 warning
逐条见行内评论。
以下 1 条落在本次 diff 的行内定位之外,改为摘要送达(位置见每条开头):
src/renderer/components/layout/AppShellTabBar.tsx:425必须修复:The PR does not enforce the stated Settings non-detach invariant: the UI still exposes detach, provider and IPC paths accept it, and the first direct Settings detach opens a sub-window.inv_73cae064ff83c6cb#c0
Signed-off-by: Pleasurecruise <3196812536@qq.com>
|
Code review of this PR's Settings-tab detach prevention: Detach prevention — verified correct across all entry paths. The toolbar/focused-tab button ( One concern (posted inline on No blocking findings. |
Signed-off-by: Pleasurecruise <3196812536@qq.com>
|
Reviewed PR #18428 (prevent detaching Settings tabs) read-only across all three guard layers — the renderer capability/menus/toolbar/drag paths in No high-confidence issues found. The detach capability is consistently disabled for Settings URLs at every entry point (toolbar button, three context-menu sites, pointer-drag detach), and the now-unreachable |
What this PR does
Before this PR:
Settings tabs could be detached through the toolbar button, context menu, or drag gesture, allowing multiple Settings interfaces with independent window-local state.
After this PR:
Settings tabs remain focused tabs in the main window and cannot be detached. Regular tabs retain their existing detach behavior.
Fixes # N/A
Why we need it and why it was done in this way
Settings tabs belong to the main window's focused-tab flow. The tab bar disables Settings detach capabilities for the toolbar, context menu, and drag gesture;
TabsProviderrejects direct renderer requests; and the main-processtab.detachhandler rejects direct IPC requests as the final boundary.Settings navigation keeps the existing local-first route delivery with IPC fallback, so this change only affects detach behavior.
The following tradeoffs were made:
The detach policy is specific to Settings routes; general multi-window and tab detach behavior remains unchanged.
The following alternatives were considered:
Hiding only the toolbar button was rejected because the context menu, drag gesture, and direct detach requests would still bypass the policy.
Links to places where the discussion took place: N/A
Breaking changes
None.
If this PR introduces breaking changes, please describe the changes and the impact on users.
N/A
Special notes for your reviewer
Please verify that Settings has no detach affordance, direct Settings detach requests are ignored, regular tabs can still be detached, and Settings navigation retains its local-first behavior.
Checklist
This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.
mainfor active development,v1for v1 maintenance fixes/gh-pr-review,gh pr diff, or GitHub UI) before requesting review from othersRelease note