文档同步:资源管理页改为栏目分页纵向滚动网格
- 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:更新改名后的专题文档入口
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# AGC 资源管理页纵向网格与排序
|
||||
|
||||
状态:现役。本文是 AI 游戏创作客户端(AGC)资源管理页的权威合同,约束 `apps/ai-game-creator-shell/src/view/project-development/` 的页面结构、栏目内顺序、列数偏好与拖拽排序,跨端范围以 `packages/shared/src/contracts/gameCreationApp.ts` 的 `game-creator-resource-layout.v1` 为准。资源派生编辑与快速编辑的资源合同见《【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09》。
|
||||
|
||||
## 页面形态
|
||||
|
||||
- 资源管理页是**栏目分页的纵向滚动网格**,不是画布。主视图展示全部栏目的入口,进入栏目后是该栏目的卡片网格;两者都只纵向滚动,页面没有横向滚动条,也没有可视世界坐标层。
|
||||
- 页面不提供画布平移、缩放、适应内容、空白拖动、框选、依赖连线和排序撤销/重做;滚轮与触控板只滚动当前栏目,原生 wheel 监听不得 `preventDefault` 拦截为画布手势。
|
||||
- 栏目顺序固定为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。完全空项目显示全部分区展览;任一栏目出现资源后,`dependency / type` 两种模式都进入栏目分页网格,默认停留在该顺序中的第一个非空栏目。Dock 常驻可读可点,空栏目也可打开空网格。
|
||||
- 网格单元高度统一,图片在单元内等比完整显示,避免行高参差。卡片本体化交互(选中、浮出工具条、快速编辑、非模态详情、媒体播放、搜索与平台资源筛选)全部保留;点卡选中仍走共享 `ImageCanvasSelectedLayerToolbarView` / `ImageCanvasQuickEditPanelView`。
|
||||
- 栏目导航入口(Dock、底部下一页标题、总览入口、资源定位)与搜索行为不变:滚轮不切换栏目,显式切页前先终止本栏目的排序拖拽与 pointer capture。
|
||||
|
||||
## 栏目内顺序
|
||||
|
||||
- 顺序**不新增持久化字段**:栏目内顺序固定由布局 sidecar 中该 `section` 记录的 `(y, x, resourceId)` 字典序推出,`reconcileResourceCanvasLayout` 已有的读序排序就是本页唯一顺序来源。历史坐标因此天然无损地变成"顺序",既有 sidecar 打开即正确,首次渲染零写入。
|
||||
- 网格渲染忽略 `x / y` 的像素语义:列宽与单元高度由 CSS 决定,位置不再由坐标表达。
|
||||
- 用户拖动排序时,把该栏目**全部记录**按新顺序重编码为顺序阶梯 `x = 0`、`y = 序号 × 144`(`144` 是固定行距常量),并一次性提交 `manuallyPlaced=true` 的布局 CAS。阶梯必须与显示列数解耦:列数只改 CSS `grid-template-columns`,**绝不回写坐标**,否则切一次列数就会全量重写 sidecar。
|
||||
- 该栏目全部记录都是 `manuallyPlaced=true` 时,新资源追加到栏目末尾:协调阶段把新资源放到该栏目现有最大 `y` 之后,`x = 0`,`y` 按 `144` 递增,并保持 `manuallyPlaced=false`(仍是可派生记录)。没有任何用户排序记录的栏目继续按 `dependency / type` 默认排版推出的坐标落位。
|
||||
- 历史 `manuallyPlaced=true` 记录、旧栏目读时归并、CAS 单写者 FIFO、冲突重试、`expectedProjectId` 身份栅栏和 `-1_000_000..=1_000_000` 坐标范围全部不变。没有用户动作时坐标一字不改。
|
||||
- **用户主动排序会重写该栏目全部记录的 `x / y`**,这是"顺序"落盘的必然代价;同栏目其它卡片的记录位置随之变化不属于自动补位,不得把它当作"拖拽写入触发其它自动卡重派生补位"的回归去修。
|
||||
- `dependency` 的 Rust 只读图与 `type` 的默认排版都保留,但只作为**未排序栏目的初始顺序种子**;页面不再派生 SVG 几何,也不再因 viewport 变化重算顺序。
|
||||
|
||||
## 列数偏好
|
||||
|
||||
- 顶部列数控件提供「自动」与固定 `1..6` 列。自动模式按容器宽度与最小单元宽度计算列数。
|
||||
- 选择结果按**全局单值**记在本机 `localStorage` 的 `agc.resource-canvas.columns.v1`,两个 `mode` 与所有项目共享,切换列数**零 Tauri 写入**,也不写入布局 sidecar、manifest 或后端。
|
||||
- 列数是本机 UI 偏好;localStorage 不可写时只当次生效,不阻断页面。
|
||||
|
||||
## 拖拽排序
|
||||
|
||||
- Pointer Events 统一 `5px` 移动阈值:未超过阈值保持点击语义,超过后进入排序拖动态并捕获指针,跟随指针显示目标插入位。
|
||||
- 拖动期间只更新当前会话内的插入位预览,不写坐标、不写 sidecar。释放且顺序已变化时提交一次布局 CAS;`pointer cancel`、未超过阈值、顺序未变化、跨栏目悬停都不写入。
|
||||
- 不支持跨栏目拖动,改分类仍走既有「分类与标签」面板。
|
||||
- 顺序预览使用 `requestAnimationFrame` 合并的会话状态,不引入第三方拖拽/排序库,也不构造第二份顺序真相。
|
||||
|
||||
## 权威数据与持久化
|
||||
|
||||
### 栏目分页投影与设计文档缺失边界
|
||||
|
||||
- 栏目内容只由当前权威资源投影构造:manifest assets、已完成任务的 artifacts、用户已导入附件和持久化 Agent 文本回执。前端不得扫描项目目录自行发明资源。
|
||||
- 资源依赖图的精确引用身份同时支持两种稳定索引:远端 `source.resourceId` 与本地 `local-asset:<manifest assetId>`。派生资源把 `referenceResourceIds` 写成本地 canonical 身份时,必须能解析到同一项目的唯一资源;候选去重后才允许建立一条 `asset-reference`,无法唯一解析的引用继续进入 `unresolvedReferenceResourceIds`,不得在前端猜测连线。
|
||||
- AGC 侧共享契约维护 canonical 资产类型目录和历史读时映射:`game-background -> scene`、`character-art -> character`、`ui-prototype -> ui-design`、`art-spritesheet -> icon-spritesheet`、`art-spritesheet-slice -> icon`、`illustration/game-art -> image`。该目录只约束 AGC 新写入与投影,不要求现役 Web 美术画布同步改造;历史 manifest 保持原值,读取时可归一到 canonical 展示。
|
||||
- 角色动画派生结果的 manifest `kind` 固定写 `character-animation`,`source.generationKind` 同步保留 `character-animation`,媒体仍以预览视频加正式序列帧登记。图片编辑等普通派生继续继承源资源语义 kind;后续如需改写为 canonical kind,只能在读取投影或显式迁移中完成,不重写历史 manifest。
|
||||
- 固定栏目页始终为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。分区口径是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`);项目版本不是资源资产、6 类资产分类轴对它不适用,固定单独成栏排在末尾;没有 `category` 事实源的任务产物与导入附件归 `待归类`,合法 Agent 文本回执归 `文档`。若某栏目没有对应投影,Dock 仍显示该栏目并允许打开空网格;空网格只表达“当前没有已登记资源”,不得由 UI 补假数据。
|
||||
- 布局 sidecar 的 `section` 允许继续出现旧四 / 五栏目取值(`code / document / version / art / audio`),读取时按资源当前分区归并:`document / audio / version` 同名 1:1 保留,旧 `art` 按资源当前分类落入 `ui-interaction / character / scene / unclassified`,旧 `code` 落入 `unclassified`,无法精确归并时回落 `unclassified`。归并只改写 `section`,`x / y / manuallyPlaced` 原样保留,不写迁移脚本、不删除、不重置。`game-creator-resource-layout.v1` schema 不升版,否则既有 sidecar 会被直接拒读。
|
||||
- Direct Codex 的游戏生成链路以 `game/index.html`、`game/style.css`、`game/game.js` 作为代码资产登记;是否额外产出设计文档由当前任务和 Agent 决定,资源页只消费已登记结果。
|
||||
|
||||
## 关系说明(无连线)
|
||||
|
||||
- `dependency` 模式不再渲染 SVG、连线或 marker,但仍把当前栏目内两端可见的合法精确引用挂成关联的视觉隐藏文本(`aria-describedby` 挂在栏目网格容器上),逐条说明引用关系;说明与可见引用同源,搜索隐藏任一端点时该条说明同步消失。task-flow 不进入该说明,`type` 模式不产生该说明。
|
||||
- 精确引用只接受唯一有效的外部资源 ID 映射:`source.referenceResourceIds` 的外部 ID 必须先唯一匹配另一项资产的 `source.resourceId`,再映射为当前资源卡 ID;缺失、重复或已删除的目标不产生幽灵关系。
|
||||
- 前端只消费 Tauri Rust 返回的 `ProjectResourceGraph` 只读 DTO(资源过滤、去重、迭代式环检测、SCC 压缩后的确定性依赖深度、任务流聚合与一跳连接索引都在 Rust 侧),不引入 D3、React Flow 等图表库。
|
||||
- `producerMappingTruncated=true` 只关闭依赖有界 Agent DB 审计的 `producerAssignments`、`taskFlows` 和 `cyclicTaskIds`;Rust 返回的 `dependencyDepths` 仍是 manifest / 精确引用 read model 的权威结果,前端必须过滤未知资源、负数、非整数和非安全整数后继续消费,不得在前端重算替代深度。`referenceEdges`、connection index 中的 reference 关系、`cyclicResourceIds` 和 unresolved reference 继续保持可信。
|
||||
- 画布资产 producer 只能来自 `agent.runtime.canvas.asset_generate` 的 `assetId -> agentId` 审计且 `agentId` 必须存在于当前 manifest;External Editor `source.taskId` 属于平台生成任务命名空间,禁止当作 manifest task ID。
|
||||
|
||||
## 操作边界
|
||||
|
||||
- 栏目顺序固定为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。Dock 常态即可读可点:底板、与栏目标题栏同款的栏目图标和 12px 栏目文字常驻,当前栏目常态带选中态强调;悬停或键盘聚焦只做同一套 token 的加强。默认停留在该顺序中的第一个非空栏目,空栏目仍可打开空网格。游戏代码资产按其 manifest `category` 落入 `待归类` 并正常显示卡片。
|
||||
- 切页前必须终止旧栏目的排序拖拽和 pointer capture,避免旧会话状态写入新栏目。搜索、详情开关和列数切换不得改变栏目内顺序或滚动位置。
|
||||
- 卡片拖动只调整本栏目顺序;拖动后的释放点击不打开详情,媒体控制点击不进入排序。
|
||||
- 资源详情是非模态独立卡片,包含元数据、媒体预览和“编辑资源”操作,但不使用全屏 backdrop、不声明 `aria-modal=true`、不把 `focusedResource` 作为背景工具栏渲染条件。打开、切换和关闭详情都不得卸载背景工具栏、资源卡或重置搜索、排序模式与顺序。
|
||||
- 完全空项目继续使用可纵向滚动的分区展览;低高度窗口在两种排序模式下都能滚动到“项目版本”。页面容器不得用 `overflow: hidden` 造出画布语义。
|
||||
- 图片浮动工具条与图片精修草稿画布均已退役。图片的“编辑资源”入口走资源派生编辑,在资源编辑壳内提交,不再打开独立画布或图片下方快速编辑卡。
|
||||
- 资源页顶部只保留播放、未完成编辑恢复、搜索、排序与列数等页面级动作,不展示“生成视频”“生成音效”“生成背景音乐”和“新增 UI 设计”等手动新建入口。无源资源生成由 Agent 语义工具进入既有生成合同,已有资源的编辑和图片“生成动画”入口不受影响。
|
||||
- 图片派生编辑使用 `/api/editor/images/edits`,严格遵循 `EditorImageEditRequest`,不得携带 `assetKind`、`sourceImageSrc` 或 `sourceResourceId`。Game Agent 本地 kind 在登记编辑器来源前映射到现役 canonical kind;服务端安全业务错误必须结构化返回,不能把全部 400 折叠为同一个“平台明确拒绝”。
|
||||
|
||||
## 编辑结果投影与焦点竞态
|
||||
|
||||
资源派生编辑或资产命令成功返回后,当前项目仍匹配时必须执行:
|
||||
|
||||
```text
|
||||
用返回的完整 manifest 更新项目上下文
|
||||
→ 重建资源投影
|
||||
→ 以新投影更新依赖图输入
|
||||
→ 分别协调 dependency/type 布局中的新资源
|
||||
→ 当前模式布局 ready 后再决定是否选择和定位新资源
|
||||
```
|
||||
|
||||
- 不刷新页面,不关闭/重开项目,不额外重新拉一份旧 manifest。
|
||||
- 新资源卡身份固定为 `asset:<asset.id>`。布局使用现役确定性默认位置与 CAS/FIFO 合同;编辑事务本身不直接写资源布局 sidecar。用户已排序的栏目按上文把新资源追加到栏目末尾。
|
||||
- manifest 投影可以在布局协调完成前显示加载占位,但不得用临时坐标持久化错误布局。
|
||||
- 投影与焦点的迟到结果守卫按当前 ref/store 中的完整身份复核,不依赖 React 闭包中的旧布尔值:
|
||||
- 用户已切项目:结果只能更新原项目 key 下的后台缓存,不得写当前项目 manifest、切换中央状态或抢焦点。
|
||||
- 用户已切到运行视图、资源总览或另一编辑流程:可以按精确项目身份更新缓存/投影,但不得把中央主视窗切回本次流程。
|
||||
- 用户在等待期间选择了其它资源:保持用户当前选择;新资源仍进入投影和布局,但不得自动选中。
|
||||
- 搜索或筛选 epoch 已变化且新资源被隐藏:保持条件和当前选择,显示“新资源已保存,但被当前搜索条件隐藏”,并提供显式“清除搜索并定位”动作;不得自动清空条件。
|
||||
- 只有项目身份、编辑流程身份、selection epoch 和 query epoch 全部仍匹配,且新资源在当前条件下可见时,才在布局 ready 后自动选择、滚动并聚焦新资源。
|
||||
|
||||
## 美术预览几何
|
||||
|
||||
- 网格单元高度统一,图片与视频在单元内按真实纵横比完整显示(`object-fit: contain`),不拉伸、不裁剪;极端长宽比由单元边界夹取。
|
||||
- 图片预览读取返回真实 `pixelWidth/pixelHeight`。PNG、JPEG、WebP 复用 Tauri 已完成的头部尺寸校验;GIF、AVIF、BMP 和安全 SVG 的媒体预览也只读取受限头部/根元素元数据,无法安全解析时回退固定卡片尺寸。
|
||||
- 真实像素尺寸只用于**默认排版**(即未排序栏目的初始顺序种子):`resourceCanvasLayoutModel` 的默认排版、section extent 与卡片尺寸继续消费每个资源自己的 `width/height`。
|
||||
- 网格渲染不把像素尺寸当几何:列数、单元高度和单元宽度由 CSS 与列数偏好决定,媒体测量结果不得改写任何记录或顺序。
|
||||
- 可见性预取以**栏目滚动容器**为 `IntersectionObserver` 交叉根,不再以画布视口或整页窗口为根;媒体调度器的并发、去重、LRU、`revokeObjectURL` 与失败语义不变。
|
||||
- `ui-interaction/character/scene/audio/document/unclassified/version` 继续使用固定 `180x128`;尺寸只属于展示投影,不写入 manifest,也不成为第二份资源业务真相。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 栏目页只纵向滚动:卡片按列排布,页面无横向溢出,不需要左右拖动;不存在画布平移、缩放、适应内容或空白拖动,滚轮与触控板只滚动当前栏目、不切换栏目。
|
||||
2. 完全空项目在按依赖与按类型下都显示相同的分区展览;任一栏目出现资源后,两种模式都进入栏目分页网格,Dock 常态就带底板、全部栏目的图标和文字,空栏目仍可打开空网格。栏目顺序为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`,只登记游戏代码的项目在「待归类」正常显示代码卡片。
|
||||
3. 栏目内初始顺序等于该栏目 sidecar 记录按 `(y, x, resourceId)` 的字典序;把既有坐标写进 sidecar 后打开页面,卡片顺序与历史摆放一致,且首次渲染不产生任何写入。
|
||||
4. 顶部列数控件提供自动与固定 `1..6`;重新打开客户端后列数保持;切换列数不产生 Tauri 写入、不改变任何记录,也不改变栏目内顺序。
|
||||
5. 拖动卡片超过 `5px` 阈值后显示插入位,释放只提交一次 `manuallyPlaced=true` 的布局 CAS,把该栏目重编码为顺序阶梯;取消、跨栏目悬停、媒体控制点击与未超过阈值都不写入。重新打开项目后顺序保持。
|
||||
6. 用户已排序的栏目里新资源追加到末尾;没有任何排序记录的栏目继续按 `dependency / type` 默认排版推出初始顺序。删除资源后,下一次成功写入不再包含已确认失效的 ID。
|
||||
7. 点击任意资源后选中态与浮出工具条仍可用,快速编辑仍可用,详情仍是非模态独立卡片,背景栏目的顺序、搜索和滚动位置不重置。
|
||||
8. `dependency` 模式通过 `aria-describedby` 关联的视觉隐藏文本逐条说明当前可见的精确引用,搜索隐藏任一端点时该条说明同步消失;页面不渲染连线或 marker,`type` 模式不产生该说明。
|
||||
9. 图片、SVG、视频、音频、文档、任务产物、Agent 回执和项目版本卡片在统一单元高度下完整显示,不拉伸、不裁剪;媒体测量不改变顺序。
|
||||
10. 资源派生编辑或资产命令返回后,manifest、资源卡和当前模式布局不刷新即完成投影;搜索隐藏新资源时保持搜索条件并提供显式“清除搜索并定位”。
|
||||
11. `1280×720` 最小横屏下页面无横向溢出、无页面级纵向溢出,右侧对话与底部 Agent 状态栏保持可见;`1280×800` 继续作为默认窗口基线。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```powershell
|
||||
npm run ai-game-creator-shell:typecheck
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/resourceCanvasLayoutModel.test.ts apps/ai-game-creator-shell/tests/resourceCanvasLayoutContract.test.ts apps/ai-game-creator-shell/tests/resourceCanvasSectionMapping.test.ts apps/ai-game-creator-shell/tests/resourceCanvasOrderModel.test.ts
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/appSurface.test.ts
|
||||
cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project::resource_editor -- --test-threads=1
|
||||
npm run check:encoding
|
||||
git diff --check
|
||||
```
|
||||
@@ -591,29 +591,28 @@ game-project/
|
||||
|
||||
- 页面骨架固定为左侧现有全局导航、中间主视窗、右侧陶泥儿对话和底部子 Agent 状态栏;不新建第二套客户端或平行项目页。
|
||||
- 中间主视窗提供 `资源管理 / 运行` 切换。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源管理只修改前端展示态,不伪造后端预览暂停结果。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执和已导入附件派生资源,固定按 manifest 资产功能分类 `category`(`UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`)加末尾独立的「项目版本」栏目分区;未知任务产物不再兜底为版本,任务声明中的未登记音频也不冒充正式音频。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外绘制当前 manifest 与资源投影可证明的依赖关系。排列与图层都不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源卡拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供工具栏、工具侧边栏或可拖动标题栏。2026-08-10 起聚焦态以资源元数据、Rust 权威深度、同类型上下游 / 任务流和版本信息为首屏;美术图片与视频只保留卡内本体,不在详情重复放大。安全文档正文与按意图读取的音频控制位于元数据之后;美术编辑、音频编辑 / 替换、版本替换或运行模块仍不在本阶段。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执和已导入附件派生资源,固定按 manifest 资产功能分类 `category`(`UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`)加末尾独立的「项目版本」栏目分区;未知任务产物不再兜底为版本,任务声明中的未登记音频也不冒充正式音频。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外把当前 manifest 与资源投影可证明的精确引用挂成关联的视觉隐藏说明,页面不绘制连线。排列结果只作为未排序栏目的初始顺序种子,不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。资源管理页是栏目分页的纵向滚动网格:栏目内顺序由布局 sidecar 记录按 `(y, x, resourceId)` 字典序推出,用户可拖动卡片调整本栏目顺序并一次性落盘;顶部可选自动或固定 `1..6` 列,列数是本机 `localStorage` 偏好,不写入 sidecar。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、排序模式、滚动位置与选中资源;不提供工具栏、工具侧边栏或可拖动标题栏。2026-08-10 起聚焦态以资源元数据、Rust 权威深度、同类型上下游 / 任务流和版本信息为首屏;美术图片与视频只保留卡内本体,不在详情重复放大。安全文档正文与按意图读取的音频控制位于元数据之后;美术编辑、音频编辑 / 替换、版本替换或运行模块仍不在本阶段。页面不提供画布平移、缩放、自由摆放、框选与依赖连线,可见性预取以栏目滚动容器为交叉根。
|
||||
- `@genarrative/image-canvas-react` 的 `CanvasWorld` 接收逻辑 `viewport`,`viewport.scale` 表示最终视觉比例;渲染细节由共享包封装。调用方测试将预期逻辑 `{ x, y, scale }` 传入公开 helper `canvasViewportToWorldTransform`,再比较实际 world 的 `style.transform`;不自行换算渲染值,不以缩放按钮文案替代 viewport 断言。world 继续使用共享默认正方形 `CANVAS_WORLD_SIZE = 12000`,不新增矩形尺寸 API;资源页的 `navigationBounds` 只用于布局、fit、关系线 geometry 和数据属性,不能再设置 world DOM 宽高。非缩放描边只应用于资源依赖关系线等几何 overlay,Lucide 等静态卡片图标保持自身正常缩放,避免被统一 SVG 规则压窄;普通位图不强制 `pixelated` 插值。依赖线、卡片拖拽、平移和滚轮锚点继续使用同一逻辑 viewport 坐标。
|
||||
|
||||
#### 资源管理串行改造:本体卡、分区缩放与依赖聚类
|
||||
#### 资源管理串行改造:本体卡、栏目网格与顺序派生
|
||||
|
||||
- 三项必须串行落地:阶段一先把资源卡改为媒体 / 文档 / 版本本体;阶段二再引入分区级可视高度与独立内容倍率;阶段三最后在 dependency 自动坐标派生中增加稳定聚类。三阶段都复用 `ProjectDevelopmentView`、`projectResourcesFromReadModels`、现有详情状态机、安全 Tauri 读取命令、布局 sidecar 和 Rust 资源图,不新建页面、manifest 真相或平行资源模型。
|
||||
- 阶段一只保留一个 `ResourceCard`。卡外层用非交互容器承载绝对坐标、选中与关系高亮;其内的详情覆盖按钮和媒体播放按钮是同级 DOM,分别键盘聚焦。视觉层对读屏隐藏,详情按钮使用“打开资源详情:<类别> <名称>”的精确 `aria-label`;播放键使用“播放 / 暂停 <名称>”,不依赖视觉文件名。
|
||||
- 三项必须串行落地:阶段一先把资源卡改为媒体 / 文档 / 版本本体;阶段二把资源管理页做成栏目分页的纵向滚动网格;阶段三在 dependency 自动坐标派生中保留稳定聚类,作为未排序栏目的初始顺序种子。三阶段都复用 `ProjectDevelopmentView`、`projectResourcesFromReadModels`、现有详情状态机、安全 Tauri 读取命令、布局 sidecar 和 Rust 资源图,不新建页面、manifest 真相或平行资源模型。
|
||||
- 阶段一只保留一个 `ResourceCard`。卡外层用非交互容器承载选中与关系高亮;其内的详情覆盖按钮和媒体播放按钮是同级 DOM,分别键盘聚焦。视觉层对读屏隐藏,详情按钮使用“打开资源详情:<类别> <名称>”的精确 `aria-label`;播放键使用“播放 / 暂停 <名称>”,不依赖视觉文件名。
|
||||
- 卡片类型与读取路由一个纯分类函数决定:PNG / JPEG / WEBP 调用 `read_local_project_image_preview`;GIF / SVG / AVIF / BMP 和 MP4 / WebM / MOV 调用 `read_local_project_media_preview`;文件文档调用 `read_local_project_text_preview`;Agent 文本回执直接消费已有安全字符串;音频只在播放意图发生后调用 `read_local_project_media_preview`;版本和不可预览类型不发 IPC。文档卡摘要只以 React 纯文本渲染,不执行 Markdown HTML、图片或链接。
|
||||
- `ProjectResourceCardPreviewController` 在画布级持有唯一 `IntersectionObserver`,`root` 指向当前资源画布,只为交叉卡片入队。当前 Hook 的逻辑读取并发上限固定为 `3`,总排队硬上限固定为 `96`;终态内存预览 LRU 同时限制为最多 `48` 项和 `64 MiB` 总载荷,媒体以 Blob 实际字节、文档以 UTF-8 字节计入。排队中、在途与已缓存的相同身份都去重。调度优先级固定为 `play > detail > visible`,可见预取不得占满全部主动容量;队列已满时,播放 / 详情请求替换最低优先级的排队预取并优先执行。音频不接受可见预取,只接受用户播放意图。缓存替换、淘汰、资源失效、scope 切换和卸载统一撤销 Blob URL;活动媒体可在预算内免于普通 LRU 淘汰,但不能突破总字节硬上限。
|
||||
- `ProjectResourceCardPreviewController` 在栏目级持有唯一 `IntersectionObserver`,`root` 指向当前栏目滚动容器,只为交叉卡片入队。当前 Hook 的逻辑读取并发上限固定为 `3`,总排队硬上限固定为 `96`;终态内存预览 LRU 同时限制为最多 `48` 项和 `64 MiB` 总载荷,媒体以 Blob 实际字节、文档以 UTF-8 字节计入。排队中、在途与已缓存的相同身份都去重。调度优先级固定为 `play > detail > visible`,可见预取不得占满全部主动容量;队列已满时,播放 / 详情请求替换最低优先级的排队预取并优先执行。音频不接受可见预取,只接受用户播放意图。缓存替换、淘汰、资源失效、scope 切换和卸载统一撤销 Blob URL;活动媒体可在预算内免于普通 LRU 淘汰,但不能突破总字节硬上限。
|
||||
- 失败预览区分 transient 与 permanent:读取漂移、文件替换和通用暂时错误可由用户再次打开详情或点击播放显式重试;超尺寸、损坏、类型不支持和危险 SVG 等永久错误继续进入有界失败状态。滚动可见性不自动重试任一失败项,错误 UI 只在 transient 情况提示关闭后重试。
|
||||
- 预览身份键固定包含 `projectPath / projectId / mode / resourceId / category / path / mediaType`。每次 Controller 挂载以及每次 `projectPath + projectId + mode` 变化都生成新的不透明 `scopeId`,`A → B → A` 不复用第一轮 A;每个真正进入 IPC 的读取再生成唯一 `requestId`。Controller 的 epoch、逻辑活动计数、队列、缓存和 Promise 回调仍按当前 scope 隔离,旧 `then / catch / finally` 不能写入或扣减新 scope。
|
||||
- Tauri app state 注册唯一 `ProjectResourcePreviewReadManager`,三个既有读取 command 共同使用进程级 `3` permit semaphore 和活动 request / scope registry;不同 Controller、项目、mode 或窗口都不能扩张物理读取上限。scope 切换与卸载先失效前端 epoch、暂停媒体、清理队列与 Blob,再异步调用窄职责 `cancel_local_project_resource_preview_scope(scopeId)`;取消未知或已结束 scope 幂等成功,重复 request ID 失败关闭。等待 permit 的任务同时等待取消;已取得 permit 的任务在命令入口、安全策略与登记复核后、打开文件后、每个固定块之间、签名 / 结构校验前、漂移复核前和 base64 前检查共享取消状态。
|
||||
- 图片、文档和媒体读取改为受原 `2 / 8 / 32 MiB` 上限约束的固定小块读取,permit 只由对应原生任务在成功、失败或取消后释放。取消任务不编码 data URL、不返回字节给前端、不创建 Blob URL;成功、失败、取消和 join error 都通过清理守卫移除活动 request / scope registry。seen request tombstone 保留最近至多 `8192` 项;非活动 cancelled scope tombstone 的保留预算为 `1024` 项,仍有请求的已取消 scope 必须跳过淘汰并可临时超出预算,最后一个请求结束后立即重新收敛。因此终态断言只要求活动 registry 清零,不要求 tombstone 清零。明确取消只静默收口旧 scope;当前 scope 的真实 transient / permanent 错误分类不变。详情打开、mode / 项目切换、运行视图、搜索 / 筛选隐藏当前媒体、资源删除或当前媒体身份变化继续暂停旧元素并清除待播放 ID;卡片卸载时再执行防御性暂停。
|
||||
- 解码容错在 DOM 层二次收口:`img` 只在安全读取成功后挂载,`error` 后立即卸载并回退类型占位;`video` 在 `loadeddata` 前保持占位,只在首个可解码画面后显示,`error` 后卸载。图片与视频的卡内几何统一 `object-fit: contain`,透明媒体壳使用棋盘底。
|
||||
- 卡片宽高从 `resourceCanvasLayoutModel.ts` 的 `RESOURCE_CANVAS_CARD_WIDTH / HEIGHT` 作为唯一权威值,React 把它们以 CSS 变量注入资源画布;卡片 CSS、slot 间距、分区 extent 与 `ResourceDependencyOverlay` 端点继续消费这两个常量,不允许在 CSS 中再写第二套尺寸。
|
||||
- 阶段二把“分区高度”和“分区内容倍率”作为两个独立表现态。高度、倍率和分区内部滚动的前端会话 Map 以 `projectId + mode + section` 隔离;外层资源画布滚动以 `projectId + mode` 隔离。它们都不使用 localStorage / sessionStorage,不写 manifest 或 `game-creator-resource-layout.v1`。高度模型继续定义默认值、步长、“标题操作 + 至少一排 180×128 卡片”下限和由 `.game-resource-canvas.clientHeight` 夹取的上限;倍率模型固定 `0.5..2`、按钮步长 `0.1`,触摸板 `Ctrl/Cmd + wheel` 使用有界指数映射,WebKit `gesturestart/change/end` 使用同一 clamp。普通 wheel 不缩放;`Ctrl/Cmd + wheel` 由画布上的原生委托监听以 `{ passive: false }` 注册,先真实取消 WebView 默认缩放,再按最近的 section viewport 执行锚点缩放。捏合以 viewport 内手势中心换算逻辑内容坐标,并在下一帧补偿 scroll,避免缩放目标跳离指针。
|
||||
- 每个分区使用固定标题行与 `minmax(0, 1fr)` 内部 viewport;分区本体仍是 `.game-resource-canvas-content` 中的普通 grid item。逻辑 plane 继续承载未缩放的卡片坐标和 `180×128` 本体尺寸,外层 frame 按倍率设置真实滚动 extent,plane 仅使用 `transform-origin: 0 0` 的视觉 scale。超出当前高度或缩放范围时由分区 viewport 内部滚动,外层 `.game-resource-canvas` 只负责分区之间滚动。高度、倍率和内部滚动均不调用布局 CAS,也不改变媒体 React key;详情、项目或 mode 切换前捕获对应 scope,返回时分别恢复外层与分区滚动及原卡片焦点,未保存过的 scope 从 `0` 开始。
|
||||
- 固定四区分别在自己的 `.game-resource-plane` 内挂载 `ResourceDependencyOverlay`。每个分区最多创建一个 `ResizeObserver`,并把本区 scroll / resize / observer 回调合并到本区单一在途 `requestAnimationFrame`;SVG 和卡片直接消费同一逻辑坐标与父 plane scale,不通过异步屏幕测量重绑端点。分区 viewport 原生裁剪本区 SVG;一端离屏时只保留对应方向、带同语义 marker 的边界继续线,两端离屏时隐藏。项目 / mode 切换、搜索隐藏、资源删除和卸载继续丢弃旧 viewport / path 并清理 observer 与监听。
|
||||
- 卡片宽高从 `resourceCanvasLayoutModel.ts` 的 `RESOURCE_CANVAS_CARD_WIDTH / HEIGHT` 作为默认排版的唯一权威值;网格单元尺寸与列数由 CSS 和列数偏好决定,不允许在 CSS 中再写第二套默认排版尺寸。
|
||||
- 阶段二把资源配置成栏目分页的纵向滚动网格。顶部列数控件提供自动与固定 `1..6` 列:列数是全局单值,保存在本机 `localStorage` 的 `agc.resource-canvas.columns.v1`,两个 `mode` 与所有项目共享;不使用 sessionStorage,不写 manifest 或 `game-creator-resource-layout.v1`,localStorage 不可写时只当次生效。列数模型只产出网格列数,不改写任何记录顺序。
|
||||
- 每个栏目是一个普通 grid 容器,单元高度统一、图片 `object-fit: contain`,栏目本体在页面纵向滚动流内。不再有 `.game-resource-plane`、分区 viewport、分区高度 / 倍率 transform、`ResizeObserver` 端点测量或 `Ctrl/Cmd + wheel` 画布缩放;页面容器不得用 `overflow: hidden` 造出画布语义。dependency 模式的精确引用关系改为挂在栏目网格容器上的关联视觉隐藏文本,与可见引用同源变化。
|
||||
- 阶段三的聚类已落在 `reconcileResourceCanvasLayout` 的 dependency 自动坐标派生步骤。它先按资源分类过滤 reference edge,并把 task-flow 按固定分类切成仅在同类 source / target 同时存在时有效的聚合超边;每个 section 再用精确边与 flow 临时节点建无向邻接表,以迭代遍历生成弱连通组。task-flow 只以“流节点 -> 成员”的线性成员关联参与布局,绝不展开 source × target 资源组合,不绘制 SVG,也不把搜索后的 visible set 用作输入。`dependencyDepth` 仍唯一决定横向业务层级;相关簇按 `minDependencyDepth + minStableResourceId` 排序,孤立集合置于所有相关簇之后,簇间使用单一布局常量留白。每个相关簇内先按稳定资源 ID 建同层初始序,再做固定两轮左至右 / 右至左的中位数扫描:精确引用读取相邻层的上下游 rank,task-flow 读取另一端成员 rank 的中位数,平局按稳定资源 ID 收口。dependency 自动位置使用 `48px` 列间走线区和 `40px` 行间走线区;每个相关簇以最大层行数确定高度,资源较少的层增加确定性半差偏移而在簇内居中,菱形 / 分叉两侧因此保持均衡。type 模式仍使用原 `16px` 行列间距。跨分类 read model 关系不进入前端聚类、边界偏置或拓扑签名,但 Rust 深度与原始图真相不改。显示坐标必须遵守前后端共享的 `0..=1_000_000` 上限;超深依赖在最后合法列确定性饱和,保留原始 `dependencyDepth`,同列资源继续按稳定顺序纵向避让。若任一自动 `x / y` 无法在合法域内落槽,协调必须在 IPC 前失败关闭,不持续提交必然被 Rust 拒绝的坐标。算法保持 `O(V + E)` 图遍历,加固定轮数的层内稳定排序和现有有界占用索引;4096 资源不允许全量配对。旧的 `manuallyPlaced=true` 坐标先占位并原样保留,聚类只派生自动坐标;同类型拓扑身份签名只记录有界的资源 ID 端点 / 成员,以便深度未变但邻接变化时触发重派生。图边、cluster ID 和签名都不写 sidecar。
|
||||
- 中间主视窗提供 `resource-overview / resource-editor / ui-editor / run` 四种状态。2026-08-10 起普通用户“新增资源”显示为禁用态且处理函数拒绝 create;所有现役资源从聚焦态“编辑资源”进入非破坏性派生:静态图片走 `derive + editKind='image-reference'`,SVG、视频、音频、文档/代码、Agent 回执和项目版本进入统一资源编辑壳并按能力分流。编辑面板只替换中央区域,不覆盖右侧 Supervisor 或底部 Agent。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源总览只修改前端展示态,不伪造后端预览暂停结果。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执、已导入附件和已完成任务明确登记的产物派生资源,固定按 manifest 资产功能分类 `category`(`UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`)加末尾独立的「项目版本」栏目分区;未知任务产物不再兜底为版本,未完成任务或未在 `artifacts` 中登记的任意本地音频也不冒充正式资源。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外绘制当前 manifest 与资源投影可证明的依赖关系。排列与图层都不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-30 视觉验收修正:资源总览所有栏目初次适配与复位最多以 `1.5` 倍缩放卡片,避免单个低尺寸卡片被插值放大成糊图;用户主动缩放仍沿用通用画布倍率,并按“排序模式 + 栏目”保留当前会话内的平移和缩放。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执、已导入附件和已完成任务明确登记的产物派生资源,固定按 manifest 资产功能分类 `category`(`UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`)加末尾独立的「项目版本」栏目分区;未知任务产物不再兜底为版本,未完成任务或未在 `artifacts` 中登记的任意本地音频也不冒充正式资源。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外把当前 manifest 与资源投影可证明的精确引用挂成关联的视觉隐藏说明,页面不绘制连线。排列结果只作为未排序栏目的初始顺序种子,不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。资源管理页是栏目分页的纵向滚动网格,卡片按列排布并只纵向滚动。栏目内顺序由布局 sidecar 记录按 `(y, x, resourceId)` 字典序推出:用户拖动卡片排序时把该栏目全部记录重编码为顺序阶梯(`x = 0`,`y = 序号 × 144`)并一次性落盘,用户已排序的栏目里新资源追加到末尾。顶部列数控件提供自动与固定 `1..6` 列,选择按全局单值记在本机 `localStorage`,不写入布局 sidecar。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、排序模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源页边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 运行表现层首版直接嵌入当前项目的 loopback 游戏画面,并保留素材信息和数值微调面板;两个面板保持原有 `156px` 最小高度,没有真实数据时只让正文为空,不渲染预设字段、默认数值、未载入控件或自然语言功能占位,也不随空内容收缩。`preview.start` 启动本地 server 后把真实 URL 回写工作台,`preview.open` 只激活客户端内运行视图,不再调用系统浏览器;参数调整首版仍只保留本地 UI 草稿,不修改代码或 manifest。preview server 对 UTF-8 HTML 响应注入固定同源尺寸桥脚本;注入点通过真实 HTML tokenizer 边界定位,保守处理注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text、template、plaintext、foreign content 与重复 `src`,并支持省略 `</body>` / `</html>`。桥以 `ResizeObserver` 观察 `documentElement / body` 根布局,结合页面 load、窗口 resize 与字体就绪重新测量;页面可见时另以 `500ms` 低频兜底探测至多 `512` 个元素的实际边界,探测截断时保留 body / scroll 上界,并按连续测量排除随 viewport 同步变化的 `100vh / 100% / bottom / right` 自反馈。相同尺寸元组去重后才以固定版本 `postMessage` 上报,不订阅整页 `MutationObserver`。宿主同时校验消息 origin 和 `event.source`,以实际内容宽高与当前容器宽高计算不超过 `1` 的等比缩放;宿主单独记录最近一次合法上报的 iframe viewport,首次收到由自身 fit 切换产生的新 viewport 测量时只确认该 viewport、不反向改写内容尺寸,待 viewport 稳定后仍接受真实内容宽高变化,从而阻断 `100vh` / 百分比布局在两个适配尺寸之间回灌振荡。重复内容尺寸不更新 React 状态,陈旧 viewport 消息继续忽略。容器 resize 期间保留内容尺寸与已观察 viewport,只按新容器尺寸连续重算缩放,避免拖动窗口时在原生尺寸和 fit 之间闪烁;preview URL 变化时才清空状态并重新测量。放得下时保持 `1:1`,超出时完整缩小并居中,iframe 禁止横纵滚动条,不能以 `overflow: hidden` 直接裁掉超出内容。非 UTF-8 HTML 原样返回,不因适配桥破坏已有预览。
|
||||
- 右侧继续复用现有 Project Supervisor 会话、Runtime 澄清和确认链路;输入区展示 `严格审批 / 风险审批 / 无需审批` 独立面板。P0 只有严格审批可选;风险审批和无需审批保持视觉不可用但允许点击查看原因,不替代 Runtime 的逐动作权限、确认、sandbox 或 reconciliation 门禁。风险 Rank 算法记录在 `docs/project-memory/todos/【待解决】AI游戏创作高风险审批Rank-2026-07-20.md`,前端不得自行计算。
|
||||
- 底部状态栏默认展示策划、美术、程序 3 组,并允许在同一栏展开数值、音频、发布组;状态来自 manifest 与当前 Supervisor run 的 Runtime,悬停显示当前任务与进度。累计泥点必须等待后端计费归因投影;Agent.md 编辑和自定义 Skill 在来源审核、版本、权限、sandbox 与回滚合同完备前不向普通用户开放。
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# Game Agent 资源自由画板与快速编辑
|
||||
|
||||
## 2026-09-05 资源画本独立转场
|
||||
|
||||
- `resourceBookModel` 立即提交目标 `main / child + category`,`entering / returning-main` 只表示尚未收尾的视觉过程,不承载待提交的栏目。缩略入口、大纲、下一页与资源定位共用分类导航入口;栏目互切直接到达目标,不经过总览。滚轮不切换栏目。
|
||||
- 转场对**真实元素**做 FLIP,不使用克隆快照层。总览与子画布之间同一张卡是同一个 DOM 节点(同一 React key、同一父节点,只有布局函数在 `resourceBookOverviewCardLayout` 与 `resourceBookChildCardLayout` 之间切换),标题栏同理。`resourceBookController.begin` 在状态提交前按 key 记录 First 屏幕矩形与当前不透明度;提交后的 layout effect 调 `play` 测 Last,给同一个节点加反向 transform 再过渡回 identity。动画结束没有"克隆 → 真实节点"的交接,因此不存在交接瞬间的跳变;遮挡关系由真实 DOM 的 z-index/DOM 顺序决定,与静止态一致。
|
||||
- 反向 transform 写在宿主节点自身坐标系:`transform-origin: 0 0`,缩放比取 `First.size / Last.size`,位移按宿主原点偏移换算后除以 `.game-resource-book-scene-world` 的缩放。标题栏激活时内联的"抵消 world"transform 作为 base 与反向 transform 组合,结束帧回到该 base。
|
||||
- `begin` 之后的所有布局提交都调 `sync`:比较每个节点的布局签名,变化时取消旧动画、重新测量,以**当前视觉矩形**(含运行中的 transform)为起点重基到新的干净终点。转场进行中用剩余时长(`startTime + 420 - now`,下限 80ms),空闲布局变化用完整 420ms。卡片预览尺寸是异步到达的,因此"动画期间甚至动画结束之后"的二次布局变化同样是动画而不是瞬移。窗口/容器尺寸变化与拖拽中的卡片不做布局动画。
|
||||
- 目标不可见的节点(内联 `opacity` 为 0)钉在 First 位置淡出,没有 First 的挂载节点淡入;搜索/排序导致的卸载保留一帧后用同一套淡出撤下,进入栏目期间其他栏目保留挂载淡出,返回总览时源栏目未回到摞上的卡片按子画布布局淡出。转场时长统一 420ms;动画创建失败、零尺寸、容器尺寸变化或 `prefers-reduced-motion` 时不创建动画,也不等待定时器。
|
||||
- `begin` 只记录**当时可见**的节点。不可见节点的屏幕矩形是"看不见的位置":非活动栏目标题栏在子画布态保留总览布局坐标,却被子画布 world 变换推到屏幕外,若把它当 First,返回总览时标题栏会从屏幕外飞进来。不可见节点没有 First,只走淡入,位置保持不动;进入栏目方向它们仍然可见,因此照旧钉在原地淡出。同理,卡片只有在提交前可见时才参与几何 FLIP。
|
||||
- 再次导航先读取当前像素位置,再取消旧动画并重新建立目标;token 隔离旧 promise,过期完成和取消均不能提交新状态。
|
||||
- 缩放、适应内容和空白拖动先结束视觉转场再执行用户输入;转场中的资源卡 pointer-down 不启动持久化拖拽。普通滚轮只平移当前视图,Ctrl/Meta 滚轮以指针为锚点缩放,均不切换栏目。
|
||||
- viewport 只属于用户交互状态,按主画布以及 `sortMode/category` 隔离保存。转场不插值也不持久化 viewport;测量主画布缩略入口时先逆变换回布局坐标,避免缩放后的 DOMRect 被二次缩放。只有真实非零测量才能标记首次 fit 完成。
|
||||
|
||||
验收使用 `resourceBookController.test.ts`、`resourceBookLayout.test.ts`、`resourceBookModel.test.ts`、`resourceBookViewport.test.ts` 和 `appSurface.test.ts`;页面 `.suite.ts` 由 `appSurface.test.ts` 注册,不能作为独立 Vitest 文件运行。真实浏览器需覆盖总览/栏目往返、连续改选、滚轮平移、缩放、尺寸变化、减少动画偏好,以及真实元素的图片和字体比例。jsdom 不实现 Web Animations,只覆盖几何计算、状态机与调用契约,不能代替浏览器验收。
|
||||
|
||||
### 2026-09-05 子画布无限平移与初始可读比例
|
||||
|
||||
子画布改为固定视口加可无限平移的 world 层:视口本身保持 `overflow: hidden`,平移和缩放只作用于 `.game-resource-book-scene-world`,不再让内容容器的边界限制拖动范围。首次 fit(包括隐藏分页画布提前完成的 fit)统一保证比例不低于 `1:1`,避免子画布展开后卡片缩成不可读的小块;用户后续主动缩放或平移的视口仍按每个分类独立保留。新增 viewport 回归测试覆盖初始可读比例和超出内容边界双向平移。
|
||||
|
||||
返回主画布时立即提交主画布布局,由真实元素 FLIP 完成缩回;world/main viewport 不播放附加动画。
|
||||
|
||||
总览和栏目视图同样使用固定铺满 manager 的交互视口,只有内部 world 层承担平移和缩放,内容层不裁剪缩略入口。空白拖动允许双向越过全部内容边界;移走内容后仍可在原视口继续拖动。普通滚轮/触控板平移,Shift 滚轮横向平移,Ctrl/Meta 滚轮以指针为锚点缩放;wheel 监听阻止浏览器滚动或页面缩放,但不切换栏目。总览/栏目往返保留总览原有位置和比例,只有主动适应内容才复位。
|
||||
|
||||
## 2026-09-04 资源画本 UI 阶段补充
|
||||
|
||||
资源工作台采用“主画布 + 子画布”的统一画本模型。主画布展示各子画布的缩略入口,内部按资源类型显示有限层叠卡片,最多三层,更多资源以虚化卡片提示。主画布预览和子画布展开态复用同一套卡片视觉与标题栏结构,同一批 DOM 节点从当前屏幕位置过渡到目标位置。子画布标题栏提供缩回主画布的按钮;子画布互切直接到达目标,不强制经过主画布。
|
||||
|
||||
本阶段先实现前端 UI、导航状态和转场表现,资源真实跨画布移动、主画布正式资源登记、上传/生成任务、版本绑定以及 UI 编辑器导入与 UI 包工作流继续沿用现有权威链路,待 UI 编辑器完善后再接入。临时画本视图状态不得替代 manifest、草稿或其它后端业务真相。
|
||||
|
||||
## 目标
|
||||
|
||||
资源管理在空项目中展示现有按类型式分区展览。任一栏目出现资源后,“按依赖”和“按类型”共用同一套栏目分页画布:固定七个栏目各自拥有一个铺满资源管理区域的独立画布,空栏目也保留可打开的空画布;栏目大纲悬浮在画布左侧中间,只显示图标和栏目名称,不占用画布布局宽度;顶部显示当前栏目标题,底部显示下一页标题。普通滚轮平移当前视图,按住 Ctrl/Meta 的滚轮以指针为锚点缩放当前画布。两种模式唯一差异是“按依赖”在当前栏目内绘制引导线。资源详情是叠加在资源画板之上的非模态卡片;打开、切换和关闭详情都不得卸载背景工具栏、资源卡、依赖连线或重置 viewport、搜索和排序模式。
|
||||
|
||||
图片资源从“编辑资源”进入资源派生编辑(`derive + editKind='image-reference'`),结果固定创建新的本地 asset,源资源与源文件保持不变;具体来源核验、请求 DTO 与幂等合同见《【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09》。
|
||||
|
||||
资源总览中的美术图片卡按真实纵横比计算展示矩形,并使用统一边界限制极端长图或宽图。非图片资源继续使用固定卡片尺寸。
|
||||
|
||||
## 权威数据与持久化
|
||||
|
||||
### 栏目分页投影与设计文档缺失边界
|
||||
|
||||
- 栏目内容只由当前权威资源投影构造:manifest assets、已完成任务的 artifacts、用户已导入附件和持久化 Agent 文本回执。前端不得扫描项目目录自行发明资源。
|
||||
- 资源依赖图的精确引用身份同时支持两种稳定索引:远端 `source.resourceId` 与本地 `local-asset:<manifest assetId>`。派生资源把 `referenceResourceIds` 写成本地 canonical 身份时,必须能解析到同一项目的唯一资源;候选去重后才允许建立一条 `asset-reference`,无法唯一解析的引用继续进入 `unresolvedReferenceResourceIds`,不得在前端猜测连线。
|
||||
- AGC 侧共享契约维护 canonical 资产类型目录和历史读时映射:`game-background -> scene`、`character-art -> character`、`ui-prototype -> ui-design`、`art-spritesheet -> icon-spritesheet`、`art-spritesheet-slice -> icon`、`illustration/game-art -> image`。该目录只约束 AGC 新写入与投影,不要求现役 Web 美术画布同步改造;历史 manifest 保持原值,读取时可归一到 canonical 展示。
|
||||
- 角色动画派生结果的 manifest `kind` 固定写 `character-animation`,`source.generationKind` 同步保留 `character-animation`,媒体仍以预览视频加正式序列帧登记。图片编辑等普通派生继续继承源资源语义 kind;后续如需改写为 canonical kind,只能在读取投影或显式迁移中完成,不重写历史 manifest。
|
||||
- 固定栏目页始终为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。分区口径是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`);项目版本不是资源资产、6 类资产分类轴对它不适用,固定单独成栏排在末尾;没有 `category` 事实源的任务产物与导入附件归 `待归类`,合法 Agent 文本回执归 `文档`。若某栏目没有对应投影,Dock 仍显示该栏目并允许打开空画布;空画布只表达“当前没有已登记资源”,不得由 UI 补假数据。
|
||||
- 布局 sidecar 的 `section` 允许继续出现旧四 / 五栏目取值(`code / document / version / art / audio`),读取时按资源当前分区归并:`document / audio / version` 同名 1:1 保留,旧 `art` 按资源当前分类落入 `ui-interaction / character / scene / unclassified`,旧 `code` 落入 `unclassified`,无法精确归并时回落 `unclassified`。归并只改写 `section`,`x / y / manuallyPlaced` 原样保留,不写迁移脚本、不删除、不重置。`game-creator-resource-layout.v1` schema 不升版,否则既有 sidecar 会被直接拒读。
|
||||
- Direct Codex 的游戏生成链路以 `game/index.html`、`game/style.css`、`game/game.js` 作为代码资产登记;是否额外产出设计文档由当前任务和 Agent 决定,资源页只消费已登记结果。
|
||||
|
||||
## 操作边界
|
||||
|
||||
- 栏目顺序固定为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。完全空项目显示全部栏目的分区展览;任一栏目出现资源后,分页大纲以左侧垂直居中的悬浮 Dock 展示全部栏目。Dock 常态即可读可点:Dock 底板、与栏目标题栏同款的栏目图标和 12px 栏目文字常驻,当前栏目常态带选中态强调;悬停或键盘聚焦只做同一套 token 的加强(更深的边框、阴影与背景模糊),不再用"常态缩小、降低不透明度、只露栏目文字"表达层级。默认停留在该顺序中的第一个非空栏目,空栏目仍可打开空画布。游戏代码资产按其 manifest `category` 落入 `待归类` 并正常显示卡片。
|
||||
- 滚轮不切换栏目。点击大纲、底部“下一页”标题、总览入口或自动定位资源属于显式切页;切页前必须终止旧栏目的画布拖动和 pointer capture,避免旧 viewport 写入新栏目。
|
||||
- 每种排序模式下的每个栏目画布都保留独立 viewport;首次进入该“排序模式 + 栏目”组合时按当前内容适配视口,离开后再返回则恢复该组合上次的平移和缩放。空白处拖拽平移画布,资源卡拖拽移动卡片并更新依赖线,Ctrl/Meta 缩放只作用于当前组合,不能牵动其它排序模式或栏目。
|
||||
- 依赖画布复用 `@genarrative/image-canvas-core` 的 viewport 计算,并复用现有资源卡片、布局和依赖连线模型。
|
||||
- 非空状态不使用资源分区滚动条、分区缩放或分区高度操作作为主要导航;栏目通过大纲、总览入口和底部下一页标题切换。
|
||||
- 当前栏目画布背景是无限的:用户可以将 viewport 沿 x/y 任意方向平移,画布不以资源 extent 作为导航边界,也不显示可见画布边缘。资源卡片的持久化布局坐标允许落在 `-1_000_000..=1_000_000`,用于支撑元素位于世界原点左上方;超出该范围仍拒绝写入,避免持久化非法布局。这与 viewport 能否继续平移是两层独立语义。搜索、详情卡和临时隐藏不得改变 viewport。
|
||||
- 指针拖动空白平移画布,普通滚轮平移当前视图,Ctrl/Meta 缩放、复位以及容器 resize 后都必须保持同一套 viewport 数据流。只有“排序模式 + 栏目”组合首次获得可测量容器尺寸或用户显式复位时才重新适配内容;返回已访问组合、图片尺寸测量、布局拖动或资源 extent 变化只归一化并保留该组合的当前 viewport,不得意外重置用户已经完成的平移和缩放。普通平移不夹取 x/y;缩放仍受共享画布的最小/最大比例限制。初次 fit 与显式复位只使用资源卡真实包围盒,不把导航最小尺寸、原点空区或额外布局 gap 算入,并以 `16px` 紧凑留白在共享缩放上限内尽量铺满视口。
|
||||
- 资源卡拖动使用 `5px` 阈值区分点击与移动;移动期间按当前 scale 乐观换算世界坐标、显示拖动态并同步依赖线,释放时提交一次 `manuallyPlaced=true` 布局 CAS,取消则回滚预览且不提交。拖动后的释放点击不打开详情。
|
||||
- 依赖模式只在当前栏目画布内显示两端都属于该栏目的合法精确引用;装饰 SVG 与视觉隐藏的关系说明消费同一组可见边,搜索隐藏任一端点时两者同步移除。依赖线的 viewport 测量按动画帧合并,平移和缩放只更新已挂载观察器消费的最新 viewport,不得在每次输入时重建 ResizeObserver、scroll 或 resize 监听。任务流仍只参与同类型布局聚类,不绘线也不进入关系说明。
|
||||
- 资源详情卡包含元数据、媒体预览和“编辑资源”操作,但不使用全屏 backdrop、不声明 `aria-modal=true`、不把 `focusedResource` 作为背景工具栏渲染条件。角色资源同时显示“生成动画”时,两个业务操作按钮必须使用一致样式,不能依赖 DOM 中的首按钮位置。桌面端允许继续操作背景画板;窄屏可以使用有边界的贴边卡,但背景组件必须保持挂载。
|
||||
- 完全空项目继续使用可纵向滚动的分区展览,不挂载分页画布或依赖画布的 `overflow: hidden` 交互壳;低高度窗口在两种排序模式下都能滚动到“项目版本”。
|
||||
- 在“按依赖 / 按类型”之间切换或离开资源管理进入运行视图后返回时,恢复对应“排序模式 + 栏目”的现有 viewport,不自动触发“复位资源画布”,也不得用另一排序模式的 viewport 覆盖用户已经完成的平移和缩放。首次 fit 与用户显式复位仍使用同一套真实资源包围盒算法。
|
||||
- 资源详情卡定位相对 `.game-workbench-stage`,桌面端在中间主视窗居中并受主视窗宽高边界约束;禁止使用相对整个窗口的右上角 `position: fixed` 定位。
|
||||
- 图片浮动工具条与图片精修草稿画布均已退役。图片的“编辑资源”入口走资源派生编辑,在资源编辑壳内提交,不再打开独立画布或图片下方快速编辑卡。
|
||||
- 资源自由画板顶部不展示“生成视频”“生成音效”“生成背景音乐”和“新增 UI 设计”等手动新建入口;顶部只保留播放、未完成编辑恢复、排序与复位等画布级动作。无源资源生成由 Agent 语义工具进入既有生成合同,已有资源的编辑和图片“生成动画”入口不受影响。
|
||||
- 图片派生编辑使用 `/api/editor/images/edits`,严格遵循 `EditorImageEditRequest`,不得携带 `assetKind`、`sourceImageSrc` 或 `sourceResourceId`。Game Agent 本地 kind 在登记编辑器来源前映射到现役 canonical kind;服务端安全业务错误必须结构化返回,不能把全部 400 折叠为同一个“平台明确拒绝”。
|
||||
|
||||
## 编辑结果投影与焦点竞态
|
||||
|
||||
资源派生编辑或资产命令成功返回后,当前项目仍匹配时必须执行:
|
||||
|
||||
```text
|
||||
用返回的完整 manifest 更新项目上下文
|
||||
→ 重建资源投影
|
||||
→ 以新投影更新依赖图输入
|
||||
→ 分别协调 dependency/type 布局中的新资源
|
||||
→ 当前模式布局 ready 后再决定是否选择和定位新资源
|
||||
```
|
||||
|
||||
- 不刷新页面,不关闭/重开项目,不额外重新拉一份旧 manifest。
|
||||
- 新资源卡身份固定为 `asset:<asset.id>`。布局使用现役确定性默认位置与 CAS/FIFO 合同;编辑事务本身不直接写资源布局 sidecar。
|
||||
- manifest 投影可以在布局协调完成前显示加载占位,但不得用临时坐标持久化错误布局。
|
||||
- 投影与焦点的迟到结果守卫按当前 ref/store 中的完整身份复核,不依赖 React 闭包中的旧布尔值:
|
||||
- 用户已切项目:结果只能更新原项目 key 下的后台缓存,不得写当前项目 manifest、切换中央状态或抢焦点。
|
||||
- 用户已切到运行视图、资源总览或另一编辑流程:可以按精确项目身份更新缓存/投影,但不得把中央主视窗切回本次流程。
|
||||
- 用户在等待期间选择了其它资源:保持用户当前选择;新资源仍进入投影和布局,但不得自动选中。
|
||||
- 搜索或筛选 epoch 已变化且新资源被隐藏:保持条件和当前选择,显示“新资源已保存,但被当前搜索条件隐藏”,并提供显式“清除搜索并定位”动作;不得自动清空条件。
|
||||
- 只有项目身份、编辑流程身份、selection epoch 和 query epoch 全部仍匹配,且新资源在当前条件下可见时,才在布局 ready 后自动选择、滚动并聚焦新资源。
|
||||
|
||||
## 美术预览几何
|
||||
|
||||
- 图片预览读取返回真实 `pixelWidth/pixelHeight`。PNG、JPEG、WebP 复用 Tauri 已完成的头部尺寸校验;GIF、AVIF、BMP 和安全 SVG 的媒体预览也只读取受限头部/根元素元数据,无法安全解析时回退固定卡片尺寸。
|
||||
- 第一版图片展示边界为最大宽 `220px`、最大高 `180px`、最小短边 `96px`,展示框纵横比夹取在 `1:2..2:1`;图片始终 `object-fit: contain`,不得拉伸或裁剪。
|
||||
- `resourceCanvasLayoutModel` 的碰撞、自动布局、section/world extent、适配视口和拖动几何必须消费每个资源自己的 `width/height`,不能只改 CSS。
|
||||
- `ResourceDependencyOverlay` 的端点和绕障矩形必须消费同一资源矩形表,保证横图、竖图和 fallback 卡片的连线均对齐。
|
||||
- `ui-interaction/character/scene/audio/document/unclassified/version` 继续使用固定 `180x128`;尺寸只属于展示投影,不写入 manifest,也不成为第二份资源业务真相。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 完全空项目在按依赖与按类型下都显示相同的分区展览;任一栏目出现资源后,两种模式都切换为栏目分页画布,大纲以左侧垂直居中的悬浮 Dock 覆盖在全宽画布上,常态就带 Dock 底板、全部栏目的图标和文字(不显示数量),悬停/键盘聚焦只做加强,空栏目仍可打开空画布。
|
||||
2. 栏目顺序为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`;点击大纲、总览入口和底部下一页标题切页,滚轮不切换栏目。只登记游戏代码的项目在「待归类」栏目正常显示代码卡片。
|
||||
3. 在当前栏目内拖拽空白可以无限平移画布,拖拽资源卡可以移动卡片并同步依赖线,Ctrl/Meta + 滚轮以指针位置为锚点缩放,复位按钮可以适配当前栏目内容;普通平移不会因资源 extent、图片测量或窗口 resize 被拉回,连续缩放仍停在共享画布的最小/最大比例范围内。
|
||||
4. 按依赖模式显示当前栏目内同类型两端资源的合法精确引用,并具有与当前可见连线一致的无障碍关系说明;按类型模式不显示引导线;搜索隐藏任一端点后连线和说明同时消失。
|
||||
5. 点击任意资源后“按依赖 / 按类型 / 复位”等画布级动作仍保持挂载和原状态;顶部不出现手动新建入口。详情为非模态独立卡片,背景画板 viewport、搜索、排序、卡片和连线不卸载、不重置。
|
||||
6. 图片派生请求通过严格 DTO 合同且不携带非法 `assetKind`;参数、来源类型、余额、鉴权和对账错误显示不同的安全错误码。
|
||||
7. 1:1、16:9、9:16、2:3、3:2 和极端横竖图片按真实比例显示并受边界限制;卡片不重叠,当前栏目 extent、复位视口和依赖连线端点正确。非图片资源尺寸不变。
|
||||
8. GIF、AVIF、BMP、SVG 等不满足栅格图片派生能力的资源继续使用能力模型中的降级编辑路径,不伪装成可提交的 PNG 候选。
|
||||
9. 历史 `game-background` 等私有类型来源不得直接作为远端来源;首次编辑完成 canonical 登记,后续同源请求复用缓存,并实际到达 `candidate-ready`。
|
||||
10. 资源派生编辑或资产命令返回后,manifest、资源卡、依赖图输入和当前模式布局不刷新即完成投影;搜索隐藏新资源时保持搜索条件并提供显式“清除搜索并定位”。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```powershell
|
||||
npm run ai-game-creator-shell:typecheck
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/resourceBookController.test.ts apps/ai-game-creator-shell/tests/resourceBookLayout.test.ts apps/ai-game-creator-shell/tests/resourceBookModel.test.ts apps/ai-game-creator-shell/tests/resourceBookViewport.test.ts
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx apps/ai-game-creator-shell/tests/resourceCanvasLayoutModel.test.ts apps/ai-game-creator-shell/tests/ResourceDependencyOverlay.test.ts apps/ai-game-creator-shell/tests/useProjectResourceCardPreviews.test.ts apps/ai-game-creator-shell/tests/resourceCanvasLayoutContract.test.ts
|
||||
node node_modules/vitest/vitest.mjs run apps/ai-game-creator-shell/tests/appSurface.test.ts
|
||||
cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project::resource_editor -- --test-threads=1
|
||||
npm run check:encoding
|
||||
git diff --check
|
||||
```
|
||||
Reference in New Issue
Block a user