Skip to content

feat(tabs): move tabs into detached windows - #4805

Open
zorohu wants to merge 13 commits into
t8y2:mainfrom
zorohu:feat/tab-floating-window
Open

feat(tabs): move tabs into detached windows#4805
zorohu wants to merge 13 commits into
t8y2:mainfrom
zorohu:feat/tab-floating-window

Conversation

@zorohu

@zorohu zorohu commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

变更说明

支持将主窗口中的标签页拖拽分离到独立窗口,并完成标签页所有权转移:

  • 标签页移动到子窗口后,从主窗口中移除,不保留重复标签页。
  • 子窗口能够继续执行 SQL、浏览表数据及操作 MongoDB 等数据源。
  • 支持拖拽时显示窗口尺寸和落点预览。
  • 子窗口初始化期间显示轻量级预览界面,减少白屏感知。
  • 优化子窗口启动流程,将非关键服务改为异步初始化。
  • 修复 MongoDB 标签页分离后原标签未移除、窗口关闭无响应的问题。
  • 修复子窗口关闭后 WebView 未及时释放的问题。
  • 完善标签页传输确认、失败回滚和窗口销毁机制。

当前 apps/desktop/src/App.vue 存在合并冲突,需要在合并前解决并重新执行验证。

变更类型

  • 新功能
  • Bug 修复
  • 性能优化
  • 代码重构
  • 文档更新
  • CI / 构建

涉及前端

  • 本 PR 涉及前端改动,已附截图/录屏(见下方)
image image

验证

  • make check 通过
  • make cargo-check-fast 通过
  • 相关测试通过

已执行:

.\node_modules\.bin\vitest.cmd run \
  apps/desktop/src/lib/tabs/__tests__/tabWindow.spec.ts \
  apps/desktop/src/lib/tabs/__tests__/tabWindow.handshake.spec.ts \
  apps/desktop/src/components/layout/__tests__/DetachedTabWindow.spec.ts \
  apps/desktop/src/components/layout/__tests__/DetachedWindowShell.spec.ts

.\node_modules\.bin\vue-tsc.cmd --noEmit --project apps/desktop/tsconfig.json

.\node_modules\.bin\oxlint.cmd --vue-plugin \
  apps/desktop/src/App.vue \
  apps/desktop/src/lib/tabs/tabWindow.ts \
  apps/desktop/src/main.ts \
  apps/desktop/src/components/layout/AppTabBar.vue

.\node_modules\.bin\vite.cmd build --config apps/desktop/vite.config.ts

git diff --check

验证结果:
4 个测试文件通过,共 12 个测试通过。

  • TypeScript 类型检查通过。
  • oxlint 检查通过。
  • Vite production build 通过。
  • git diff --check 通过。
    手动验证步骤:
  1. 打开多个数据库标签页。
  2. 将其中一个标签页拖出主窗口标签栏。
  3. 确认拖拽期间显示目标窗口尺寸和位置预览。
  4. 确认子窗口显示后,原标签从主窗口移除。
  5. 在子窗口执行 SQL、刷新数据并切换结果视图。
  6. 使用 MongoDB 连接打开集合并分离标签页。
  7. 确认 MongoDB 标签页能够在子窗口正常加载和操作。
  8. 关闭子窗口,确认窗口销毁且对应 WebView 资源得到释放。

关联 Issue
Closes #4656

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

这个 PR 暂时不能合并,当前有三个 P1 blocker:

  1. 分离窗口禁用了 open-tabs 持久化,而主窗口在转移后会持久化删除原 tab。分离后的 SQL、固定状态和 tab 元数据没有新的持久化 owner,正常重启或异常退出后会直接丢失。

  2. detached runtime 的表导航会用 closeTabAndWait(..., { force: true }) 替换当前唯一 tab,绕过已有的 dirty/save/discard 确认。未保存 SQL、结构编辑或 DataGrid pending changes 可能无提示丢失。

  3. transfer accepted 与主窗口 timeout 不是原子提交。子窗口把 emitTo("main", accepted) 成功当作主窗口已经消费;若 accepted 落在主窗口 timeout/unlisten 之后,主窗口会恢复原 tab 并关闭子窗口,但子窗口不会 rollback,形成双 owner,关闭流程还可能释放恢复后 tab 仍依赖的 session。

