Skip to content

fix(tabs): 批量关闭前先问——Close Others / Close to the Right 不再静默丢改动 - #238

Merged
oratis merged 5 commits into
mainfrom
fix/tabs-bulk-close-confirm
Aug 24, 2026
Merged

fix(tabs): 批量关闭前先问——Close Others / Close to the Right 不再静默丢改动#238
oratis merged 5 commits into
mainfrom
fix/tabs-bulk-close-confirm

Conversation

@oratis

@oratis oratis commented Aug 23, 2026

Copy link
Copy Markdown
Owner

设计文档:docs/design/10-close-many-tabs.md(本 PR 带入)。这是三个叠放 PR 里的 第 1 个,后两个:#239 Close to the Left + ⌘⌥W → #240 多选 + ⌘W 认选择集。

Summary

  • 两个数据丢失缺陷(设计 §1.2):closeOtherTabs / closeTabsToRight 完全不检查脏状态,直接丢;closeAllTabs 只报第一个脏文件的名字就把全部丢掉——比不问更糟,它制造"我知道我在丢什么"的错觉。修法:所有关闭共用 confirmDiscard(victims),一次批量问一次,2 个以上把名字都列出来(超过 5 个截断注明还有几个),没有"有路径且脏"的不打扰。
  • 四个 reducer 的移除逻辑收口成 removeTabs():压 recentlyClosed(按 strip 顺序,所以 ⌘⇧T 从左到右依次回来)、落激活页(最左被关位置的左邻,与单个关闭一致)、关空回 welcome。closeTab 仍不跳过 pinned——显式点名就该关掉你点的那个,只有批量手势才扫不到固定页。
  • 顺手修一个 pre-existing bug(设计 §5):右键菜单面板宽度随条目数线性增长(8 条实测 684px,横跨半个窗口),因为 <button> 是 inline-block,块级容器的 max-content 是"全排一行"的宽度。加 flex flex-col,实测回到 160px。
  • Review 后补的(第 4 个提交):closeOtherTabs 其余全是固定页时仍把轴心页激活(改前行为,重构时丢了);t() 用函数做替换值,文件名里的 $& 不再被展开成 {1}(既有 bug,确认文案现在会列一串文件名)。

一个有意的行为变化

closeAllTabs 之后的落点:改前是第一个固定页,现在是最后一个固定页(= 最左被关位置的左邻)。这是所有关闭动作统一的规则,视线不用跳到 strip 最前面。有测试钉住。

How to verify

pnpm install
pnpm tauri:dev
  1. 开三个 vault 文件,设置里把 autosave 关掉(autosaveMs = 0),在其中两个里各打几个字不保存。
  2. 右键第三个 → Close Others:改前一声不响全关;改后弹一个确认,列出两个文件名,取消则一个都不关。
  3. 右键 → Close All:确认文案列出全部未保存的文件,不是只有第一个。
  4. 右键任意标签页:菜单是窄的一条(约 160px),不再横跨窗口。

Checks

  • pnpm tsc --noEmit clean
  • pnpm lint clean
  • pnpm test --run green(1021,本 PR +10)
  • cd src-tauri && cargo test — 未动 Rust
  • Tested manually on macOS(dev server + 浏览器 smoke,非 Tauri 壳)
  • Performance budget unchanged — 只动标签页 reducer
  • No new dependencies added

Notes for the reviewer

  • E2E 那个红叉是既有的 flake,不是本 PR:wide-tables.spec.ts 量到 685 而不是 720,main 的 head(703402d)同一条也红。本地 4 workers 跑 27 次能复现 8 次——根因是 pasteMarkdown 点编辑区中心放光标,落进列表/引用块就量到容器宽度。另开一个小 PR 修。
  • 仍用 window.confirm,不造三选一对话框(保存并关闭 / 直接关闭 / 取消)——理由见设计辩题三:autosave 默认 300ms 开着,有路径的文档几乎不存在"脏"这个状态,今天的真问题是"根本不问",先把 ❌ 修成 ✅。
  • 无路径草稿从头到尾不设防(缺陷三)是既有设计,不在本轮;设计 §7 记着。
  • 合并方式照旧 squash。合并后 feat(tabs): 补上 Close to the Left,⌘⌥W 关掉其他标签页 #239 会自动改基到 main,但 diff 里会带上本 PR 的提交——我会跟着 rebase,或者你跑 git rebase --onto main fix/tabs-bulk-close-confirm feat/tabs-close-left-shortcutpush -f

