同步资源分类口径的文档:读显示 / 写回两个口径、别名表大小写不敏感

- PRD 的分类取值段落改为「读显示 / 写回两个口径」:写清跨端同构的两个函数名、Agent 投影不许再透传落盘 `category`、面板保存回传落盘原值、别名表大小写不敏感(`"UI"`、`font`)
- 技术方案(AI游戏创作智能体App实施计划)的 manifest `category` 段落同步:`"UI" → ui-interaction`、`font → document`,其余映射不到的 kind 才落 `unclassified`;补上两侧口径函数名
- `pitfalls.md`:把「补别名只影响新登记,不重算 `category`」这条坑更新为已修形态,并写明「同 kind 重登记禁止动 `category`」这条必须保留的不变量;「读时重派生」条目补上两个口径不能混用与跨端必须同构两条
- `pitfalls.md` 新增条目「大写 `UI` / `font` 不在 alias 表 → 8 条真机 UI 资产永远落「待归类」,且自愈救不回」,记录成因链与三层守卫
- `decision-log.md` 新增决策条目:口径拆两半、自愈不下沉到反序列化、别名表大小写不敏感、重登记重派生但不抹掉显式分类,含变异验证与影响范围
- 端到端验收用例文档的 S5 栏目归属补一句:判定「编辑标签后分类有没有被改」必须比 manifest 文件,不能拿自愈后的显示值当落盘值
This commit is contained in:
2026-09-11 18:49:32 +08:00
parent f476eb89e5
commit d9822eca05
5 changed files with 26 additions and 5 deletions
@@ -357,7 +357,7 @@ type UpdateProjectResourceCanvasLayoutResult =
实现状态(2026-09-10):资源画布分区口径是 manifest 资产的功能分类 `category``ui-interaction / character / scene / audio / document / unclassified`)加末尾独立的「项目版本」栏目,不再按扩展名或 mediaType 派生分区。`icon / icon-spritesheet / icon-spec / ui-design` 进入 UI 交互,`character / character-animation` 进入角色与对象,`scene` 进入场景与环境,`sound-effect / background-music / audio` 进入音频,`spec` 与合法 Agent 文本回执进入文档;`image / video / code / publication-material` 以及任务产物、导入附件进入待归类,只登记游戏代码的项目因此有可见栏目与卡片,不再出现四栏全空;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本。扩展名分类器只保留准入与卡片显示类型职责:无法识别的二进制任务产物和附件不进入资源画布。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。既有布局 sidecar 的旧栏目坐标按读时归并继续生效,`x / y / manuallyPlaced` 原样保留。
分类取值优先级2026-09-11 收口:落盘 `category` 是权威值,缺失或非法时按 `assets[].kind` 派生唯一例外是读时自愈——落盘值为 `unclassified` 而该资产 `kind` 能派生出明确的非 `unclassified` 分类时采用派生值,用于修复历史上被系统误写成 `unclassified` 的存量数据(无需迁移脚本、永久自愈);`kind` 派生结果本身就是 `unclassified` 的(`image / video / code / publication-material`)仍信任落盘值。该例外的已知盲区是「资产 `kind` 已能明确分类而落盘值为 `unclassified`」会被读时自愈覆盖,属有意接受的最小覆盖窗口。写入侧必须只产出 canonical kind(画板导出推断同样如此),别名表仅用于兼容存量数据。分类没有用户手动设置的入口:资源卡工具条的「编辑标签」面板只编辑 manifest `assets[].tags`,保存时把读到的 `category` 权威值原样回传
分类取值口径2026-09-11 收口2026-09-11 二次收口统一跨端实现):分「读显示」与「写回」两个口径,两者不是同一个函数。**读显示口径**:落盘 `category` 是权威值,缺失或非法时按 `assets[].kind` 派生唯一例外是读时自愈——落盘值为 `unclassified` 而该资产 `kind` 能派生出明确的非 `unclassified` 分类时采用派生值,用于修复历史上被系统误写成 `unclassified` 的存量数据(无需迁移脚本、永久自愈);`kind` 派生结果本身就是 `unclassified` 的(`image / video / code / publication-material`)仍信任落盘值。该例外的已知盲区是「资产 `kind` 已能明确分类而落盘值为 `unclassified`」会被读时自愈覆盖,属有意接受的最小覆盖窗口。**这个口径必须跨端同构**:TS 侧 `gameCreationAppAssetCategory`、Rust 侧 `game_creation_app_asset_effective_category`,由 `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts` 解析 Rust 源码里的 `EFFECTIVE_CATEGORY_CONTRACT` 决策矩阵交叉钉住;Agent 侧的资源投影(`direct_tool_bridge.rs`)走 Rust 那条,不许再直接透传落盘 `category`——否则同一条资产会出现「UI 显示 UI 交互、Agent 读到待归类」。**写回口径**:`gameCreationAppAssetPersistedCategory`(只做缺失 / 非法兜底,不套自愈),等于 manifest 反序列化后的落盘原值。分类没有用户手动设置的入口:资源卡工具条的「编辑标签」面板只编辑 manifest `assets[].tags`,保存时回传**落盘原值**(不是自愈值),保证「只改标签」不会静默改分类。写入侧必须只产出 canonical kind(画板导出推断同样如此);alias 表**大小写不敏感**,因为 UI 设计资产的现役写入侧写的是大写 `"UI"`,字体上传写 `font`——两者都必须落进明确栏目(`"UI" → ui-design → UI 交互``font → document → 文档`),留在别名表外就会落成 `image → unclassified` 且读时自愈也救不回来
资源身份固定使用 manifest asset ID、正式 version ID、Agent ID + run ID 或已导入资源稳定路径;显示标题、来源文案变化不得改变 `resourceId`,从而避免布局、依赖边、选择和聚焦状态因改名失效。
@@ -15,6 +15,17 @@
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## 2026-09-11 资源分类口径拆成「读显示 / 写回」两个口径,读显示口径下沉到 Rust 并与 TS 同构
- 背景:`category` 此前只有 TS 一处实现(`gameCreationAppAssetCategory` 落盘 `category` 权威 + 读时自愈),Rust 侧反序列化只做「缺失 / 非法回退 `kind` 派生」,Agent 资源投影还直接透传落盘 `asset.category`。于是同一条资产有两套口径:真机 122 条资产里 **55 条** `{kind:"ui", category:"unclassified"}` 在 UI 显示「UI 交互」、Agent 读到「待归类」。另有两条写读路径在真机上继续漂移:①「编辑标签」面板用**读显示**口径取值再原样回传,把自愈值写回落盘,把「只改标签」变成静默改分类(真机同一个 `kind:"ui"` 同时存在 55 条 `unclassified` 与 2 条 `ui-interaction`,后者正是被回写的签名);②UI 设计资产的现役写入侧写的是**大写** `"UI"``ui_editor/resource_bridge.rs``workflow.rs``persistence.rs`)、字体上传写 `font`,两者都不在别名表里 → 落 `image → unclassified`,且派生值本身就是 `unclassified`,读时自愈的触发条件「派生值不是 unclassified」永不成立,**8 条**真机 UI 资产永远归不了类;③`register_local_asset_entry` 的更新分支从不重派生 `category`,同路径重登记换了 `kind` 就留下「新 kind + 旧分类」,且陈旧的非 `unclassified` 值会被无条件信任。
- 决策一(口径拆两半):读时自愈规则**保留**,但拆成两个口径并在两侧同构。**读显示口径**=TS `gameCreationAppAssetCategory` / Rust `game_creation_app_asset_effective_category`(落盘 `category` 权威,唯一例外是落盘 `unclassified``kind` 能派生出明确的非 `unclassified` 分类时采用派生值);**写回口径**=TS `gameCreationAppAssetPersistedCategory`(只做缺失 / 非法兜底,等于 Rust 反序列化后的落盘原值),UI 与 Agent 都走读显示口径,写 manifest 一律走写回口径。两侧不许各写一份:TS 测试直接解析 Rust 源码里的 `EFFECTIVE_CATEGORY_CONTRACT` 决策矩阵与 canonical kind→栏目表逐条对照。
- 决策二(自愈不下沉到反序列化):`GameCreationAppAssetManifestEntry` 的反序列化**刻意不做自愈**,结果就是落盘原值——「编辑标签」面板要靠它回写。自愈只属于读显示口径,Agent 资源投影改走有效分类而不是透传落盘值。这是「一条资产一个口径」的最小实现:口径在函数层统一,落盘值不被读时改写。
- 决策三(别名表大小写不敏感 + 收口 `font`):别名表对 trim 后的小写值查表,`"UI" → ui-design → UI 交互``font → document → 文档`。写在别名表是因为读时自愈救不回来(派生值本身就是 `unclassified`);`game_creation_app.rs` 里那句「现役写入侧仍会写出这些非 canonical 值,必须在这里收口」此前只收口了小写 `ui`,与写入侧实际写的大写矛盾,现按注释本意收口。
- 决策四(重登记重派生,但不抹掉显式分类):`register_local_asset_entry` 只在 `existing.kind != kind` 时重派生 `category``kind` 未变时不动 `category`(落盘分类是权威值)。既修掉「新 kind + 旧分类」的错位,又不让同 kind 重登记吃掉 Agent / 客户端写入的显式分类。
- 影响范围:`server-rs/crates/shared-contracts/src/game_creation_app.rs``packages/shared/src/contracts/gameCreationApp.ts``apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx``apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs``apps/ai-game-creator-shell/src-tauri/src/assets.rs`。跨端契约与 sidecar schema 不变,manifest 字段构成与顺序不变。
- 验证方式:Rust `asset_effective_category_follows_the_shared_contract_matrix` 逐条断言决策矩阵;TS `assetKindCanonicalMapping.test.ts` 解析同一矩阵与 kind→栏目表后喂给 TS 实现对照;`resource_bridge.rs` 的既有用例走真实生产函数 → 真实 `register_local_asset_at(..., "UI", ...)` → 断言落盘 `category`;TS 写侧字面量用例解析写侧源码第 3 个实参;面板新增 `{kind:'ui', category:'unclassified'}` 用例断言回传的 `category` 仍是 `unclassified`。变异验证:把 Rust 有效分类退回「返回落盘值」→ 矩阵用例与 TS 对照用例同时变红;把面板改回 `gameCreationAppAssetCategory` → 面板漂移用例变红;把别名表退回 `value.trim()` → Rust `"UI"` 断言、resource_bridge 端到端与 TS 写侧用例一起变红;去掉更新分支的重派生 → 既有重登记用例的 `category` 断言变红。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(分类取值口径)、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md``docs/project-memory/shared-memory/pitfalls.md`
## 2026-09-11 DirectProject 历史格式切换改为「读侧白名单兼容 + 写侧统一」,不做数据迁移
- 背景:格式切换到 `response_item`#282 / `d3f5d0a35`)当年的决策前提是「Rust 是该历史文件的唯一写入方」+「存量测试数据由测试手动清理」,据此在技术方案里写了「不迁移旧 `{role,content}` 行」「不会对旧格式做迁移或兼容」。这两个前提在真实用户项目上都不成立:切换前 DirectProject 主对话由通用对话写入器写到同一份 `.agent/conversations/project.jsonl`,存量项目整份都是旧行(实测 27 个项目里 25 个是纯旧格式),而读侧只认 `{"type":"response_item","payload":…}`,于是这些项目的 DirectProject 回合全部失败在「DirectProject 历史记录类型无效」;写侧也没真正唯一——`agent/direct_tools_mcp.rs``conversation.record_codex_response` 还在往同一份文件插旧行(它本来就有自己的 journal `.agent/conversations/codex-responses.jsonl`),历史就算修好,Codex 一调该工具就会再次毒化。同时这条失败没有专门 hint,落进默认的「Codex 未完成本轮代码修改,请检查运行时配置后重试」,且 `retryable=true`,用户看到「可直接重试」但重试永远不会过(同一份历史)。
+12 -2
View File
@@ -5196,7 +5196,7 @@
- 现象:真机 `What do u wanna do kitten` 的 66 项资源里 58 项落「待归类」,其中 57 项是 `kind:"ui"` 的 UI 资产,本该在「UI 交互」。
- 成因链:`ui` 不在 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS` 里、也没有别名,于是走 `canonicalGameCreationAppAssetKind``image` 兜底,再经 `image → unclassified` 被误分到「待归类」。它并不是历史遗留值——`assets.rs:1656``infer_canvas_export_asset_kind`(画板导出导入)**现在仍在写出** `ui` / `animation` / `asset`
- **关键陷阱**:补别名只影响「今后新登记」的资产。`register_local_asset_entry``assets.rs:1748-1757`)命中同 `localPath` 的既有资产时只覆盖 `kind` / `media_type` / `source`**不重算 `category`**;而读取侧 `gameCreationAppAssetCategory` 优先信任落盘 `category`、只在它缺失或非法时才按 `kind` 派生。所以这 57 条的落盘 `category: "unclassified"` 会一直有效,重导入也自愈不了。
- **关键陷阱2026-09-11 已修)**:补别名只影响「今后新登记」的资产。`register_local_asset_entry``assets.rs`)命中同 `localPath` 的既有资产时,旧实现只覆盖 `kind` / `media_type` / `source`**不重算 `category`**;而读取侧优先信任落盘 `category`、只在它缺失或非法时才按 `kind` 派生。所以这 57 条的落盘 `category: "unclassified"` 会一直有效,重导入也自愈不了。**修法与必须保留的不变量**:更新分支只在 `kind` 真的变化时才重派生 `category`,否则「同路径重登记且 kind 变了」会留下「新 kind + 旧分类」的错位,而陈旧的非 `unclassified` 值会被无条件信任、自愈也不触发;反过来同 kind 重登记**禁止**动 `category`,落盘分类是权威值,被 `register_local_asset_keeps_explicit_category_when_kind_is_unchanged` 钉住。
- 为什么读取侧要信任落盘值:存在用户手动改分类的正式链路 `update_manifest_asset_classification_at``project/manifest.rs:1110`),读时无条件重派生会吃掉用户的手动设置。
- 根治选项(需产品拍板):① 读时把 `unclassified` 当作「未设置」再按 kind 派生(简单但失去"我就是要 unclassified"的表达力);② 一次性回填这 57 条(保留人工设置语义,需迁移脚本);③ 只改写入侧让新素材 canonical 化(治不了存量)。
- 另一处必须成对维护:别名表有**两份实现**——TS 侧 `packages/shared/src/contracts/gameCreationApp.ts``GAME_CREATION_APP_LEGACY_ASSET_KINDS`(读投影用)与 Rust 侧 `server-rs/crates/shared-contracts/src/game_creation_app.rs``canonical_game_creation_app_asset_kind`(写入侧按 kind 派生 category 用)。只改一边就会让落盘 category 与读侧栏目互相矛盾。交叉守卫见 `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts`(直接解析 Rust 源码比对)。
@@ -5206,13 +5206,23 @@
## 读时重派生:为什么可以覆盖落盘 category,以及它的唯一盲区(2026-09-10)
- 规则(已拍板落地):`gameCreationAppAssetCategory` 在「落盘 `category === 'unclassified'` **且** 该资产 `kind` 能派生出明确的非 `unclassified` 分类」时采用派生值,其余情况信任落盘值。
- 为什么需要这条:`register_local_asset_entry``assets.rs`)命中同 `localPath` 的既有资产时只覆盖 `kind` / `media_type` / `source`**重算 `category`**;于是历史上被写成 `unclassified` 的资产(典型是 `kind:"ui"` 因不在 canonical 目录而落到 `image → unclassified`)在补齐别名后**不会自愈**。选读时重派生而不是写迁移脚本:不需要迁移、且永久自愈(任何历史上被系统错判成 unclassified 的都会自动归位)。真机验证:`What do u wanna do kitten` 的待归类从 58 降到 1(只剩 code 类 `game-entry`),UI 交互从 2 升到 59。
- 为什么需要这条:`register_local_asset_entry``assets.rs`)命中同 `localPath` 的既有资产时**只在 `kind` 变化时**重算 `category`2026-09-11 起);所以历史上被写成 `unclassified` 的资产(典型是 `kind:"ui"` / `kind:"UI"` 因不在 canonical 目录而落到 `image → unclassified`)在补齐别名后**不会自愈**。选读时重派生而不是写迁移脚本:不需要迁移、且永久自愈(任何历史上被系统错判成 unclassified 的都会自动归位)。真机验证:`What do u wanna do kitten` 的待归类从 58 降到 1(只剩 code 类 `game-entry`),UI 交互从 2 升到 59。
- **两个口径不能混用(2026-09-11 收口)**`gameCreationAppAssetCategory` / `game_creation_app_asset_effective_category` 是**读显示**口径;写回 manifest 必须用 `gameCreationAppAssetPersistedCategory`(只做缺失 / 非法兜底),它等于 Rust 反序列化后的落盘原值。把自愈值回写会把「只改标签」变成静默改分类——真机上同一条 `kind:"ui"` 资产因此同时存在 `unclassified``ui-interaction` 两种落盘值。
- **跨端必须同构**Agent 侧资源投影(`direct_tool_bridge.rs`)走 Rust 的 `game_creation_app_asset_effective_category`,不许直接透传落盘 `category`;两侧不一致时同一条资产会出现「UI 显示 UI 交互、Agent 读到待归类」(真机 55 条)。守卫是 `assetKindCanonicalMapping.test.ts` 解析 Rust 源码里的 `EFFECTIVE_CATEGORY_CONTRACT` 决策矩阵。
- 为什么可以覆盖落盘值:落盘 `category` 的权威性来自「用户可在分类与标签面板手动设置」(`update_manifest_asset_classification_at``project/manifest.rs`)。收窄条件把覆盖窗口压到最小——只有当落盘值是 `unclassified`(即"没有明确分类")时才覆盖。
- **唯一盲区**:用户**手动**把一个 kind 已能明确分类的资产设成「待归类」时,该手动值会被覆盖。这是有意接受的取舍:「手动设为待归类」意图边缘,且 kind 已经表达了分类;而漏掉这条规则,所有历史误判都无法自愈。若将来产品需要"显式待归类",应改成在 manifest 里区分"未设置"与"显式 unclassified"(例如 `category` 缺省 vs 显式写入),而不是取消本条规则。
- 不受影响:`image` / `video` / `code` / `publication-material` 的 canonical 分类本身就是 `unclassified`,派生结果等于落盘值,规则不触发(已用断言钉住)。
- 写入侧已同步 canonical 化:`infer_canvas_export_asset_kind``assets.rs`)现在直接产出 canonical kind`ui → ui-design``animation → character-animation``asset → image`),不再依赖别名表兜底;`canvas_export_asset_kind_is_always_canonical` 用例逐分支钉死,别名表从此只承担存量兼容。
- 关联:`packages/shared/src/contracts/gameCreationApp.ts``gameCreationAppAssetCategory``apps/ai-game-creator-shell/src-tauri/src/assets.rs``apps/ai-game-creator-shell/tests/resourceCardPreviewRealManifest.test.ts`
## 大写 `UI` / `font` 不在 alias 表 → 8 条真机 UI 资产永远落「待归类」,且自愈救不回(2026-09-11)
- 现象:真机 `What do u wanna do kitten` 有 8 条资产的 `kind` 是**大写** `"UI"``mediaType``application/json``localPath``ui/UI 设计 N.json`,永远停在「待归类」。
- 成因链:写入侧写大写——`ui_editor/resource_bridge.rs``register_local_asset_at(root, &relative_path, "UI", "application/json", …)``workflow.rs` / `persistence.rs` 同;而 alias 表只有小写 `"ui" => "ui-design"`,于是 `"UI"` 落到 `canonical_game_creation_app_asset_kind``image` 兜底 → `image → unclassified`
- **为什么读时自愈救不回来**:自愈规则的前提是「派生值不是 unclassified」,而 `"UI"` 的派生值**就是** `unclassified`,规则永不触发。所以只能在 alias 表收口——别名表必须**大小写不敏感**(`UI` / `ui` / `ui-prototype` 都要落 `ui-design`),并把 `font``ttf / otf / woff / woff2` 上传登记的 kind,见 `commands.rs``register_local_asset_entry(root, &relative_path, "font", …)`)一并补进别名表落 `document`
- 守卫三层:Rust `asset_category_mapping_covers_every_canonical_kind` 逐条断言(旧断言曾把 `"UI" → unclassified` 钉死,正是这条 bug 的护栏反向加固);`ui_editor/resource_bridge.rs``bridge_is_idempotent_and_installs_source_image` 走真实生产函数 → 真实写入 → 断言落盘 `category`TS `assetKindCanonicalMapping.test.ts` 的「写侧 kind 字面量 → 分类」直接解析写侧源码的第 3 个实参,写点换个新字面量就会红。
- 关联:`server-rs/crates/shared-contracts/src/game_creation_app.rs``packages/shared/src/contracts/gameCreationApp.ts``apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs``apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts`
## AGC 资源搜索栏改成「临时叫出」的浮层,工具条带与它的下移逻辑一并撤掉(2026-09-11)
- 现象:资源搜索框与栏目标题栏在真机上仍然重合;分页态标题栏右端的「资源总览」(回到资源总览)按钮**点不动**。
@@ -22,7 +22,7 @@
## 2026-09-09 manifest 资源功能分类与自定义标签(数据层)
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类由 `GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 按 canonical kind 穷举映射:`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction``character / character-animation → character``scene → scene``audio / sound-effect / background-music → audio``document / spec → document``image / video / code / publication-material → unclassified`;映射不到 canonical kind 的原始 kind(例如 `font``UI``test`)经既有 `canonicalGameCreationAppAssetKind``image` 后同样`unclassified`
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类由 `GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 按 canonical kind 穷举映射:`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction``character / character-animation → character``scene → scene``audio / sound-effect / background-music → audio``document / spec → document``image / video / code / publication-material → unclassified`alias 表**大小写不敏感**(UI 设计资产的现役写入侧写的是大写 `"UI"``font` 是字体上传登记的 kind),`"UI" → ui-design → ui-interaction``font → document → document`;其余映射不到 canonical kind 的原始 kind(例如 `test``canonicalGameCreationAppAssetKind``image``unclassified`读显示口径在 TS`gameCreationAppAssetCategory`)与 Rust`game_creation_app_asset_effective_category`)同构,写回 manifest 必须用落盘原值 `gameCreationAppAssetPersistedCategory`
历史 manifest 缺少字段时读取按 `kind` 派生分类、`tags` 取空数组;显式写入的合法分类原样保留,未知分类字符串按前向兼容退回 `kind` 派生。新建资源条目写 `kind` 派生默认值,更新既有条目保留已有 `category` / `tags`,不覆盖用户值。`tags` 归一化(trim、去空、去重)只以纯函数交付,本轮不接入写入路径。本轮不做任何 UI(画布功能画布与筛选、@ 面板标签筛选、标签管理面板)、数据迁移脚本、标签库聚合派生与 Agent 检索工具。
@@ -39,7 +39,7 @@
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
| --- | --- | --- | --- | --- | --- |
| **S4** 首轮生成 | 在右侧 Supervisor 对话里发起首轮素材生成 | 生成由**对话启动**(不用画布按钮);素材写入对应栏目并登记进 manifest;未生成的素材不显示为可编辑对象 | L25、L94、L500;飞书 §4 首轮生成 | 对话完成后 `.agent/manifest.json``assets[]` 长度增加;新卡 `data-resource-card-id``asset:<id>` 形态 | 「首轮进度投影」只在 Issue 交付范围的"应该"清单出现、**无编码级判据**;现有可见性由底部 Agent 状态栏承载 |
| **S5** 栏目归属 | 看资源总览的栏目缩略卡与各栏目卡片 | 固定 7 栏:`UI 交互 → 角色与对象 → 场景与环境 → 音频 → 文档 → 待归类 → 项目版本`;分区口径是 manifest `assets[].category`,前端交互不能改分类 | L50、L56、L324、L358360、L539 | `[...document.querySelectorAll('.game-resource-book-thumbnail[data-resource-book-category]')].map(e=>e.dataset.resourceBookCategory)`;再数每栏 `.game-resource-card` 数量 | **读时自愈**:落盘 `unclassified``kind` 能明确分类时按派生值显示(L360)。真机 57 条落盘 `unclassified` 在 UI 上应显示到正确栏目,**判定必须看 UI 计数,不能看 manifest 文件计数** |
| **S5** 栏目归属 | 看资源总览的栏目缩略卡与各栏目卡片 | 固定 7 栏:`UI 交互 → 角色与对象 → 场景与环境 → 音频 → 文档 → 待归类 → 项目版本`;分区口径是 manifest `assets[].category`,前端交互不能改分类 | L50、L56、L324、L358360、L539 | `[...document.querySelectorAll('.game-resource-book-thumbnail[data-resource-book-category]')].map(e=>e.dataset.resourceBookCategory)`;再数每栏 `.game-resource-card` 数量 | **读时自愈**:落盘 `unclassified``kind` 能明确分类时按派生值显示(L360)。真机 57 条落盘 `unclassified` 在 UI 上应显示到正确栏目,**判定必须看 UI 计数,不能看 manifest 文件计数**。⚠ 反过来**不能**把自愈值当落盘值:判定「编辑标签后分类有没有被改」必须比 manifest 文件(自愈只在读显示层,写回用 `gameCreationAppAssetPersistedCategory` |
| **S6** 卡片预览台账 | 逐栏目看卡片是否显示真实主体 | 图片与安全 SVG 直接显示主体并保留透明棋盘底;视频、音频、文档、项目版本用稳定固定卡;失败显示类型占位、不挂破图、不降级为项目外 URL | L55、L6162、**L67**、L347349 | 见 5.1 的四条脚本:区分"没读 / 读失败 / 解码为空 / 不适用" | **可见卡停 `idle` 的真 bug 已修但未提交**`useProjectResourceCardPreviews.ts`);未用新构建时会出现假失败 |
### C 阶段 · 资源管理闭环