Skip to content

feat(tabs): 标签页多选——⌘点挑、⇧点连选,⌘W 关掉选中的一批 - #240

Merged
oratis merged 4 commits into
mainfrom
feat/tabs-multi-select
Aug 24, 2026
Merged

feat(tabs): 标签页多选——⌘点挑、⇧点连选,⌘W 关掉选中的一批#240
oratis merged 4 commits into
mainfrom
feat/tabs-multi-select

Conversation

@oratis

@oratis oratis commented Aug 23, 2026

Copy link
Copy Markdown
Owner

三个叠放 PR 里的 第 3 个,基于 #238#239。设计:docs/design/10-close-many-tabs.md §3 辩题一、二、五,§4.1–4.3,以及本 PR 新增的 §9 Review 之后改了什么。用户手册 docs/USAGE.md 补了 "Closing tabs" 一节。

Summary

  • 多选:⌘点增删、⇧点从锚点连选。选中态是蓝底 + 内描边、opacity: 1(不是"稍微亮一点"——它得显眼到没人会忘了还选着),挂 data-selected 供测试。
  • 右键选中项:菜单首条「Close N Tabs」,N 取自 liveSelection()(在 strip 上且非固定),所以是真实会关掉的数量;加「Clear Selection」。右键选中项先清空选择集再弹常规菜单(Finder 的行为,菜单说的和用户看的永远是一回事)。
  • 选择集生命周期selectedTabIds 进 store)逐条钉死:任何关闭清空、普通点击清空、Esc 清空、Save As 时 id 跟着迁移、固定页进不来固定一个已选中的页会把它剔出去——pinned 的契约是"批量手势碰不到我"。
  • ⌘W 认选择集:File ▸ Close Tab 改走 closeSelectedOrActive()——有(真会被关的)选择集关选择集,否则关当前页。
  • 命令面板加 Close Selected Tabs(没选时 toast 提示)。

Review 抓到的三个 bug(都已修,第 3 个提交)

发现 修法
⌘点选中 b → 右键 Pin → ⌘W:选择集里留着一个固定页,closeTabs 过滤掉固定页后一个受害者都没有 → return state连当前页都不关、选择集也不清,⌘W 从此是死键 toggleTabPinned 固定时剔出选择集;closeSelectedOrActive 改用 liveSelection(),空了退回关当前页
同一根因让「Close N Tabs」撒谎:选 1 固定 + 2 非固定显示 3、实关 2——正是辩题五要防的 标签和 run 都用 liveSelection()
Esc 双击:App 在编辑态把 Esc 映射成回阅读模式,⌘点标签页后焦点在 body,一个 Esc 同时清选择集 + 翻回阅读,削弱了辩题二的第 2 道闸 App 那一支看到选择集非空先让路(TabBar 负责清),下一次 Esc 才回阅读;TabBar 的监听跳过 input/textarea/contenteditable——查找栏里的 Esc 归查找栏

外加一个 nit:⇧ 连选的锚点被关掉后 anchorId ?? activeTabId 不会退回(?? 只认 null),改成点击时核对锚点还在不在。

How to verify

pnpm install
pnpm tauri:dev
  1. 开四个文件,⌘点第 2、4 个 → 两个有蓝框,当前页没变。
  2. 右键第 2 个 → 首条「Close 2 Tabs」→ 点它 → 剩两个,激活页落到左邻。
  3. 再 ⌘点两个,按 ⌘W → 关的是选中的两个,不是当前页。
  4. ⌘点一个,右键它 → Pin → 蓝框消失、📌 出现;此时 ⌘W 关的是当前页(不是死键)。
  5. E 进编辑态,⌘点一个标签页,按 Esc → 蓝框消失、还在编辑态;再按 Esc → 回阅读。
  6. ⌘N 起一个草稿、⌘点选中它、⌘⇧S 存盘 → 蓝框还在(id 迁移了)。

Checks

  • pnpm tsc --noEmit clean
  • pnpm lint clean
  • pnpm test --run green(1064,本 PR +31:store 21、TabBar 10)
  • cd src-tauri && cargo test — 未动 Rust
  • Tested manually on macOS(dev server smoke:⌘点 → data-selected + 实测蓝底/描边 → 右键「Close 2 Tabs」→ 4 变 2;Pin 选中页 → 选择集清空;Esc 先退选择集再退阅读,两步实测)
  • Performance budget unchanged
  • No new dependencies added

Notes for the reviewer

  • 最值得你盯的一条决定:⌘W 有选择集时关选择集(辩题二)。反方"肌肉记忆 + 破坏性 + 隐藏状态 = 事故"没被驳倒,只是被三道闸压到可接受:显眼标记 / 清空窗口很短 / 批量确认 + ⌘⇧T 逐个找回。另加一条:标签条被设置关掉(showTabBar: false)时 ⌘W 无视选择集——看不见的选择不许指挥破坏性操作。退路是降级成"选择集为空才生效",只改 closeSelectedOrActive 一处。
  • 批量撤销(一次 ⌘⇧T 回来整批)没做——辩题六,recentlyClosed 是路径栈,不记批次;压栈按 strip 顺序,连按 ⌘⇧T 从左到右回来。
  • 多选暂时只服务关闭,不做多选拖拽/固定/书签——设计 §6。

