From d2068d416a45805ecb2a3aa9323fcdb99761b610 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 16:27:01 +0800 Subject: [PATCH] docs(canvas): explain recovery and import UI lifecycle --- src/components/canvas/draft-recovery.tsx | 1 + src/components/canvas/headless-imports.tsx | 4 ++++ src/components/canvas/headless-recovery-dialog.tsx | 4 ++++ src/components/canvas/legacy-canvas-migration.tsx | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/src/components/canvas/draft-recovery.tsx b/src/components/canvas/draft-recovery.tsx index df2df4d..77cb76c 100644 --- a/src/components/canvas/draft-recovery.tsx +++ b/src/components/canvas/draft-recovery.tsx @@ -11,6 +11,7 @@ export function DraftRecovery({ restore, discard, canRestore = true, exportDraft const dialog = useRef(null); // 挂载时showModal启用浏览器模态行为,卸载时close;不额外保存一份open状态。 useEffect(() => { const element = dialog.current; element?.showModal(); return () => element?.close(); }, []); + // 点击按钮只同步调用宿主,不自动 close,也没有 busy/重复点击互斥;宿主完成选择后负责卸载对话框。 // 无document不创建Portal;取消事件阻止默认Escape关闭,要求用户通过明确的版本选择按钮处理。 if (typeof document === "undefined") return null; return createPortal( event.preventDefault()}> diff --git a/src/components/canvas/headless-imports.tsx b/src/components/canvas/headless-imports.tsx index 9f6f97c..e270121 100644 --- a/src/components/canvas/headless-imports.tsx +++ b/src/components/canvas/headless-imports.tsx @@ -22,13 +22,17 @@ export function HeadlessImports({ engine, canvasId, viewId }: { engine: ProjectE const input = useRef(null), [open, setOpen] = useState(false), [error, setError] = useState(""); // 每次渲染更新engine/canvas身份,用于异步文件探测和控制回调完成后避免更新另一画布的错误显示。 const scope = useRef({ engine, canvasId }); scope.current = { engine, canvasId }; + // scope 只比较 engine/canvas,不带挂载代次;同一目标卸载后的已兑现回调并非由此 ref 自动取消。 const jobs = [...(snapshot.runtime.imports?.values() ?? [])].filter(job => job.source?.targetCanvasId === canvasId).slice(-3).reverse(); /** 统一承接runtime控制Promise的成功/失败;这里只更新面板错误,不自行重排或重启任务。 */ const control = (work: Promise) => { void work.then(() => { if (scope.current.engine === engine && scope.current.canvasId === canvasId) setError(""); }).catch(error => { if (scope.current.engine === engine && scope.current.canvasId === canvasId) setError(errorText(error)); }); }; + // control 接收已经开始的 Promise:同步调用抛错不在它的 catch 内;这里也不阻止多次点任务按钮。 // canStart决定文件入口可用性;input每次先清value,允许再次选择同一个文件。 // .json读取前64KiB探测首个非空白字符:对象走文档导入,数组走记录数组;其它后缀走NDJSON。 // 探测前捕获canvas父关系及入口生命周期,await后重新核对,防止向已被替换/移走的目标启动任务。 + // 64KiB 只是后缀为 .json 时的格式探测;真正解析、校验、文件持久保存和逐步加入由 runtime 负责。 // integrityVersion=1源校验未完成显示校验中;=2允许内容继续编辑,但提示校验完成后同步。 + // “重试源文件校验/保存”调用 engine.retry,作用于项目待重试工作,不是只重试当前展示的 job。 // 任务按钮按runtime状态显示;失败详情最多列前10项,停止提示明确已加入内容继续同步。 return