suzmii
|
a4bec3d018
|
记下「不再提醒」偏好保持单键、不按入口分域的理由
Project CI / Repository checks (pull_request) Successful in 2m35s
Project CI / Frontend tests (pull_request) Successful in 3m28s
Project CI / Backend tests (pull_request) Successful in 5m48s
Project CI / Native shell tests (pull_request) Successful in 18m25s
- 该键关掉的只有聊天输入区的「发送前提醒」;资源侧生成素材与快速编辑没有可关的提醒,润色是显式按钮动作
- 分域只在「每个入口都有可关的提醒」时才有意义,现在分域等于给不存在的提醒提前留位
- 只补注释,不改行为与断言
|
2026-09-11 17:06:35 +08:00 |
|
suzmii
|
327a5d38ee
|
同步验收文档里 #309 C3 的口径与两条入口的润色判据
- S9 行与 7.4 差异表补一句:只读信息浮层已回归(工具条「信息」动作),C3 取消的仍只是可编辑详情面板与全屏编辑路由
- S10 / S11 行补上两处入口的「AI 润色」判据:按钮 aria-label 与 aria-busy、成功回填与「恢复原文」、失败保留原文、超类型上限截断并提示「已按长度上限截断」
- 纯文档改动,不动代码与断言
|
2026-09-11 17:03:04 +08:00 |
|
suzmii
|
c2d3292769
|
快速编辑浮层接入提示词润色,提示词变了就重铸请求身份
- 选中工具条的快速编辑面板通过共享 composer 的注入位渲染同一个 ResourcePromptPolishSlot,场景约束为「图片素材的快速编辑提示词」
- 润色回填走 applyResourceQuickEditPrompt:与共享 composer 的 resetFailedDialogStatus 同口径,提示词变了就把上一次的失败状态清掉
- 提交侧请求身份按提示词收口:同 sourceLayerId 且提示词未变时复用(失败重试仍命中同一 operation 账本),提示词变了则重铸 operationId / 幂等键——Rust 的 request_fingerprint 含 prompt,复用旧身份会被判「已绑定到不同资源编辑请求」;正规化结果与 revision 属于别的事实,换身份时原样保留
- 打开面板时的初始身份改用统一的 createResourceEditRequestIdentity(绑定面板草稿提示词)
- 测试:live integration 补两条——润色成功回填并换身份(断言 prompt 与 operationId/幂等键同时变化)、润色失败保留原文且身份不变
- 变异验证:提交侧去掉按提示词重铸(同 sourceLayerId 直接复用旧身份)→ 「润色后换身份」用例红灯(两次 operationId 相同,实测)
|
2026-09-11 17:02:17 +08:00 |
|
suzmii
|
d44e1ae8ea
|
生成素材弹窗接入提示词润色,并让请求身份绑住它铸造时的那句提示词
- 新增 ResourcePromptPolishSlot:素材侧两处入口共用的「AI 润色 / 恢复原文 / 润色中…」动作,取数走 AGC 侧 usePromptPolish,聊天框同款失败态(失败保留原文、可重试文案)
- 场景约束走 polish_local_project_prompt 的 context:「这是素材生成提示词(音效)」+「只润色这一句、不要扩写成需求/方案/验收标准、不要新增事实」(本批不动后端提示词文件),文案落在 resourceCanvasPromptPolishModel
- resourceEditModel 新增 truncateResourceEditPrompt:超上限先按类型截断(背景音乐 140 / 音效 1900 / 视频与角色动画 4000 / 其余 32000)再回填,并在状态行给出「已按长度上限截断」,不静默丢内容;后端上限未改
- resourceEditModel 新增 resolveResourceEditRequestIdentity:请求身份绑定到铸造时的 prompt(Rust request_fingerprint 含 prompt,改了提示词还复用同一 operationId 会被判「已绑定到不同资源编辑请求」);提示词没变时仍复用,失败重试照旧命中同一账本
- 生成素材弹窗用注入位渲染润色动作(共享 composer 只提供插槽,不上 Tauri 调用),提交时按同一口径解析身份;原先只在这一处使用的 createResourceCanvasGenerationRequest 随之下线
- 样式落在 resourceCanvasChrome.css(resource-prompt-polish 一族),弹窗与 composer 共用一行
- 测试:模型层补截断边界与身份复用/重铸;弹窗层补润色回填与上下文、超限截断提示、失败保留原文、失败后提示词与润色一起锁定且重试复用同一请求
- 变异验证:截断改成恒等 → 模型用例与「超限截断」用例双双红灯(实测字段长度 206 ≠ 140);身份去掉重铸 → 模型用例红灯
|
2026-09-11 16:58:49 +08:00 |
|
suzmii
|
62029fdc23
|
给共享生成 composer 加一个可选的提示词动作注入位
- ImageCanvasBasicGenerationComposerView 新增 promptActionSlot(可选):不传时整块不渲染,默认 DOM 与行为逐字不变
- 注入位紧跟在提示词输入框之后,节点由宿主提供并自己负责在 composer 网格里的落位;共享组件内不做任何 Tauri 调用
- ImageCanvasQuickEditPanelView 原样透传该 prop,网页端两条调用链不传即不受影响
- 测试:composer 与快速编辑面板各补一条「不传时无占位节点、传入时落在提示词之后」,默认不变由既有用例继续覆盖
|
2026-09-11 16:54:57 +08:00 |
|
suzmii
|
25c55fcb7f
|
把提示词润色的状态机抽成 usePromptPolish,聊天输入区先迁过去
- 新增 features/project-workspace/usePromptPolish.ts:失败保留原文、首次成功落原文快照(反复润色只覆盖结果)、可重入保护、可注入的上下文与规范化回调,Tauri 调用固定在 AGC 侧(默认 requestChatPromptPolish)
- 聊天输入区改用它,行为逐字不变:润色按钮 aria-busy/disabled、Loader2/Sparkles、「润色中…」/失败文案、失败保留原文、恢复原文回到最初原文、草稿清空后重置
- 聊天特有的「发送前提醒」留在输入区:提醒偏好、本轮已确认草稿指纹、表单捕获拦截、面板内润色用另一句失败文案
- chatPromptPolish.ts 的 requestChatPromptPolish 及其单测未改动,继续作为这条 LLM 通道的契约测试
- 护栏:chatPromptPolish.test.tsx(16 条)与 resourceReferenceInput.test.tsx(20 条)全绿
|
2026-09-11 16:53:33 +08:00 |
|
suzmii
|
3de2ec734b
|
预览门禁去掉单点依赖:observer 必定建出,并加可见性兜底扫描
真机现场:整个「UI 交互」栏(51 项)全部只剩占位图标。根因链是**同一对文件里的两个缺陷叠加**,因此作为一个原子提交收口(拆成两半任一半都不足以独立成立:光修 observer 仍可能被回调时序漏掉,光加扫描则 observer 根本不存在时的判定精度无从恢复)。
一、确认 2a157ea6f 引入的回归:observer 可能永远不建
- 那次改动把「root 为 null」当成「不建 observer」,并指望「卡片注册」作为 root 就绪信号重建。
- 但那个 effect 每个 scope **实际只跑一次**(`requestPreview` 是稳定引用,其余依赖在 scope 内不变),一旦这一轮因 root 为 null 直接返回就再无第二次机会;
- 而「卡片注册」信号只在**后续还有新注册**时才来 —— 初次挂载时 51 张卡一次性注册完,之后再没有新注册,信号永不再来。
- 结果:observer 永不创建、所有卡永不被观察、永远停在 `idle`,整栏只剩占位图标(`categoryIcons['ui-interaction'] = LayoutGrid`)。
- 修复:**绝不拿"root 未就绪"当作"不建 observer"的理由**。拿到容器 root 最好;拿不到就退到 canvas、再退到视口(`root: null`)。视口判定严格优于"没有 observer"——屏幕上真实可见的卡一定落在视口矩形内;代价只是视口外但仍在画布内的卡晚一步由滚动触发。容器 root 迟到时再有界重试(5 次、退避到 512ms)重建一次,恢复按画本容器裁剪的精确判定。
二、系统性防线:可见性兜底扫描(去掉单点依赖)
- 门禁原先只有 `IntersectionObserver` 一条路,observer 没建出来 / 回调没送达 / 注册与创建交错,卡片就再无第二次机会。
- 新增 `sweepVisiblePreviews()`:按**与 observer 对齐的视口几何判据**(含 160px 余量)独立复核登记表,只对 `idle`(从未请求)的卡补一次 `visible`,`loading`/`queued`/`loaded`/`failed` 一律不碰。
- **不是第二条加载通路**:仍经 `requestPreview` 走同一条队列,受同一套去重、优先级、全局 3 槽与 `48` 项 / `64 MiB` 预算约束;**只补"可判定为可见"的卡,不退化成全量预读**(PRD §3.3.2 合同不变)。
- 触发点:scope 变化后 0 / 250 / 1000ms 各扫一次(覆盖晚挂载的卡)、observer 就绪后立即扫一次、每次卡片注册时扫一次、窗口 resize 与页面重新可见时各扫一次。
测试(`tests/useProjectResourceCardPreviews.test.ts`)
- 更新「等真正的 root 就绪」这条契约:它原先断言"root 为 null 时不得建 observer"——那正是本轮回归的语义,改为断言"**仍然必须建出 observer**(`root: null` 视口判定)",并要求容器 root 迟到后重建为容器 root;
- 新增「不在热预取窗口内的可见卡不得停在 idle」(20 张 / 热预取 12);
- 新增「没有任何 observer 回调时,可见卡也必须被放行」——完全不触发回调,只靠几何可见性,专门守住兜底扫描;
- 新增「视口外的已登记卡不得被请求」——守住"不退化成全量预读"。
变异验证:把兜底扫描改成直接 `return 0`(等价于去掉扫描)→ 「没有任何 observer 回调时可见卡也必须被放行」立即失败(`expected undefined to be 'loaded'`);恢复后 21/21 通过。断言不是恒真假守卫。
可观测性:`data-preview-status` / `data-preview-error` 已在位,因此"可见却 idle"这类问题以后读一个 DOM 属性即可判定,不再需要解码图片或猜调度。
验证(同一时刻、同一命令的前后对照;树上另有并发改动):改动前 `86 files / 1225 passed / 4 skipped / 0 failed`;改动后 `86 files / 1227 passed / 4 skipped / 0 failed`(多出的 2 条为本提交新增用例)。`src/components/image-editor` 1385 passed;typecheck exit 0;check:encoding 4384 文件;prettier 与 eslint 干净;`git diff --check` 干净。
|
2026-09-11 16:49:09 +08:00 |
|
suzmii
|
e088ede217
|
让「当前使用」的资源卡真的发光,并修掉版本高亮的两处既有缺陷
- .game-resource-card.is-current-version 的外向扩散层从 0 0 0 2px /14%(那是描边)改成 0 0 0 3px /32% + 0 0 20px /34% 的真发光,边框口径仍是 1px
- 新增 game-resource-card-current-version-breathe 关键帧与 ::after 光环(只呼吸 opacity,照本页 .ui-editor-status-attention 的既有写法):声明档位 38%,呼吸区间 0.58 ↔ 1,视觉上约 22% ↔ 38%
- prefers-reduced-motion 下关掉动画但保留可见档位(opacity 0.75),收敛动画不收敛可见性
- 缺陷①:.game-resource-card.is-relation-version-binding 原本只写 border-color,而卡片本体 border: 0,规则从未生效(「点击版本卡后高亮绑定资源」这条 PRD 语义一直看不见);改为真实的 1px 边框 + ring,并把该规则移到 .is-current-version 之前,让常驻光环在两者同时命中时仍然赢
- 缺陷②:当前版本规则与 hover/focus/selected 同权重且在后,导致当前版本卡几乎看不出被选中;新增 .is-current-version.is-selected / :hover / :focus-within / .is-dragging 变体,显式提权到 (0,3,0) 并带上选中态自己那层阴影
- 测试:新增 resourceCardCurrentVersionStyle.test.ts(外向扩散层档位、呼吸实现方式与关键帧区间、选中态变体权重更高、reduced-motion 下仍可见、版本聚焦规则不再是死规则)
- 测试:resourceVersionSwitch 补 data-used-by-current-version 的 DOM 断言(命中为 "true"、未命中不含该属性、切版本后跟着换手)
- 测试夹具修正:project-development.suite 的版本夹具把 slotId 从 'player' 改成恒等映射 'asset:asset-player'('historical' → 'asset:asset-removed'),否则「当前版本」判定永远不命中、照它写断言必成假守卫;并补一条自证断言钉住它真的命中
|
2026-09-11 16:42:58 +08:00 |
|
suzmii
|
082d8d62de
|
把运行页签的资源信息搬到画布选中工具条,两处共用一份只读字段
- 新增「信息」动作:排在「@引用」之后、「编辑标签」之前,点开非模态信息浮层,再点一次即收起(aria-pressed 反映开合)
- 新增只读字段组件 ResourceInfoFieldsView(ResourceInfoPanelView.tsx),运行页签的「信息展示」与画布浮层共用,字段清单只在 resolveResourceInfoFieldRows 单点定义
- 字段口径:名称 / 路径 / 类型 + 只读分类与标签(分类取画布栏目口径,标签为空时给「暂无标签」),刻意不含来源任务、资产 ID 等 manifest 内部标识
- 浮层 chrome 与快速编辑浮层同族(同一锚点、同档圆角与阴影),样式落在 resourceCanvasChrome.css;关闭入口三处:动作按钮、面板关闭按钮、画布浮层统一的 Esc / 点边界外
- 关闭判据收口:resolveResourceCanvasQuickEditDismissOpen 更名为 resolveResourceCanvasFloatingPanelDismissOpen、参数改为 isFloatingPanelOpen,宿主把「快速编辑开」与「信息开」合成一个布尔
- .game-resource-info-panel 补进 RESOURCE_CANVAS_INTERACTION_SELECTOR,否则点在只读字段值上会被判成点画布空白而清掉选中
- 换选 / 清空选中 / 切项目时信息浮层自动收起:在选中派生处按 selectedResourceId 收口,不在每个重置点各补一行
- 测试:新增 resourceCanvasInfoModel 三条与「信息面板在画布浮层与运行页签里渲染同一份只读字段」(工具条顺序、开合、关闭按钮只收面板、点画布以外收起、Esc 连工具条一起收、运行页签字段逐行相同);resourceCanvasFloatingDismiss 的判据用例与交互选择器夹具改为浮层通用口径
- resourceCanvasChrome.css 的字段排版收在浮层选择器下,不跨上下文改运行页签的字号与列宽
- decision-log 新增一条:记录这次对 Issue #309 C3「取消资源详情面板」的有意局部回退(只回退「看信息」,不恢复可编辑详情面板与全屏编辑路由),并登记需要同步的 C3 引用位置
|
2026-09-11 16:42:40 +08:00 |
|
suzmii
|
d80011656b
|
把资源卡的 @ 引用入口挪进选中资源工具条
- 删除资源卡右上角的 @ 引用圆钮,连同它的 44×44 热区伪元素与全部样式一起退役
- 选中资源的画布工具条新增「@引用」动作,排在「UI 编辑器」之后,出站负载与来源逐字未变(source: 'resource-card')
- 测试:原「卡片圆钮视觉尺寸」用例改写为「入口只留在工具条里」的源级 + 样式级断言,并补反向断言卡片组件内不再出现插入调用
- 测试:新增键盘通路用例,聚焦后回车必须真正派发 agc-resource-reference-insert,且两条通路负载逐字相同、每次激活只派发一次
- harness 注释同步:资源卡本体只剩「选中资源」与媒体播放钮
|
2026-09-11 16:28:48 +08:00 |
|
suzmii
|
3bfc3f0849
|
新增 AGC 资源工作台 V3 端到端验收用例
Project CI / Repository checks (pull_request) Successful in 3m14s
Project CI / Frontend tests (pull_request) Successful in 3m39s
Project CI / Backend tests (pull_request) Failing after 6m3s
Project CI / Native shell tests (pull_request) Failing after 6m22s
- 新增 docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md
- 给出从建项、对话生成素材到运行收尾的一条主线,按 S1–S21 分步列出操作、期望结果、对应 PRD 条款、可观察判据与已知例外
- 落定每步的可观察形态:资源卡 data-preview-kind / data-preview-status / data-preview-error、data-used-by-current-version、data-resource-book-category、data-resource-view-state 与控制台判据脚本
- 记录前置条件与门禁口径:dev 态前端走 Vite HMR、Rust 侧改动需重启客户端、服务端改动需重启 api-server、build-gate 代理假红需清代理变量
- 列出只靠视频或截图表达的 7 处条款清单与补齐方式,并更正 F12 无法观察 Rust 到 api-server 的 HTTP 这一前提
- 列出已知未做与已取消项,以及文档与代码的四处偏差(资源卡打开详情按钮、运行入口提示文案、画布生成入口进度、真机落盘分类分布)
- 附验收记录表与逐步骤失败排查索引
|
2026-09-11 15:45:01 +08:00 |
|
suzmii
|
5bf348d88a
|
把误入上一个提交的验收文档移出提交,交回其作者
- 81482b458 原本只应包含 tests/appSurface/project-development.suite.ts 一条断言同步,
但该验收文档当时已被预置在索引里,随那次提交一起被带走;这里只做「移出提交」,
不改动文档内容,文档仍完整保留在工作区,由它的作者自行提交
- 未使用 --amend:按纪律不改写已有提交
|
2026-09-11 15:42:24 +08:00 |
|
suzmii
|
81482b4587
|
同步「编辑标签」入口文案的既有断言
- tests/appSurface/project-development.suite.ts:跟随 6bdc8bbd9 把资源卡工具条入口由
「分类与标签」改为「编辑标签」,同步音频资源工具条那条既有断言(否则 HEAD 的树是红的)
- 同一处注释同步说明:该面板只编辑 manifest `assets[].tags`,分类不再有手动入口
|
2026-09-11 15:41:17 +08:00 |
|
suzmii
|
ea9668e4a9
|
资源总览「所有资源」卡补上资源预览
- 现象与归因:「所有资源」卡的预览区一直是空的,原因是我在 1cc9a2381 里加的闸 `!isResourceBookAllTarget(category)`(当时为避开"同一张资源卡挂两份")让它一张卡片计划都不生成;预览卡从来只由画本场景注入,占位摞本身不含卡片
- 改为真正生成卡片计划:喂进去的仍是全量投影(与标题栏计数徽标同一份口径),但当成一摞交给栏目卡同一个选取函数 `selectResourceBookOverviewCards`——没有视图中心时按投影顺序取前 `RESOURCE_BOOK_OVERVIEW_STACK_LIMIT`(=3) 张,与栏目卡每摞上限同口径;不分类型列,避免 66 项一口气铺出十几列
- 预览用同一个 `renderCard` 与同一套 `resourceBookOverviewCardLayout` 几何渲染,唯一差别是宿主类名换成 `.game-resource-book-preview-card`:转场层按 `.game-resource-book-scene-card` 收集 FLIP 节点并以 `card:${resourceId}` 为键,同一张卡两个宿主会让两条记录互相顶掉,因此预览宿主不进收集范围(`resourceBookController` 一行未改)
- 预览宿主带 `aria-hidden` + `pointer-events: none`:无障碍树与 `getByRole` 里每张资源卡仍然只有栏目卡那一份,不会重复命中;点预览区仍走进「所有资源」页,栏目卡预览卡仍可选中
- 转场淡出集合(`resourceBookPlanKeysRef` 那段)跳过「所有资源」组,避免预览节点被当成"离开视图的卡片"按子画布坐标重新贴到画布别处
- `ResourceBookThumbnail`:该卡正文的占位摞从最多 3 摞改为对齐真实摞数的 1 摞,卡片高度回到与栏目卡同一条口径
- `styles.css`:把 `.game-resource-book-preview-card` 并进 `.game-resource-book-scene-card` 的两条既有规则(宿主几何、主态卡片圆角/阴影)与 `prefers-reduced-motion`;没有新写一套预览样式
- 断言(`project-development.suite.ts`):预览卡的 `data-resource-card-id` 序列恰等于全量投影前 3 个、条数 == `min(3, 计数徽标)`、预览里是完整卡片(视觉体 + 选中按钮)、宿主在 `aria-hidden` 子树里、宿主几何规则与栏目卡共用同一条
- 断言(新增用例):空项目下沿用既有空态「暂无资源」、预览节点 0 个、计数 `0 项`、入口行仍在,卡片不破版
- 单测(`resourceBookLayout.test.ts`):把已被新行为取代的旧断言(`all` 组不出卡片、全计划无重复卡片键)删掉,换成新合同(预览 = 全量投影前 3 张、只铺一列、各栏目摞不变),并补空投影 → 0 张
- 变异验证(两条都跑过并已还原):① 喂进去的资源改回空数组 → `AssertionError: expected +0 to be 4`;② 恢复旧闸(计数仍正确、预览为 0)→ `AssertionError: expected [] to deeply equal [ Array(3) ]`
|
2026-09-11 15:37:17 +08:00 |
|
suzmii
|
6bdc8bbd93
|
AGC 资源分类面板改为「编辑素材标签」:只编辑标签、分类不再有手动入口
- ResourceClassificationPanel.tsx:面板改为只编辑 manifest `assets[].tags`,移除分类 6 类
chip 那一排;标题与 aria-label 改「编辑素材标签」,副标题由资源路径改为素材名
(`localPath` 的 basename,仓库没有独立显示名字段)
- ResourceClassificationPanel.tsx:标签状态由「整段逗号分隔字符串」改为字符串数组,
每个已有标签渲染成自带删除按钮的胶囊 pill;保存时把未落成 pill 的输入尾巴一并切分,
归一化仍走 `normalizeGameCreationAppAssetTags`,回车 / 中英文逗号 / 顿号提交方式不变
- ResourceClassificationPanel.tsx:写入命令 `category` 是必填,读一次当前权威值并
在保存时原样回传,保证「只改标签」不漂移分类;底部按钮「保存」改「保存标签」,
删除资源入口按既有保留(截图未画,但它是唯一的资源删除入口)
- resourceClassificationTagPanel.css:新增标签 pill 样式,删除按钮嵌在 pill 内部
(视觉图标 11px、热区 32×32、负外边距抵消不撑高 pill);若第二处出现带删除按钮的
标签 pill,抽到 `packages/shared` 做 `PlatformRemovableTagPill`,不要复制这份实现
- index.tsx:资源卡工具条入口 label / title / 文案由「分类与标签」改「编辑标签」,
与面板标题一致(否则入口写着分类、打开的是纯标签面板)
- tests/resourceClassificationPanel.test.tsx:断言对齐截图文案、逐个 pill 自带删除、
点某个 × 只删对应标签、aria-label 可区分、保存 payload 的 tags 数组与 category 原样回传、
取消不写盘、入口 label 与面板标题一致、删除热区不小于 32px
- 文档:PRD §5.3「分类取值优先级」改写为当前状态(分类没有用户手动设置入口、
该面板只编辑标签),并新增 decision-log 条目记录本次产品决策与「用户手动设
category 的能力就此移除」这一事实
- 说明:截图文案(编辑素材标签 / 新增标签,多个用逗号分隔 / 保存标签)在全仓检索
无命中,属仓库之外来源,本次按用户截图对齐,不是 PRD 明文
|
2026-09-11 15:34:36 +08:00 |
|
suzmii
|
2a157ea6f8
|
预览可见性门禁等 root 就绪再建 observer,并把登记表补挂齐
真机现场:同一屏里 `idle` 与 `loaded` 交错,12 张相交卡从未入队(`idle` 的语义就是「从未请求」,不是被淘汰回退)。根因在 observer 的**创建时机**:`useProjectResourceCardPreviews` 建 IntersectionObserver 时把 `intersectionRootRef.current ?? canvasRef.current` 直接当 root 传下去,而 root 是资源画本容器、由被观察卡片所在的子树持有。创建那一刻 root 还是 `null` 时,浏览器会**退回按视口判定**,于是被画本容器裁掉的卡片永远报「不可见」,可见性门禁再也不放行它们 —— 卡面只剩占位图标,且没有任何错误提示。
- `useProjectResourceCardPreviews`:root 为 `null` 时**不建 observer**,等 root 就绪后由卡片注册触发的 `rootEpoch` 重建。判据是「卡片一定渲染在画本容器内部」,所以**有卡片注册本身就等价于 root 已就绪**,不需要新增跨组件契约、也不必改 `index.tsx`。
- 新增 `attachObservedCards()`:observer 就绪或重建后,按登记表把**每一张已注册卡片**补挂一遍。此前只在新 observer 创建时补挂一次,注册与创建分属不同 effect 存在时序窗口,错过那次补挂的卡会停在登记表里却从未被观察。
- root 未变且 observer 已在时只补挂、不重建,避免每次渲染重建观察器。
- `observePreview` 在没有 `IntersectionObserver` 的环境(如单测)保持静默,不制造多余渲染。
测试(`tests/useProjectResourceCardPreviews.test.ts` 新增 3 条契约):
- 「不在热预取窗口内的可见卡不得停在 idle」:注册 20 张、`eagerPreviewLimit: 12`,断言注册进 observer 的元素数等于注册数、回调报可见后全部落 `loaded`、且没有一张停留在未请求状态;
- 「等真正的 root 就绪后再观察并加载」:root 首次为 `null` 时**不得建 observer**(否则按视口判定),注册后必须以真正的 root 建出来并完成加载;
- 「建 observer 之前就注册的卡要被补挂」:断言 observer 就绪后登记表里的每一张都在观察集合内,且报可见后全部 `loaded`。
变异验证:把 root 守卫退回旧行为(允许 root 为 `null` 时照建 observer)后,「等真正的 root 就绪」这条立即失败(`expected [] to have a length of 0 but got 1`);恢复后 19/19 通过。断言不是恒真假守卫。
边界说明:只改预览 hook 与它的测试,未动 `index.tsx` / `styles.css` / `resourceBookLayout.ts`(均在他人手上),未放宽任何既有断言、未取消可见性门禁(仍是按需加载)。
验证:`npm run test -- apps/ai-game-creator-shell/tests` 84 files passed / 1216 passed / 4 skipped / 0 failed;`src/components/image-editor` 1385 passed;typecheck exit 0;check:encoding 4379 文件;prettier 与 eslint 干净;`git diff --check` 干净。
|
2026-09-11 15:31:29 +08:00 |
|
suzmii
|
39a96aa418
|
模型下拉菜单高度有界:健壮性补强,条目多时不再盖住消息
Project CI / Repository checks (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / Backend tests (pull_request) Successful in 6m29s
Project CI / Native shell tests (pull_request) Successful in 18m34s
- 性质说明:本条是**健壮性补强**,不是用户本轮报的那几条之一;由上一提交(锚点修复)排查时顺手发现的未修观察收尾。
- styles.css:`.conversation-model-menu` 补 `max-height: min(240px, 40vh)` + `overflow: auto` + `overscroll-behavior: contain`。菜单此前没有高度上限,模型条目很多时会长到 composer 顶边以上、盖住消息;240px 与视口 40vh 取小者,矮窗 / 窄屏(移动端优先)更稳。`overscroll-behavior: contain` 与既有 `.resource-reference-menu` 同一口径,菜单内滚动不传染给背后的消息列表。不新增视觉:border / radius / padding 与 token 全部沿用原值。
- 语义零改动:锚点与流式归属不变(`position: absolute; right: 0; bottom: calc(100% + 8px)` 未动),打开 / 选择 / 刷新模型列表的交互不变。
- project-development.suite.ts:新增声明级守卫「bounds the model dropdown height so a long catalog cannot cover the composer」——菜单规则必须同时声明 `max-height: min(240px, 40vh)`、`overflow: auto`、`overscroll-behavior: contain`,并仍是 `position: absolute`(高度有界不改锚点)。
- 变异验证(已实测):把 `max-height` 摘掉 → 用例红灯(expected '…' to match /max-height:\s*min\(\s*240px\s*,\s*40vh\s*\)/u)。
- 门禁:typecheck exit 0;AGC 子集 1206 passed / 4 skipped / 0 failed(基线 1205 + 本次新增 1 条);src/components/image-editor 1385 passed;check:encoding 4379 文件通过;`git diff --check` 全量与本次两个文件范围均 exit 0。
|
2026-09-11 14:59:42 +08:00 |
|
suzmii
|
4f3516a5d9
|
修复分类 chip 条叠字:分段页签子项补内容宽度下限
- packages/shared/src/components/PlatformSegmentedTabs.tsx:容器补 `platform-segmented-tabs`
稳定钩子类,供共享样式表精确命中子项(不依赖 Tailwind 任意值扫描)
- packages/shared/src/components/styles.css:新增 `.platform-segmented-tabs > button`
的 `min-width: max-content`,给每个子项一个「内容宽度」下限;放不下时由容器横向滚动兜底
- apps/ai-game-creator-shell/src/view/project-development/ResourceClassificationPanel.tsx:
「资源分类与标签」的 6 类分类条由 `columns="threeToSix"` 改为 `layout="scroll"` +
`frame="bare"` + `surface="transparent"`,与 `@` 面板和资源画布的分类条同一条既有形态
- packages/shared/src/components/PlatformResourceFilterBar.test.tsx:新增两条声明级断言,
钉住「子项不换行 + 容器横向可滚 + 子项有不低于内容宽度的下限」
|
2026-09-11 14:54:27 +08:00 |
|
suzmii
|
ff287c19fc
|
模型下拉弹层改回以触发钮为锚点,输入区高度变化不再牵动它
- styles.css:direct-codex 控制排里的 .conversation-model-select 由 position: static !important 改为 position: relative !important(只改这一个词,另补一段注释说明它同时是弹层锚点)。这颗容器是 .conversation-model-menu(position: absolute; right: 0; bottom: calc(100% + 8px))与模型提示([role='alert'] / [role='status'],position: absolute; bottom: 36px)的包含块;写成 static 时包含块回落到最近的有定位祖先——也就是 position: absolute 的 composer 盒子,菜单于是出现在整个 composer 上方、与触发钮之间隔着整个输入区,输入区一变高(多行、引用 chip、AI 润色)菜单与提示跟着往上飘:这就是"编辑框把元素挤开"的形态。
- styles.css:right/bottom: auto、flex: 0 1 auto、min-width: 0、max-width: 180px 全部保持原样——relative 不会把模型选择钮移出控制排,也不改它被压缩换行的行为;交互语义零改动(打开/选择/刷新模型、发送、@ 引用、快速模式全在原位)。
- project-development.suite.ts:新增一条声明级守卫。① 弹层不参与父容器流式布局(菜单与模型提示都是 absolute,菜单 bottom: calc(100% + …)、right: 0);② 锚点必须是有定位的容器(relative 且不是 static),同时仍在原位(right/bottom auto、flex 0 1 auto、min-width 0);③ 输入区高度变化不得推动同排操作元素(composer 是底边锚定的两行网格:position: absolute + bottom: 12px + grid-template-rows: auto auto;控制排是单行 flex 且不换行,@ 与发送钮 flex: 0 0 30px 不可压缩,窄屏下被压缩的是可收缩的模型选择钮)。
- conversationModelSelect.test.tsx:新增一条 DOM 守卫——菜单必须是 .conversation-model-select 的直接子节点,且不得成为控制排容器 .project-supervisor-composer-controls-right 的流式子节点。
- 变异验证(均已实测红灯):① 锚点改回 static → "expected … to match /position:\s*relative\s*!important/";② 菜单改成 position: static(弹层回流式)→ "expected … to match /position:\s*absolute/";③ 把菜单渲染挪出触发钮容器(变成同排容器的流式子节点)→ DOM 守卫 "expected null not to be null"。
- 门禁:typecheck exit 0;AGC 子集 1205 passed / 4 skipped / 0 failed(基线 1203 + 本次新增 2 条);src/components/image-editor 1385 passed;check:encoding 4379 文件通过;本次三个文件单独 `git diff --check` exit 0(全量 `git diff --check` 目前报的是另一位 Agent 在途修改的 packages/shared/src/components/styles.css:1281 EOF 空行,与本次无关)。
|
2026-09-11 14:53:11 +08:00 |
|
suzmii
|
9c4778c881
|
把资源卡右下角的 @ 引用按钮做小:视觉缩到 0.8×,热区保持 44×44
Project CI / Repository checks (pull_request) Successful in 3m31s
Project CI / Frontend tests (pull_request) Successful in 4m24s
Project CI / Backend tests (pull_request) Successful in 6m51s
Project CI / Native shell tests (pull_request) Successful in 18m37s
- styles.css:`.game-resource-card-reference` 视觉尺寸 30×30 → 24×24(0.8×),新加 `.game-resource-card-reference svg` 把内部 `AtSign` 图标同步从 16px 收到 13px,圆角/描边/底色/阴影 token 全部沿用原值。
- styles.css:`bottom` 由 9px 调到 14px,与右侧 34px 播放钮圆心对齐(9 + 34 / 2 = 14 + 24 / 2),避免缩小后两颗圆钮错位。
- styles.css:新增 `.game-resource-card-reference::before` 伪元素撑出 44×44 热区(本仓 window chrome / 素材选择列表同口径的最小可点尺寸),只扩命中范围、不参与布局也不改变绘制;整体向左让 4px,让热区右边缘与播放钮的盒子恰好相邻,不覆盖它。
- 语义未动:仍是「插入素材引用」入口,className、`aria-label={\`引用资源 ${resource.label}\`}`、key 与 `dispatchResourceReferenceInsert` 调用一行未改,本次只改 CSS 声明。
- 测试:新增 CSS 声明级用例,断言按钮与图标确实变小(24 < 30、13 < 16)、热区不小于 44×44、热区右边缘不越过播放钮盒子,并顺带钉住 `index.tsx` 里 @ 入口的 className / aria-label / 插入动作仍在。
- 变异验证:把按钮改回 30px、热区改回 24px、图标改回 16px → 用例红灯;只把热区缩回 24px → 用例同样红灯(均已实测,随后原样还原)。
|
2026-09-11 14:00:32 +08:00 |
|
suzmii
|
399fcacb8e
|
同步删除栏目大纲导航的权威文档:PRD 与技术方案改成总览卡片/所有资源/下一页口径
- decision-log.md:新增「删除资源画布左侧的栏目大纲悬浮导航」决策,先记录查证结论(decision-log / pitfalls / PRD / 技术方案里此前只有要求保留该导航的条文,没有任何删除决策,本次是新决策),再写决策、语义变更、不做什么与变异验证结果。
- PRD:§阶段说明与 §7.2 验收两条不再写"大纲展示全部栏目""通过栏目大纲和滚轮访问全部栏目",改为栏目大纲由「资源总览」的栏目缩略卡片承载、切栏目走总览卡片 +「所有资源」入口 + 页内「下一页」。
- 技术方案:目标、操作边界与验收共五处删掉"栏目大纲悬浮在画布左侧/只显示图标和栏目名称""点击大纲切页""分页大纲以悬浮 Dock 展示全部栏目"等条文,改成当前状态(左侧不再有悬浮导航)。
- pitfalls.md:补记一条 jsdom 坑——栏目页 fit key 需要非零可测量尺寸,jsdom 下恒为 0,所以"总览→栏目"往返会重新适配视口;进入栏目后应恢复上次 viewport 的用例在 jsdom 里必须避免这种往返,不要误判成产品回归、也不要去改 fit 逻辑。
|
2026-09-11 13:54:27 +08:00 |
|
suzmii
|
29b99f213d
|
删除资源画布左侧的栏目大纲导航:切栏目改由总览卡片、所有资源入口与下一页承担
- index.tsx:整条删除 `ResourceBookOutlineNav` 组件(`.game-resource-outline` / `aria-label="资源栏目大纲"` / 栏目图标 + 栏目文字 + 未读红点)以及栏目页与「所有资源」页两处挂载点;`ResourceBookAllResourcesPage` 不再接收只服务导航的 `unreadCategories` / `onOpen`。
- index.tsx:一并清掉只服务这条导航的「未读红点」状态链(`unreadResourceCategoryState`、`resourceCategorySnapshotRef`、`resourceIdsByCategory`、`viewedResourceCategory` 与对应 effect),不留死代码、不留墓碑注释。
- styles.css:删除全部 `.game-resource-outline*` 规则(悬浮 Dock、常态/悬停态、图标列宽、`.game-resource-outline-label`、`.game-resource-outline-unread`,共 106 行)。
- 保留未动:分区栏目本身、栏目标题栏、资源卡、总览缩略卡片、第 0 张「所有资源」入口、「所有资源」汇总页、搜索、总览、快速编辑与选中工具条。
- 测试:切栏目 helper(appSurface/project-development、appSurface/home、resourceRename、resourceVersionSwitch)改为走「资源总览」缩略卡片(需要时先点「收起资源」回总览),不再点导航;栏目顺序断言改钉在总览缩略卡片的 `aria-label` 序列上(第 0 张「所有资源」+ 固定七栏目),断言内容未放宽。
- 测试:按「四不写」删除只守这条导航的断言——大纲常态可读性的 CSS 声明用例、大纲条目图标/文字的 DOM 用例(该用例保留其中与搜索浮层相关的断言并改名)、未读红点用例(该用例保留"只登记游戏代码的项目照常分页并落在待归类"的部分并改名)、空项目与「所有资源」页里的导航存在性断言。
- 测试:新增反向守卫 + 切栏目链路用例(`has no outline nav and keeps every section reachable through the remaining entries`):断言 `资源栏目大纲` 标签、`.game-resource-outline` 类名与对应 `<nav>` 都不再渲染,并逐条走通总览缩略卡片(含空栏目)→ 下一页 → 标题栏「资源总览」→ 「所有资源」入口往返。
- 测试:`preserves independent art viewports across sort and workbench mode switches` 保留全部原断言,只去掉排序模式切换后那些"点当前栏目"的空操作往返(旧导航点在同栏目上是提前返回,改 helper 后变成真实往返,会在 jsdom 里触发重新 fit)。
|
2026-09-11 13:54:12 +08:00 |
|
suzmii
|
d1a3702062
|
记录构建版本判据与 loopback HTTP 预览方案的评估结论
本轮「资源卡图片加载不出来」连续两轮按现役缺陷排查,最终发现真机数据在当前 HEAD 上判定全对、现象来自更早的客户端构建。把这次的方法教训与两条廉价判据固化成文档,避免后续重复投入。
- `pitfalls.md` 新增「用真机现象定性之前必须先确认客户端构建版本」:现象与「数据在 HEAD 上的实际判定」不一致时,第一个怀疑对象是构建版本而不是代码;两条廉价判据为真机 manifest 的 `category` 分布(旧构建 `unclassified:58`,新构建 `unclassified:1 / ui-interaction:59 / scene:1`)与控制台读 `[data-preview-kind]` 分布(新构建应为 `raster-image 52 / document 8 / code 1` 且无 `placeholder`)。同时记下方法教训:不要用 PowerShell 或手抄正则去复算 TS 判定表,誊本会引入假结论——本轮第一轮取证正是因此得出了不存在的「角标与预览分支分叉」,要跑就用真实源码跑。
- `decision-log.md` 新增「AGC 资源预览评估过本机 loopback HTTP 资源服务,因收益不匹配代价而暂缓」,并显式注明它与既有的 `asset:` 否决**不是同一条、理由不同**:`asset:` 是「绕过安全门禁」的原则问题,loopback HTTP 是「收益与代价不匹配」的性价比问题,其门禁可在服务端逐条复用。记录内容含 token 化不暴露路径、服务端复用同一套校验、与「不向 WebView 暴露本机文件协议」合同的关系判定(同构替代而非违约,信任边界转移到 token 与审计),以及流式下**必须明确降级的三条**:读取漂移复核、全局 3 permit 并发闸门、有界 tombstone 预算。量化依据为真机 52 张 PNG 的分层实测——Rust + JS 冷开合计约 0.3 s,大头是已由 `decoding="async"` 移出主线程的 37.2 MPx 解码,因此当前量级下 0.3 s 换「不向 WebView 暴露文件系统」这条合同是划算交易;并写明重新评估的触发条件(几百张资源 / 大图量级)。
- 同批一并暂缓的备选也记在案:Tauri 自定义协议(非 `asset:`)+ opaque token 因拿不到 HTTP 缓存语义而比 loopback HTTP 更差;磁盘缩略图缓存与 PRD 的纯内存口径及既有价值观冲突。
- 纯文档改动,无代码改动;`tauri.conf.json`、`Cargo.toml`、Rust 命令与 CSP 均未动。
|
2026-09-11 13:30:26 +08:00 |
|
suzmii
|
083df0aa2c
|
类型判定改为内容证据优先:消除 kind 与内容证据同权的脆弱点
**口径更正(后加的)**:本条提交**不是**"修复真机那 8 张卡"。经用真源码复算真机 manifest 确认,当前 HEAD 上这 8 张 `.json` 本就正确落 `document`、角标「文档」,该现象不复现——用户看到的是更早构建的行为。本条的真实价值是**消除 `kind` 与内容证据同权的脆弱点,并把「派发决策由内容证据决定」钉成不变量**,见下。
- 背景:真机「待归类」栏目曾出现 8 张卡——角标写「图片」、卡面却只有占位图标。该现象在当前 HEAD 上不复现(见上),但它对应一个**真实存在且此前只是碰巧没被踩中的脆弱点**:`projectedResourceKind` 里 `kind` 与内容证据**同权**,而 `artKind` 含 `ui`;一旦界面规格类资产丢了 `.json` 扩展名,预览派发就会翻车。原先能通过**完全依赖 `.json` 恰好在那张扩展名表里**,属巧合而非设计。
- 机制(旧行为):`kind:"UI"` 被 `artKind` 命中 → 判定为 `art`;预览调度再按 `art` 走图像分支,又因 `mediaType:"application/json"` 不是图像而兜底成 `placeholder`,而 `requestPreview` 对 `placeholder` 直接返回——**一次 IPC 都不发**,卡片既不报错也没有图。
- `resourceProjectionModel.ts` 的 `projectedResourceKind` 改为两趟判定:第一趟只看 `mediaType` 与路径扩展名(文件内容的可验证证据),第二趟才看 `kind`(登记标签,可能过时或宽泛)。`kind` 不再与内容证据同权,因此 `kind:"ui"` 无法再把 `application/json` 改写成 `art`。
- 不改 `artKind` 的取值(`ui` 仍在表内),只调整判定顺序:最小改动面,`kind:"ui"` + `image/png` 这类正常资产行为不变(仍由 `mediaType` 命中)。
- 界面规格 JSON 现在落 `document` 分支,走 `read_local_project_text_preview`(Rust 侧 `.json` 与 `application/json` 本就在支持范围内且这些条目已登记),卡面渲染 JSON 文本摘要,角标随 `projectResourceTypeLabel` 变为「文档」。预览链条数、缓存、并发与门禁一律未动。
- `tests/resourceCardPreviewRealManifest.test.ts` 中记录旧行为的用例同步跟上新合同:`placeholder: 8` 改为 `document: 8`,用例名与注释改写为「52 张 PNG 走图片分支,8 条 UI 规格走文档分支,无一落占位」,并新增「角标与预览分支必须说同一件事」的逐条断言。旧断言记录的是缺陷本身,按「四不写」直接更新为当前权威行为,不做兼容保留;其余既有断言一条未放宽。
- 验证:`npm run test -- apps/ai-game-creator-shell/tests` 1202 passed / 4 skipped / 0 failed;`src/components/image-editor` 1385 passed;typecheck exit 0;check:encoding 通过;`git diff --check` 干净。
|
2026-09-11 13:30:14 +08:00 |
|
suzmii
|
5a37ccfd2b
|
资源卡暴露预览状态,并补「派发决策由内容证据决定」的契约用例
占位分支对「还没读 / 读失败 / 压根不适用」给的是同一个图标,卡面也没有任何文字提示,现场只能看到「没有图」,无法区分是调度没发请求、原生拒绝了读取,还是这类资源本来就没有预览。本轮排查「图片加载不出来」时,这个不可观测性正是反复误判的来源。
- `index.tsx` 的 `ResourceCard` 根节点新增 `data-preview-status`(`idle` / `loading` / `loaded` / `failed`)与 `data-preview-error`(仅失败态给出原因)。排障从此只需读一个 DOM 属性,而不必去猜调度层。纯新增属性,不改变任何渲染分支与调度行为。
- 新增 `tests/resourcePreviewPredicateContract.test.ts`:把「派发决策必须由可验证的内容证据(mediaType,缺失时退路径扩展名)决定,登记标签 `kind` 只能在两者都给不出结论时兜底」钉成不变量。用 7 组「mediaType + 同通道扩展名」× 20 个 `kind` 的全量笛卡尔积断言 `kind` 不能改写预览分支,并断言组合总数等于笛卡尔积(防止将来收窄枚举让断言悄悄变弱)。另附本轮缺陷形状的最小复现(`application/json` + 任意图片类 `kind` 必须落 `document` 而不是 `placeholder`)、mediaType 缺失时由扩展名决定、以及扩展名与 mediaType 冲突时按既有设计由扩展名决定这三组显式合同。
- 该用例在**变异验证**下确实会红:把 `kind` 判定移回第一趟(还原本轮的分叉)后,本文件 4 条用例失败(不变量报出 56 处违规),恢复后全绿。断言不是恒真假守卫。
- 验证:`npm run test -- apps/ai-game-creator-shell/tests` 84 files passed / 1202 passed / 4 skipped / 0 failed;`src/components/image-editor` 1385 passed;typecheck exit 0;prettier 与 eslint 干净;check:encoding 4378 文件;`git diff --check` 干净。
|
2026-09-11 13:26:17 +08:00 |
|
suzmii
|
cd1aceb3d9
|
按 rustfmt 折行退出收尾用例:纯格式,无逻辑变更
Project CI / Frontend tests (pull_request) Successful in 7m44s
Project CI / Backend tests (pull_request) Successful in 10m11s
Project CI / Repository checks (pull_request) Successful in 23m14s
Project CI / Native shell tests (pull_request) Successful in 38m54s
- tests/project.rs:gui_exit_stops_the_preview_and_clears_the_stale_record 里 init_existing_html_project_at 那行超过 rustfmt 行宽(中文按双宽计),按 `cargo fmt --all --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 的结果折成两行。
- 用 cargo fmt(不是裸 rustfmt):裸 rustfmt 会把文件当模块根、跟随 mod 递归格式化整个模块树,本次已有人因此误改 50 个文件。
- 范围核对:`git diff --stat` 只有 apps/ai-game-creator-shell/src-tauri/src/tests/project.rs(1 file changed, 2 insertions(+), 1 deletion(-)),crate 里其余文件本来就是 rustfmt 干净的,未被牵连。
- 门禁:`cargo fmt --all --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check` 与 `... --manifest-path server-rs/Cargo.toml -- --check` 均 exit 0;`cargo check --locked --all-targets` exit 0;AGC 子集 1198 passed / 4 skipped / 0 failed;四条预览定向用例仍各 1 passed。
|
2026-09-11 12:47:22 +08:00 |
|
suzmii
|
531392e506
|
退出路径统一收尾本地预览,陈旧预览记录不再留给下次进入
- preview.rs:新增 stop_local_game_preview_on_exit——退出时若 registry 里确有 running 预览,就用 stop_local_game_preview_for_root 把它真正停掉并写 stopped(顺带落 preview.log 与 trace 收尾)。预览服务器是进程内线程,进程一退 URL 就永久失效;不在这里收尾,.agent/manifest.json 会一直写着 running。
- main.rs:handle_game_creator_gui_run_event 的 RunEvent::Exit 分支先做预览收尾,再做既有的 Agent server / runner 关闭;失败按 `preview.gui_exit.stop_failed` 记日志,不阻断退出。
- 语义变更(用户已确认接受):关窗不再保留预览——退出即停止预览并把记录写成 stopped,下次进项目一律落到资源管理。与"进入项目时按 registry 活体核对"配套:退出路径覆盖正常关闭,强杀 / 断电那类不走退出路径的情况由进入时的核对自愈。不引入 stale DTO 字段(判定仍要活体探测,且会把状态扩到跨端契约与全部消费方)。
- tests/project.rs:新增 gui_exit_stops_the_preview_and_clears_the_stale_record——退出收尾后 registry 归零且 manifest 记录为 stopped。
- tests/sessionPreview.test.ts:补两条 Rust 侧结构性守卫(本仓既有做法:直接解析源码)。① RunEvent::Exit 分支必须出现 preview::stop_local_game_preview_on_exit——这条接线没有别的行为测试覆盖(注册全局 registry 的用例会互相打架);② 同项目重启预览的替换分支里,收尾必须排在"旧预览是否属于另一个项目"的判定之后。变异验证:把 main.rs 的退出收尾摘掉 → 守卫①立即红灯(已实测)。
- decision-log.md:新增 2026-09-11 决策条目,逐条写下背景(偶发进运行界面的成因链:进程内预览线程 + 随机临时端口 + 退出零清理 + 进门不核对 + 同项目重启覆盖刚写的 running)、A/B/C 三项决策、关窗不再保留预览这条语义变更及"为什么两者都做而不加 stale 字段"、不做什么与验证方式。同文件被 prettier 补了几处标题前空行(格式规范化,无内容变化)。
- 验证:cargo check --locked --all-targets exit 0;Rust 定向用例 restarting_preview_for_the_same_project / replacing_preview_for_another_project / stopping_without_a_live_preview / gui_exit_stops_the_preview 各 1 passed;AGC 子集 1198 passed / 4 skipped / 0 failed;共享组件 1385 passed;typecheck exit 0;编码 4378 文件;git diff --check 干净。
|
2026-09-11 12:36:08 +08:00 |
|
suzmii
|
669c71d7e8
|
补记三条踩坑:rustfmt 作用域、build-gate 代理假红、worktree 与 MSYS bash 不兼容
- 新增「裸 rustfmt 会把一个文件放大成整个模块树」:现象是本想改 1 个文件实际改了 src/agent/ 下 50 个;原因是裸 rustfmt 把传入文件当模块根并跟随 mod 递归;处理口径是改格式前先 git diff --stat 确认范围、先备份,精确到文件用 cargo fmt -p <pkg> -- <文件...>,并行工作树里禁止盲跑 cargo fmt --all,误伤后用 git apply -R 反向补丁还原而不是 git checkout --
- 新增「build-gate 的警告门被代理环境变量误伤」:NODE_USE_ENV_PROXY=1 / HTTP_PROXY 会让 Node 打印 UNDICI EnvHttpProxyAgent experimental 警告,而 build-gate.mjs 只忽略含 ExperimentalWarning 字样的行,于是构建成功却判红;判据是同一 commit 清空这些变量后 npm run build 即 exit 0;CI 无这些变量不会触发,若要让本地带代理也能跑需在忽略规则补 EnvHttpProxyAgent / UNDICI-EHPA(属改门禁规则,本次未改)
- 新增「worktree 里 MSYS bash 跑 git 脚本会失效」:check-repository-ci.sh 这类脚本在 worktree 里第一步就报比较基线不可用,根因是 bash 用 MSYS 的 /usr/bin/git、而 worktree 的 .git 存 Windows 绝对路径,解析成 /mnt/c/.../C:/Users/...;口径是按脚本同序同命令用 Windows git 逐条等价执行
- 只追加,未重排既有条目
|
2026-09-11 12:28:58 +08:00 |
|
suzmii
|
66bb240115
|
进项目时按内存 registry 核对预览活体,陈旧记录不再冒充可运行预览
- 新增 features/app-shell/sessionPreview.ts 的 resolveSessionPreviewOnProjectOpen:进入项目时问一次 get_local_game_preview_status(内存 registry 是活体的唯一真相),确实 running 才把它作为本次会话的预览;记录写着 running 而 registry 没有活体时判为陈旧,复用现役 stop_local_game_preview 把落盘记录对齐成 stopped,并同时把会话 manifest 按真相投影成 stopped(否则运行入口会继续拿着打不开的 URL 渲染运行画面)。
- useHomeProjectCreation.ts:enterProjectDevelopment 改为 async,先做这次核对再进工作台,并按核对结果决定会话预览与 manifest 投影;三个调用点(新建项目、创建项目、打开项目)都改为 await。
- preview.rs:同一个项目重启预览时,旧 registry 身份的收尾不再覆盖刚落盘的 running——只有当旧预览属于**另一个项目**时才替它收尾。此前同项目重启会把刚写进去的 running 覆盖成 stopped,下次进项目便丢掉"预览在跑"这条事实(记录反方向失真)。
- preview.rs:stop_local_game_preview_for_root 在没有活体可停、但落盘记录仍是 running 时也把记录收尾成 stopped(stop 的语义变成"真的停干净,包括记录"),因此陈旧记录既可由进入项目触发对齐,也可由用户显式停止对齐。
- tests/project.rs:新增三条 Rust 定向用例——同项目重启后记录仍为 running 且端口是新预览的;换项目启动时旧项目记录写 stopped、新项目写 running;registry 无活体而记录写着 running 时 stop 会把记录对齐成 stopped。
- tests/sessionPreview.test.ts(新增):五条用例覆盖活体优先、陈旧记录对齐并投影、registry 不可读按没有在跑处理、记录本来不是 running 时不发多余停止命令、无 invoke 时零请求。变异验证:把活体核对去掉(直接信落盘记录)→「registry 里没有活体时不给会话预览」与「registry 不可读」两条立即红灯(已实测)。
- project-development.suite.ts:把「打开项目不查预览状态」那条断言改为「恰好核对一次预览活体」,并保留邻近的 start_local_game_preview 必须为 0 的断言——被新行为取代的是"从不核验","开项目不偷偷启动预览"这条原意一字未改。
- PRD §4.1:状态机按当前实现改写(进入项目一律落到 resource-overview;run 的自动进入只认内存 registry 的活体确认;落盘 preview 是记录而非活体,陈旧记录不触发切换也不参与渲染),并把退出收尾一并写进该节说明。
|
2026-09-11 12:27:31 +08:00 |
|
suzmii
|
1801d2b227
|
清 AGC src-tauri 既有 rustfmt 欠账:5 个文件纯格式化,无逻辑变更
- `check:rustfmt` 跑 server-rs 与 AGC src-tauri 两条命令,CI 在 server-rs 那条就退出,AGC 侧欠账从未被跑到;本提交只做格式化,不改任何语义
- `src/agent.rs`:`mod` 声明与 `pub(crate) use` 按 rustfmt 排序口径调整 `direct_codex_references` 的位置(声明顺序无语义影响)
- `src/assets.rs`:`infer_canvas_export_asset_kind` 的 `else if` 条件行重排;两处 `assert!` 参数按 use_small_heuristics 折叠;两个返回 `Result<..>` 的函数签名断行
- `src/project/asset_export.rs`:`resolve_export_source_file` 签名折成单行,`File::open`/`File::create` 两行重排,用例里 `SaveLocalProjectAssetFileInput` 实参折叠
- `src/tests/asset_rename.rs`:两处用例断行/折叠
- `src/tests/asset_delete.rs`:一处 `assert!` 实参折叠及连带重排
- 已用 `cargo fmt -p genarrative-ai-game-creator-shell -- --check <这 5 个文件>` 与整 crate `cargo fmt --all -- --check` 双重验证通过
|
2026-09-11 12:25:49 +08:00 |
|
suzmii
|
775b3792cd
|
修 rustfmt:resource_editor 新增用例按仓库格式折行
- `editor_api_rejection_reason_from_body` 用例里 3 处 `serde_json::json!(...).to_string().as_str()` 实参按 rustfmt 口径断行
- `editor_api_rejection_reason_from_body("<html>502</html>")` 断言折行
- 连带收掉 `remote-terminal-failed` 的 match 分支:该 arm 因上一提交改宽而超行宽,按 rustfmt 折成块
- `resource_editor.rs` 现在通过 `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`
|
2026-09-11 12:21:04 +08:00 |
|
suzmii
|
8a1917b7e1
|
同步分类自愈规则的契约用例:改断言并把盲区补成显式用例
- 修正 resolves asset category and tags with legacy and forward compatibility 中 { kind: 'character', category: 'unclassified' } 的期望:由 'unclassified' 改为 'character'
- 该断言是 09-09 引入的旧口径,与 2026-09-10 落地的分类自愈规则冲突;按拍板保留自愈规则(它修复真机 57 条 kind:ui 资产落待归类),因此精确更新断言而非放宽为 not.toBe
- 在断言处补注释说明这是本批有意行为,并写明已知盲区:用户手动把可明确分类的资产设为待归类时会被覆盖
- 新增用例「自愈规则覆盖落盘 unclassified:这是显式接受的盲区」,覆盖 character / ui / art-spritesheet 三条具体映射
- 新增用例「派生结果本身就是 unclassified 的 kind 不受自愈规则影响,保持落盘值」,覆盖 image / video / code / publication-material 与未知 kind
- 新增用例「落盘为明确分类时信任落盘值,不被 kind 派生结果覆盖」,覆盖 image+scene / character+audio / ui+document
- 变异验证:临时移除自愈规则后上述 2 条断言失败(expected 'unclassified' to be 'character'),恢复后全部通过
|
2026-09-11 12:16:26 +08:00 |
|
suzmii
|
caa188873d
|
删掉快速编辑目标校验里已失效的 generationInputs 形参
- `ensure_editor_image_edit_target_matches_source` 的 `generation_inputs` 形参在来源类型判据改为静态图集合、且 AGC 例外分支删除后再无任何使用,函数体内已无引用
- 同步删除该形参,并更新 1 处生产调用点与 8 处用例调用点传入的多余实参
- 该形参此前只用于把 generationInputs 透传给已删除的 `_for_request` 包装层,删除后判据与请求体字段彻底解耦
|
2026-09-11 12:14:40 +08:00 |
|
suzmii
|
49a30d51ff
|
修正 icon 全局放行后的两处判据:删掉死分支并更新旧断言
- 删除 `ensure_editor_image_edit_source_kind_allowed_for_request` 里 `AGC && assetKind=="icon" → Ok` 的例外分支:`icon` 已进静态图类型表被全局放行,该分支恒不改变判定结果
- 连带删除因此不再被引用的 `is_game_creator_resource_editor_generation` 与其定向用例(同一 scope 无人调用);AGC 来源别名口径仍由 `EditorGenerationQueueResultContext::from_job` 的 queue consumer 判定承担并有既有用例覆盖
- 两处调用点改为直接调 `ensure_editor_image_edit_source_kind_allowed`,删掉没有行为的 `_for_request` 包装层与 `generation_inputs` 形参
- 旧用例 `game_creator_resource_editor_bypasses_quick_edit_whitelist_only_for_its_source` 改名 `quick_edit_source_kind_policy_ignores_generation_source`,改为断言来源类型判据与 generation source 无关
- 修正 master 侧旧断言 `editor_image_edit_target_binding_prefers_object_id_and_fails_closed`:反例 effective_asset_kind 从 `icon` 换成仍被拒的 `character-animation`,断言保持 Err,守卫意图(目标图层不能借壳成允许来源)不变
|
2026-09-11 12:11:37 +08:00 |
|
suzmii
|
a5aba86493
|
修 rustfmt:editor_project 新增行按仓库格式折行
- `ensure_editor_image_edit_source_kind_allowed` 的 `is_none_or(...)` 折叠为仓库 rustfmt 口径的换行
- 白名单用例里 `ensure_editor_image_edit_source_kind_allowed(Some("art-spritesheet"), Some("video"))` 断行
- `is_game_creator_resource_editor_generation(Some(&json!({})))` 断言断行
- `let queued_payload: Value = serde_json::from_str(...)` 折行
- 仅 `server-rs/crates/api-server/src/editor_project.rs` 一个文件,无其他文件被 rustfmt 改动
|
2026-09-11 12:08:06 +08:00 |
|
suzmii
|
3ed567d99a
|
运行视图的自动切换只认会话内确认过的预览
- index.tsx:自动切运行的那条 effect 从「合并后的预览 URL」收窄为「会话预览 URL」(resolveEmbeddedPreviewUrl(previewOverride))。manifest.preview 是"上次留下的一条落盘记录":预览服务器是进程内线程、非 Linux 端口还是随机临时端口,客户端退出后记录仍会写着 running,照着它自动切视图就是把用户丢进一个打不开的运行界面。运行入口(运行 tab、播放按钮)与 runAvailable 一行未动,PRD §4.1「运行入口不可用时仍允许点击…但不切换状态」继续成立。
- useHomeProjectCreation.ts:enterProjectDevelopment 不再把 context.manifest.preview 播种成本次会话的预览(改为 null)。会话预览此后只由活体确认(下一提交)与本次会话里 preview.start / preview.status 的返回值经 onPreviewChange 回流两处写入。
- 有意接受的代价:「回到仍在运行的预览」从自动变成用户点一下「运行」;下一提交会把"确实还在跑"这条路径按活体确认接回自动进入。
- project-development.suite.ts:新增两条用例——① 仅 manifest.preview 为 running(不传 preview)时初始仍是「资源管理」、运行 tab 仍可用、点一下仍能进运行视图;② 会话预览为 running 时自动切到运行表现层。
- 变异验证:把这条 effect 还原成读合并后的 URL(即撤掉本次收窄),用例①立即红灯(已实测)。
|
2026-09-11 12:07:44 +08:00 |
|
suzmii
|
95c5418c0f
|
合并 master(#318 写锁等待与可诊断)入 V3 分支
Project CI / Repository checks (pull_request) Failing after 1m3s
Project CI / Frontend tests (pull_request) Failing after 2m48s
Project CI / Backend tests (pull_request) Failing after 4m27s
Project CI / Native shell tests (pull_request) Successful in 17m21s
- 三个文档冲突按两边都保留解决:decision-log.md、pitfalls.md、实施计划技术方案;两边追加的是不同主题(资源画布/预览/聊天区 vs 写锁),语义无冲突
- Rust 侧 direct_runtime.rs、direct_tool_bridge.rs、project_gates.rs、project.rs 自动合并成功,无手工介入
- 冲突处仅移除 Git 标记行,未删改任何一方内容(每个文件恰好减少 3 行)
|
2026-09-11 11:33:12 +08:00 |
|
suzmii
|
28bb5800ea
|
资源总览去掉大标题旁的副标题
- 删除资源总览标题栏里那颗副标题的**两个分支**:`${canvasResources.length} 项正式与候选资源` 与空项目时的 `从资源栏目开始整理资源`
- 位置:`apps/ai-game-creator-shell/src/view/project-development/index.tsx` 的 `<header className="game-resource-book-main-heading">`(基线 364492de3 的 5867-5871 行那颗 `<span>`,标题栏自 5863 行起)
- 同步删除只服务于它的 `.game-resource-book-main-heading > span` 样式,模块内不再留墓碑规则
- 该文案没有承载 `aria-label` 等语义(区域语义一直在容器的 `aria-label="资源总览"` 上),因此不需要把语义迁移到容器,也没有改成"更短的说明"
- 全量计数没有丢:同一次改动后由总览第 0 张「所有资源」卡的计数徽标承载,仍是同一份画布投影
- 卡片内部的说明/装饰文案(「打开 UI 交互」「打开角色与对象」「1 个直接子版本」等)保持不动
- 断言:资源总览标题栏整段 `textContent` 恰为「资源总览」(精确相等,不放宽成 `not.toContain`),并复核总览第 0 张卡与「文档」栏目卡的内部文案仍在
- 既有用例没有任何一条断言过这段副标题文案(全仓检索只命中源文件),因此没有"按文案已删更新"或"按四不写删除"的对象;本用例是补上的正向合同断言
|
2026-09-11 11:28:59 +08:00 |
|
suzmii
|
1cc9a23815
|
资源总览新增「所有资源」汇总卡与全部资源页
- 渲染层新增 `RESOURCE_BOOK_ALL_TARGET = 'all'` 特殊项:跨端契约 `ProjectResourceCanvasCategory` 不加 `'all'`,sidecar schema 不升版,落盘布局的 `section` 也不会出现它
- 资源总览第 0 张卡即「所有资源」:排在全部栏目卡之前,复用同一套缩略图类名/圆角/底色/阴影与栏目标题栏组件,不新增卡片样式
- 该卡的计数徽标取全量资源数,口径等于各栏目计数之和(同一份画布投影,不另算一套)
- 画本场景的栏目顺序与资源分组各多一个渲染层入口;`buildResourceBookScenePlan` 对特殊项只出计数与标题栏,不铺卡片,避免同一张资源卡在总览里挂载两份(`data-resource-card-id` 查询与 FLIP 的 `card:*` 键都会撞车)
- 新增「所有资源」页:栏目大纲 + 收起资源按钮 + 按栏目分组的滚动卡片网格;卡片复用栏目页同一颗渲染器与卡宽高,容器与网格样式补齐在 styles.css
- 该页的卡片显式关掉拖拽(卡片坐标不属于任何栏目画布,写回会污染布局 sidecar),保留选中/播放/菜单交互,并把光标与 `touch-action: pan-y` 交给滚动容器
- 栏目大纲抽成 `ResourceBookOutlineNav`,栏目页与全部资源页共用同一条入口带;渲染出的 DOM 与抽取前一致
- `renderResourceBookCard` 增加可选 `draggable` 参数,`ResourceCard` 的三个移动端指针回调改为可选
- 断言:总览存在「所有资源」卡且为第 0 张、计数等于全量资源数且等于各栏目计数之和、点它进入的页面按栏目分组且每组都挂真实资源卡、场景计划里特殊项不出卡片且整份计划无重复卡片键、全部资源页分组只保留非空栏目且保持总览顺序
- 既有「N 项」徽标断言从全文档文本查找收窄到 character 栏自己的标题栏(新增第 0 张卡后同文案会撞车,收窄而非放宽)
|
2026-09-11 11:21:03 +08:00 |
|
suzmii
|
364492de37
|
Revert "文档同步:资源管理页改为栏目分页纵向滚动网格"
This reverts commit c2b851cc36.
|
2026-09-11 11:07:59 +08:00 |
|
suzmii
|
c2b851cc36
|
文档同步:资源管理页改为栏目分页纵向滚动网格
- PRD §3.3:栏目页改为纵向滚动网格,顺序由 sidecar 记录的 (y, x, resourceId) 读序推出,新增列数控件与栏目内拖拽排序条文
- PRD §3.3.1:三阶段改写为当前形态(本体卡保留;阶段二改为栏目分页网格;阶段三改为未排序栏目的初始顺序派生)
- PRD §3.3.2:可见性预取口径改为以栏目滚动容器为交叉根
- PRD §5.2:实现状态与字段语义改为「x / y 只承载栏目内顺序」,新增顺序阶梯、已排序栏目新资源追加末尾、列数偏好与关系说明条文
- PRD §5.2.4:删除 viewport、wheel、拖动改写坐标等画布条文,改为网格、列数与拖拽排序
- PRD §5.2.5:手动拖动合同改为拖拽排序合同
- PRD §5.2.6:删除资源依赖关系图层整节
- PRD §7.2:验收改为顺序持久化;§7.3 依赖关系图验收整节删除;§7.5 重写为栏目分页纵向网格验收,新增「纵向滚动无横向溢出」与「列数被记住」两条判据
- PRD §6/§8:分阶段范围与非目标同步
- 技术方案:原《GameAgent资源自由画板与快速编辑》改名为《AGC资源管理页纵向网格与排序》,重写为当前形态(页面形态、栏目内顺序、列数偏好、拖拽排序、关系说明),保留权威数据、焦点竞态与派生编辑条文
- 实施计划:资源卡与资源管理页边界改为栏目网格、顺序落盘、列数偏好与滚动容器可见性
- decision-log:新增 2026-09-11 决策(含用户明确判断优于 PRD 的事实依据,以及有意推翻 1da7dc62a)
- pitfalls:整体改写「AGC 资源管理页真正自制的交互面」条目
- docs/README.md 与 document-map.md:更新改名后的专题文档入口
|
2026-09-11 11:00:31 +08:00 |
|
suzmii
|
6cc929e47d
|
修复陶泥儿对话外框没把输入区包住:外框底边不再与输入区重合
- 根因:`.project-supervisor-message-list` 在文件末尾还留着一条同选择器、同权重的
`bottom: 156px`("列表在上、输入区在下"那版两盒布局的残留)。它按"后写胜出"把上一轮
bdb07c141 改的 `bottom: 0` 悄悄顶掉,外框底边落在输入区腰上、输入区下半截露在框外,
真机上看到的就是"消息区外框底边与输入区压在一起、外框没把输入区完整包住"。
- styles.css:把那条残留规则换成对话框盒子的最终几何(top/right/bottom/left 全 0,
`padding-bottom` 与 `scroll-padding-bottom` 都是 256px)。它位置在文件靠后,同权重后写
胜出,顺带把 `:has(.plan-gdd-surface, .planning-lane-runtime-strip)` 给列表留的
`padding-bottom: 12px` 压回去,滚到底时最后一条消息不会被输入区盖住。
- styles.css:输入区补上第四条边——`max-height: calc(100% - 24px)`(24px = 上下各 12px)。
左右下三边仍显式内缩 12px,上边由 max-height 兜底,于是外框完整包住输入区:四条边都不
与外框描边重合,底边留出 12px 边距(外框底边 0 / 输入区底边 12px)。
- styles.css:窄屏(≤760px)改成一个模型——外框画在列表与输入区共同的父节点
`.project-supervisor-conversation` 上,列表撤掉自己那只框(框里不套框),输入区回到
文档流,靠外框 12px 内边距四周留白、随列高增长不溢出。原来那条窄屏输入区规则权重低于
`.project-supervisor-composer.is-direct-codex` 那条,其实一直是死声明,这次才真正生效。
- tests/chatDialogFrameLayout.test.ts:新增用例,先按真实层叠(媒体查询是否命中 -> 特异性
-> 源码顺序)求生效值,再断言外框四边归 0、输入区四边内缩同一个值、底边与左右留白严格
大于 0、留白 256 = 12px 内缩 + 228px 输入区最高高度 + 16px 间距(228 由编辑器
max-height 140、输入框行距 8、操作排 28px 方钮、输入框下内边距 4、输入区上下内边距 16、
操作条 2 + 30 方钮逐项从真实声明里算出来),以及窄屏下外框/列表/输入区各自落位、输入区
仍在外框节点子树里且交互控件原位。
- tests/appSurface/project-development.suite.ts:把那条按"第一条匹配规则"取值、用 230 估
算的留白断言同步成按声明算出的 228 与实际间距 16,并注明层叠生效值的守卫在新用例里。
- 变异验证:输入区 `bottom: 12px -> 0`(还原"贴死重合")时新断言失败;外框
`bottom: 0 -> 156px`(还原上一轮残留)时"外框底边必须严格低于输入区底边"失败(-144)。
- 说明:本机无无头浏览器、vitest 未开 `css: true`,像素级观感只能真机复核;这里钉的是
层叠生效的声明级几何关系与"输入区仍在外框节点的子树里"。
|
2026-09-11 10:36:45 +08:00 |
|
suzmii
|
1da7dc62a0
|
fix(资源画布): 拖拽写入不再触发其它自动卡重派生补位
- useProjectResourceCanvasLayout 新增 AutomaticPositionPolicy(rederive / preserve),取代原先裸布尔 rederiveAutomaticPositions 传参
- 拖拽(manual 意图)写链固定用 preserve:不再丢弃其它自动坐标,其余自动卡原地不动,也不再补位到被拖走的空位
- 资源集合与拓扑身份签名变化仍按 PRD 用 rederive 重派生,关系图就绪后的深度修正不受影响
- 新增 automaticCoordinatesMatch(忽略数组顺序的坐标比较):手动写入后只在坐标真的变化或有新资源需要落位时才继续排一次资源同步,避免只由排序归一触发的第二次 CAS 写入再次把其它卡补位
- 写入后的 updated / conflict 分支统一按 writePolicy 计算 reconciledAfterWrite,并据此判定是否需要资源同步
- rebuildOptimisticLayout 改为接收策略参数,手动写入后的会话布局同样保持其它自动卡不动
- 新增用例:拖走一张后其余自动卡在所有写入里坐标逐项不变、视图里也不补位;变异验证(把拖拽路径改回 rederive)后该断言失败
- 副作用(已确认接受):把卡正好丢在另一张卡上时不再自动挪开它,直到下一次资源集合或拓扑变化触发重派生
- 门禁:AGC 1178 passed / 4 skipped / 0 failed,共享组件 1385 passed,typecheck exit 0,编码检查 4374 通过
|
2026-09-11 10:27:32 +08:00 |
|
suzmii
|
921d86ed44
|
feat(资源画布): dependency 默认铺排改为层内网格平面,不再退化成一列
- 新增 RESOURCE_CANVAS_DEPENDENCY_LAYER_MAX_COLUMNS = 6 与槽位宽高比常量,作为层内网格列数上限
- 新增 normalizedDependencyDepth:落位、层带与层内网格共用同一深度归一,避免同一深度被当成两层
- 新增 dependencyLayerColumnCount:列数 = clamp(floor(sqrt(卡数 × 槽位宽高比)), 1, 6),是卡片数量的纯函数
- 新增 dependencyLayerGrid:层内网格几何,行高统一取该层最高卡,使层高与层内顺序无关(中位数扫描重排后几何不抖)
- 用 dependencyColumnGeometryByDepth 取代 dependencyColumnXByDepth:x 仍只由 dependencyDepth 决定层带基址(depth 仍是唯一横向层级权威),层带宽度 = 列数 × 列间距;全部层为单列时与旧值逐值一致
- 层内按行优先落位,层内排序(稳定 ID 初序 + 两轮左至右/右至左中位数扫描)与深度层级语义不变;网格之外仍按既有向下找空位回避手动坐标与饱和层带
- 移除 dependencyBucketHeight,簇高、簇行数与层内 rank 改用层内网格行数
- 同步 resourceCanvasLayoutModel 测试:新增层内网格平面(12 张同深度 → 4 列 3 行)、重复计算与输入顺序稳定、层带随深度单调且不与换行层重叠、列数封顶 6、手动坐标在自动网格重派生前后逐项不变;同层 3 张卡的环连续性用例改为行优先网格口径
- 影响面:既有项目里已落盘的自动坐标(manuallyPlaced=false)会在下次协调时按新口径一次性重排成平面;manuallyPlaced=true 的历史手动坐标不删除、不迁移、不被自动排序覆盖
- 不新增持久化字段、sidecar schema 不升版,ProjectResourceCanvasSection/Position 契约与 RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE 均未改动
|
2026-09-11 10:16:01 +08:00 |
|
suzmii
|
dcb750c318
|
补记跨层字段名静默忽略的排障口径,并把逐栏目用例提为首选证据
- `pitfalls.md` 新增「manifest 资产分类字段写错名会被静默忽略,跨层字段名相近时尤其危险」:manifest 上的字段名是 `category`,而 `assetCategory` 是前端投影后 `ProjectResource` 的字段名,写错层不报错也不告警,本文档记录现象、原因、处理与通用规则(「设置无效但没有任何报错」时第一优先怀疑字段名或层级错配)。
- 同文件「预览 Hook 的资源入参收窄会连带收窄 identity map」那条的验证行改为以新用例 `mounts a real card body in every non-empty resource section overview` 为首选证据:它逐栏目覆盖五个非空栏目并含变异验证;既有 `uses one full-page canvas per resource section with dependency-only guide lines` 降为补充(只覆盖一两个栏目)。
- 只追加与就地更正该条验证行,未重排任何既有条目。
|
2026-09-11 02:00:38 +08:00 |
|
suzmii
|
e3616a44cc
|
补逐栏目断言:资源总览每个非空栏目都必须挂载真实卡片本体
上一轮把「总览其它栏目不画卡片」的回归结论建立在推理上(identity map 由全量投影构建 ⇒ 不会有卡片因身份缺失而不挂载)。本轮把它变成实测。
- 新增 `appSurface > mounts a real card body in every non-empty resource section overview`:自带 manifest,把 `category` 显式写死,让 `character` / `audio` / `document` / `unclassified` / `version` 五个栏目同时非空(外加空的 `ui-interaction` / `scene`),再逐栏目断言 `.game-resource-book-scene-card[data-resource-book-category=…]` 内存在 `.game-resource-card` 本体,并用 `.game-resource-card` 的 `data-resource-card-id` 回查投影结果,证明卡片确实落在被断言的栏目(不是靠「有子节点」这种恒真守卫蒙过去)。`audio` 刻意保持 `idle`(音频不接受可见性预取),用来证明卡片挂载不依赖预览是否已就绪。
- 自带 manifest 而不是改共用 fixture:`addSectionResources` 的四个资产被同一测试后续多处断言引用(例如 `选中资源:待归类 section.md`),给它补 `category` 会把 `section.md` 从「待归类」挪进「文档」,连带打挂那些既有断言。新用例因此只新增、不改动既有断言。
- 分类字段名踩坑记录:manifest 资产上的字段名是 `category`,不是 `assetCategory`(`assetCategory` 是前端投影后的 `ProjectResource` 字段)。先写成 `assetCategory` 时它被静默忽略,`gameCreationAppAssetCategory` 只能按 `kind` 派生,`design-document` / `game-code` 都不是 canonical kind,于是 `section.md` 落在「待归类」、`document` 栏目为空。
- 变异验证(本断言的价值所在):把 `resources` 改回 `activePageResources`(即回到回归状态)后,本用例与既有 `uses one full-page canvas per resource section with dependency-only guide lines` **双双失败**(`expected null not to be null`);恢复后两者通过。断言确实守住了这条回归。
- 验证:`npm run test -- apps/ai-game-creator-shell/tests` 81 files passed,1172 passed / 4 skipped / 0 failed;typecheck exit 0;eslint 与 prettier 干净;check:encoding 4374 文件;`git diff --check` 干净。本轮只改测试文件,`src/**` 与 HEAD 逐字节一致(变异验证后已还原并核对)。
|
2026-09-11 01:59:21 +08:00 |
|
suzmii
|
84b8f5130b
|
修复资源总览只画当前栏目卡片的问题:拆开身份口径与热预取口径
前一次改动(60d8b8fbb)把 `useProjectResourceCardPreviews` 的 `resources` 从全量投影收窄成当前分页栏目,造成产品回归:该入参不只喂热预取,它同时决定返回的 `identityByResourceId`,而 `renderResourceBookCard` 在身份缺失时 `return null`,于是资源总览(main 态)只有当前分页栏目画出真实卡片,其余栏目只剩栏目标题栏加空的层叠占位。
- 拆成两个入参:`resources` 恢复全量资源投影语义(决定 `identityByResourceId`、缓存与卡片挂载),新增 `eagerResources` 只用于 `eagerPreviewLimit` 计数的热预取,缺省回退 `resources`。两个入参的语义写进 Hook 参数注释,调用方 `index.tsx` 在传参处注明「收窄 `resources` 会让其它栏目只剩标题栏」。
- 纠正前一次提交的说法:**identity map 必须保持全量**,不能跟着热预取一起收窄。热预取可以只喂当前栏目(上一个提交的意图保留、`A → B → 回 A` 会重读 A 可见卡的已知代价也保留),但身份 / 渲染口径收窄会让总览其它栏目不再挂载卡片——技术方案要求「主画布展示各子画布的缩略入口,内部按资源类型显示有限层叠卡片……主画布预览和子画布展开态复用同一套卡片视觉」,这是产品回归而非可接受的行为变更。
- 不使用「身份未知时用 idle 占位兜底渲染」这条备选:它会让总览卡片失去真实预览,观感更差。
- `pitfalls.md` 追加同题排障口径,含通用规则:凡 Hook 入参同时参与「身份 / 缓存 key」与「调度 / 预热」,就不能为优化调度去收窄它,应先确认它还有没有别的消费者。
- 回归用例 `appSurface > uses one full-page canvas per resource section with dependency-only guide lines` 已自行变绿(未改该断言)。验证:`npm run test -- apps/ai-game-creator-shell/tests` 1171 passed / 4 skipped / 0 failed;`appSurface.test.ts` + `useProjectResourceCardPreviews.test.ts` 415 passed;`src/components/image-editor` 1385 passed;typecheck exit 0;check:encoding 4374 文件;`git diff --check` 干净。
|
2026-09-11 01:47:04 +08:00 |
|
suzmii
|
7fa61231ae
|
AGC 资源画布两条过期断言跟上新合同,并补项目切换的预览缓存清理行为断言
- appSurface > renders one body-first card system:把原先钉「全局只读 1 次」的断言收窄成「同一身份不因重复请求新增物理读取」——热预取之后先让 IntersectionObserver 再报一次可见、再打开一次详情,读取次数必须与快照一致;并先钉住快照非空且读取目标都是 assets/hero.png,避免空集恒真。60d8b8fbb 把热预取改成只喂当前栏目后,用例里每进出一次「角色与对象」都会重读该栏目的可见卡(该 commit 已明确接受这个代价),全局总次数会随进出次数漂移;「同一身份同时只读一次」是 PRD 里真实存在的合同,比总次数更精确,没有放宽。
- appSurface > keeps the resource preview version cache wired and cleared on project switch:60d8b8fbb 删掉了从未被写入的 resourcePreviewVersionByResourceId 死接线(只被 new Map() 初始化与清空,previewVersionByResourceId 恒为空串),断言守的 prop 与其清空语句都已不存在,按「四不写」删除这条形态断言;用例改名 clears the resource preview cache and cancels the old preview scope on project switch。
- 同用例把意图从源码正则换成可执行行为断言(原断言只证明接线存在,不证明缓存真被清):渲染 A 项目并等卡片预览落到 Blob URL 后切到 B 项目,断言 ① 旧卡片预览的 Blob URL 被撤销、② 旧 scope 的在途读取按旧 scopeId 取消、③ 同一资源以新项目身份与新 scopeId 重新读取。
- 变异验证在 %TEMP% 下的独立 worktree 沙箱里做(未改本工作树产品代码):去掉预览 Hook 里 loaded 早退 → 第二条用例失败(同身份读取 6→7);去掉 cancelLocalProjectResourcePreviewScope(previousScopeId) → 第三条 ② 失败;切项目不复用新 scopeId → ③ 失败;拆掉两条撤销 Blob URL 的路径 → ① 失败。
- 未改 uses one full-page canvas per resource section with dependency-only guide lines:沙箱实验证明只把预览 Hook 入参从 activePageResources 换回 canvasResources,该用例即整体通过。它守的「总览逐栏绘制各栏目真实卡片」是被 60d8b8fbb 意外收窄的(renderResourceBookCard 在 identityByResourceId 缺该资源时返回 null,而该 map 从该 commit 起只覆盖当前栏目;其 commit message 只声明了预取口径与重读代价,未声明总览不再绘制其它栏目卡片),属产品回归,已上报,不以改断言掩盖。
- 门禁:ai-game-creator-shell:typecheck exit 0;apps/ai-game-creator-shell/tests 1 failed / 1170 passed / 4 skipped(唯一失败即上面上报的回归;改前 3 failed / 1168 passed);src/components/image-editor 1385 passed;check:encoding 4374 文件;git diff --check 干净。
|
2026-09-11 01:33:10 +08:00 |
|
suzmii
|
1ac8cc3eca
|
补记本轮 AGC 修复的排障口径与既有约定
- pitfalls.md 新增「资源搜索栏改成临时叫出的浮层」条目:写清"回到资源总览点不动"的两层成因(横跨整行、z-index 40 的透明工具条带盖在 z-index 30 的栏目标题栏上吃掉命中区域;管理区 padding-top 与场景 inset 只为给搜索框腾带子)、浮层的几何口径(bottom: 58px = Dock 14px + 34px 高 + 10px 间隙,刻意不写 top;提示层 top: 58px = 标题栏 42px + 16px)、必须钉住的两条(提示层 pointer-events: none + 提示条 auto;收起不清搜索条件、由 Dock 按钮 is-active 表达),以及门禁为什么抓不到(jsdom 不做命中判定)。
- pitfalls.md 新增「画布侧分类筛选条已移除」条目:记录用户口径、只移除画布这一处用法而保留共享组件、随它删掉的画布筛选轴,以及"不要顺手改分区、将来恢复要重新设计入口"。
- pitfalls.md 新增「项目对话输入区必须留在对话框内部」条目:记录两只绝对定位盒子上下紧贴的成因、对话框=消息列表盒子 + 输入区三边内缩 12px 的做法,以及列表留白 256px 与编辑器 min/max 高度必须成对维护。
- 本次只补文档,不改代码;编码检查 4374 文件通过,文件保持 LF。
|
2026-09-11 01:24:17 +08:00 |
|
suzmii
|
60d8b8fbb2
|
资源卡异步解码、热预取只喂当前栏目,并删掉未接线的预览版本 map
- 资源卡 `<img>` 补 `decoding="async"`,把真机 37.2 MPx 量级的 PNG 解码移出主线程。刻意不加 `loading="lazy"`:卡片已经由资源画本的 `IntersectionObserver`(`rootMargin: 160px`,root 指向真正包含卡片的容器)精确门禁,只对进入可见区及预取边界的卡片发 IPC;再叠一层浏览器视口懒加载会让「预取边界 + 热预取 12 张」读到的视口外卡片被压住不解码,等于白预取。`decoding="async"` 不改变何时加载,与既有调度正交。
- 卡片预览的热预取改为只喂当前分页栏目:原先传的是 61 项全量投影,前 12 张可能落在别的栏目,用户看不到的卡片先占满全局 3 个物理读取槽,看得见的卡片反而要排队。
- 为此把 `projectResourcesByCategory`、`resourcePageCategoryKey`、`resourcePageCategories`、`activePageCategory`、`activePageResources` 整块上移到 `useProjectResourceCardPreviews` 之前并删除原位置定义;`projectResourcesByCategory` 只依赖 `canvasResources` 与 `categoryOrder`,其余只在既有依赖之上派生,上移不引入前向引用。`activePageResources` 合并为单一声明(原先在 `activePageLayoutReady` 之前另有一份同定义局部变量)。
- 已知副作用并接受:Hook 的 `identityByResourceId` 只覆盖当前栏目,因此「A 栏目 → B 栏目 → 回到 A」会重新读取 A 的可见卡。这正是「栏目级热预取」的定义,也符合 PRD §3.3.2「项目 / mode 切换必须清理预览缓存」的口径;代价是来回切换栏目各重读一次可见卡,单张实测 1–7 ms 且仍受全局 3 槽限制。
- 删除 `resourcePreviewVersionByResourceId` 的三处引用与 `useState` 声明。它从未被写入过:只被 `new Map()` 初始化和清空,`previewVersionByResourceId` 恒为空串,属死代码。
- 选择「删」而不是「填真值」:填真 manifest revision 会让每次 manifest 变更把全部缓存 identity 失效并触发全量重读;而现有 identity(`projectPath` / `projectId` / `id` / `category` / `path` / `mediaType`)对不可变素材已经足够——路径变了就是另一个资源。本轮不新增失效判据。
- 遗留:`appSurface > keeps the resource preview version cache wired and cleared on project switch` 断言的是刚被删除的 prop 接线,需要同步删掉该断言;该测试文件当时正被另一个 Agent 在途编辑,按分工不在本次改动内,已上报由主人处理。
|
2026-09-11 01:04:06 +08:00 |
|