Commit Graph

3495 Commits

Author SHA1 Message Date
suzmii dc243b0d57 资源工作台「替换素材」候选窗接上预览管线,补齐候选排序、禁用态可读性与删除素材入口
Project CI / Repository checks (pull_request) Successful in 2m48s
Project CI / Frontend tests (pull_request) Successful in 3m14s
Project CI / Backend tests (pull_request) Successful in 7m49s
Project CI / Native shell tests (pull_request) Successful in 18m27s
- 「替换素材」候选卡缩略图接入资源卡同一条预览管线:新增 ResourcePreviewMedia,复用同一份预览身份、同一条队列与 LRU 缓存,不再只渲染类型占位图标
- 预览 hook 暴露 sweepVisiblePreviews:弹窗内容挂在 portal 下、不在资源画本 observer 的 root 子树内,改为在「弹窗打开」与「候选区滚动」两个几何变化点显式按同一套视口几何判据兜底放行,不新增第二条加载通路
- 候选排序抽出纯函数 sortResourceReplacementCandidates:可替换(无硬门禁失败)的排最前,其余按后端原名次跟随且仍渲染但禁用;判据与弹窗禁用状态同源,不会出现「排最前却点不动」
- 抽出纯函数 projectResourcesByManifestAssetId,让弹窗素材映射与候选缩略图共用同一份资源投影口径
- 共享选择弹窗的禁用态不再整卡压透明度:变灰只压在缩略图与名称上,「分类不同」这类禁用原因保持全对比度可读(网页端美术画布不传禁用原因,视觉逐字不变)
- 资源卡选中工具条末位新增「删除素材」,前置共享工具条同一套分隔线与前面的非破坏性动作隔开,只在 manifestAssetId 存在时才渲染
- 删除流程抽成 useResourceAssetDeleteFlow(读引用信息 → ResourceAssetDeleteDialog 二次确认 → delete_local_project_asset 的 deleteReferencedVersions 三分支),工具条与面板共用同一份实现,没有第二套删除
- 「编辑素材标签」面板底部只留右下角一个「添加」:点它把输入框内容(含未按回车的尾巴)落成 pill 并按现有写入路径保存,分类仍原样回传落盘原值;删除素材/取消/保存标签三个按钮移除,关闭仍走头部 ×
- 标签 pill 内的删除标签按钮保持不变
- 更新与补充用例:候选排序、排序与禁用同源、候选缩略图真链路与滚动兜底放行、工具条删除入口位置与 IPC 载荷、连带删除分支、面板无删除入口与「添加」语义、禁用态可读性类名契约、工具条动作清单
2026-09-12 15:30:38 +08:00
suzmii 33a85a67b8 Merge remote-tracking branch 'origin/master' into fix/agc-manifest-revision-cas
Project CI / Repository checks (pull_request) Successful in 3m11s
Project CI / Frontend tests (pull_request) Successful in 3m14s
Project CI / Backend tests (pull_request) Successful in 5m38s
Project CI / Native shell tests (pull_request) Successful in 18m27s
2026-09-12 14:44:15 +08:00
suzmii c76ecd07ab 修正合并带入的 rustfmt 漂移与换行符,恢复 Repository checks
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 3m3s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Native shell tests (pull_request) Successful in 20m2s
- cargo fmt 三个由 feat/agc-resource-replace 带入且从未跑过 CI 的 Rust 文件:version_resource_replacement.rs、manifest/version_binding_rewrite_tests.rs、tests/version_resource_replacement.rs
- 四个文件统一为 LF,修掉 rustfmt 报的 Incorrect newline style:shared-contracts/game_creation_app.rs、view/project-development/index.tsx、decision-log.md、技术方案文档
- 本地按 CI 链复跑:encoding / npm-workspaces / spacetime-schema / production-ops / preview-deployer / maintenance-page / rustfmt / eslint / typecheck 全部 exit 0
- check:git-hooks 仍红,但红在已知 Windows EBUSY 抖动(rmdir 临时 repo 被占用),Linux CI 不受影响
2026-09-12 14:36:23 +08:00
lhk229 013c756b9e Merge pull request 'CI 偶发失败修复:独立 crate 的 crates.io 预热缺口与两条用例自身缺陷' (#328) from fix/ci-flaky-and-crates-io-fetch into master
Project CI / Repository checks (push) Successful in 2m42s
Project CI / Frontend tests (push) Successful in 3m28s
Project CI / Backend tests (push) Successful in 7m1s
Project CI / Native shell tests (push) Successful in 20m19s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/328
Reviewed-by: 孔令弘 <ink29535@proton.me>
2026-09-12 14:23:46 +08:00
suzmii e4ee6c2be8 合并 feat/agc-resource-replace:把「替换素材」接进资源工作台
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m14s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Native shell tests (pull_request) Successful in 20m52s
- 解冲突 project/manifest.rs:保留版本写入公共体 write_manifest_locked_with_version_guard(版本只追加 + 绑定改写放行),并把 schema 校验并入该公共体,两侧语义都不丢
- 解冲突 shared-contracts:取当前分支的读显示口径 game_creation_app_asset_effective_category(含与 TS 交叉钉住的 EFFECTIVE_CATEGORY_CONTRACT 矩阵),同步改写 version_resource_replacement 对旧函数名的调用
- 解冲突 index.tsx:两处均为纯 import 追加,两侧都保留
- 解冲突 decision-log 与技术方案文档:两侧条目都是追加,全部保留
- 随之进入本分支:版本绑定改写窄放行通道、直接替换命令与其兼容性判据、资源卡工具条「替换素材」入口
2026-09-12 14:19:03 +08:00
lhk229 e602d3b420 Merge branch 'master' into fix/ci-flaky-and-crates-io-fetch
Project CI / Repository checks (pull_request) Successful in 2m38s
Project CI / Frontend tests (pull_request) Successful in 3m23s
Project CI / Backend tests (pull_request) Successful in 13m24s
Project CI / Native shell tests (pull_request) Successful in 23m40s
2026-09-12 13:56:23 +08:00
suzmii 3015370286 补 C7 版本切换「重载当前预览」验收测试
- resourceVersionSwitch 新增用例:运行模块内切换版本必须既上报宿主又触发 onPlay 重载预览
- 覆盖验收用例 S16 的「并重载当前预览」条款(此前版本入口只测了记录层与高亮,重载没有守卫)
- 变异验证:摘掉 selectActiveVersion 里的 onPlay 调用后,该用例以 expected "spy" to be called at least once 失败
- 定向验证:resourceVersionSwitch 4 passed
2026-09-12 13:34:32 +08:00
suzmii 028b20a590 修复清单快照误判:CAS 判据面收窄为「资源 + 版本」,预览起停不再触发用户可见拒收
- projectResourceLiveUpdateModel 新增 manifestCasFingerprint,按 { assets, versions } 判定同版本号冲突
- 同 revision 且判据面一致时,仅非 CAS 账目状态(preview 运行地址/端口、任务进度、项目名)变化改为 accepted 并让内容落地,revision 不推不退
- 判据面变化仍判 revision-conflict,保留「改了被保护内容却没推 revision」的可观测信号,不吞真实缺陷
- 新增回归用例:同 revision 仅预览状态变化必须被接受;同 revision 资产变化必须仍被拒收
- 变异验证:把判据改回整份 JSON 指纹后,新用例以 expected 'revision-conflict' to be 'accepted' 失败
- 定向验证:projectResourceLiveUpdateModel 17 passed、workspaceLauncherManifestMerge 5 passed、appSurface 413 passed、npm run typecheck exit 0、npm run check:encoding 4399 files passed、git diff --check 干净
- 同步 decision-log 与 pitfalls 记录 CAS 判据面契约与本次取证
2026-09-12 13:30:18 +08:00
suzmii 38cd688d71 Merge remote-tracking branch 'origin/master' into fix/agc-all-resources-zoom
Project CI / Repository checks (pull_request) Successful in 3m3s
Project CI / Frontend tests (pull_request) Successful in 3m41s
Project CI / Backend tests (pull_request) Successful in 6m37s
Project CI / Native shell tests (pull_request) Successful in 17m17s
2026-09-12 12:25:36 +08:00
suzmii 349c9d80ce 修复「所有资源」页面缩放读取错误视口
展开态缩放、滚轮、平移和卡片拖动改读 all 专用内存视口 ref

避免复用普通栏目页视口导致按钮按下后倍率被旧状态覆盖

补充所有资源连续缩放 appSurface 回归断言,并同步更新项目决策与踩坑记录
2026-09-12 12:25:27 +08:00
k88936 e50d678411 Merge pull request '修复[dev]下agc启动时长时间白屏' (#333) from boot-time-diagnosis into master
Project CI / Repository checks (push) Successful in 2m47s
Project CI / Frontend tests (push) Successful in 3m19s
Project CI / Backend tests (push) Successful in 7m46s
Project CI / Native shell tests (push) Successful in 18m17s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Reviewed-on: #333
2026-09-12 11:19:25 +08:00
k88936 727794bc95 Merge branch 'master' into boot-time-diagnosis
Project CI / Repository checks (pull_request) Successful in 2m43s
Project CI / Frontend tests (pull_request) Successful in 3m12s
Project CI / Backend tests (pull_request) Successful in 7m13s
Project CI / Native shell tests (pull_request) Successful in 20m20s
2026-09-12 10:58:33 +08:00
k88936 cc9b95ddef 优化调试配置:提升SHA-256计算性能,避免调试构建阻塞
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Native shell tests (pull_request) Successful in 20m20s
2026-09-12 10:54:51 +08:00
suzmii 3f7e41e466 「所有资源」展开态改回平铺画布:不再为每个栏目渲染标题栏
Project CI / Repository checks (pull_request) Successful in 2m50s
Project CI / Frontend tests (pull_request) Successful in 3m19s
Project CI / Backend tests (pull_request) Successful in 7m23s
Project CI / Native shell tests (pull_request) Successful in 19m4s
展开态真实栏目不再渲染栏目标题栏,整个场景只留 all 那一条钉在视口上、带「资源总览」收起入口的标题栏

原因是分带只为有可见资源的栏目生成,空栏目拿不到标题栏矩形后全部回落到世界原点叠成一摞(空项目必现,现场是 7 条)

ResourceBookSceneCategoryPlan 新增 titlebar 字段,渲染层按它决定是否产出标题栏节点

分带去掉标题栏横带的高度预留与矩形换算,删除 RESOURCE_BOOK_ALL_BAND_TITLE_HEIGHT 与 resourceBookAllTitlebarRects

拖动写回口径不变(resourceBookAllBandLocalPoint 一行未改),零 CSS 变更

resourceBookLayout.test 与 appSurface 两条「所有资源」用例加反向守卫;变异验证改回旧行为时两条同时红(got 7)

decision-log 与 pitfalls 记这次倒因为「缺一路坐标输入时不要静默回落到原点」
2026-09-12 09:38:03 +08:00
suzmii 5b62d1e4ef 修复资源登记把任意图片写成 ui 类型
Project CI / Repository checks (pull_request) Successful in 3m17s
Project CI / Frontend tests (pull_request) Successful in 3m55s
Project CI / Backend tests (pull_request) Successful in 6m29s
Project CI / Native shell tests (pull_request) Successful in 17m16s
停用本地导入图片的 ui 默认 kind:agent_local_project_file_type 的图片扩展名分支改写 canonical image(派生 unclassified,落「待归类」),并注明不许把 ui 当图片默认值及其不可恢复原因——读时自愈只在落盘 category 为 unclassified 时触发,kind 本身写错时自愈只会把错值放大成 ui-interaction
账户素材导入改用平台真实类型:新增 imported_platform_asset_kind,账户素材库记录自带的 assetKind 不再被常量 ui 顶掉,缺失才退回中性 image
平台素材导入改用响应里的 assetKind:网页项目画布与平台素材导入同样走 imported_platform_asset_kind,不再写死 ui
上传素材按内容证据推导 kind:upload_local_asset_at 不再把来源词 uploaded 当类型,改由 uploaded_asset_kind 按 mediaType 与扩展名推导 audio/video/image/document/code,判不出才用中性 asset;同一命令也收 .wav 与 .md,因此不能一刀切成 image
补 4 处回归与反查用例:本地导入与上传断言落盘 kind 与 category 且不得出现 ui、ui-interaction、uploaded;两处平台导入的 kind 解析加纯函数单测与调用点反查门禁;变异把错值改回来必须变红(M1/M2/M3 实测 4 个用例全红,还原后 diff 与基线逐字节一致)
覆盖边界:账户素材库与平台素材导入是异步 HTTP 路径,仓库内没有可复用的端到端夹具,这两条覆盖是纯函数单测加调用点反查门禁,不是端到端断言;本地导入与上传是读落盘 manifest 的行为级断言
后续事项:ImportedAsset.asset_kind 仍返回平台原始值(缺失为 none),与已落盘 kind 可能不一致,本次未改动
2026-09-12 02:30:31 +08:00
suzmii 90177308e0 pitfalls:补记「同一条命令的口径差异」——CI 用提交范围比较 git diff --check
Project CI / Repository checks (pull_request) Successful in 3m17s
Project CI / Frontend tests (pull_request) Successful in 3m55s
Project CI / Backend tests (pull_request) Successful in 6m17s
Project CI / Native shell tests (pull_request) Successful in 17m11s
- docs/project-memory/shared-memory/pitfalls.md:在「只跑门禁子集 → 同一天两次 CI 红」条目的「原因」里补第三类成因
- 口径差异:CI 跑 git diff --check "${base_ref}"..."${head_ref}"(提交范围),本地裸跑 git diff --check(工作树 vs 索引)在干净树上恒为空,所以本地绿不能证明 CI 这一跳会过
- 实例:pitfalls.md 末尾多出的空行(f16aa440b 引入),因前两次推送分别停在 check:git-hooks 与 check:rustfmt 而一直未被这一跳检查
2026-09-12 02:07:49 +08:00
suzmii a5c7938d05 修 CI:去掉 pitfalls.md 末尾多余空行(git diff --check 报 new blank line at EOF)
- docs/project-memory/shared-memory/pitfalls.md:删掉文件末尾多出的一个空行,使文件以单个换行结尾(UTF-8 无 BOM、LF 不变,仅 -1 行)
- 该空行由先前提交 f16aa440b 引入;前两次 CI 分别停在 check:git-hooks 与 check:rustfmt,都没走到最后一道 git diff --check,因此一直潜伏
- 自检口径按 CI:git diff --check fafe6b63c..HEAD(CI 是提交范围比较;本地裸跑 git diff --check 在干净树上恒为空,看不出来)
2026-09-12 02:02:12 +08:00
suzmii 31fcbb79dc pitfalls:记一条「只跑门禁子集 → 同一天两次 CI 红」
Project CI / Repository checks (pull_request) Failing after 2m51s
Project CI / Frontend tests (pull_request) Successful in 3m29s
Project CI / Backend tests (pull_request) Successful in 7m5s
Project CI / Native shell tests (pull_request) Successful in 17m8s
- 现象:dd7cf401a 给 lint-staged 加 *.rs 键,但本地从未跑过 check:git-hooks(它是 npm run lint 链里的一环),而 scripts/git-hooks.test.mjs 用 assert.deepEqual 钉住 lint-staged 的整份配置形状 → CI 以 deepStrictEqual 失败;本批更早还因 check:rustfmt 红过一次(15660a98b 修 8 处格式偏差),成因是 pre-commit 的 lint-staged 当时只覆盖 *.{js,mjs,cjs,ts,tsx},Rust 格式本地无守卫
- 原因:npm run lint 是一条 && 长链(encoding / npm-workspaces / git-hooks / rustfmt / spacetime-schema / production-ops / preview-deployer / maintenance-page / eslint / typecheck),中间任一步失败则其后步骤根本不执行,「跑到第 N 步就以为本地绿了」;更隐蔽的是本地若因环境原因在中间断掉(如 Windows 上 check:git-hooks 第 2 个用例的 EBUSY),后面那些本来能通过的步骤也从未被验证
- 处理:push 前按完整链路跑;&& 链在某一步失败时,必须把失败步之后的每一步单独再跑一遍(10 步 + git diff --check)并逐条记 exit code(取 exit code 不要接管道);改动碰到 package.json / .husky/ / scripts/ 下的门禁资产时,check:git-hooks 是必跑项
- 验证:本次 check:git-hooks 因本机 Windows EBUSY 中断后,补跑其后各步全部 exit 0(spacetime-schema / production-ops / preview-deployer / maintenance-page / lint:eslint / typecheck / encoding),只有 check:rustfmt 红且红在别人在途的 .rs(assets.rs:519 落在其未提交 hunk +470,62、commands.rs:3079 落在其未提交 hunk +3068,58);变异验证:把 *.rs 从 package.json 摘掉 → 第 1 个用例以同样的 deepStrictEqual operator 变红,还原(SHA256 一致)后回 ok
- 纯文档追加:文件末尾 +8 行、无删改;本条只提交 docs/project-memory/shared-memory/pitfalls.md 一个路径
2026-09-12 01:28:05 +08:00
suzmii 21da95387b decision-log:把守卫范围与本地限制两条裁定写进既有条目
- 取舍 bullet 补裁定理由:保持「整个 workspace」检查、不换成只查暂存文件(rustfmt --check --skip-children 与 cargo fmt 的口径不一致);本批两次 CI 红的共同根因是「本地绿 ≠ CI 绿」,守卫必须与 CI 同口径
- 取舍 bullet 补流程归因:共树里的硌人是「共享 worktree」的症状、不是守卫的问题,对应流程修正是「一条线一个 worktree」
- 本地限制 bullet 定性为「本机环境限制,不是 CI 会红」,写明根因(Windows/WSL 跨 /mnt/c 的临时目录句柄在子进程退出后仍被持有)、删除重试实测无效且已还原、残留目录会累积(清前 16 个、最早 2026-09-05,本轮已清 0),并给出「Linux CI 上通过」的依据
- 明确裁定:接受该用例在 Windows 上红,不改它语义、本批不再修
2026-09-12 01:15:11 +08:00
suzmii 9fea60b850 append 锁:预算与项目写锁对齐、超时报可诊断的持锁方、争用做有界退避重试
Project CI / Repository checks (pull_request) Failing after 3m22s
Project CI / Frontend tests (pull_request) Successful in 3m46s
Project CI / Backend tests (pull_request) Successful in 6m30s
Project CI / Native shell tests (pull_request) Successful in 17m14s
- project/agent_db.rs:等待预算从 10ms×100≈1s 改为 5ms×2000≈10s,与项目写锁完整窗口同口径;新增 lock_short(5ms×200≈1s)供只读路径使用
- project/agent_db.rs:超时文案在锁路径后追加持锁方线索(读锁文件里的诊断元数据,读不到就明说不可读),现场不再只拿到一句"检查运行时配置"
- project/agent_db.rs:取锁成功后把 label/pid/processStartedAt/acquiredAt 写进锁文件;仅作诊断,不参与判活、回收或抢占,同一进程重复取同一把锁不重写
- project/agent_db.rs:抽出 PROJECT_APPEND_LOCK_TIMEOUT_MARKER 常量,控制流不再各自复制中文
- project/conversation.rs:整份读对话记录的两处只读入口改用短窗口,避免把面板读路径一起拖住
- agent/direct_project_history.rs:争用类失败做一次有界退避重试(250ms);格式类失败不重试
- agent/direct_runtime.rs:锁争用给可操作提示(另一个客户端进程正在读写该项目历史或项目锁,请稍后重试,确认没有其它客户端再重启),retryable 保持 true,但现在确实会自动重试
- 用例:预算口径、锁文件诊断元数据、持锁方线索、被独占持有时超时且不留残留、争用重试成功、超出预算失败关闭、格式类不重试
- 变异验证:预算常量改回 1s → 预算用例红(left 10ms / right 5ms);去掉重试 → 重试用例红(append after retry 直接报跨进程锁超时);验证后已还原
2026-09-12 01:09:37 +08:00
suzmii cafd989bfb decision-log:记录 pre-commit 新增 Rust 格式守卫的取舍与本地限制
- 新增 2026-09-11 条目:背景(本地无 Rust 守卫、CI check:rustfmt 是唯一防线)、决策(lint-staged 增 *.rs + 包装脚本对两个 workspace 只查不改)、连带维护点(git-hooks.test.mjs 钉住整份配置形状,增键必须同步)
- 记下刻意保留的取舍:无暂存 .rs 时不触发;一旦暂存 .rs 则按整个 workspace 检查(与 CI 同口径),副作用是别人的未暂存未格式化 .rs 会挡住本次提交
- 记下未修的本地限制:check:git-hooks 第 2 个用例在 Windows 因 finally 里 rmSync 报 EBUSY 而红(断言全过、红在清理),TEMP 里堆了 16 个残留目录最早到 2026-09-05,加删除重试实测无效,CI 在 Linux 上不受影响
- 不动历史条目:2026-08-12 那条的「提交门禁」一行按本文件顶部口径只用于追溯
2026-09-12 01:01:51 +08:00
suzmii 2a7bfadd72 修 CI:git-hooks 回归用例跟上 lint-staged 新增的 *.rs 守卫
- scripts/git-hooks.test.mjs 用 assert.deepEqual 钉住 package.json 的 lint-staged 整份配置形状,dd7cf401a 新增 *.rs 键时漏了同步,Repository checks(经 npm run lint → check:git-hooks)deepStrictEqual 失败
- 按新形态补上 '*.rs': ['node scripts/lint-staged-rustfmt.mjs'],不放宽断言:仍是整份配置的 deepStrictEqual,改回「只要含 js 那条就算过」这类弱化写法一律不做
- 第 2 个用例里的本地 lintStagedConfig 是 temp repo 的测试替身,不是对真实配置的期望:temp repo 里没有 Rust 文件,加 *.rs 只会命中 0 个文件,故不动
- 变异验证:把 *.rs 从 package.json 摘掉 → 本用例以同样的 deepStrictEqual operator 变红(# fail 2);还原(package.json 字节级哈希一致)后复跑该用例回到 ok
2026-09-12 00:43:49 +08:00
suzmii 77871bfa56 把可能弹 UAC 的 ACL 提权修复移出 append 锁的持锁窗口
- project/agent_db.rs:Windows 取锁成功路径不再在"已持有零共享句柄"时做带提权的 ACL 修复;持锁期间只做不提权的严格校验,校验不过就先释放句柄、再提权修复,并返回"这轮没取到锁"由外层重试循环按修复后的 DACL 重新打开
- project/agent_db.rs:新增 release_project_append_os_lock_then_repair_acl,参数按值接收锁句柄且函数体第一件事是 drop,使"边持锁边等 UAC"在签名层面无法被表达
- 原因:提权修复走 powershell Start-Process -Verb RunAs -Wait 同步等用户点 UAC,在持锁期间等它等于把用户犹豫的时间记进别人的持锁窗口;只有 Windows 存在这条路径
- 判据(结构级):提权修复只在此一处被调用,且调用点必须交出句柄所有权;测试环境不弹真 UAC(config.rs 测试分支本地收紧),故不写假 UAC 用例
- 真机判据:DACL 有缺陷的机器上,持锁方进入修复期间不再持锁,等待方从"超时"变为可成功
- 不变项:首次创建锁文件的本地 harden 仍留在持锁期间(本进程新建对象不因继承 DACL 自动提权)
2026-09-12 00:12:52 +08:00
suzmii 1affb5eec3 锁内只保留 append:DirectProject 历史的幂等回扫移出持锁窗口
- direct_project_history.rs:幂等回扫与序列化移到取锁之前,取锁后常态只做一次追加,不再在锁内读完并逐行解析整份历史
- direct_project_history.rs:回扫到取锁之间若历史文件变了(len/mtime 与回扫时不一致)才回到锁内重扫一次;同一 id 不写第二行的语义不放宽,只是常态不再为它读整份文件
- direct_project_history.rs:新增 direct_project_history_duplicate_at 三态判定(Absent/Identical/Conflict)与回扫状态快照,对外行为与原实现一致(幂等 no-op、id 冲突失败关闭)
- direct_project_history.rs:新增回扫探针(两个时点回调)与三条用例:回扫在 append 锁外、锁外回扫后并发追加仍不重复写、id 冲突仍失败关闭
- project/agent_db.rs:新增测试专用探针 project_append_os_lock_is_held_for_test,用与生产同一套打开方式判断追加写目标的锁此刻是否被持有
- 变异验证:把回扫塞回锁内后 idempotency_reverse_scan_runs_outside_the_append_lock 变红(exit 101),报出「幂等回扫必须发生在 append 锁外」,验证后已还原
2026-09-12 00:07:04 +08:00
suzmii f11942f357 展开态补回空态提示:复用栏目页同一个 .game-resource-page-empty 与文案,不新增样式
Project CI / Repository checks (pull_request) Failing after 1m18s
Project CI / Frontend tests (pull_request) Successful in 3m29s
Project CI / Backend tests (pull_request) Successful in 6m42s
Project CI / Native shell tests (pull_request) Successful in 17m44s
- index.tsx:展开态在"可见资源为 0"时渲染 <p class="game-resource-page-empty">没有匹配资源</p>,与栏目页那一条同一条判据口径与同一句文案
- 它挂在既有的画布浮层 host 下(该 host 自身不定位),因此按画本场景根居中,观感与栏目页一致,不需要任何新 CSS
- 测试:空项目的「所有资源」用例补三条断言——进入展开态后标题栏是 .is-active[data-resource-book-category="all"]、卡片宿主数为 0、空态文案是「没有匹配资源」(把"复用既有空态、不新增样式"变成可执行判据)
2026-09-11 23:55:36 +08:00
suzmii 85358b2ca5 「所有资源」页的决策与验收判据跟上当前形态:分带几何口径 + 两页共用同一份布局数据
- decision-log 新增「各栏目按分带铺在同一张画布上」:记分带几何(buildResourceBookAllLayout + resourceBookAllTitlebarRects 复用总览标题栏渲染路径)与冻结口径(只在项目/排序/可见资源集变化时重算,拖动不重算;签名不含 token 的理由),以及本轮不动的三处"仍是另一套"(依赖连线 / 空态分带 / 总览缩略卡网格)与它们的后续顺序
- decision-log 新增「两页共用同一份布局数据」:记裁决(不开独立空间、写回同一 mode 同一 sidecar 的同一 section、提交前减带原点)、"为什么不会写偏"的依据、与"历史手动坐标只读恢复/不删除/不重置/不迁移"不冲突的理由,以及用户可自证的往返判据
- 验收文档 S7 行判据改写:旧判据 data-resource-book-all-page / data-resource-book-all-host 已随网格分支删除,改钉 .game-resource-book-scene-card[data-resource-book-category](每张资源恰好一个宿主)、.is-active[data-resource-book-category="all"] 与 .game-resource-book-view;补上"展开态可拖且在栏目页看到同一位置"这条往返判据
- 验收文档 S7 期望结果同步:由"汇总页"改为"与栏目页同一套画本场景、各栏目按竖向分带铺开"
2026-09-11 23:40:12 +08:00
suzmii 0d98bd05cc 删掉「所有资源」页的独立网格样式:它已经走画本场景,这六条没有调用方了
- 删除 .game-resource-all-resources-host:整页 absoulte 层与自己的底纹(原本是为了盖住画本场景底纹、并躲开画布容器的滚动处理)
- 删除 .game-resource-all-scroll:分组网格的自身滚动容器
- 删除 .game-resource-all-section:按栏目分组的分节卡片(边框 / 圆角 / 半透明底)
- 删除 .game-resource-all-grid:repeat(auto-fill, 180px) 卡片网格
- 删除 .game-resource-all-card:网格里的卡片占位格
- 删除 .game-resource-all-grid .game-resource-card:只读页的光标与 touch-action 覆盖(展开态已可拖,卡片要保留画布那套 touch-action: none)
- 不留任何 all 专属替代规则:展开态的外观全部由既有的 .game-resource-book-scene-card / .game-resource-card 与共享规则给出("零新增 CSS"是本次红线)
2026-09-11 23:33:59 +08:00
suzmii 9788248d9b 「所有资源」页改走画本场景:与栏目页同一套卡片宿主与几何,并可拖动
- 新增 resourceBookShowsAllCategories:把"子画布里的 all"这一态判定收在 resourceBookModel 一处
- resourceBookCategoryCardPresentation:展开态下真实栏目返回 child(卡片仍由各栏目组承载,每张资源只有一个宿主)、all 自己返回 null(只出标题栏与全量计数)
- 新增纯函数 buildResourceBookAllLayout:按栏目顺序纵向排"分带",用各栏目可见资源的 extent 定带宽高;卡片世界坐标 = 栏目内局部坐标 + 带原点
- 新增 resourceBookAllTitlebarRects:分带 → 栏目标题栏矩形,让展开态复用总览态"按矩形钉标题栏"的同一条渲染路径(ResourceBookScene 不需要知道"展开态"这个概念)
- 新增 resourceBookAllBandLocalPoint:展开态世界坐标 → 栏目内局部坐标,这是落盘前唯一的换算(减带原点)
- buildResourceBookScenePlan:展开态下 all 只出标题栏(钉在视口 + 全量计数),真实栏目按带偏移铺满 child 卡;真实栏目这一支仍按 presentation === child 取卡,"能出卡的判定"只有一处
- buildResourceBookScenePlan:返回总览的淡出改为按"刚离开的那段子画布"判定(exitingCategory 传 all 时覆盖所有栏目),并用带偏移位置淡出,否则返回动画会跳位置
- buildResourceBookScenePlan:「所有资源」组不再接 child / exiting 卡,避免同一张卡在两组里各挂一份(DOM 查询与 FLIP 键撞车)
- index.tsx:删掉独立网格页组件、其 host 块与 resourceBookAllResourcesSections memo,展开态整体交给画本场景
- index.tsx:新增内存态 resourceBookAllViewport(不落盘、不进按真实栏目键的 resourceCanvasViewports),滚轮 / 缩放 / 适应 / 平移 / 框选都作用到它
- index.tsx:分带几何按"项目 + 排序 + 可见资源集"冻结(签名 + ref 短路;签名不含 state.token——返回总览时 token 也会变,含它会在淡出期间重算),拖动不重算带高,避免"拖上面一带让后面所有带整体位移"
- index.tsx:拖动放开——展开态下每张卡都能拖,section 取 resource.category(栏目页仍只拖当前分页栏目)
- index.tsx:提交前用 resourceBookAllBandLocalPoint 把世界坐标减回带原点,写回的仍是同一 mode 同一 sidecar 的同一 section(.agent/workbench/resource-layouts/{dependency,type}.json),没有第二份空间,不改契约与 schema
- index.tsx:renderResourceBookCard 去掉 options.draggable:展开态已可拖,这个"只读"开关没有调用方了
- 测试:resourceBookModel.test 钉住展开态 presentation 与"栏目页仍是原判据";resourceBookLayout.test 退役旧分组函数用例,新增分带几何 / 标题栏矩形 / 减带原点换算 / 展开态计划(含"每张资源只有一个宿主"与按带位置淡出)断言
- 测试:appSurface 两处「所有资源」判据从旧网格宿主改为画本场景(.game-resource-book-scene-card[data-resource-book-category] 与 .is-active[data-resource-book-category="all"]),并断言旧网格容器 / 宿主一个都不再存在
2026-09-11 23:33:08 +08:00
suzmii f16aa440b9 补踩坑:第四类索引事故——git add <路径> 会把同一文件里别人的 hunk 一起暂存
- 记录 28781a420 的事故形态:该笔除我新增的用例(@@ -1260,6 +1260,51 @@)外,还混入了 peer 对「所有资源」测试期望的在途改动(@@ -9562,37 +9607,83 @@),提交描述与实际内容不符,且对方那份文件随即显示为干净、容易被误判为已收口;内容未丢
- 写清正确护栏:对可能被他人同时编辑的文件,把 git add <path> + git diff --cached(看内容、不只看文件名)+ git commit 压进同一条命令,并逐 hunk 确认每一个都是自己这次要写的;发现混入他人 hunk 后保持现状并立即上报,不在共享树里用 --amend/reset/checkout --/revert 做手术
- 写清状态信号:M (已暂存且工作区与索引一致)/ MM(add 后又改过)/  M(仅工作区),并点明「M 只能说明 add 之后没人再动过,不能说明这份文件里只有我的改动」
- 写清同类隐患:pre-commit 的 lint-staged 会对它格式化过的文件做 git add(prettier --write 之后 Applying modifications from tasks),与 npx lint-staged --diff=… 属同类反向 git add
- 与前三类索引事故(git add -A 卷走别人的文件 / stash pop 弹出别人的 stash / lint-staged --diff 反向 add)并列,指明本类正是「按文件名校验」这道护栏的失效点
- 门禁:npm run check:encoding exit 0(4399 file(s))
2026-09-11 23:08:57 +08:00
suzmii 3a850ee6e7 补两条踩坑:app-server 会 1:1 回显注入内容(读侧上限不得小于写侧允许量),以及“掉了就补读”的补扫不是修复
- 新增「Codex app-server 会 1:1 回显注入内容:读侧上限不得小于写侧允许量」:记录隔离探针实测(捆绑 codex-cli 0.147.0)——注入 5 MiB 的 item,app-server 接受并回一条 5 243 245 字节的 rawResponseItem/completed 单行,而读侧上限当时 4 MiB、写侧无上限,于是合法的注入被读行判成连接级故障,报错方向指向 app-server;同时记下 stderrBytes=405 那类摘要不能当证据(正常启动就有 275/452/546/5996 B 的插件/别名警告)、experimentalRawEvents 需要 initialize.capabilities.experimentalApi、以及探针必须用管道 stdin 与按 CommandLine 精确清理
- 新增「「掉了就补读」的补扫不是修复:自激回路会一路耗到内存」:明确标注**由并行工作线报告、本线未独立复核**,并给出取证据前不得引用的警示与“去掉补扫症状是否回来”的判据
- 门禁:npm run check:encoding exit 0(4399 files)
2026-09-11 22:51:24 +08:00
suzmii 28781a420c 补资源总览网格的声明级断言:钉住 width:100% 与 max-width 并存,防止退回单列
- 新增用例「keeps the resource overview grid off fit-content so its columns stay responsive」:断言 .game-resource-book-main-grid 同时声明 width: 100% / max-width: 1120px / margin: 0 auto / repeat(auto-fit …),并同款断言 .game-resource-book-main-heading 的 width: 100%
- 取声明体前先剥掉 CSS 注释:本次修复的注释里就写着 width: 100% 等字样,不剥掉会让断言匹配到注释而假绿(删掉真正的声明也照样通过)
- 注释里写明这条断言**验不到布局**:AGC 的 vitest 没开 css: true、styles.css 不会被加载、jsdom 也没有布局引擎,gridTemplateColumns 的解析结果与缩略卡列数只能在真机量;真机判据(3 个非 0 轨道 / grid 宽 432→1120 / 8 张缩略卡 top 只有 3 个取值,且需在总览态取样)写在用例注释里
- 变异验证:临时删掉 .game-resource-book-main-grid 的 width: 100% → 用例变红且失败信息打印的是真实声明体(证明注释剥离生效);还原后 styles.css 逐字节哈希与已提交版本一致,复跑绿灯
- 门禁:npm run typecheck exit 0;npm run check:encoding exit 0(4399 files);npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts 本用例 1 passed
2026-09-11 22:50:01 +08:00
suzmii 3db6afa052 对齐 Codex app-server 读写单行上限:写侧守卫 + DirectProject 历史注入前置校验,并把注入超限改判为不可重试
- 隔离探针实测(codex-cli 0.147.0,AGC 捆绑版本):experimentalRawEvents 线程收到 thread/inject_items 后会逐条原样回显 rawResponseItem/completed —— 注入一个 5 MiB 的 item,stdout 就回一条 5 243 245 字节的单行;而读侧上限原为 4 MiB,于是「我们注入得进去」却「我们读不回来」,报错方向指向 app-server,实际是我方读行判死
- codex_app_server.rs 的 GAME_CREATOR_CODEX_APP_SERVER_LINE_MAX_BYTES 由 4 MiB 提到 32 MiB,注明它同时充当 stdout 读侧上限与 stdin 写侧守卫;取值依据写进注释(单张图 base64 上限 10 MiB、单次图片总量 16 MiB 折 base64 约 21.3 MiB,再加 JSON 信封),再大就失去内存/DoS 边界的意义
- 新增写侧守卫 game_creator_codex_app_server_message_oversize_error:write_message 超限即失败关闭并给出字节数,绝不写出自己读不回来的行(两侧共用同一常量即这条不变量)
- 新增 DirectProject 历史注入前置校验 direct_project_history_injection_oversize_error:单条 item(扣掉 rawResponseItem/completed 回显信封余量)与整份载荷都必须落在上限内;超限指名 itemId/type/字节数并失败关闭,且不截断、不摘要、不改写历史(技术方案末节口径)
- 新增专属前缀 DIRECT_PROJECT_HISTORY_INJECTION_OVERSIZE_PREFIX,供 direct_runtime 判可重试性与给专属提示
- direct_runtime.rs:direct_codex_failure_is_retryable 把注入超限改为不可重试(同一份历史每次读结论相同,且本轮用户消息已先追加进同一文件、载荷只会更大);direct_codex_failure_recovery_hint 与 direct_codex_failure_public_summary 各补一条专属文案,用户不再看到笼统的「Codex 未完成本轮代码修改,请检查运行时配置后重试(可直接重试)」
- 新增 2 条用例:写侧守卫与读侧共用同一上限的边界(等于上限必须放行、上限+1 必须拒绝)、注入侧整份载荷与单条 item 双超限必须拒绝
- 变异验证:把写侧守卫改成 >=、把单条上限放成 usize::MAX,两条用例各在自己那一处变红;还原后逐字节哈希一致并复跑绿灯
- 门禁:npm run check:rustfmt exit 0;npm run typecheck exit 0;cargo test 定向 2 passed(2257 filtered out)
2026-09-11 22:24:30 +08:00
suzmii 1b7261685c 登记素材上传配对的踩坑与覆盖边界:断言打在生产函数上,端到端 UI 未覆盖
- `pitfalls.md` 新增「素材上传不要把『上传前读到的 revision』贴到『上传后读到的清单』上」:现象(每次上传都先吃一条拒收提示)、根因(上传本身推进 revision,错配必然发生而非竞态)、处理(上传动作与配对读收进同一个函数,调用方拿不到中间那个 revision,也就没机会贴错)、同类文案坑(`unresolved` 三条进入路径里有两条重读是**成功**的,写成"读取磁盘清单失败"就是误报)、验证与变异,并记录关联文件与提交。
- 覆盖边界(按当下口径写清,防止后人误读):断言打在生产函数 `uploadProjectAssetFilesAndReadSnapshot` 上 —— 它同时拥有上传与配对读,所以配对语义是真测的;但 AGC 侧**没有**「资源面板上传 → 拒收提示条」的端到端 UI 用例(现有 harness 只有聊天入口的 `/asset.upload`,资源面板 file input 无用例),因此「界面上不再出现拒收提示」是推理结论而非端到端断言,禁止当成端到端覆盖引用。
- 同一条边界同时写进 `projectResourceLiveUpdateModel.test.ts` 用例组头部,随断言一起走,避免只看测试文件的人误读。
- 纯文档 + 测试注释,无功能改动。
2026-09-11 22:19:14 +08:00
suzmii c3ccae75af 按实测收回 cfd89ba57 顺手加宽的一处测试超时,并写明另一处保留的理由
- `prefetches only cards that are actually in view, and still fills the first screen` 的 `}, 30000)` 收回(回到 vitest 默认 5 s):实测 93 ms,约 54 倍余量,而且真正的时间门是内层 `waitFor` 自带的 1 s。cfd89ba57 那次只是给负载留余量,没有对应的结构性成本 —— 对照 `releases Blob URLs while continuously browsing beyond the cache limit`(79 轮串行读盘、实测 6120 ms、超默认 5 s)那条的预算属结构性必需,保持不动。
- `keeps a visible card cached when later loads push the cache over its limit` 的 `}, 60000)` **保留**并写明理由:它的职责是挂起护栏,必须大于内层 `waitFor` 的 20 s 断言预算,否则内层断言自己的失败信息永远不出现(vitest 会先在测试超时处中止,只剩笼统的 "test timed out")。用例实测 120 ms,20 s 与 60 s 都不是耗时预期;硬收会让内层预算变成不可达配置,是更差的诊断而不是更紧的门禁。
- 无功能改动。
2026-09-11 22:18:40 +08:00
suzmii fef366a66b 文档跟进:资源画布的引用入口按当前状态描述(图标 @ + 文案「引用」)
- 「当前已完成」清单里「资源画布素材卡提供 `@引用`」既不准确也不再是当前形态:入口已从卡片挪到选中工具条,且可见文案与 title 都不再自带 `@`(`@` 由 `AtSign` 图标承担,文案再写一次会渲染成「@ @引用」)。
- 按「四不写」把该条直接改写成当前状态:入口位置(选中工具条)、图标与文案口径(图标 @ + 文案「引用」)、以及插入对话里仍然是 `@素材名` 芯片。
- 纯文档,无代码改动;单独提交便于 cherry-pick。
2026-09-11 22:18:09 +08:00
suzmii a88034dab1 修复资源总览网格退化成单列:父链宽度不定让 auto-fit 只解析 1 个轨道
- styles.css 的 .game-resource-book-main-grid 增加 width: 100%:父级 .game-resource-book-main-world 是列向 flex 容器,交叉轴 auto 外边距(margin: 0 auto)会吃掉自由空间并取消 stretch,使该网格按 fit-content 定宽;fit-content 定宽时网格 inline size 在内在尺寸阶段不定,按 CSS Grid 规范 auto-fit 的重复次数解析为 1,于是 2560x910 窗口下 grid 宽只有 432px、getComputedStyle(grid).gridTemplateColumns 只有 1 个轨道、8 张栏目缩略卡竖向排成一列(top 步距 230)
- 同款修 .game-resource-book-main-heading:它在同一个 flex 列里带 margin:auto,同样退化成 fit-content,标题栏只占 <h2> 宽度且 space-between 无从展开
- 两处都补注释写明「width:100% 是 auto-fit 按 max-width 解析列数的前提」,防止后人当作冗余声明删掉
- 修复后 auto-fit 按 max-width:1120px 解析重复次数:3x280+2x18=876 <= 1120、4x280+3x18=1174 > 1120,即 3 列
- 真机判据:gridTemplateColumns 应给出 3 个非 0 轨道(约 361.33px)、grid 宽 432 -> 1120、8 张缩略卡 top 只有 3 个取值;且必须在总览态核对(栏目态该层带 is-background,opacity 为 0,是背景层)
- 未改动任何既有断言;AGC vitest 不加载 styles.css(无 css: true),声明级断言随下一笔提交补进 appSurface 用例
2026-09-11 22:10:43 +08:00
suzmii e98066ee7e 驱逐后不再补扫:删掉那条会把可见卡反复重读的自激回路,并如实标注超时预算
- 删掉 publishPreview 里"驱逐后立刻补一次可见性扫描"(原 useProjectResourceCardPreviews.ts 的 `if (evictedCount > 0) sweepVisiblePreviews()`)。它原先的注释宣称"递归深度恒为 1、不自激",该不变量是假的,判据有三条:(1) 触发条件是"本轮驱逐了任意条目",而第一轮只淘汰视口外条目,那种情况下根本没有可见卡丢状态,补扫却照样把空闲且可见的卡重新入队;(2) 真正驱逐到可见卡时(全表都在视口内且仍超预算,第二轮全表 LRU 回退)补扫把刚被驱逐的卡重新入队 → 读回来又超预算 → 再驱逐 → 再补扫,每个周期跨一次异步读取,是不收敛的回路;(3) 那种回读换不来稳定结果,读回的卡立刻被下一轮 LRU 淘汰(第二轮回退是全表 LRU,刚读回的卡恰好最新),只在可见集合里轮转。因此正确反应是接受这次有界淘汰(由「全可见且超预算时仍必须淘汰」钉住),而不是反复重读;代码注释按这三条重写,不留误导后人的陈述。
- 顺带删掉只为那段补扫存在的 `evictedCount`(否则是死变量)。
- 断言:新增 useProjectResourceCardPreviews.test.ts「settles instead of re-reading evicted visible cards forever」——全部登记且都在视口内(第一轮无从下手),断言读取次数在观察窗口内不再增长、且缓存稳定在上限。
- 变异验证(提交前已跑):把补扫加回去 → 该用例**不是断言变红而是 worker 被撑爆**(`ERR_WORKER_OUT_OF_MEMORY`,约 74 秒后终止),即回路真的不收敛;还原后复跑 32 passed,该用例 2.1s 通过。
- 超时预算改为"标注并给出实测依据",不再用放宽时间门吸收负载代价:`releases Blob URLs while continuously browsing beyond the cache limit` 的 `}, 30000)` 保留,并在用例前写明理由 —— 它逐个 waitFor 等 79 张卡串行读完,轮数由条目上限 48→72 机械地从 55 涨到 79(+44%),实测 6120 ms,已超 vitest 默认 5s,属结构性必需。同一提交里的 `waitFor({ timeout: 30000 })`(由 20000 顺手放大)按实测**回到 20000** 并写明依据(该用例实测 124 ms,约 160 倍余量)。
- 门禁:useProjectResourceCardPreviews 32 passed。
2026-09-11 22:02:31 +08:00
suzmii b12b0998d0 分类改动不再丢弃已落盘的画布坐标:分区不一致时归并到资源当前分类并保留 x / y
- 原缺陷(3082c3e85 的连带副作用):`kind:"UI"` 的资产投影分类由「待归类」修到「UI 交互」后,它们落盘的坐标 section 仍是现行栏目值 `unclassified`,于是 resolveResourceCanvasSection 走「现行栏目值 != 资源当前分类 ⇒ null」直接判丢弃,8 条用户手摆的坐标没能跟过来、被自动排布重算。
- resourceCanvasSectionMapping.ts:现行栏目值改为返回**资源当前分类**("这张卡属于哪一栏"的唯一真源本来就是 resource.category,协调时也按它分组),与旧栏目归并支路同一条语义 —— 只改写 section,x / y / manuallyPlaced 原样保留。判据:坐标是栏目内局部坐标(reconcileResourceCanvasLayout 先按 section 分组再算栏目带原点),所以换栏目保留 x / y 得到的正是"这张卡在新栏目里的同一个槽位";而"丢弃 + 自动重排"会连 manuallyPlaced 一起抹掉,是用户可见的损失。
- 判断与理由:**保坐标重写 section 可行**。同一函数对更难的旧栏目 case(旧 `art` 可能归并到 4 个不同栏目)已经在做同样的事,对 `unclassified → ui-interaction` 反而更窄;拒绝它是自相矛盾。
- 丢弃口径收窄:resolveResourceCanvasSection 只在**确实无从归并**时返回 null —— 无法识别的分区值,以及旧栏目归并目标集合不含资源当前分类(如旧 `code` 遇到已归为 `document` 的资源)。useProjectResourceCanvasLayout.ts 的 droppedSectionMismatch 文档与读盘统计注释按此更新(用户可见文案未变,它对新口径仍然成立)。
- 断言:resourceCanvasSectionMapping.test.ts 把原「沿用既有行为丢弃该坐标」用例改写为守新契约(归并 + changed=true),并新增 x / y / manuallyPlaced 逐项不变用例;resourceCanvasLayoutModel.test.ts 原「drops a persisted position…」改写为「rewrites … keeping the coordinates」;useProjectResourceCanvasLayout.test.ts 的读盘统计由 normalized 1 / droppedSectionMismatch 1 改为 normalized 2 / droppedSectionMismatch 0,并新增"归并必须连坐标一起保住"的断言,另补一条用例钉住 droppedSectionMismatch 只剩"无从归并"两类来源;appSurface/project-development.suite.ts 的提示文案与 data-* 计数同步,并新增写盘坐标断言。
- 变异验证(提交前已跑):把 resolveResourceCanvasSection 还原成 `section === resourceCategory ? section : null` → 6 条用例变红(映射 2 条、布局模型 1 条、读盘统计 2 条、appSurface 1 条),还原后复跑 83 passed + appSurface 该条 passed。
- 门禁:resourceCanvasSectionMapping 9 passed、resourceCanvasLayoutModel 47 passed、useProjectResourceCanvasLayout 27 passed。
2026-09-11 21:48:43 +08:00
suzmii 006e9cc2f3 素材上传不再每次触发一次拒收提示:上传与清单配对读放进同一个函数
Project CI / Repository checks (pull_request) Failing after 1m31s
Project CI / Frontend tests (pull_request) Successful in 3m46s
Project CI / Backend tests (pull_request) Successful in 8m21s
Project CI / Native shell tests (pull_request) Successful in 19m10s
- 原缺陷:上传路径在上传**前**读 get_local_game_project_revision,上传完把那个**旧 revision** 交给 reloadManifestAfterAssetCommand;后者重新读盘拿**新清单**再打上旧 revision。上传本身会推进项目 revision(Rust advance_agent_runtime_project_revision_locked),于是这一对是撕裂的,「同 revision 不同指纹」被 projectResourceLiveUpdateModel 判成 revision-conflict —— 用户每上传一次素材就先看到一条「资源清单更新被拒收…」。
- projectResourceLiveUpdateModel.ts 新增 uploadProjectAssetFilesAndReadSnapshot:上传动作与"配对读"由同一个函数拥有,配对读复用既有 rereadAuthoritativeProjectManifestSnapshot(同一次读里配对 revision + 清单,并再读一次 revision 确认没被写盘插队)。调用方拿不到中间那个 revision,也就没有机会把它贴错。
- index.tsx 的 uploadResourcePanelFiles 改用它,删掉上传前那次 revision 读取与旧 revision 的传递。
- 附带文案误报:describeProjectManifestMergeRejection 的 unresolved 文案由「重新读取磁盘清单失败,请重新打开项目」改为「未能按磁盘清单重新对齐,请重新打开项目」。unresolved 有三条进入路径,其中两条**重读是成功的**(读到的一对比手上旧、或读到的是撕裂的一对),写成"读盘失败"是误报;新文案对所有路径都成立。WorkspaceLauncher.tsx 在"读到了但不采用"那一支补注释说明 stage 不是读失败。
- 断言:projectResourceLiveUpdateModel.test.ts 新增「素材上传后的清单快照必须配对」——假后端在上传时推进 revision,断言 merge 判 accepted 且 projectManifestMergeRejectionDecision 为 null(即"上传一次不产生拒收提示"),并附一条对照钉子显式写出旧形状、断言它必被判 revision-conflict;另新增 unresolved 文案不得包含"读取磁盘清单失败"的用例。
- 变异验证(提交前已跑):把 uploadProjectAssetFilesAndReadSnapshot 还原成"上传前读 revision + 上传后读清单"→ 配对用例变红(并给出 revision-conflict 的失败信息),还原后复跑 15 passed。
- 门禁:projectResourceLiveUpdateModel 15 passed、workspaceLauncherManifestMerge 5 passed。
2026-09-11 21:33:54 +08:00
suzmii 09ed7b099b 视图切换取消排队预取在生产路径上真正生效:视图标识改为必填
- 原缺陷:预览 Hook 的预取作用域 key 缺省退化为"被喂进来的资源集合签名",而宿主按设计要求恒传**全量投影**(传全量才不会让总览里其它栏目只剩栏目标题栏),换栏目/换视图一字不改 ⇒ key 不变 ⇒ 取消上一视图排队预取的 effect 永不执行,"同级裁决"一并退化为 no-op。已有用例用"改 resources"模拟切视图,测的是生产不存在的调用方形状。
- resourceCanvasController.ts 新增 createResourceCanvasPrefetchScopeKey({ view, target, mode }):由视图身份(main / child:<栏目>)加排序模式算出标识。
- index.tsx 在资源画布处显式算出该标识并传给预览 Hook(栏目取 resourceBookState.view / resourceBookState.category,排序取 sortMode)。
- useProjectResourceCardPreviews.ts 把 prefetchScopeKey 由可选改为**必填**并删掉那段兜底:它能"看起来还在工作",正是它掩盖了漏接线;现在漏传是类型错误而不是静默少取消一次。
- 断言:resourceCanvasController.test.ts 新增标识派生两组用例(换栏目/换视图/换排序模式必须变,同一视图必须稳定);useProjectResourceCardPreviews.test.ts 改写两条既有切视图用例为生产形状(resources 恒为全量、只改视图标识与 eagerResources),并在用例内前置断言"换栏目必须换标识";其余既有用例经用例侧包装补齐标识,形状与生产一致,断言一条未放宽。
- 变异验证(提交前已跑):把 createResourceCanvasPrefetchScopeKey 的栏目分量去掉(只留 view + mode)→ 3 条用例变红(标识派生 / 取消上一视图排队预取 / 取消后重新可见再入队),还原后复跑 34 passed。
- 门禁:`npx vitest run` 上述两文件 34 passed;typecheck 见随后整轮。
2026-09-11 21:31:30 +08:00
suzmii 4175d82fa1 工具条「引用」不再渲染成「@ @引用」:@ 只留图标,文案去掉重复的 @
- index.tsx 选中工具条的引用按钮:title 由「@引用」改为「引用」,可见文案 <span>@引用</span> 改为 <span>引用</span>。图标 AtSign 本身就是 `@`,文案再写一次就渲染成「@ @引用」。
- label={`引用资源 ${selectedResource.label}`} 保持不变:无障碍名仍要能读出「引用资源 <素材名>」,本次只去掉可见文案与 title 里的 `@`。
- 断言(appSurface/project-development.suite.ts):静态形状用例改判 title="引用" / <span>引用</span>,并新增 not.toContain('@引用') 反向守卫;键盘通路用例改判 title === '引用'、可见标签 .genarrative-image-canvas__chrome-button-label 文本为「引用」,并新增 aria-label === '引用资源 hero.png',钉住无障碍名没有被一起去掉。
- 变异验证(提交前已跑):把 index.tsx 的 title 与可见文案一起改回「@引用」→ appSurface 两条用例变红(资源卡的 @ 引用入口只留在选中工具条里 / 工具条里的「引用」用键盘也能插进聊天输入框),还原后复跑 412 passed。
- 门禁:`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts` 412 passed(改动前后同一时刻对照,均为 412 passed)。
2026-09-11 21:25:14 +08:00
suzmii dd7cf401a9 补 lint-staged 的 Rust 守卫:pre-commit 也跑 check:rustfmt
- 新增 scripts/lint-staged-rustfmt.mjs:对 server-rs 与 apps/ai-game-creator-shell/src-tauri 两个 workspace 跑 cargo fmt --all --manifest-path <m> -- --check,只查不改
- 为什么需要包装脚本:lint-staged 会把命中的暂存文件路径追加到命令末尾,而 cargo fmt 只按 workspace 粒度格式化、不接受文件参数,直接写成 npm run check:rustfmt 会被多余参数打断;脚本因此忽略 argv,并按 workspace 逐个检查
- package.json 的 lint-staged 配置新增 "*.rs": ["node scripts/lint-staged-rustfmt.mjs"]
- 成因:原配置只覆盖 *.{js,mjs,cjs,ts,tsx},Rust 格式在本地完全没有守卫,唯一防线是 CI 的 check:rustfmt,本 PR 已因此红过一次(见 15660a98b)
- 已实测:把包装脚本直接跑一遍 exit 0;lint-staged 分派层面确认 "*.rs" 任务会被真的触发(对 6 个 .rs 文件跑通);脚本本身过 eslint 与 prettier --check
2026-09-11 21:24:43 +08:00
suzmii 5fff7a134c ④ 资源分类写入补一条 agent.db 审计记录
- update_manifest_asset_classification_at 在 manifest 写成功后追加 recordType=asset.classification.update 的审计,复用既有 append_agent_db_record 惯例(照 assets.rs 的 asset.register / asset.update:manifest 写成功后追加)
- 字段:assetId(沿用 assets.rs 的 assetId)、expectedProjectRevision(本次写入实际校验的 CAS 基准 revision,沿用分类输入契约里的同名参数)、previousCategory / previousTags(变更前值)、category / tags(变更后值)
- 不记 localPath / kind / mediaType / source:本记录针对的是已有 assetId 的分类变更,这些字段并未改变,且能从 manifest 或既有 asset.register 记录追到,多记一份会在改名后产生互相矛盾的审计
- 不用 projectRevision 这个名字:既有 agent.runtime.action_receipt 里 projectRevision 的语义是「动作完成后的 revision」,本条审计追加在 revision 推进之前,沿用同名会指代不一致;审计是持久化数据,故在新记录类型里另起不冲突的名字,不动既有记录格式约定
- 位置放在推进 revision 之前是刻意的:分类已经真实落盘,审计不能因为紧随其后的 revision 推进失败而缺失,否则「改过但查不到」正是这条缺陷;推进失败仍然照旧报错
- 审计追加失败按既有惯例映射为可见错误「资源分类已写入,但审计记录失败:{error}」,不静默吞掉
- project/manifest/classification_tests.rs 补 3 条断言:成功写入恰好一条且前后值正确(含第二次写入的 previous 必须取第一次的落盘值)、四类被拒写入不产生任何审计、审计写失败时错误可见且不留假审计(同时确认分类本身已落盘、不回滚)
2026-09-11 21:18:55 +08:00
suzmii 4161888219 ③ 资源画布布局的读/写命令补项目权限门
- read_local_project_resource_canvas_layout 补 enforce_project_auto_permission_policy(root, "asset.list"),与紧邻的 read_local_project_resource_graph 完全同口径
- update_local_project_resource_canvas_layout 补 enforce_project_permission_policy(root, "asset.register"),与本文件 update_local_project_resource_classification / delete_local_project_asset / rename_local_project_asset 同口径
- 权限位依据:读路径是同一块资源画布的渲染读路径,邻居用 asset.list;写路径是「改动项目内资源相关持久化数据」,manifest 侧的分类写入本身就用 acquire_project_write_lock(root, "asset.register"),故沿用同一权限位,不新造权限名
- 读用 auto、写用普通门是刻意的:asset.list 是 Auto,auto 口径默认放行,只有策略显式 deny/confirm 才拒绝(读路径没有可插入的确认交互,要求确认等同拒绝);asset.register 默认是 Confirm,若写路径也用 auto 会直接打死默认路径
- tests/project.rs 补断言:默认策略下读与写都放行;deny asset.list 读失败、confirm asset.list 也读失败(钉住 auto 口径这一选择);deny asset.register 时读仍放行、写被拒且被拒的写不留任何落盘副作用(断言 sidecar revision 未变);恢复默认策略后写入照旧成功
2026-09-11 21:18:41 +08:00
suzmii 6cc73b07ac ② GameCreationAppManifest 加 deny_unknown_fields:未知顶层字段失败关闭
- shared-contracts 的 GameCreationAppManifest 加 #[serde(deny_unknown_fields)],并写明取向与取舍
- 选「失败关闭」而不是「保留未知字段 round-trip」的理由:AGC 读写 manifest 是「整结构体反序列化 + 整结构体重新序列化覆盖落盘」,放行未知顶层字段就等于让「读一次 + 任意一次写」静默抹掉未来版本新增的字段;而 flatten catch-all 只能覆盖加了它的那一层,tasks / assets / versions / preview / commandRuns 内部的未来新增字段照样被抹掉,且写侧的 skip_serializing_if 会同时把已知字段归一化,落盘结果是「新字段原样 + 旧字段被规范化」的混合体,比直接报错更难排查
- 与既有取向同口径:本批新增的资源布局 sidecar 对未知 schema 就是失败关闭,UpdateLocalProjectResourceClassificationInput 也已 deny_unknown_fields
- 影响面已核实:server-rs 内 game_creation_app 模块只被自身引用,不进 /api/external/v1、不进 SpacetimeDB;全仓 GameCreationAppManifest 的反序列化点只有 src-tauri 的 read_manifest 一处;仓库自带 smoke 脚本写的 manifest 只有 schemaVersion/projectId/name/assets 四个已知键
- 断言:shared-contracts 补契约级用例(未知顶层字段反序列化失败且报出字段名、已知字段含可选字段照旧往返);project/manifest/import_tests.rs 补消费侧用例(纯读报错、读+写入口 mutate_manifest_at 也报错、两次失败后磁盘文件逐字节未变即未知字段没被抹掉)
2026-09-11 21:18:29 +08:00
suzmii 5eaf9faefd ① manifest 的 schemaVersion 补读/写失败关闭门
- 新增 validate_manifest_schema_version:只接受 GAME_CREATION_APP_MANIFEST_SCHEMA_VERSION,未知版本报「manifest schemaVersion 不受支持:{实际值}(当前支持 {当前值})」
- read_manifest 在解析后立即校验 schemaVersion,与既有的 godotProjectRoot / versions 校验同级;读到未知版本直接拒绝打开,不再被当成已知版本继续使用
- write_manifest_locked 落盘前同样校验,保证本客户端永远不会把未知 schemaVersion 写进项目(该函数是 write_manifest 与 mutate_manifest_at 共用的唯一落盘入口)
- 前向兼容取舍:刻意不做「接受未来版本 + 读时就地升级」——当前并不存在 v2 定义,凭空写一个升级只能把未知数据改写成当前版本的形状,正是本次要修掉的「静默接受」;代价是未来发 v2 时旧客户端明确报错要求升级,而不是把项目按旧结构写回
- project/manifest/import_tests.rs 补 3 条断言:当前版本必须被接受且全字段回读相等(正向断言,挡住「无条件拒绝」这种改法)、未知版本读失败且磁盘文件逐字节未变、未知版本写失败且不落盘
2026-09-11 21:18:15 +08:00
suzmii 15660a98b2 修 CI:补跑 AGC src-tauri 的 rustfmt,修正本批遗留的 8 处格式偏差
Project CI / Repository checks (pull_request) Successful in 2m52s
Project CI / Frontend tests (pull_request) Successful in 3m30s
Project CI / Backend tests (pull_request) Successful in 5m44s
Project CI / Native shell tests (pull_request) Successful in 19m31s
- 只做格式重排(应合成长行/应拆多行),无语义改动
- 命中 agent/direct_project_history.rs、agent/direct_runtime.rs、main.rs、project/conversation.rs、project/conversation/tests.rs
- 分别来自 bfb928295 / 2b44b6259 / 458a65cb5 / f0d5687ab 四笔提交的新增行
- 成因:pre-commit 的 lint-staged 只覆盖 *.{js,mjs,cjs,ts,tsx},Rust 格式本地无守卫
2026-09-11 21:09:09 +08:00
suzmii 47c88809b6 decision-log:记录冷启动首屏同卡重复读为后续项(本轮不修)
- 新增一条 2026-09-11 决策记录:冷启动首屏同一张卡被读两次的现象、两条对照证据(eagerPreviewLimit 置 0 同形;hook 整份换回 A/B 之前的 HEAD 版本同样重复)、怀疑方向(observePreview 的注册回声扫描 + 取消 visible 预取后的立刻重扫 + 0/250/1000ms 多延迟点叠加)、未修原因(赶 DDL、非用户可见故障、属门禁与队列时序这条更脆的通路)、影响(首屏请求量比理论最小值多约 20%)与建议的修复断言;
- 记录里写明 A+B 只改「取哪些、按什么顺序」,与这条重复读无关;
- prettier 顺带把该文件整份规范化(改动 2 行缩进 + 2 个空行,属既有旧条目,纯格式无语义)。该文件在 HEAD 上本就不是 prettier-clean(已用 HEAD 副本 prettier --check 实测为 warn),本次一并收口。
2026-09-11 21:00:36 +08:00
suzmii 87968e3be3 布局落盘 / 读盘失败不再吞掉原因:脱敏后留一行日志
- 两处 .catch(() => {…}) 没有 message 参数,失败原因被整个吞掉:用户只看到「布局保存失败」「布局读取失败」,排障端也拿不到为什么(revision 越界 / IPC 拒绝 / 权限);
- 写回失败(update_local_project_resource_canvas_layout 的 .catch)与读盘失败(read_local_project_resource_canvas_layout 的 .catch)都补上 error 参数,并落一行 console.warn;
- 新增 describeResourceCanvasLayoutFailure:把 error.message 压成一行、截断 400 字,并复用 errorReporting 的 normalizeDiagnosticText 脱敏(路径 / token / URL / 长 id 打码);
- errorReporting 的 normalizeDiagnosticText 改为导出:脱敏口径只留一份,业务文件不再各写一套正则;
- 用户可见文案一字不改(LayoutNotice 联合类型与既有四条用例的断言原样保留),原因只进日志;日志由客户端已装的 webview→application.log 桥带进诊断日志;
- 新增两条用例:写回失败必须留下原因(并断言可见文案仍是「布局保存失败,已恢复上次布局」)、IPC 拒绝且原因里带本机绝对路径与 token 时必须脱敏成 <path> 且压成一行;
- 变异验证:把写回那处改回吞掉原因 → 写回用例红灯;把读盘那处改回吞掉原因 → 脱敏用例红灯(各自只红对应一条,既有 24 条全绿)。
2026-09-11 21:00:16 +08:00
suzmii 3a4f95d7ca Merge remote-tracking branch 'origin/master' into feat/agc-canvas-resource-workbench-v3
Project CI / Repository checks (pull_request) Failing after 1m23s
Project CI / Frontend tests (pull_request) Successful in 5m4s
Project CI / Backend tests (pull_request) Successful in 6m33s
Project CI / Native shell tests (pull_request) Successful in 19m45s
# Conflicts:
#	apps/ai-game-creator-shell/src/App.tsx
2026-09-11 20:56:14 +08:00