🤖 Generated with Claude Code

@oratis oratis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review

选择集生命周期表(§4.2)逐行核对代码,八行全部落实且各有测试:任何关闭清空(收在 removeTabs 一处)、Save As 跟着改 id、pinned 进不来、pin 一个已选中的会把它踢出去、普通点击/Esc 清空、右键未选中的先清再弹(Finder 行为)、liveSelection 让「Close N Tabs」的 N 永不撒谎、⌘W 只认可见的选择集。closeSelectedOrActive 放 store 里可测——辩题二那条最危险的路径有直接的单测,三道闸都在。

修了一处(8ec949a)

§4.2 的「strip 隐藏时选择集保留、⌘W 不认」写到了,但 Esc 那条没跟上:清选择集的监听挂在 TabBar 上,showTabBar: false 时 TabBar 整个不渲染、监听不存在;而 App 的 Esc 守卫只看 selectedTabIds.length > 0 就提前 return。于是「先 ⌘ 点选几个标签页,再去设置里关掉标签条」之后,Esc 回读模式永久失灵——隐形选择集清不掉(清空要么靠点击 strip(已隐藏)要么靠一次关闭动作)。

改法:App 的守卫只在 showTabBar && selectedTabIds.length > 0 时让位;strip 隐藏时 Esc 直接回读模式,选择集按 §4.2 继续保留。设计文档同一行已补记。tsc + 89 项相关测试通过;整栈 merge 到当前 main 后 1064 项单测全绿。

另外确认:⇧ 连选的锚点失效回退(fallback 到激活页)、selectTabRange 反向与跳 pinned、closeTabs 空集 no-op 都有测试;USAGE 的表格与实际行为一致。

@oratis
oratis changed the base branch from feat/tabs-close-left-shortcut to main August 24, 2026 04:17
oratis and others added 4 commits August 24, 2026 12:26
三个批量动词全是"以某一个为轴心的全有全无","把这五个不相邻的关掉"
没有任何路径,只能点五次悬停才显形的 ×。这一轮把 strip 本身当清单:
⌘点增删、⇧点从锚点连选;选中态是蓝底 + 内描边(不是淡淡的高亮),
挂 data-selected 供测试断言。右键选中项,菜单首条是「Close N Tabs」,
N 是真实会关掉的数量;右键未选中项先清空选择集再弹常规菜单。

选择集进 store(selectedTabIds),生命周期逐条钉死:任何关闭清空、
普通点击清空、Esc 清空、Save As 时 id 跟着迁移、固定页进不来——pinned
的契约是"批量手势碰不到我",让它进来计数就会撒谎。

⌘W(File ▸ Close Tab)改走 closeSelectedOrActive():有选择集关选择集,
否则关当前页。这是设计里风险最高的一条,所以放在 store 里可测,并配
三道闸(显眼标记 / 清空窗口很短 / 批量确认 + ⌘⇧T 逐个找回),外加
标签条被设置关掉时无视选择集——看不见的选择不许指挥破坏性操作。
退路写在 docs/design/10-close-many-tabs.md 辩题二。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
三条 review 发现,全是选择集生命周期没钉死的地方:

- ⌘点选中 b → 右键 Pin:b 既带 📌 又带蓝框。按 ⌘W:closeSelectedOrActive
  只查"还在不在",不查 pinned → live=[b] → closeTabs 过滤掉固定页 →
  一个受害者都没有 → return state,连当前页都不关,选择集也不清,⌘W
  从此是死键。两头都堵:toggleTabPinned 固定时把它从选择集里剔掉;
  closeSelectedOrActive 改用 liveSelection()(在 strip 上且非固定),
  空了就退回关当前页。
- 同一根因让「Close N Tabs」的 N 撒谎(选 1 固定 + 2 非固定写 3 关 2)。
  标签和 run 都改用 liveSelection(),辩题五要的"计数永远说真话"才真成立。
- Esc 双击:App 的 window keydown 在编辑态下把 Esc 映射成回阅读模式,
  ⌘点标签页后焦点在 body,按 Esc 清选择集的同时也把编辑器翻回了阅读。
  App 那一支看到选择集非空就先让路(TabBar 负责清),下一次 Esc 才回阅读。
  TabBar 的监听器则跳过 input/textarea/contenteditable 里的 Esc——
  查找栏里的 Esc 归查找栏。
- ⇧ 连选的锚点被关掉后 `anchorId ?? activeTabId` 不会退回(?? 只认
  null),只会选中目标一个。改成点击时核对锚点还在不在。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
showTabBar 关掉时选择集保留但不可见(设计 10 §4.2),清选择集的
监听挂在 TabBar 上——strip 隐藏时它不在场。原守卫只看
selectedTabIds.length,于是隐形选择集会把 Esc 回读模式变成死键,
直到某次关闭动作把选择集清掉为止。改成 strip 可见才让位。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oratis
oratis force-pushed the feat/tabs-multi-select branch from 8ec949a to 7dd67dc Compare August 24, 2026 04:26
@oratis
oratis merged commit 2a27545 into main Aug 24, 2026
6 checks passed
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