合并最新主分支并同步审核修复

合入画布副本持久化与素材类型覆盖变更
合入画布编辑器多行输入框样式调整
保留资源管理审核修复与项目记忆更新

# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
2026-08-04 18:40:45 +08:00
52 changed files with 2565 additions and 1062 deletions
@@ -6007,3 +6007,12 @@
- 存储决策:`.agent/manifest.json` 的版本追加不可变约束由同目录持久专用锁保护,读取旧状态、校验版本前缀、安装临时文件和安装后回读必须处于同一临界区;进程内 Mutex 不能替代跨进程文件锁。
- UI 决策:Project Supervisor 持有运行中 manifest 状态并向外层启动器同步完整快照;外层项目上下文继续是工作台投影的唯一输入,只接受当前项目路径的更新,不另建资产、任务或版本平行状态。
- 依赖图决策:Agent DB 尾部读取一旦截断,审计 producer 及其 task flow / depth 派生失败关闭;manifest 精确资源引用与审计生产者证据分离。SVG 保持装饰性,辅助技术消费画布关联的文本关系列表。
## 2026-08-04 图片画布素材类型采用资源默认值与布局覆盖双层模型
- 背景:画布复制逻辑曾为副本生成 `local-resource-copy-*`,导致同一媒体被伪装成未登记资源;随后改为复用 `resourceId`,但手动修改图层标签仍通过“按新 `assetKind` 查找 / 创建项目资源并换绑当前图层”实现。这会让单纯标签修改增加资源行、漂移 `resourceId`,并在异步回填与复制交错时形成“新类型 + 旧资源”的副本。
- 决策:`editor_project_resource.asset_kind` 是跨布局共享的资源默认类型,`editor_canvas_layer.asset_kind_override` 是当前布局实例的可空类型覆盖;有效类型唯一按 `override ?? resource default` 计算。点击当前图层标签只新增、修改或清除 override,不创建资源、不更换 `resourceId`;清除后恢复继承。若另行提供资源默认类型编辑,必须原地更新同一资源行,并只影响没有 override 的引用图层。
- 复制语义:`layerId` 是同一 canvas 内唯一的布局实例身份,`resourceId` 是允许多图层共享的项目媒体身份。复制、粘贴、创建副本和剪切后粘贴只生成新 `layerId`,复用来源 `resourceId` 并复制 `assetKindOverride`;已登记资源不得再次上传或创建,副本后续可独立修改 override。
- local 状态:`local-*` 只是 ID 形状,不能直接解释为“素材仍在保存”。新上传 / 新生成素材是否 pending 取资源登记在途状态;严格满足兼容谓词的历史自包含本地角色动作序列是持久化终态,不得误报等待。若当前版本尚不能复制这类序列,以准确原因失败关闭;既非 pending 又不满足历史谓词的 unresolved local 图层也失败关闭,但不得承诺稍后一定自动恢复。layout PATCH pending 不参与资源登记判断,系统剪贴板图片导入不受影响。
- schema 与迁移:在现有 `EditorCanvasLayer` 结构体末尾追加 `#[default(None::<String>)] asset_kind_override: Option<String>`,不删除、改名、重排或改类型。legacy 图层类型与资源默认相同则迁移为 `None`,不同则迁移为 override;资源无默认值时只有全部引用图层显式同值才补资源默认,否则保留各自 override;自包含历史序列的显式类型迁入 override,不伪造资源。同步 `migration.rs`、表目录 / 数据契约、生成 bindings、HTTP DTO 与结构化 canonical hash,并运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`。
- 并发边界:未登记图层被禁止复制后,不再按临时资源 ID 合并项目资源创建请求,也不再用一次响应批量改写共享临时 ID。每个合法新增图层保留自己的响应快照与回调;layout PATCH 的串行 latest-wins 队列、共享资源的多布局引用和 session 资源快照按 `resourceId` 去重继续保留,它们与资源创建 single-flight 是不同机制。
@@ -481,6 +481,22 @@
- 验证:`npm run test -- src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx -- --runInBand` 应覆盖裁扩先上传再创建项目资源,以及去背景队列完成后对未完成占位进行二次项目读取。
- 关联:`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 图片画布不能用 `local-*` 前缀代替资源登记状态
- 现象:刚加入画布、资源登记仍在途的图层被复制后,权威项目快照刷新与资源创建响应交错,副本可能刷新后消失;反过来,历史自包含角色动作序列虽然也使用 `local-*`,却会被永久禁用并持续提示“素材仍在保存”。
- 原因:`local-*` 同时覆盖两种不同状态:新素材的临时 ID,以及没有项目资源行、但已凭完整持久化帧成为终态的历史兼容序列。ID 前缀不是状态机;把所有本地 ID 当 pending,或把多个布局调用压进一个临时 ID single-flight Promise,都无法表达每次请求的真实生命周期与恢复上下文。
- 处理:新素材是否 pending 必须读资源登记在途集合;存在明确在途请求时,复制、剪切、创建副本和内部粘贴整体拒绝,正式 `resourceId` 回填后开放。历史序列按结构化持久化的严格自包含谓词识别,不能显示保存中;暂不支持复制时用准确原因失败关闭。既非 pending 又不满足历史谓词的 unresolved local 图层也拒绝,但提示“资源尚未登记”而非“仍在保存”。不要把普通 layout PATCH pending 当资源登记状态,也不要阻止系统剪贴板图片导入。资源创建不再按临时 ID single-flight 合并;布局保存的串行 latest-wins 队列仍保留。
- 验证:`ImageCanvasLayerCommandModel.test.ts`、`useImageCanvasLayerCommands.test.tsx` 和 `ImageCanvasContextMenusView.test.tsx` 分别覆盖登记在途整体拒绝、正式 ID 回填后恢复、历史 self-contained local sequence 不误报保存中,以及 unsupported / unresolved 的准确提示;`useImageCanvasProjectPersistence.test.tsx` 继续覆盖单个资源响应与权威快照交错恢复。
- 关联:`src/components/image-editor/ImageCanvasLayerCommandModel.ts`、`src/components/image-editor/useImageCanvasLayerCommands.ts`、`src/components/image-editor/useImageCanvasProjectPersistence.ts`、`docs/【编辑器】图片画布结构化持久化与迁移回滚方案-2026-07-19.md`。
## 图片画布修改图层标签不能通过创建资源和换绑实现
- 现象:用户只修改一个图层的素材类型,项目资源数量却增加且该图层的 `resourceId` 改变;如果修改请求完成前复制,原图层会换绑到新资源,副本仍引用旧资源,刷新后副本类型回退。
- 原因:把资源 `assetKind` 同时当作共享默认值和布局实例标签,只能通过按类型查找 / 创建资源来模拟局部修改。异步响应只知道原 `layerId`,无法自动追踪期间复制出的新布局实例;layout 又没有独立覆盖字段,最终形成资源身份漂移和类型错位。
- 处理:固定双层模型:`editor_project_resource.asset_kind` 是资源默认类型,`editor_canvas_layer.asset_kind_override` 是可空布局覆盖,effective 值为 `override ?? resource default`。图层标签动作只写 / 清除 override,保持资源行数量和 `resourceId` 不变;复制复用 `resourceId` 并复制 override。`asset_kind_override` 必须是追加在表末尾、默认 `None` 的 typed 字段,不能塞入 `item_json`;schema 同步 migration、表目录、bindings、DTO 和 canonical hash。
- 验证:覆盖“修改标签不新增资源且不换 ID”“同资源两个图层可有不同 override”“复制保留 override 后可独立修改”“清除 override 恢复资源默认值”“刷新与 structured round-trip 不丢覆盖”,并运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、定向 Rust / API / 前端测试。
- 关联:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-module/src/migration.rs`、`src/components/image-editor/useImageCanvasProjectPersistence.ts`、`src/services/image-editor/editorProjectClient.ts`。
## 图片画布项目封面上传失败要有本地展示兜底
- 现象:画布项目已反复打开、保存或操作,但 `/project` 列表卡片仍只显示“项目”占位,没有封面图。