完成客户端素材创作无限画布阶段三闭环
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 20s
Project CI / Native shell tests (pull_request) Successful in 11m31s

新增客户端共享画布宿主适配与独立创作界面
实现草稿并发保存、恢复副本和本地媒体安全校验
实现项目写锁内的可恢复素材提交与清单登记
补齐非破坏精修、幂等并发和故障恢复测试
同步阶段三技术方案与团队共享记忆
This commit is contained in:
2026-08-05 15:01:43 +08:00
parent a5a8a6b085
commit 02b16ae8ad
17 changed files with 6708 additions and 6 deletions
@@ -1,5 +1,15 @@
# 决策记录
## 2026-08-05 无限画布阶段三以 Tauri 持久草稿和项目 mutation 事务形成独立图片闭环
- 决策:阶段三新增独立 `AssetCanvasSurface` 和 Tauri Host Adapter,直接消费共享 core/React/ports;前端状态固定为 `canvas.creating / editing / saving / recovering / failed`,generation 明确使用 unsupported mock。本阶段不接资源总览入口、保存后自动选中或真实 AI,避免提前修改工作台状态合同。
- 草稿边界:`.agent/workbench/asset-canvas/` 使用持久 OS 文件锁和 draft revision CAS,不使用 React state、localStorage 或进程内 mutex 作为正式状态。主草稿损坏时,固定 recovery JSON 必须通过针对实际字节的 SHA-256、schema/身份和全部受控媒体复核才可恢复;连续保存产生的 revision 直接从 CAS 结果推进。
- 提交边界:正式提交复用项目 mutation 写锁,锁内按身份/revision 重检、prepared journal/ledger、最终图片安装、manifest、project revision、回读验证、ledger/草稿终态推进。`commitId/idempotencyKey` 与完整请求指纹绑定;同请求重放返回同一 asset/event,同 revision 竞争不得覆盖。释放锁后才发布至少一次 Tauri event,emit 失败不回滚已提交 manifest。
- 恢复边界:最终文件分为 absent、matches、mismatch。只有 absent 或摘要/大小/尺寸完全匹配时可自动回滚;mismatch 保留现场并标记 `reconciliation-required`。manifest after/revision before 前向补 revision,二者 after 时补 ledger、草稿和事件。只能删除能证明属于当前事务的新文件。
- 精修与异步边界:refine 源必须是 manifest 唯一登记且通过签名、完整解码、容量、普通文件、符号链接/硬链接门禁的图片;源 asset/文件永久保留,新 `canvas-<commitId>` 通过规范 resourceId 引用源。前端以 project/draft/session epoch 丢弃迟到 Promise/事件,以 eventId 去重 command/event 投影。
- 验证:阶段三 Surface `8/8`、共享 core/React `7/7`、AppSurface `351/351`、Rust 素材画布定向测试 `11/11`,并执行客户端 typecheck、Rust check、编码与 diff 门禁。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-05 无限画布阶段二以共享包成为网站与 Tauri 唯一核心源码
- 决策:把现役网站画布的纯交互模型、history/viewport/stage hooks 和通用 React 容器迁到 `packages/image-canvas-core`、`packages/image-canvas-react`;原 `src/components/image-editor` 同名模型/hook 只保留 re-export。网站 Stage/World/Dock/Portal 已直接使用共享 React,Tauri 壳只建立共享源码编译入口和作用域 CSS,不在本阶段接素材创作业务闭环或修改资源总览。
@@ -14,6 +14,14 @@
- 关联:相关文件、文档、提交或 Issue
```
## 正式保存不能等待 React state 才取得草稿 CAS 的新 revision
- 现象:用户刚完成编辑就点击“保存到项目”,自动草稿保存已经成功,但正式提交仍携带旧 `expectedDraftRevision`,于是单窗口也得到 draft revision conflict;快速连续保存时还可能使用不同 commitId 重复 staging。
- 原因:`setDraft(result.value)` 的 React state 提交晚于当前 Promise 链,正式保存若从闭包或下一次 render 读取 revision,会把 UI 调度时序误当成持久化顺序。相同问题也会出现在选择变化未标脏、父组件每次 render 新建 scope 对象而重复恢复、项目切换后旧 generation 回调继续写 notice。
- 处理:草稿保存队列在 CAS 成功后同步更新 `draftRef.current` 并直接返回权威 draft;正式提交继续使用该返回值的 revision。scope 按 project/draft/intent/source 原始字段稳定化,所有导入、保存、生成和事件回调捕获当前 epoch,选择变化属于草稿合同并必须标脏。首次正式保存冻结 commitId/idempotencyKey,未知结果只重放原请求。
- 验证:用 deferred Promise 证明草稿 CAS 完成后正式 commit 使用新 revision;相同 scope 值重渲染不重复 recover/load;锁定图层选择进入草稿更新;项目切换后迟到 generation/commit/event 均不改变新会话。
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。
## 共享画布不能用全局 DOM 查询或宿主整包 CSS 作为隐式依赖
- 现象:页面挂两个画布时,第二个画布点击小地图会移动第一个画布;反复挂载后 wheel 触发多次。Tauri 单独引入网站 `index.css` 时还会带入账号、项目页和历史业务样式,或因仓库外源码解析到第二份 React 而出现 Hook 错误。
@@ -928,3 +928,13 @@ game-project/
- 共享样式固定为 `packages/image-canvas-react/src/styles.css`,全部以 `.genarrative-image-canvas` 作用域开头;网站可以继续用 `src/index.css` 提供站点皮肤,Tauri 只导入共享样式和自身 `styles.css`。两套 Vite 都把共享源码显式 alias 到仓库 `packages/`,Tauri dev server 保留 `fs.allow=[repoRoot]`,并通过 `resolve.dedupe=['react','react-dom']` 保证单 React 实例。
- portal 默认进入当前 Provider 指定的 root,未指定时才回退 `document.body`;事件监听、`ResizeObserver`、animation frame 和小地图查询均按组件实例清理或限定在当前 viewport。文档中只有一个小地图的旧调用形态允许唯一元素回退,页面存在多个小地图时不做全局猜测。
- 阶段二定向证据为共享 core/React 7 项与现役 `src/components/image-editor` 74 个文件、892 项测试通过;主站与 AI 游戏创作壳 typecheck 通过。构建、编码和 diff 门禁以本次任务最终验证记录为准。
## 2026-08-05 客户端素材创作无限画布阶段三 Tauri 独立闭环
- 阶段三在 `apps/ai-game-creator-shell/src/features/asset-canvas/` 增加独立 `AssetCanvasSurface` 与薄 Tauri Host Adapter,直接消费阶段二的 core/React/ports。页面实现 `canvas.creating / editing / saving / recovering / failed`,支持 PNG/JPEG/WebP 本地导入、选择、多选、移动、缩放、删除、60 步撤销重做、viewport 平移缩放、小地图、复位和浏览器合成导出;generation port 明确返回 mock unsupported,不发送真实 AI 请求。
- 草稿正式保存到 `.agent/workbench/asset-canvas/`,由 Tauri Rust 使用持久 OS 文件锁和 `expectedDraftRevision` CAS 串行化;主草稿、固定 recovery JSON 与实际 recovery 字节的裸 SHA-256 一起安装。主文件损坏时只有 schema、项目/草稿身份、摘要及全部媒体签名、摘要、尺寸和普通文件身份都可信才恢复,否则进入 `reconciliation-required`。单文件限制 `64 MiB`,单草稿媒体总量限制 `512 MiB`。
- 正式提交先把合成结果安全写入受控 staging,再进入既有项目 mutation 写锁。锁内重读并校验 manifest、项目身份、项目 revision、草稿 revision 和提交指纹,写 prepared journal/ledger 与 before/after 快照,安全安装唯一最终图片,原子更新 manifest 和 project revision,回读图片/manifest/revision 后提交 ledger 与草稿;释放锁后才发布 `game-creator-local-asset-committed`。同 `commitId/idempotencyKey` 同请求返回原权威结果,同键不同请求失败关闭,同 expected revision 竞争最多一个成功。
- 崩溃恢复只按 journal、ledger、before/after 摘要和最终文件三态推进:图片与 manifest/revision 都在 before 时,只清理摘要和尺寸可证明属于本事务的新文件;最终路径不存在可直接回滚;最终路径存在但身份不匹配必须保留文件并进入对账。manifest 已安装而 revision 未安装时前向补 revision;manifest/revision 都是 after 时补齐 ledger、草稿和事件。业务事实完成后 emit 失败不回滚,后续恢复继续按固定 eventId 发布。
- refine 只允许从当前 manifest 唯一登记的 PNG/JPEG/WebP 普通文件建立草稿,并复核签名、完整解码、大小、符号链接和硬链接。正式保存保留源 asset 与源文件,为缺失 resourceId 的源补 `local-asset:<sourceAssetId>`,新建 `canvas-<commitId>` 并以 `referenceResourceIds` 引用源资源。
- 页面以 project/draft/session epoch 丢弃项目切换、会话切换和卸载后的旧 Promise/事件,并以 eventId 去重 command 返回与至少一次事件。相同 scope 字段的父级重渲染不会重复打开草稿,选择变化也进入草稿 CAS;正式保存直接消费 CAS 返回的新 draft revision,不等待 React state 提交时序。
- 本阶段只提供独立 Surface/fixture 与 Tauri commands,不接 `project-development` 资源总览按钮、不做保存后的资源总览自动选中,也不接真实 External Editor API。定向证据为阶段三 Surface `8/8`、共享 core/React `7/7`、既有 AppSurface `351/351`、Rust 素材画布 `11/11` 与客户端 typecheck 通过;AppSurface 保留仓库既有 React `act(...)` 警告,不影响测试结果。