当前与 main 的机械冲突虽然只集中在 App.vuequeryStore.ts,但它们正好是 ownership、持久化和 session 生命周期核心,不能安全地用小型维护者 patch 解决。请先明确跨窗口持久化 owner、可靠的两阶段 transfer coordinator,以及 dirty navigation 的保存/丢弃/取消行为,并补充 timeout race、重启恢复和 session 生命周期测试后再更新。

…indow

# Conflicts:
#	apps/desktop/src/App.vue
#	apps/desktop/src/stores/queryStore.ts
@zorohu

zorohu commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

针对 Review 提出的三个问题,分别进行了以下修复:

  1. 分离标签页没有持久化 owner

    • 由主窗口统一持久化主窗口和分离窗口的标签页状态。
    • 分离窗口通过 IPC 上报状态,确保重启后能够恢复 SQL、固定状态及标签页元数据。
  2. 分离窗口导航会绕过未保存确认

    • 移除直接强制替换标签页的逻辑。
    • 导航、关闭窗口或退出 DBX 前,统一检查 SQL、结构编辑及 DataGrid 修改,并提供“保存 / 丢弃 / 取消”操作。
  3. Transfer timeout 可能产生双 owner

    • 将标签页转移改为 prepare → persist → commit → acknowledgement 两阶段提交流程。
    • 任一阶段失败都会回滚到主窗口;临时子窗口不会释放主窗口仍在使用的 session,避免双 owner 和状态丢失。

其他优化

  • 支持 WebView 重载及重新编译后的分离标签页状态恢复。
  • 移除不再使用的 DetachedApp.vue,统一主窗口与分离窗口的运行逻辑。
  • 优化分离窗口初始化过程,在内容加载前显示轻量窗口预览。
  • 优化窗口关闭与 WebView 销毁流程,减少关闭窗口后的资源残留。
  • 增加重复分离保护,避免同一标签页并发创建多个子窗口。

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

request changes: The dirty-navigation path and the previous transfer timeout race are now fixed, but one P1 persistence-owner issue remains.

  1. apps/desktop/src/stores/queryStore.ts:558 and :1194 lose the detached owner when the main WebView reloads while the detached window is still alive.

    initOpenTabs() restores every persisted tab into the main window. The child can then report a newer snapshot through updateDetachedOpenTab(), but saveTabs() serializes main tabs first and skips detached snapshots with duplicate tab IDs.

    This produces two live owners. More importantly, the child receives a successful persistence acknowledgement even though the on-disk document still contains the stale main-window copy. A later quit/restart can therefore restore old SQL, pinned state, or tab metadata.

    Please persist enough owner information to distinguish main and detached tabs, or reconcile currently existing detached windows before restoring tabs into the main store. Add a regression test covering:

    1. detach a tab and persist it;
    2. reload/recreate only the main store while the child remains alive;
    3. update the tab from the child;
    4. flush persistence;
    5. verify the child snapshot wins and the tab is not simultaneously restored into the main owner.

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The current head 80021289 fixes the previous dirty-navigation and timeout issues, but three cross-WebView ownership and performance blockers remain:

  1. apps/desktop/src/App.vue:2274 acknowledges a detached-tab transfer even when the first durable ownership write fails. After repeated persistence failures, reloading the main WebView can restore a second live owner while the child still believes the transfer committed. The acknowledgement must follow a successful durable owner transition, with recovery tests for failed writes and main-store recreation.

  2. apps/desktop/src/App.vue:2789 lets a detached window's independent Pinia snapshot replace the main window's entire connections array. A stale child can therefore delete newly added connections or resurrect removed/edited ones. Synchronize connection changes through an authoritative owner or merge versioned per-connection updates instead of replacing the collection.

  3. apps/desktop/src/App.vue:2380 sends the full QueryTab through a Tauri event, including result caches that can reach roughly 128 MiB. A transfer can duplicate hundreds of MiB across serialization/deserialization and block or terminate a WebView. Transfer only lightweight tab identity/state and keep large results in shared/native storage, or enforce a proven bounded payload strategy.

This is a major UI/architecture change and the PR currently conflicts with main in App.vue. The conflict is not the blocker by itself; please resolve it only after the ownership protocol is corrected so neither side's behavior is lost.

@t8y2 t8y2 added area/desktop Desktop application or Tauri shell enhancement New feature or request ui-change Changes user-visible interface, text, or visual assets labels Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/desktop Desktop application or Tauri shell enhancement New feature or request ui-change Changes user-visible interface, text, or visual assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 允许将查询标签页拖出到独立窗口以并行对比数据

2 participants