同步共享记忆与专题文档:并发槽身份、素材类型入口、退役同步命令
- PRD §3.10 本地排队一条:删掉已失效的「共用 single-flight 输出槽」理由,改为「本地槽已按精确动作指纹分槽(具备并行能力),本批前端仍按单条在途排队,真并行派发留待下一批」 - 栏目画布底部工具栏入口矩阵 §4a:同上更正,并写明槽身份材料的 8 个字段与旧槽懒迁移 - decision-log:追加三条 2026-09-14 条目(放开手工图片生成本地并发 / 素材类型拆成独立入口 / 同步命令 generate_local_project_asset 退役为仅测试调用) - decision-log:更正同日「图片类生成后台化」条目里已被同批次推翻的两处表述(槽身份、本地排队的理由),并给 2026-09-11 的类型选择器入口决策加「已被取代」标记 - decision-log/pitfalls:把「严格 IPC 桩缺登记新命令」的排障经验记入共享记忆(unhandled rejection 与不相干提示断言的组合症状、桩必须返回数组、形状错会「看着绿」) - 端到端验收用例 S12:更正「用户手动设置 category 已移除」与「面板内删除资源是唯一入口」两处与当前实现不符的描述
This commit is contained in:
@@ -3,6 +3,33 @@
|
||||
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
|
||||
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
|
||||
|
||||
## 2026-09-14 放开 AGC 手工图片生成的本地并发:durable 输出槽身份改为「精确动作指纹」
|
||||
|
||||
- 背景:AGC 手工图片生成的 durable 输出槽身份是 `run_id = slot-<sha256({outputPath, requireSlices})>`,而工具栏除「图标规范」首次外 `outputPath` 恒为 `null`、`requireSlices` 恒 false → 同项目所有图片类生成共用一个槽,第二条并发请求在任何远端 POST 之前就被拒(`external_generation_state.rs` 的 singleflight 报「durable 图片生成输出槽已有请求执行中」)。验收反馈里的高优问题(生图期间不能退出、也不能在生成 A 的过程中生成 B)既要前端可退出,也要后端具备并行能力。
|
||||
- 决策(槽身份 = 精确动作身份):`run_id = slot-<sha256(动作身份材料)>`,材料为 `prompt / output_path / aspect_ratio / image_size / asset_kind / asset_label / replace_existing / require_slices`(`canvas_generation.rs`)。不同 prompt 或素材名 → 不同槽 → 不同进程锁键与不同 `.lock` 文件 → 可同时在途。**不用随机 uuid**:随机身份会让「同一精确动作重放」落到新路径,必须再造一层 action→ledger 索引才能保幂等;用动作指纹让「槽身份 ≡ 精确动作身份」,路径查找即幂等查找。
|
||||
- 决策(幂等不变):同一精确动作 → 同一路径 → 命中已有 prepared/accepted 账本并复用原 `idempotencyKey` / `operationId`,不二次 POST;相同动作并发仍被拒的既有语义保持。
|
||||
- 决策(旧槽账本最小懒迁移):旧槽账本形状可读、不 panic、不 fail-closed;在 durable guard 之后、任何远端 POST 之前,**仅当**旧槽账本的 `agentId / runId / actionFingerprint` 与本次精确动作一致时,把它迁移到新路径(保留 `idempotencyKey` / `operationId` / 状态)并删除旧文件;属于其他动作的旧账本一律不动。旧「固定槽」(`run_id == agent_id`)账本的 fail-closed 拒绝保持原样。
|
||||
- 已知边界:`slice_count` **不进**身份(保留升级前粒度,也是旧账本迁移可行的前提)→ 仅切片数不同的两条图集请求仍共槽、第二条失败关闭;当前所有 standalone 槽的生产调用方都把 `slice_count` 传成 `None`(唯一能传 sliceCount 的是 agent 工具通道,它不走 standalone 槽),该边界当前不可达,但**缺负向用例**。
|
||||
- 前端口径:本批**仍保留单条在途的前端排队**(提交节流),真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`、`.../external_generation_state.rs`。**未改** `/api/external/v1` 契约 / OpenAPI / DTO,未改 `recovery_scan.rs`(身份白名单与孤儿清理语义不变),账本 schema 仍是 v3、字段集不变,只改 `runId` 取值来源。
|
||||
- 验证方式:新增 `standalone_generation_binds_each_exact_request_to_its_own_stable_slot`、`distinct_standalone_actions_hold_independent_durable_output_slots`、`concurrent_distinct_standalone_generations_both_succeed_with_one_post_each`(端到端:两条 `outputPath=None` 的不同动作要求两条 POST 同时到达,各自 poll → read-url → 下载 → 落盘)、`legacy_output_slot_ledger_is_adopted_by_the_same_exact_action_only`。变异验证(已实测):把 `run_id` 退回旧公式 → 4/4 红(含「durable 输出槽身份必须等于该精确动作的身份」与并发用例的「任何远端 POST 前拒绝并发请求」);把懒迁移短路 → 旧账本用例红。定向 `agent::generation::` + `recovery_scan` 95 passed、`direct_runtime media` 195 passed。
|
||||
- 关联文档:[栏目画布底部工具栏入口矩阵](../../technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md)、[踩坑记录](pitfalls.md)。
|
||||
|
||||
## 2026-09-14 「素材类型」从「编辑素材标签」面板拆成独立入口
|
||||
|
||||
- 背景:2026-09-11 的决策把类型选择器放进「编辑素材标签」面板(与标签同一次保存、同一条写入路径,不在工具条另开入口)。真机使用暴露两个问题:① 那排 chip 在弹窗里没有任何标题,读不出是什么;② 类型改动**没有自己的提交动作** —— 点 chip 只写本地 state,落盘发生在底部「添加」上(那是标签语义的按钮),只选类型后直接关弹窗会静默丢失。
|
||||
- 决策(入口独立):新增 `ResourceTypePanel`(标题与 `ariaLabel` 均为「设置素材类型」),**选中即落盘**(一次动作一步完成),`category` 传用户选中值、`tags` 传 `gameCreationAppAssetTags(asset)` 的落盘原值。入口两处:选中卡浮动工具条「素材类型」按钮 + 信息浮层「分类」行的「设置」。
|
||||
- 决策(标签面板去掉类型控件):「编辑素材标签」面板删除 chip 与 `categoryChoice` 分叉,保存时 `category: gameCreationAppAssetPersistedCategory(asset)`;「没碰过分类就回传落盘原值」这条不变量改为**结构性保证**(面板里根本没有类型控件),两条对照用例迁到新面板并保留。
|
||||
- 影响范围:`src/view/project-development/{ResourceTypePanel.tsx,ResourceClassificationPanel.tsx,ResourceInfoPanelView.tsx,resourceCanvasInfoModel.ts,index.tsx}`、`src/features/project-workspace/resourceTypePanel.css`、`tests/{resourceTypePanel.test.tsx,resourceClassificationPanel.test.tsx,projectResourceLiveIntegration.test.tsx,appSurface/project-development.suite.ts}`。不改 `update_local_project_resource_classification` 的入参形状与 CAS 口径、不改读时自愈语义、无后端与 schema 变更。
|
||||
- 验证方式:`resourceTypePanel.test.tsx` 新 12 条 + `resourceClassificationPanel.test.tsx` 19 条 + `appSurface.test.ts` 431 passed。变异验证(已实测):新面板 `category` 改回回传落盘原值 → 「改类型生效」用例红;标签面板改用显示口径 → 对照用例出现 `- "category": "unclassified" / + "category": "ui-interaction"`;去掉浮层判据里的新 state → 点外部串台用例红。
|
||||
- 关联文档:[AGC 资源工作台 V3 端到端验收用例](../../technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md) 的 S12。
|
||||
|
||||
## 2026-09-14 同步命令 `generate_local_project_asset` 退役为「仅测试调用」
|
||||
|
||||
- 决策:图片类生成接线改为 `start_local_project_asset_generation` + `list_local_project_asset_generations` 后,同步命令 `generate_local_project_asset` **已无生产调用方**,只剩 `src-tauri/src/tests/project.rs` 的三条集成用例与 `commands.rs` 的自身单测在调它;因此登记进 `scripts/check-config.mjs` 的 native-only 白名单(该门禁有「App invoke 与白名单互斥」断言,谁重新给它接调用方就必须同时删掉这条白名单项)。
|
||||
- 待办:它是**注册中的可调用 IPC**,一旦被将来代码调用就是一条绕过任务账本、单次阻塞最长 35 分钟的并行生成路径。下一批次应删除它,或改为转调 `start_local_project_asset_generation`(连带迁移那三条集成用例)。
|
||||
- 关联文档:[栏目画布底部工具栏入口矩阵](../../technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md)。
|
||||
|
||||
## 2026-09-14 AGC 壳 Rust 套件改为「分片并行 + 片内串行」,客户端 Rust 关键路径压到 7 分钟以内
|
||||
|
||||
- 背景:`AI game creator shell Rust tests` 是客户端 CI 的关键路径(run 2097 实测 15 分 27 秒)。拆开来看:前置 5 分 30 秒(checkout 10s + `npm ci` 2m45s + Cargo fetch 2m35s)、编译 1m39s、**AGC 壳 bin target 的 2466 条单测串行 507s**、`agent-run` smoke 51s。这 2466 条全在 `apps/ai-game-creator-shell/src-tauri/src/main.rs` 的 bin target 里,一条 `cargo test … -- --test-threads=1` 跑完。
|
||||
@@ -64,7 +91,7 @@
|
||||
## 2026-09-11 素材类型(功能分类)重新提供用户入口;资源卡角标改显示资源类型而非媒体类型
|
||||
|
||||
- 背景:`6bdc8bbd9` 把「分类与标签」面板收敛为纯标签面板,并明确记下「随之的事实是:**「用户手动设置 `category`」这项能力就此移除**」(本文件 2026-09-11 那条以「用户给出的目标样式截图里,这个面板标题是「编辑素材标签」…」开头的条目,其决策一行即该结论)。用户随后要求「用户可以自己变更素材类型」,并追加要求把资源卡右上角角标从"文件/媒体类型"(图片 / 视频 / 文档 …)改成"资源类型"(功能分类中文名)。现状核实:写入链路本来就是完整的 —— `update_local_project_resource_classification`(`src-tauri/src/commands.rs:2162`)→ `update_manifest_asset_classification_at`(`src-tauri/src/project/manifest.rs:1112`)已接受任意合法 `category` 并校验 6 个合法值,**本次不需要改 Rust**;缺的只有 UI 入口与角标口径。本条**只回收「面板不再编辑分类」这一项**,「面板标题仍是「编辑素材标签」、删除资源入口仍在选中工具条」等其余结论不变。
|
||||
- 决策一(入口):类型选择器加进**「编辑素材标签」面板**(与标签同一次保存、同一条写入路径,不新增第二条命令、不在工具条另开第二个入口)。选择器复用 `GAME_CREATION_APP_ASSET_CATEGORIES` × `resourceReferenceCategoryLabel`,不新造第二套中文译名。
|
||||
- 决策一(入口)【已被 2026-09-14「「素材类型」从「编辑素材标签」面板拆成独立入口」取代】:类型选择器加进**「编辑素材标签」面板**(与标签同一次保存、同一条写入路径,不新增第二条命令、不在工具条另开第二个入口)。选择器复用 `GAME_CREATION_APP_ASSET_CATEGORIES` × `resourceReferenceCategoryLabel`,不新造第二套中文译名。
|
||||
- 决策二(两个口径的分叉,本次核心不变量):选择器**读显示口径** `gameCreationAppAssetCategory`(与画布栏目 `projectResourceAssetCategory` 同源,用户看到的选中项就是他看到的栏目);**写回**用 `categoryChoice` 区分用户是否主动选过 —— `null`(没碰过控件)回传 `gameCreationAppAssetPersistedCategory` 的落盘原值,非 `null` 写用户选的值。这条分叉同时满足"只改标签不漂移分类"与"用户选了就写用户的值",两个方向都有对照用例(见验证方式)。
|
||||
- 决策三(角标):资源卡右上角角标改为**资源类型**,取值 `categoryLabels[resource.category]`(栏目与筛选共用的同一份文案),因此角标恒等于该卡所在栏目;媒体类型仍由卡面视觉(图片 / 视频 / 音频 / 文档摘要)表达。只改这一处渲染(`index.tsx` 的 `ResourceCard`),三处面(栏目画布卡、「所有资源」展开态卡、总览缩略摞上铺的卡)自动一致;`projectResourceTypeLabel` 保留给「资源管理面板」的「分类 · 类型」小字与总览摞分列,不再用于角标。
|
||||
- 画布跟随链(核实结论,**无需额外迁移代码**):`useProjectResourceCanvasLayout` 的 `createResourceSignature` 已把 `resource.category` 计入签名,分类变化 → 签名变化 → `reconcileResourceCanvasLayout` 按新的 `section` 归并(`resourceCanvasSectionMapping.resolveResourceCanvasSection`:现行栏目值原样归到资源当前分类,x / y / `manuallyPlaced` 原样保留)→ 需要时写回 sidecar。卡片随分组落到新栏目。
|
||||
@@ -8677,11 +8704,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
|
||||
## 2026-09-14 AGC 图片类生成后台化:提交即返回 + 项目内任务账本 + 本地排队
|
||||
|
||||
- 背景:栏目画布图片类入口原先是一条同步 IPC `generate_local_project_asset`,一次调用最长等 35 分钟;提交期间两块生成浮层把 × / 遮罩 / Esc /「取消」全部锁死,用户既关不掉面板也看不到进度。远端图片类生成共用 single-flight 输出槽(`standalone_platform_art_generation_runtime_context` 按 `outputPath` 派生 `run_id`),并发提交会在远端 POST 之前被拒。
|
||||
- 背景:栏目画布图片类入口原先是一条同步 IPC `generate_local_project_asset`,一次调用最长等 35 分钟;提交期间两块生成浮层把 × / 遮罩 / Esc /「取消」全部锁死,用户既关不掉面板也看不到进度。远端图片类生成当时共用 single-flight 输出槽(`standalone_platform_art_generation_runtime_context` 按 `outputPath` 派生 `run_id`)——**同一批次内该槽身份已改为精确动作指纹,见本文件 2026-09-14「放开 AGC 手工图片生成的本地并发」条目。**
|
||||
- 决策(后台化):新增 `start_local_project_asset_generation`(校验入参 → 落 `queued` 记录 → `tauri::async_runtime::spawn` 派发 → 返回任务记录)与 `list_local_project_asset_generations`(读回账本)。生成本身仍转调既有 `generate_platform_art_asset_with_options_at`(幂等账本、计费、下载校验、manifest/revision 登记、本地预览都不复制),同步命令 `generate_local_project_asset` **保留不动**。
|
||||
- 决策(账本落项目内):`.agent/runtime/asset-generation-tasks/tasks.json`,复用既有 agent runtime sidecar 读写原语(临时文件 + rename);非终态且不在本进程 live 集合里的记录在读取时收口为「上次运行中断」失败,不假装它还在跑;账本上限 50 条按创建时间淘汰。
|
||||
- 决策(阶段文案归后端):`phaseDetail` 由 Rust 拥有(「排队中。」/「正在生成。」/「生成已完成。」/失败原因),前端面板与「生成任务」面板只渲染该字符串,不拼阶段、不做百分比。后端目前没有可播报的中间阶段(生成通道不暴露 job 的远端 `phaseDetail`),所以不伪造「正在处理。」这类前端文案。
|
||||
- 决策(本地排队):第二条提交停在**前端本地队列**(`dispatched=false`,不调用提交 IPC,阶段显示本地排队的「排队中。」),第一条终态后由同一条循环自动补发。判据是「存在 `dispatched && 未终态` 的任务时不派发下一条」——后端 single-flight 拒绝并发,前端排队是它的对应设计而不是重复实现。
|
||||
- 决策(本地排队):第二条提交停在**前端本地队列**(`dispatched=false`,不调用提交 IPC,阶段显示本地排队的「排队中。」),第一条终态后由同一条循环自动补发。判据是「存在 `dispatched && 未终态` 的任务时不派发下一条」——**本批仍保留单条在途的前端排队**:AGC 本地槽此时已按精确动作指纹分槽(具备并行能力),但真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批;所以这里的排队是本批的**提交节流**,不再是「后端拒绝并发」的对应实现。
|
||||
- 决策(面板可关 + 非模态任务面板):两块生成浮层在提交期间放开 × / 遮罩 / Esc,提交按钮旁给「后台运行并关闭」;**关闭 ≠ 取消**(表单的 `await` 挂在该任务的终局上,不是面板生命周期)。新增「生成任务」非模态浮层(不铺遮罩、不做焦点陷阱、**不进** `isResourceCanvasFloatingPanelOpen` / `resourceCanvasHostGenerationPanelOpen` 遮挡判据),入口按钮 `aria-label="生成任务"`;已完成的条目按 `assetId` 复用既有 `pendingResourceFocusRef` 聚焦链定位素材卡。
|
||||
- 影响范围:新增 `apps/ai-game-creator-shell/src-tauri/src/asset_generation_tasks.rs`(+ `main.rs` 注册)、`src/features/resource-canvas/{resourceCanvasAssetGenerationTaskModel.ts,resourceCanvasAssetGenerationQueue.ts,ResourceCanvasAssetGenerationTasksPanelView.tsx}`;改动 `ResourceCanvasAssetGenerationPanelView.tsx` / `ResourceCanvasGenerationPanelView.tsx` / `src/view/project-development/index.tsx`;测试改动 `tests/{resourceCanvasAssetGenerationBackgroundClose.test.tsx,resourceCanvasAssetGenerationQueue.test.ts,resourceCanvasAssetGenerationTasksPanel.test.tsx}`(新增)与 `tests/appSurface/project-development.suite.ts`(把「每个入口一次 `generate_local_project_asset`」改成 `start_local_project_asset_generation` + `list_...` 轮询桩,载荷断言逐字不变)。**未动**:external v1 / OpenAPI、`packages/`、SpacetimeDB、音频入口的 pending-edit 账本语义、生成参数与 IPC 载荷字段名。
|
||||
- 关联文档:`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`(§4 / §4a / §8)、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`(S11a / §7.3)。
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 踩坑与排障记录
|
||||
|
||||
## 2026-09-14 严格 IPC 桩缺登记新命令时,症状可能是「unhandled rejection + 不相干的提示断言」,而不是同一处报错
|
||||
|
||||
- **现象**:`ProjectDevelopmentView` 新增「项目打开时读生成任务账本」(`list_local_project_asset_generations`)后,两个**别的关注点**的用例同时红:`resourceCanvasManualLayout.test.tsx` 报 `AssertionError: expected [ Array(1) ] to deeply equal []`(严格桩把新命令记进 `unexpectedCommands`),并伴随 7 条 `Unhandled Rejection: TypeError: Cannot read properties of undefined (reading 'map')`;`appSurface/project-development.suite.ts` 的「布局读时提示」用例则因为新命令被当成 unexpected invoke 抛错、触发了新的提示条,导致 `queryBySelector('.game-resource-live-notice')` 断言失败。
|
||||
- **原因**:这些用例的 `invoke` 桩是**严格白名单**(未登记即抛错或返回 `undefined`)。新命令在挂载期就被调用,于是:① 桩把未登记命令记进 `unexpectedCommands`/抛错;② 生产代码若对返回值无形状防御,就在 `undefined` 上 `.map` 产生 unhandled rejection。**两条失败都指不到真正的新增调用点**,很容易被误判成各自关注点的回归。
|
||||
- **处理(现行口径)**:① 渲染 `ProjectDevelopmentView` 的桩统一登记 `list_local_project_asset_generations`(返回**数组**,空账本 `[]`;Rust 侧返回 `Vec<AssetGenerationTaskRecord>`,不是 `{ tasks: [] }`);② `unexpectedCommands` 这类门禁**不要放宽**,只登记合法命令;③ 生产代码对 IPC 返回值做形状防御(`Array.isArray` 归一化),IPC 拒绝走既有提示路径,不产生 unhandled rejection(`resourceCanvasAssetGenerationTaskModel.ts` / `resourceCanvasAssetGenerationQueue.ts` / `index.tsx` 的恢复 effect)。
|
||||
- **易错点**:① 桩返回**非数组**时用例可能"看着绿"但同时报 unhandled rejection(实测:把 `[]` 误写成 `{ tasks: [] }` 就是 8 passed + 7 unhandled error),所以判"绿"必须同时看 unhandled 计数;② 新增挂载期 IPC 后要一次性 grep 所有 `ProjectDevelopmentView` 的桩,而不是等 CI 逐个炸;③ 提示条类断言(如「无读时提示」)会把「桩抛错」翻译成「多了一条提示」,排查时先看 unhandled,再看断言。
|
||||
- **关联**:`apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTaskModel.ts`。
|
||||
|
||||
## 2026-09-14 AGC 壳 Rust 套件的「整套单线程」只放宽到「片内单线程」,且分片必须自校验覆盖
|
||||
|
||||
- **现象**:`AI game creator shell Rust tests` 一直是客户端 CI 的关键路径。run 2097 实测 15 分 27 秒,其中 `apps/ai-game-creator-shell/src-tauri/src/main.rs` 的 bin target 单测(2466 条)一条 `cargo test -- --test-threads=1` 串行占 507 秒。
|
||||
|
||||
Reference in New Issue
Block a user