diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs
index f24099dd0..e93c619f2 100644
--- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs
+++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs
@@ -690,13 +690,6 @@ fn game_chat_art_receipt_wakes_the_same_code_prototype_run() {
let root = temporary.path().join("project");
let (mut main, mut child, delegation_id) =
game_chat_main_art_child_fixture(&root, "art-asset-plan", &["core-spritesheet"]);
- main.status = "running".to_string();
- main.phase = "waiting-for-delegate-receipts".to_string();
- main.current_action = "等待临时美术 Agent 回执".to_string();
- main.waiting_on = "art-asset-plan 完成并回执".to_string();
- append_game_creator_agent_runtime_task(&root, &main).expect("persist waiting main task");
- write_game_creator_agent_runtime_state(&root, &main).expect("persist waiting main runtime");
-
child.status = "completed".to_string();
child.phase = "completed".to_string();
child.current_action = "已写入临时美术资产".to_string();
@@ -763,6 +756,12 @@ fn game_chat_art_receipt_wakes_the_same_code_prototype_run() {
assert_eq!(duplicate.status, "failed", "{duplicate:?}");
assert!(duplicate.summary.contains("最多委派一次"));
+ main.status = "running".to_string();
+ main.phase = "waiting-for-delegate-receipts".to_string();
+ main.current_action = "等待临时美术 Agent 回执".to_string();
+ main.waiting_on = "art-asset-plan 完成并回执".to_string();
+ append_game_creator_agent_runtime_task(&root, &main).expect("persist waiting main task");
+ write_game_creator_agent_runtime_state(&root, &main).expect("persist waiting main runtime");
let waiting = read_latest_game_creator_agent_runtime_task_by_run_id(
&root,
"code-prototype",
diff --git a/apps/ai-game-creator-shell/src-tauri/src/resource_inspect.rs b/apps/ai-game-creator-shell/src-tauri/src/resource_inspect.rs
index 61bd5ea04..06bd9a3be 100644
--- a/apps/ai-game-creator-shell/src-tauri/src/resource_inspect.rs
+++ b/apps/ai-game-creator-shell/src-tauri/src/resource_inspect.rs
@@ -125,7 +125,7 @@ pub(crate) fn load_local_project_text_preview(
let normalized = normalize_relative_path(relative_path.trim())?;
reject_sensitive_project_file_read(&normalized)?;
let media_type = project_text_media_type(&normalized)
- .ok_or_else(|| "文档预览只支持 Markdown、文本、JSON、YAML 和 TOML".to_string())?;
+ .ok_or_else(|| "文档预览只支持现役文本与代码扩展名".to_string())?;
let bytes = read_stable_project_resource(
root,
&normalized,
@@ -430,13 +430,19 @@ mod tests {
fs::write(root.path().join("docs/design.md"), "# 设计\n\n正文").expect("markdown");
fs::write(root.path().join("docs/legacy.txt"), [0xff, 0xfe]).expect("legacy text");
fs::write(root.path().join("docs/page.html"), "
unsafe
").expect("html");
+ fs::write(root.path().join("docs/archive.bin"), "not a text resource")
+ .expect("unsupported extension");
let preview =
load_local_project_text_preview(root.path(), "docs/design.md").expect("load markdown");
assert_eq!(preview.media_type, "text/markdown");
assert!(preview.content.contains("正文"));
assert!(load_local_project_text_preview(root.path(), "docs/legacy.txt").is_err());
- assert!(load_local_project_text_preview(root.path(), "docs/page.html").is_err());
+ let html_preview =
+ load_local_project_text_preview(root.path(), "docs/page.html").expect("load HTML");
+ assert_eq!(html_preview.media_type, "text/html");
+ assert_eq!(html_preview.content, "unsafe
");
+ assert!(load_local_project_text_preview(root.path(), "docs/archive.bin").is_err());
}
#[test]
diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts
index d10585208..72fac84b3 100644
--- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts
+++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts
@@ -309,9 +309,6 @@ export function registerClientHomeTests() {
),
).toHaveLength(2);
});
- const manifestReadsBeforeEvent = invoke.mock.calls.filter(
- ([command]) => command === 'get_local_game_manifest',
- ).length;
const inspectionsBeforeEvent = invoke.mock.calls.filter(
([command]) => command === 'inspect_local_project_directory',
).length;
diff --git a/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts b/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
index 7f98d4175..13f929ef2 100644
--- a/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
+++ b/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
@@ -114,6 +114,10 @@ describe('项目资源投影', () => {
id: 'agent-result:design-foundation:final-run',
category: 'document',
},
+ {
+ id: 'task:audio-asset-plan:audio/unregistered-bgm.wav',
+ category: 'audio',
+ },
{ id: 'version:version-1', category: 'version' },
]),
);
@@ -121,7 +125,6 @@ describe('项目资源投影', () => {
resources.some(({ id }) =>
[
'task:design-foundation:build/game.bundle',
- 'task:audio-asset-plan:audio/unregistered-bgm.wav',
'asset:unknown-binary',
'attachment:imports/archive.zip',
].includes(id),
diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md
index 113dd46a3..029739fe0 100644
--- a/docs/project-memory/shared-memory/decision-log.md
+++ b/docs/project-memory/shared-memory/decision-log.md
@@ -23,6 +23,7 @@
- 能力分流:SVG/文本/代码走结构化 LLM 内容派生与格式复核;视频使用源视频稳定引用;音效/BGM 因现役接口无源音频字段,固定定义为基于源语义的派生重制,不能宣称波形级编辑;项目版本追加子版本。
- 信任边界:前端能力提示不是授权事实,Tauri 在提交前按 manifest、已完成任务或上传登记重新核验来源并复核项目 revision / 源内容摘要。远端生成继续使用稳定幂等身份、`202` 轮询与稳定 object/resource/asset 身份,签名 URL 不落 manifest。
- 队列与结果边界:登录态媒体派生统一使用 `game-creator-resource-editor` 专用消费身份;图片 refine、视频、音效和 BGM 复用同一逻辑请求的稳定 `Idempotency-Key`,路由不得丢弃。完成结果只返回裁剪后的稳定 object/resource/asset 引用与必要媒体元数据,不暴露 provider、worker、队列内部字段或临时签名 URL。
+- 验证边界:资源投影回归必须把已完成任务在 `artifacts` 中明确登记的音频归入音乐音效资源,同时继续排除未登记音频和未知二进制;画布工具栏数量断言必须与全部现役工具清单同步;文本预览回归必须覆盖 HTML 与现役代码扩展名;委派回执测试应在显式进入等待态前完成同 action 幂等断言,避免后台 parent-wake 与断言竞争。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
## 2026-08-10 客户端素材画布临时收敛为现有图片非破坏性编辑
diff --git a/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx b/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx
index 280273eee..ed6b97b37 100644
--- a/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx
+++ b/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx
@@ -73,7 +73,7 @@ describe('ImageCanvasBottomToolbarView', () => {
);
expect(
toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-group'),
- ).toHaveLength(11);
+ ).toHaveLength(12);
expect(
toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-divider'),
).toHaveLength(2);
diff --git a/src/components/image-editor/ImageCanvasEditorModel.ts b/src/components/image-editor/ImageCanvasEditorModel.ts
index 1c905b381..7e7d8d47b 100644
--- a/src/components/image-editor/ImageCanvasEditorModel.ts
+++ b/src/components/image-editor/ImageCanvasEditorModel.ts
@@ -4,6 +4,7 @@ import {
resolveSnappedItemPosition,
viewportScaleToCanvasDisplayScale,
} from '@genarrative/image-canvas-core';
+
import {
EDITOR_SOUND_EFFECT_MODEL,
SOUND_EFFECT_DURATION_MAX_SECONDS,