🤖 Generated with Claude Code

oratis and others added 4 commits August 24, 2026 00:46
ContextMenu 的面板靠 shrink-to-fit 定宽,而 <button> 默认是
inline-block——块级容器的 max-content 对行内级子元素来说是"全排
一行"的宽度,于是面板宽 ≈ 所有菜单项之和:8 条实测 684px,横跨
半个窗口。min-w-[160px] 只管下限,管不住这个。

加 flex flex-col,max-content 退回"最宽的那一条",实测落回 160px。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
两个真缺陷,见 docs/design/10-close-many-tabs.md §1.2:

- closeOtherTabs 和 closeTabsToRight 完全不检查脏状态,直接 filter
  掉受害者就返回。右键 → Close Others,其余标签页里没保存的改动一声
  不响地没了。单个 closeTab 是问的,所以这是漏了,不是风格。
- closeAllTabs 问的是 A,丢的是 A、B、C:confirm 只报第一个脏文件的
  名字,用户点确定,后面几个的改动一起没了。比不问更糟——它制造了
  "我知道我在丢什么"的错觉。

修法:所有关闭动作共用一个 confirmDiscard(victims)——批次里没有
"有路径且脏"的直接放行;恰好 1 个沿用原文案;2 个以上用新的
tab.confirmCloseMany,把名字都列出来(超过 5 个截断并注明还有几个)。
一次批量只问一次。

顺手把四个 reducer 的移除逻辑收口成 removeTabs():压 recentlyClosed、
落激活页(落到最左被关位置的左邻,和单个关闭一致)、关空回 welcome。
closeTab 仍然不跳过 pinned——显式点名的单个关闭就该关掉你点的那个,
只有批量手势才扫不到固定页。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
现状 review(三个真缺陷 + 三个"不好关")、七道正反方辩论、设计细则、
不在本轮、验证。落地拆成三个叠放的 PR,本 PR 是第一个。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- closeOtherTabs:其余全是固定页时原先直接 return state,连轴心页都不
  激活了;改前这个手势永远会把你带到右键的那一页。补回来。
- closeAllTabs 的落点从"第一个固定页"变成"最左被关位置的左邻"(也就是
  最后一个固定页)——这是有意的,和所有关闭动作一个规则,补测试钉住。
- i18n t() 用字符串做 replace 的替换值,`$&`、`$'` 会被展开:文件名
  x$&y.md 会渲染成 x{1}y.md。换成函数替换值。既有问题,但确认文案现在
  会列一串文件名,顺手修。
- 设计文档状态行填上三个 PR 的编号。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oratis
oratis force-pushed the fix/tabs-bulk-close-confirm branch from 3ce777d to db3a417 Compare August 23, 2026 16:47

@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

设计 10 的辩论与裁决俱全,代码对得上文档。逐点核过:

  • removeTabs 作为唯一的移除路径是这轮最值钱的改动:激活页落点(min(max(0, firstIdx-1), len-1))与单关规则一致,recentlyClosed 按 strip 顺序压栈(辩题六的约定有测试钉住),关空回 welcome。五个公开 API 全部收敛到它,此后不可能再出现"某条路径忘了问"。
  • confirmDiscard 的三段式正确修掉缺陷一 + 缺陷二:0 个脏的不打扰、1 个沿用旧文案(单关体验一字不变)、N 个列名并在超过 5 个时截断——截断行自身也走 i18n。
  • closeTab 不跳过 pinned 是显式契约,注释写死了理由,和批量手势的"扫不到固定页"分工清楚。
  • t() 换函数 replacer:字符串 replacement 会展开文件名里的 $&/$',文件名恰好是这里的主要参数——这个补丁本身就值一条测试,也有了。
  • 右键菜单宽度inline-block 子元素让 max-content 变成"全排一行"的总宽,flex flex-col 一行修掉,量化数据(684px→160px)在文档里。

closeOtherTabs 无事可关时仍落到轴心、closeAllTabs 全 pinned 时 no-op,这些边界都有测试。没有要改的,直接进合并链(rebase 到当前 main 后 tsc + 1064 单测全绿)。

@oratis
oratis merged commit d3d911e into main Aug 24, 2026
6 checks passed
@oratis
oratis deleted the fix/tabs-bulk-close-confirm branch August 24, 2026 04:15
@oratis
oratis restored the fix/tabs-bulk-close-confirm branch August 24, 2026 04:16
@oratis
oratis deleted the fix/tabs-bulk-close-confirm branch August 24, 2026 04:16
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