|
1 | 1 | # Canvas 模块架构审计 |
2 | 2 |
|
3 | | -日期: 2026-03-23 |
4 | | -范围: `src/pages/canvas.tsx`、`src/stores/canvasStore.ts`、`src/stores/canvasRuntimeStore.ts`、`src/features/canvas/*` |
| 3 | +日期: 2026-03-24 |
| 4 | +范围: `src/pages/canvas.tsx`、`src/stores/canvasStore.ts`、`src/features/canvas/runtime/*`、`src/features/canvas/*` |
5 | 5 | 评价口径: 是否适合后续迭代,不评价 UI 完成度 |
6 | 6 |
|
7 | 7 | ## 状态更新 |
8 | 8 |
|
9 | | -- 状态: `canvasStore` 第一阶段 seam 拆分已落地;`CanvasViewport` 第二刀 seam 拆分也已落地,已从“stage shell + viewport navigation hook + marquee selection hook + text/overlay host”继续收缩为“composition shell + stage shell + tool/input orchestrator + overlay host + lifecycle/navigation seam”;panel 第一刀 seam 收口也已落地,已从“panel 直接决定领域更新”收缩为“panel view + model hook + pure planner/state seam”;文本会话 P1 也已落地,已从“hook + refs + effects 串联状态机”继续收口为“pure reducer + effect runner + thin hook adapter + session snapshot”。 |
10 | | -- 总评: `8.0/10` |
| 9 | +- 状态: `canvasStore` 第一阶段 seam 拆分已落地;`CanvasViewport` 第二刀 seam 拆分也已落地,已从“stage shell + viewport navigation hook + marquee selection hook + text/overlay host”继续收缩为“composition shell + stage shell + tool/input orchestrator + overlay host + lifecycle/navigation seam”;panel 第一刀 seam 收口也已落地,已从“panel 直接决定领域更新”收缩为“panel view + model hook + pure planner/state seam”;文本会话 P1 也已落地,已从“hook + refs + effects 串联状态机”继续收口为“pure reducer + effect runner + thin hook adapter + session snapshot”;runtime preview P1 也已落地,已从“隐式全局单例 + 全局 store 取数”收口为“`CanvasPage` 下的 scoped provider + runtime scope + preview controller + narrow hooks”。 |
| 10 | +- 总评: `8.2/10` |
11 | 11 | - 当前最强的一层: 文档内核 |
12 | | -- 当前最弱的两层: runtime preview / 页面与路由装配 |
13 | | -- 当前最明显的结构热点: 页面入口仍带一部分恢复策略、`canvasRuntimeStore` 仍直接依赖全局 store 取数,文本属性修改入口也还没有完全统一到同一个 use-case seam |
14 | | -- 验证基线: `pnpm test` 通过,相关回归共 `113` 个测试文件、`456` 个 case 全部通过;`pnpm lint` 通过,但保留 `5` 个既有 warning;`pnpm build:client` 通过;本轮额外做了 architecture + bug/regression 两轮 review,均无新增阻断问题;另做了基于 `agent-browser` 的文本 smoke 验证,跑通 create -> commit、create -> cancel、edit -> switch workbench 三条路径 |
| 12 | +- 当前最弱的两层: 页面与路由装配 / 渲染与导出 |
| 13 | +- 当前最明显的结构热点: 页面入口仍带一部分恢复策略,导出入口仍保留 stage snapshot 分支,文本属性修改入口也还没有完全统一到同一个 use-case seam |
| 14 | +- 验证基线: `pnpm test` 通过,相关回归共 `116` 个测试文件、`476` 个 case 全部通过;`pnpm lint` 通过,但保留 `5` 个既有 warning;`pnpm build:client` 通过;本轮额外做了 architecture + bug/regression + performance 三轮 review,均无新增阻断问题 |
15 | 15 | - 下一阶段优先级: |
16 | | - 1. 处理页面入口 / 文本会话 / runtime preview 的应用层边界 |
17 | | - 2. 再继续收紧 `canvasStore` 的 active-workbench 门面,避免它重新长回万能入口 |
18 | | - 3. 把 `CanvasExportDialog` / `CanvasImageEditPanel` 等剩余入口继续并入现有 seam,避免局部反弹 |
| 16 | + 1. 处理页面入口 / 路由恢复策略的应用层边界 |
| 17 | + 2. 统一导出 / 图片编辑 / 属性修改剩余入口,避免绕开现有 seam |
| 18 | + 3. 继续保护 `runtime preview` / 文本会话 / `CanvasViewport` / `canvasStore` active-workbench 门面,避免复杂度回流 |
19 | 19 |
|
20 | 20 | ## 评分口径 |
21 | 21 |
|
|
87 | 87 |
|
88 | 88 | 评分: `6/10` |
89 | 89 |
|
90 | | -重构优先级: `P2` |
| 90 | +重构优先级: `P1` |
91 | 91 |
|
92 | 92 | 判断: |
93 | 93 |
|
94 | | -- 还没有坏到必须立刻拆,但已经超出“纯页面壳”的范围。 |
| 94 | +- 这一层现在已经是最明确的下一刀目标。 |
| 95 | +- 如果继续把恢复策略、panel 切换策略和 URL state 叠在入口层,这里会重新成为新的复杂度汇聚点。 |
95 | 96 |
|
96 | 97 | ## 2. 文档内核 |
97 | 98 |
|
|
212 | 213 | 判断: |
213 | 214 |
|
214 | 215 | - 这层已经从“最大结构瓶颈”降到了“可继续承接后续拆分”的状态。 |
215 | | -- 目前不需要立刻继续深拆它,下一刀应该转向 `CanvasViewport`、文本会话与 runtime preview。 |
| 216 | +- 目前不需要立刻继续深拆它,下一刀应该转向页面入口 / 路由恢复与剩余导出入口统一。 |
216 | 217 |
|
217 | 218 | ## 4. 运行时预览管线 |
218 | 219 |
|
219 | 220 | 文件重点: |
220 | 221 |
|
221 | | -- `src/stores/canvasRuntimeStore.ts` |
| 222 | +- `src/features/canvas/runtime/CanvasRuntimeProvider.tsx` |
| 223 | +- `src/features/canvas/runtime/canvasRuntimeScope.ts` |
| 224 | +- `src/features/canvas/runtime/canvasPreviewRuntimeController.ts` |
| 225 | +- `src/features/canvas/runtime/canvasPreviewRuntimeState.ts` |
| 226 | +- `src/features/canvas/runtime/canvasRuntimeHooks.ts` |
222 | 227 | - `src/features/canvas/boardImageRendering.ts` |
223 | 228 |
|
224 | 229 | 职责: |
|
236 | 241 |
|
237 | 242 | 依赖方向: |
238 | 243 |
|
239 | | -- runtime store 依赖 `useCanvasStore.getState()` 和 `useAssetStore.getState()` |
240 | | -- element 组件和 edit panel 依赖 runtime store |
| 244 | +- runtime scope 依赖 `CanvasRuntimeProvider` 显式输入的 `workbenchId`、`workbench`、`viewportScale`,以及 `assetStore` 发出的 `assets:changed` |
| 245 | +- `ImageElement`、`CanvasImageEditPanel`、selection hooks 依赖 scoped runtime hooks,而不是全局 runtime store |
241 | 246 |
|
242 | 247 | 关键状态转换/不变量: |
243 | 248 |
|
244 | 249 | - interactive preview 优先于 background preview |
245 | 250 | - 交互结束后要自动升级为 settled/background 版本 |
| 251 | +- `workbenchId` 切换、页面卸载或 `currentUser:reset` 时必须 dispose active request、queued task、settle timer、backing canvas、draft adjustments 和 selection preview |
| 252 | +- scope 切换后旧请求结果不能落回新 scope |
246 | 253 | - 缓存淘汰不能踢掉 rendering/queued 中的任务 |
247 | 254 | - preview source 释放时要及时清空 backing store |
248 | 255 |
|
249 | 256 | 优点: |
250 | 257 |
|
251 | | -- 相比持久化 store,这层至少已经被单独抽出来 |
252 | | -- preview 调度和缓存语义明确 |
253 | | -- 对交互预览和背景预览做了清晰区分 |
| 258 | +- runtime preview 现在是 `CanvasPage` 下的 scoped service,生命周期终于和 workbench/page 对齐 |
| 259 | +- `CanvasPreviewRuntimeController` 只保留任务排队、优先级、落库、淘汰和 stale result 忽略,职责比原来清楚很多 |
| 260 | +- runtime asset 改成 scope 内的 per-asset snapshot + listener,`ImageElement` 不再为读单个 asset 扫整份资产表 |
| 261 | +- selection preview 和图片渲染 controller 共用 scope,但边界已经拆开,不再混成一块宽 store |
254 | 262 |
|
255 | 263 | 问题: |
256 | 264 |
|
257 | | -- 还不是真正独立的 runtime service,仍直接从全局 store 拉数据 |
258 | | -- 通过 element id 全局扫描 workbench element,边界不够清晰 |
259 | | -- 模块级队列和 slot 状态让这层更像“全局单例运行时”,未来做 workbench scope、销毁时机会更麻烦 |
| 265 | +- 这层虽然已经 service 化,但仍依赖 UI 消费端遵守 request/release discipline;后续新入口如果绕开 hooks,仍可能把泄漏重新带回来 |
| 266 | +- preview runtime 只覆盖编辑态预览,不覆盖导出主路径;“编辑预览”和“最终导出”仍是两套相邻但未统一的管线 |
| 267 | +- asset 变更现在通过 `assets:changed` 事件增量推送,性能边界更好,但事件契约需要继续保持窄而稳定,避免重新长成宽广播 |
260 | 268 |
|
261 | | -评分: `6.5/10` |
| 269 | +评分: `8.6/10` |
262 | 270 |
|
263 | | -重构优先级: `P2` |
| 271 | +重构优先级: `已完成本轮 P1,后续降为 P2` |
264 | 272 |
|
265 | 273 | 判断: |
266 | 274 |
|
267 | | -- 拆分方向是对的,但还没拆彻底。 |
| 275 | +- 这一层已经达到当前阶段的 `8.5+` 目标,可以从“首要拆分对象”降到“需要防止反弹的已收口 seam”。 |
268 | 276 |
|
269 | 277 | ## 5. 视口与交互引擎 |
270 | 278 |
|
|
518 | 526 |
|
519 | 527 | 评分: `7/10` |
520 | 528 |
|
521 | | -重构优先级: `P3` |
| 529 | +重构优先级: `P2` |
522 | 530 |
|
523 | 531 | 判断: |
524 | 532 |
|
|
531 | 539 | - 页面装配: `src/pages/canvas.tsx` |
532 | 540 | - 文档内核: `src/features/canvas/document/*` |
533 | 541 | - 持久化与提交队列: `src/stores/canvasStore.ts`、`src/features/canvas/store/*` |
534 | | -- 运行时预览: `src/stores/canvasRuntimeStore.ts` |
| 542 | +- 运行时预览: `src/features/canvas/runtime/*` |
535 | 543 | - 视口与交互: `src/features/canvas/CanvasViewport.tsx`、`src/features/canvas/CanvasViewportStageShell.tsx`、`src/features/canvas/CanvasViewportOverlayHost.tsx`、`src/features/canvas/hooks/useCanvasViewportLifecycle.ts`、`src/features/canvas/hooks/useCanvasViewportNavigation.ts`、`src/features/canvas/hooks/useCanvasViewportToolOrchestrator.ts`、`src/features/canvas/hooks/useCanvasMarqueeSelection.ts` |
536 | 544 | - 文本会话: `src/features/canvas/hooks/useCanvasTextSession.ts` |
537 | 545 | - 面板层: `src/features/canvas/CanvasLayerPanel.tsx`、`CanvasStoryPanel.tsx`、`CanvasPropertiesPanel.tsx` |
|
541 | 549 |
|
542 | 550 | - `src/stores/canvasStore.test.ts` |
543 | 551 | - `src/features/canvas/store/canvasWorkbenchState.test.ts` |
544 | | -- `src/stores/canvasRuntimeStore.test.ts` |
| 552 | +- `src/features/canvas/runtime/canvasPreviewRuntimeState.test.ts` |
545 | 553 | - `src/features/canvas/document/commands.test.ts` |
546 | 554 | - `src/features/canvas/document/resolve.test.ts` |
547 | 555 | - `src/features/canvas/renderCanvasDocument.test.ts` |
|
555 | 563 | ### P1 |
556 | 564 |
|
557 | 565 | - 页面入口的恢复策略下沉为更明确的应用层 |
558 | | -- runtime preview 从全局 store 读取模型转向更明确的 workbench-scoped 输入 |
559 | 566 |
|
560 | 567 | ### P2 |
561 | 568 |
|
| 569 | +- 保护 runtime preview 新的 scoped service seam,不让新的图片编辑或选择逻辑重新绕开 provider / narrow hooks |
562 | 570 | - 保护文本会话新的 reducer / effect runner / snapshot seam,不让新的文本能力重新堆回 hook |
563 | 571 | - 继续收紧 `canvasStore` 的 active-workbench 门面,避免它重新长回“万能接口集合” |
564 | 572 | - 把 `CanvasExportDialog` / `CanvasImageEditPanel` 等剩余入口继续并入现有 panel seam,避免局部反弹 |
| 573 | +- 统一导出主路径,减少 stage snapshot 分支 |
565 | 574 | - 保护 `CanvasViewport` 新的 stage / tool / overlay seam,不让新的交互策略重新堆回组合壳 |
566 | 575 |
|
567 | 576 | ### P3 |
568 | 577 |
|
569 | 578 | - 保护文档内核,不做推倒重写 |
570 | | -- 统一导出主路径,减少 stage snapshot 分支 |
571 | 579 |
|
572 | 580 | ## 推荐的拆分顺序 |
573 | 581 |
|
574 | | -1. 先处理页面入口 / runtime preview 的 service 化和边界下沉 |
575 | | -2. 再继续收紧 `canvasStore` 对上层暴露的 active-workbench 门面 |
576 | | -3. 同时保护文本会话与 `CanvasViewport` 新的 seam,不让新的交互策略重新回流 |
577 | | -4. 最后把剩余未收口的 panel 入口并到现有 seam 上,避免重新长回组件内规则 |
| 582 | +1. 先处理页面入口 / 路由恢复策略的 service 化和边界下沉 |
| 583 | +2. 再统一导出 / 图片编辑 / 属性修改这些仍分散的应用层入口 |
| 584 | +3. 继续收紧 `canvasStore` 对上层暴露的 active-workbench 门面 |
| 585 | +4. 同时保护 runtime preview、文本会话与 `CanvasViewport` 新的 seam,不让新的交互策略重新回流 |
578 | 586 |
|
579 | 587 | ## 最终判断 |
580 | 588 |
|
581 | | -这轮之后,最紧急的 seam 已经从 panel 层和文本会话残留宿主,转向了页面入口 / runtime preview 的应用层边界。 |
| 589 | +这轮之后,最紧急的 seam 已经从 panel 层、文本会话残留宿主和 runtime preview,全都转向了页面入口 / 路由恢复与导出入口统一。 |
582 | 590 |
|
583 | | -- 如果继续堆功能,复杂度会优先集中在页面入口和 runtime preview,而不是重新首先压垮文本会话、`CanvasViewport`、panel 层或 `canvasStore.ts` |
| 591 | +- 如果继续堆功能,复杂度会优先集中在页面入口和导出相关入口,而不是重新首先压垮 runtime preview、文本会话、`CanvasViewport`、panel 层或 `canvasStore.ts` |
584 | 592 | - panel 层现在已经能继续承接下一轮拆分,但还不值得宣布“整个 canvas 完成” |
585 | | -- 文本会话与 `CanvasViewport` 这两层已经可以承接后续功能,但前提是不要把新的恢复策略、DOM 宿主逻辑和工具策略重新塞回组合壳 |
| 593 | +- runtime preview、文本会话与 `CanvasViewport` 这几层已经可以承接后续功能,但前提是不要把新的恢复策略、导出旁路或工具策略重新塞回这些 seam |
586 | 594 | - 文档内核这条健康链路仍然应该继续被保护,后续重构仍应围绕上层应用边界展开 |
0 commit comments