fix(app): 模式切换文本闪烁——epoch 重挂统一弃旧窗格回写,源码模式禁富文本窗格回写(docs/24)

This commit is contained in:
Muse
2026-11-14 16:13:55 +08:00
parent c0f03e48dc
commit 02090f7bf4
2 changed files with 56 additions and 8 deletions

View File

@@ -82,6 +82,12 @@ export default function App() {
const [sourceMode, setSourceMode] = useState(false)
const [paneEpochs, setPaneEpochs] = useState<Record<string, number>>({})
const bumpPaneEpoch = useCallback((id: string) => {
// 凡是 epoch 重挂(源码退出重建、外部重载),旧窗格卸载时的最终回写必然
// 是过期文本(新内容已在状态里),先上闸门丢弃,免得它盖掉新内容——
// 源码模式下输的文本曾被空窗格的回写反复盖掉(docs/24)。闸门 1.5 秒
// 自清,防止没有卸载发生时长期挂着。
suppressFinalWriteRef.current.add(id)
window.setTimeout(() => suppressFinalWriteRef.current.delete(id), 1500)
setPaneEpochs((prev) => ({ ...prev, [id]: (prev[id] ?? 0) + 1 }))
}, [])
// 标签编辑器保活:最近用过的标签保持挂载(隐藏而非销毁),切回免去整篇重解析
@@ -433,11 +439,7 @@ export default function App() {
if (expectedContent !== undefined && current.content !== expectedContent) return
if (isDirty(current)) return
}
// 审计修复(卸载回写覆盖):标签重挂时,旧 EditorPane 的卸载回写会把旧正文
// 写回状态、盖掉刚载入的内容——重载前先给该标签的这次最终回写上闸门。
// 保活后非活动标签也可能正挂载着,闸门与重挂对所有标签一律生效。
suppressFinalWriteRef.current.add(target.id)
window.setTimeout(() => suppressFinalWriteRef.current.delete(target.id), 1500)
// 卸载回写覆盖的闸门已集中在 bumpPaneEpoch 内(重挂必弃旧回写)。
dispatch({
type: 'loadContent', id: target.id, content: file.content,
encoding: file.encoding, lineEnding: file.lineEnding, hadBom: file.hadBom,
@@ -1299,12 +1301,18 @@ export default function App() {
key={`${t.id}:${paneEpochs[t.id] ?? 0}`}
tabId={t.id}
initialMarkdown={t.content}
onMarkdown={(id, md) => dispatch({ type: 'updateContent', id, content: md })}
onMarkdown={(id, md) => {
if (sourceMode && stateRef.current.activeId === id) return // 源码模式中:富文本窗格是旧稿,禁回写
dispatch({ type: 'updateContent', id, content: md })
}}
onFinalMarkdown={(id, md) => {
if (suppressFinalWriteRef.current.delete(id)) return // 外部重载的卸载回写,丢弃
if (suppressFinalWriteRef.current.delete(id)) return // 重挂时的卸载回写已过期,丢弃
dispatch({ type: 'syncEditorContent', id, content: md })
}}
onSyncInitial={(id, md) => {
if (sourceMode && stateRef.current.activeId === id) return // 同上
dispatch({ type: 'syncEditorContent', id, content: md })
}}
onSyncInitial={(id, md) => dispatch({ type: 'syncEditorContent', id, content: md })}
onCursor={(c) => { if (stateRef.current.activeId === t.id) setCursor(c) }}
focusMode={ui.focusMode}
typewriter={ui.typewriter}