feat(editor): 标签编辑器保活,切换立即呈现(docs/18)

最近用过的 6 个标签保持挂载、隐藏而非销毁,切回零解析零序列化、各窗格
保留滚动位置;超限窗格卸载走最终回写。引用改为按标签注册表 + 活动变化
effect 统一指向;外部重载 epoch 按标签粒度、抑制闸门对所有标签生效。
EditorPane 新增就绪状态:首次打开大文件显示「正在打开文档…」加载态。
打印守卫:只出活动窗格。vitest 87 通过 + 2 跳过(新增 pane-loading 1 例)。
This commit is contained in:
Muse
2026-11-14 13:06:17 +08:00
parent f378c60363
commit 4a527da655
5 changed files with 206 additions and 56 deletions

45
docs/18-tab-keepalive.md Normal file
View File

@@ -0,0 +1,45 @@
# 标签切换保活与加载状态(2026-11-14)
## 用户反馈
切换大文件时要等很久才能切回去,希望立即切回;至少要有加载进度/状态。
## 根因
原架构下每次切标签都销毁旧编辑器(全量序列化回写)并为新标签从 Markdown
全量解析重建编辑器。阶段 6 实测(jsdom 悲观口径):626K 字符建模 29.8s、
序列化 2.6s——大文档来回切换就是把这个成本反复付两遍。
## 改造:编辑器保活
- App 改为按标签渲染多个编辑器窗格,只有活动窗格可见,其余 `display:none`
保持挂载:切回时零解析、零序列化,立即显示;每个窗格自带滚动容器,
连滚动位置都各自保留。
- 保活上限:最近用过的 6 个标签(LRU)。超出的窗格卸载,卸载前走既有的
最终回写(syncEditorContent),内容不丢,之后再切到时按状态内容重建。
- 编辑器引用改为注册表(paneApisRef: tabId → api);onReady 只登记,
共享引用(查找/大纲跳转/源码锚点)由「活动标签变化」effect 统一指向
活动窗格,后台窗格就绪/卸载不会再冲掉活动窗格的引用。
- 外部重载(reloadTabFromDisk)的 epoch 重挂改为按标签粒度(paneEpochs),
且闸门(抑制卸载回写)对所有标签生效——保活后非活动标签也可能正挂载,
旧实现只处理活动标签会漏。退出源码模式的重建同理改为按标签 bump。
- 状态栏光标只接受活动窗格的回调,后台窗格不再干扰。
## 加载状态
- EditorPane 新增就绪状态:编辑器 create() 完成前在窗格内显示
「正在打开文档…」+ 旋转指示(.pane-loading),就绪即撤下。
首次打开大文件时标签立刻出现、窗格明确处于加载中,不再是无反馈的卡住。
解析是单线程同步计算,做不了真实百分比进度,用明确的加载态代替。
## 打印
@media print 增加守卫:.tab-pane 打印时隐藏、仅 .active 出纸,
避免保活窗格被通用 display:block 规则一起打出来。
## 验证
- tsc 0 错误;vitest 87 通过 + 2 跳过(新增 tests/pane-loading.test.tsx:
创建未完成时显示加载提示、完成后撤下并回传就绪 API);构建通过。
- 待真机验:两个大文档来回切换应立即呈现;首次打开大文档有加载态;
外部修改重载、源码模式进出、打印只出当前文档。