+1
-1
@@ -31,7 +31,7 @@
|
||||
- [项目开发工作台 PRD](./prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md):当前工作台页面和验收边界。
|
||||
- [AGC 错误报告与诊断上传](./technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md):当前进程错误事件、应用级日志和管理员查看器合同。
|
||||
- [立项策划 Agent(Fast GDD)](<./technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md>):旧 `project-supervisor-plan` / `project-planning` 历史会话的入口、审批和恢复合同;V2 切换时未完成旧会话强制失败。
|
||||
- [AGC 资源管理页纵向网格与排序](./technical/【技术方案】AGC资源管理页纵向网格与排序-2026-09-11.md):资源栏目的纵向滚动网格、栏目内顺序、列数偏好与拖拽排序合同。
|
||||
- [GameAgent 资源自由画板与快速编辑](./technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md)
|
||||
- [UI 工作流资源桥接与 Runtime 执行](./【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)
|
||||
- [UI 编辑器 Godot 容器布局](./technical/【技术方案】UI编辑器Godot容器布局模型-2026-08-18.md)
|
||||
- [UI 编辑器变换角点偏移编辑器](./technical/【设计】UI编辑器变换角点偏移编辑器-2026-09-03.md)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
3. 右侧 Project Supervisor 对话与确认区。
|
||||
4. 底部专业 Agent 状态栏。
|
||||
|
||||
中央主视窗明确区分“资源总览”“素材创作无限画布”和“运行测试”三个状态。资源总览与素材创作不是两套项目页:前者负责 manifest 资源投影与栏目分页的纵向滚动网格(含栏目内排序),后者负责单个图片素材的无限画布创作;两者共用当前工作台中央区域。正式预览始终在客户端当前窗口内展开,只允许载入当前项目启动的 `127.0.0.1:<port>` 本地 HTTP 预览,不调用系统外部浏览器。
|
||||
中央主视窗明确区分“资源总览画布”“素材创作无限画布”和“运行测试”三个状态。资源总览与素材创作不是两套项目页:前者负责 manifest 资源投影、依赖关系和类型布局,后者负责单个图片素材的无限画布创作;两者共用当前工作台中央区域。正式预览始终在客户端当前窗口内展开,只允许载入当前项目启动的 `127.0.0.1:<port>` 本地 HTTP 预览,不调用系统外部浏览器。
|
||||
|
||||
## 2. 创作工具平台接入声明
|
||||
|
||||
@@ -42,28 +42,26 @@
|
||||
|
||||
### 3.3 资源布局
|
||||
|
||||
- 资源管理页按栏目纵向滚动排版,每个栏目是一块纵向滚动网格;页面不提供画布平移、缩放、自由摆放、框选或依赖连线。
|
||||
- “按依赖”和“按类型”分别保存栏目内顺序。
|
||||
- 栏目内顺序由布局 sidecar 中该 `section` 记录的 `(y, x, resourceId)` 字典序推出;历史手动坐标只读恢复,不删除、不重置、不迁移。分区轴由旧四栏目切换为「6 类资产分类 + 项目版本栏目」后,旧 `art / code` 值按资源当前分类读时归并,坐标与 `manuallyPlaced` 原样保留。
|
||||
- 用户拖动卡片调整栏目内顺序后,该栏目的全部记录一次性重编码为顺序阶梯(`x = 0`,`y = 序号 × 144`)并落盘;顺序归用户所有,自动协调只保留不覆盖,新资源追加到该栏目末尾。这是用户主动排序的唯一坐标改写,没有用户动作时坐标一字不改。
|
||||
- 页面顶部提供列数控件(自动或固定 `1..6` 列);列数是本机偏好,只决定网格呈现列数,不写入布局 sidecar,也不改写任何记录顺序。
|
||||
- 新资源首次进入某个布局时才执行默认排版;已有顺序不得被自动排序覆盖。
|
||||
- 依赖布局使用资源生成/引用关系;类型布局按资源大类、子类型、尺寸规格排序。未排序栏目的初始顺序即由这两种默认排版推出的坐标决定。
|
||||
- “按依赖”和“按类型”分别保存画布位置。
|
||||
- dependency / type 自动布局与资源卡手动拖动并存;空项目分区展览仍只提供分区空态。
|
||||
- 切换布局模式后恢复该模式 sidecar 中已有坐标;历史手动坐标只读恢复,不删除、不重置、不迁移。分区轴由旧四栏目切换为「6 类资产分类 + 项目版本栏目」后,旧 `art / code` 值按资源当前分类读时归并,坐标与 `manuallyPlaced` 原样保留。
|
||||
- 新资源首次进入某个布局时才执行默认不重叠排版;已有坐标不得被自动排序覆盖。
|
||||
- 依赖布局使用资源生成/引用关系;类型布局按资源大类、子类型、尺寸规格排序。
|
||||
- 资源分区由资源投影固定:manifest 资产按 `category` 归入 6 类资产栏目,项目版本单独成栏,前端交互不能改变分类。
|
||||
- 栏目内支持拖动卡片排序:超过 `5px` 阈值后显示插入位,释放时提交一次布局 CAS;拖动后的点击不触发详情。不支持跨栏目拖动,改分类仍走「分类与标签」面板。
|
||||
- 非空资源页支持资源卡手动拖动:超过移动阈值后乐观更新坐标与依赖线,结束时提交一次手动布局 CAS;拖动后的点击不触发详情。空画布拖拽仍只平移 viewport。
|
||||
|
||||
#### 3.3.1 资源管理三个串行阶段
|
||||
|
||||
1. 阶段一“资源卡本体化”:`dependency / type` 两种布局共用同一个资源卡组件和预览调度器。图片与安全 SVG 直接显示主体并保留透明棋盘底;网格单元统一高度,图片在单元内等比完整显示(`object-fit: contain`),不拉伸、不裁剪,视频、音频、文档和项目版本继续使用稳定固定卡片。资源详情是叠加在资源管理页之上的非模态独立卡片,以元数据、Rust 权威依赖关系和版本信息为主;打开详情不得卸载“按依赖 / 按类型”等现役页面级动作、资源卡、搜索和排序模式。
|
||||
2. 阶段二“栏目分页排版”:项目完全没有任何资源投影时保留“按类型式分区展览”作为空态;任一栏目出现资源后,`dependency / type` 两种模式都是栏目分页的纵向滚动网格,默认停留在固定栏目顺序中的第一个非空栏目。大纲展示全部栏目,空栏目计数为 0 且可打开空网格;不提供分区高度、分区倍率、画布平移或缩放。
|
||||
3. 阶段三“初始顺序派生”:只在栏目从未被用户排序时决定该栏目的初始顺序。`dependency` 模式按固定资源分类对当前 Rust 只读图返回的同类型精确引用边和同类型聚合任务流做弱连通分组;任务流以流节点连接其成员,绝不展开成资源笛卡尔积。`dependencyDepth` 是唯一横向层级权威;每个相关簇先按“最小深度 -> 最小稳定资源 ID”排序,再在同层进行固定两轮由左至右、由右至左的中位数扫描,平局回退稳定资源 ID;同 SCC/环成员保持连续,环后资源继续按 Rust 深度推进。孤立资源是稳定单例组,紧凑排在相关簇之后,簇之间保持固定留白。`type` 模式按资源大类、子类型、尺寸规格稳定排序。两种模式推出的坐标只作为该栏目 `(y, x)` 读序的种子,页面不绘制任何连线;搜索只隐藏卡片,不重算分组或顺序。历史 `manuallyPlaced=true` 坐标继续原样保留并参与自动坐标避让。派生顺序不新增 manifest 字段、sidecar schema、SpacetimeDB 表或第二套依赖真相。
|
||||
1. 阶段一“资源卡本体化”:`dependency / type` 两种布局共用同一个资源卡组件和预览调度器。图片与安全 SVG 直接显示主体并保留透明棋盘底;图片卡按真实纵横比在统一边界内计算展示矩形,视频、音频、文档和项目版本继续使用稳定固定卡片。资源详情是叠加在原画板上的非模态独立卡片,以元数据、Rust 权威依赖关系和版本信息为主;打开详情不得卸载“按依赖 / 按类型 / 复位”等现役画布级动作、资源卡、依赖连线或重置 viewport、搜索和排序模式。
|
||||
2. 阶段二“分区独立缩放”是历史实现;2026-08-22 起仅在项目完全没有任何资源投影时保留“按类型式分区展览”作为空态。任一栏目出现资源后,`dependency / type` 两种模式均切换为栏目分页画布,默认停留在固定栏目顺序中的第一个非空栏目;大纲展示全部栏目,空栏目计数为 0 且可打开空画布,不再提供分区高度、分区滚动和分区倍率操作。
|
||||
3. 阶段三“依赖布局聚类”(已完成):仅在 `dependency` 模式中,按固定资源分类对当前 Rust 只读图返回的同类型精确引用边和同类型聚合任务流做弱连通分组;任务流以流节点连接其成员,绝不展开成资源笛卡尔积,但只作为布局邻近提示,不绘制虚线、不进入画布关系说明。`dependencyDepth` 是唯一横向层级权威;每个相关簇先按“最小深度 -> 最小稳定资源 ID”排序,再在同层进行固定两轮由左至右、由右至左的中位数扫描,平局回退稳定资源 ID;同 SCC/环成员保持连续,环后资源继续按 Rust 深度推进。孤立资源是稳定单例组,紧凑排在相关簇之后,簇之间保持固定留白。当前栏目内同类型精确引用参与布局并在该栏目 SVG 中显示;跨分类引用不参与布局,也不在分栏画布中强行连线。搜索只隐藏卡片及任一端点被隐藏的橙色精确引用边,不重算组或压缩坐标。历史 `manuallyPlaced=true` 坐标继续原样保留并参与自动坐标避让。聚类是可派生表现,不新增 manifest 字段、sidecar schema、SpacetimeDB 表或第二套依赖真相。
|
||||
|
||||
#### 3.3.2 本体化资源卡交互与性能合同
|
||||
|
||||
- 卡片默认可视区域不再显示文件名、资源名称、来源、路径、任务和媒体类型等详细文本。这些字段继续进入搜索索引和中央详情;卡片“打开详情”入口的可访问名称必须包含稳定可辨识的资源名与类别。
|
||||
- 卡片外层是非交互容器;“打开详情”与“播放 / 暂停”必须是可分别键盘聚焦的同级按钮,禁止在 `<button>` 中嵌套 `<button>`。播放键不选择资源、不打开详情。
|
||||
- 同一时间最多播放一个卡片音频或视频。打开详情、切换布局模式、切换项目、进入运行视图、搜索 / 筛选隐藏当前资源、当前资源被删除或资源身份 / 路径变化时,必须暂停旧媒体并收口待播放意图;卡片卸载还必须执行防御性暂停。同资源 ID 的无关 manifest 更新不得重建控件或抢走焦点。
|
||||
- 图片、视频首帧和项目文档只在卡片进入资源网格可见区及小幅预取边界后读取;音频只在用户点击播放后读取。可见性判定以栏目滚动容器为交叉根,不能用画布视口或整页窗口代替。前端逻辑调度器必须使用有界并发、同身份去重、有界 LRU 淘汰,不允许按全部投影同步启动读取。`play > detail > visible` 是固定调度优先级;可见性预取不得占满全部队列容量或静默丢弃播放 / 详情请求,主动请求进入硬上限队列时必须替换最低优先级的排队预取并优先执行。终态预览缓存同时受 `48` 项和 `64 MiB` 总载荷双重上限约束,媒体按解码后的 Blob 字节、文档按 UTF-8 字节计入;Tauri 返回的 data URL 只允许作为 IPC 临时载体,进入 React 状态前必须转换为可撤销的 Blob URL。LRU 淘汰、资源删除、项目 / mode 切换和卸载都必须 `revokeObjectURL`,不能让 base64 字符串或失效 Blob 长期占用 WebView。
|
||||
- 图片、视频首帧和项目文档只在卡片进入资源画布可见区及小幅预取边界后读取;音频只在用户点击播放后读取。前端逻辑调度器必须使用有界并发、同身份去重、有界 LRU 淘汰,不允许按全部投影同步启动读取。`play > detail > visible` 是固定调度优先级;可见性预取不得占满全部队列容量或静默丢弃播放 / 详情请求,主动请求进入硬上限队列时必须替换最低优先级的排队预取并优先执行。终态预览缓存同时受 `48` 项和 `64 MiB` 总载荷双重上限约束,媒体按解码后的 Blob 字节、文档按 UTF-8 字节计入;Tauri 返回的 data URL 只允许作为 IPC 临时载体,进入 React 状态前必须转换为可撤销的 Blob URL。LRU 淘汰、资源删除、项目 / mode 切换和卸载都必须 `revokeObjectURL`,不能让 base64 字符串或失效 Blob 长期占用 WebView。
|
||||
- 卡片读取继续调用 `read_local_project_image_preview`、`read_local_project_media_preview` 和 `read_local_project_text_preview`;项目边界、manifest / 任务登记、`file.read` 策略、普通文件 / 链接、签名、尺寸和读取漂移门禁不变。每个 Hook 挂载及每次 `projectPath + projectId + mode` 变化必须生成不复用的 `scopeId`,每个实际读取生成唯一 `requestId`;每个队列任务和结果继续绑定完整的 `projectPath + projectId + mode + resourceId + category + path + mediaType`。这里绑定用的 `category` 是资源分区栏目(6 类资产分类 / 项目版本),只用于前端去重、缓存与布局身份,**不直接作为 IPC 入参**;`read_local_project_media_preview` 的线上 `category` 是原生侧的**文件读取分支**,只接受 `art` / `audio`,必须由卡片预览类型派生(`projectResourceMediaPreviewCategory`),不能把分区栏目值传过去。前端 scope epoch 只隔离逻辑队列、缓存和异步回调;三个读取命令在 Tauri 进程中共用唯一的全局 `3` 槽物理读取管理器,不能因项目、mode、Hook 或窗口不同获得额外物理槽。scope 切换或卸载必须调用窄职责 `cancel_local_project_resource_preview_scope`,使等待 permit 和分块读取中的旧任务协作取消;旧 epoch 的 `then / catch / finally` 仍不得写入、释放或扣减新 scope 的前端状态,`A → B → A` 也不得复用第一轮 A 的 scope、请求或迟到结果。
|
||||
- 原生读取必须在命令入口、权限 / 登记复核后、打开文件后、每个固定上限读取块之间、签名 / 图片结构校验前、漂移复核前和 base64 编码前检查取消。取消的排队任务不得打开文件,取消的在途任务不得生成 data URL 或 Blob URL;全局 permit 只能在对应原生任务结束后释放。重复 `requestId` 失败关闭,取消未知或已完成 scope 幂等成功;成功、失败和取消均必须清理活动 request / scope registry。为防止近期 request ID 重放和已取消 scope 复活,原生管理器继续保留有界 tombstone:seen request 最多 `8192` 项;非活动 cancelled scope 的保留预算为 `1024` 项,仍有请求的已取消 scope 必须临时钉住,最后一个请求结束后立即重新淘汰到预算内。明确取消只静默收口旧 scope,当前 scope 的真实 transient / permanent 失败语义不变。
|
||||
- 安全读取失败、图片解码失败或视频无可解码画面时,卡片展示稳定类型占位,不挂载破图,不降级为项目外 URL 或裸路径读取。滚动可见性不得自动重试已失败预取;读取漂移、文件替换和通用暂时失败标记为 transient,用户再次打开详情或点击播放时可以显式重试。超尺寸、损坏、类型不支持和不安全 SVG 等永久失败继续缓存且不得用“关闭后重试”误导用户。
|
||||
@@ -196,7 +194,7 @@ idle -> focused(document|art|audio|version) -> idle
|
||||
- 资源聚焦不提供通用工具栏或工具侧边栏;图片聚焦态允许一个明确的“精修资源”业务动作进入素材创作无限画布,该动作不是在聚焦容器中内嵌编辑器或恢复通用工具栏。
|
||||
- 点击资源后,中央主视窗从 `resource-overview.list` 切换为 `resource-overview.focused.document / art / audio / version`,左侧平台导航、右侧 Supervisor 对话和底部 Agent 状态栏保持原位;聚焦容器以路径、类型、来源任务、依赖层级、同类型上下游和版本字段为首屏主体,不使用页面级浮层或可拖动标题栏。文档正文与按意图加载的音频控制位于元数据之后。
|
||||
- 焦点转换以稳定资源 ID 为准。只有从资源列表进入详情或从一个资源 ID 切换到另一个 ID 时聚焦详情 region;同一资源 ID 因 manifest 更新而重新投影时,不得抢走详情内音频 / 视频控件、文档链接或收起按钮的当前焦点。
|
||||
- 显式收起或按 Escape 后恢复进入前的搜索条件、dependency / type 布局模式、资源栏目滚动位置和选中资源,并优先把键盘焦点还给原触发资源卡;这些只属于当前前端会话,不写入布局 sidecar。若资源已经被后台删除,必须清理 stale focused / selected ID、关闭详情并把焦点落到“搜索项目资源”,不得落到 `body`。项目切换和进入运行视图必须取消旧项目的焦点恢复意图。
|
||||
- 显式收起或按 Escape 后恢复进入前的搜索条件、dependency / type 布局模式、资源画布滚动位置和选中资源,并优先把键盘焦点还给原触发资源卡;这些只属于当前前端会话,不写入布局 sidecar。若资源已经被后台删除,必须清理 stale focused / selected ID、关闭详情并把焦点落到“搜索项目资源”,不得落到 `body`。项目切换和进入运行视图必须取消旧项目的焦点恢复意图。
|
||||
- 资源管理阶段四至阶段七交付上述受控读取、媒体展示、正式版本展示和引用高亮;`2026-08-05` 起图片编辑闭环生效,`2026-08-10` 起全类型非破坏性派生与版本子分支覆盖旧的只读限制。入口必须与草稿或 operation 恢复、正式事务提交、`referenceResourceIds` 血缘、新资源即时投影、两份布局和焦点竞态一次实现,不能只打开一个没有回写的面板。
|
||||
|
||||
### 4.4 历史成果与当前状态
|
||||
@@ -234,9 +232,9 @@ type ProjectWorkbenchViewState = {
|
||||
|
||||
旧 `game-creator-workbench-view.v1.mode='resources'` 读取时只映射到 `resource-overview`,`mode='run'` 映射到 `run`。`flowId` 是本次进入编辑流程的短生命周期 UUID,`operationId` 与幂等键只属于当前会话,编辑中状态不持久化。`approvalMode` 仍只有 `strict` 可有效写入;其它值只能作为不可用选项展示。
|
||||
|
||||
### 5.2 资源栏目布局(P1)
|
||||
### 5.2 资源画布布局(P1)
|
||||
|
||||
实现状态(2026-09-11):资源管理页是栏目分页的纵向滚动网格,不使用画布。dependency / type 双模式通过项目内 CAS sidecar 独立持久化栏目内顺序:sidecar 记录仍是 `x / y / manuallyPlaced`,读取顺序固定为该 `section` 记录的 `(y, x, resourceId)` 字典序,不新增字段、不升版、不写迁移。dependency 模式由 Tauri Rust 只读构建关系拓扑与确定性依赖深度,其结果只作为未排序栏目的初始顺序种子,页面不派生 SVG 几何。依赖图加载完成前保留布局初始化屏障,避免以临时 `dependencyDepth=0` 生成并持久化错误顺序。列数偏好是本机 UI 状态,与 sidecar 完全分离。
|
||||
实现状态(2026-08-22):dependency / type 双模式通过项目内 CAS sidecar 独立持久化;dependency 模式由 Tauri Rust 只读构建关系拓扑与确定性依赖深度、前端 SVG 派生几何,图结构和线段均不写入布局 sidecar。依赖图加载完成前设布局初始化屏障,避免以临时 `dependencyDepth=0` 生成并持久化错误坐标。非空资源总览支持自动布局、资源卡点击与手动拖动;资源画本按主画布与子画布各自保留 viewport,分区高度/倍率与 sidecar 完全分离。
|
||||
|
||||
```ts
|
||||
type ProjectResourceCanvasLayout = {
|
||||
@@ -255,14 +253,14 @@ type ProjectResourceCanvasLayout = {
|
||||
};
|
||||
```
|
||||
|
||||
两个 mode 是两份独立坐标集合(在资源管理页语义上即两份栏目内顺序);独立客户端的本地项目持久层以 `projectId + mode` 做 CAS 更新。
|
||||
两个 mode 是两份独立坐标集合;独立客户端的本地项目持久层以 `projectId + mode` 做 CAS 更新。
|
||||
|
||||
#### 5.2.1 字段语义
|
||||
|
||||
- 页面不按 `x / y` 摆放卡片,`x / y` 只承载该 `section` 内的顺序:读取顺序固定为 `(y, x, resourceId)` 字典序升序。落盘前四舍五入为整数,取值范围与校验口径不变。用户拖动排序时,该栏目全部记录被重编码为顺序阶梯 `x = 0`、`y = 序号 × 144`;`144` 是固定行距常量,只表达相邻次序,不代表网格几何。
|
||||
- `x / y` 是相对所属 `section` 内容原点的 CSS 像素坐标,落盘前四舍五入为整数;坐标不使用 viewport、页面或资源详情浮层坐标系。
|
||||
- `updatedAt` 是持久层生成的 Unix 毫秒时间戳,前端不得自行覆盖。
|
||||
- `revision` 从 `0` 开始;布局文件不存在时读取接口合成 `revision=0 / positions=[]`,首次成功写入返回 `revision=1`,后续每次成功 CAS 写入递增 `1`。JSON / Tauri / TypeScript 全链路合法范围固定为 `0..=9_007_199_254_740_991`(`Number.MAX_SAFE_INTEGER`),读取、返回或提交负数、小数、非有限值与超限整数都必须失败关闭。
|
||||
- 新资源第一次进入某个 mode 时由默认布局写入 `manuallyPlaced=false`。用户在栏目内拖动卡片排序并成功释放后,该栏目全部记录写入顺序阶梯且 `manuallyPlaced=true`;后续资源投影和 dependency 自动重算必须保留这些手动记录,新资源只在阶梯之后追加。
|
||||
- 新资源第一次进入某个 mode 时由默认布局写入 `manuallyPlaced=false`。用户在当前栏目画布中拖动资源卡并成功释放后写入 `manuallyPlaced=true`;后续资源投影和 dependency 自动重算必须保留这些手动坐标。
|
||||
- 同一份布局中 `resourceId` 必须唯一。持久层允许暂时存在当前资源投影中没有的旧 ID,因为 Agent 文本成果等资源可能晚于 manifest 恢复;前端协调后必须在下一次成功写入中清除已确认失效的坐标。
|
||||
- 单份布局最多保存 `4096` 个位置,序列化文件不得超过 `2 MiB`;`resourceId` 最多 `512` 个 Unicode 字符,`x / y` 取值范围固定为 `-1_000_000..=1_000_000`。
|
||||
|
||||
@@ -317,15 +315,13 @@ type UpdateProjectResourceCanvasLayoutResult =
|
||||
|
||||
#### 5.2.4 前端布局与协调合同
|
||||
|
||||
- 卡片容器负责拖拽排序,内部“打开详情”和媒体控制仍可点击。Pointer 距离小于 `5px` 时保持点击语义;超过阈值后进入排序拖动态,跟随指针显示目标插入位,拖动期间不写坐标。栏目网格只纵向滚动。
|
||||
- 指针释放且顺序已改变时,把该栏目全部记录按新顺序重编码为顺序阶梯并提交一次 `manuallyPlaced=true` 的布局 CAS;pointer cancel、未超过阈值或跨栏目悬停都恢复原顺序且不提交。拖动后的释放点击被抑制,不打开资源详情。不支持跨栏目拖动,改分类走「分类与标签」面板。
|
||||
- dependency 初始顺序按 Rust 返回的 `dependencyDepth` 形成层级;同层仅在所属固定分区内按精确引用、聚合 task-flow 的稳定邻接、连通簇和固定两轮中位数扫描确定次序。task-flow 只作为聚合超边参与分组和排序,不能生成资源两两边。相关簇以本簇最大层行数为高度,资源较少的层在该高度中居中。type 初始顺序使用原有 `16px` 行列间距,并固定按“资源子类型 -> 媒体类型 -> 名称 -> 资源 ID”稳定排序。布局模型的 `subtype` 必填:manifest 资产使用 `asset.kind`,任务产物、导入附件与 Agent 文本成果分别使用稳定的 `task-artifact`、`attachment`、`agent-result`,不得以缺失值或显示文案兜底;资源协调签名必须包含 subtype。卡片尺寸、间距和 `-1_000_000..=1_000_000` 坐标范围由前后端同名合同维护。超深依赖仍保留原始 `dependencyDepth` 业务真相,但派生坐标在上限确定性饱和;自动布局在 IPC 前必须保证全部 `x / y` 合法,不能向 Tauri 永久重放必然失败的坐标。
|
||||
- 两种模式派生出的坐标只决定未排序栏目的初始 `(y, x)` 读序,不作为网格几何。type 模式资源集合变化时保留全部仍存在的记录,只为新 ID 计算默认记录,并删除已确认失效的旧 ID。dependency 模式只永久保留 `manuallyPlaced=true` 的历史记录;`manuallyPlaced=false` 属于可派生自动记录,在 Rust 关系图首次就绪、`dependencyDepth` 或资源拓扑身份签名(精确引用端点和聚合 task-flow 成员)变化后按最终拓扑确定性重算。签名以稳定资源 ID 的规范端点 / 成员序列生成固定大小摘要,不使用显示名称或浏览器测量值;自动重算不得移动手动记录,协调结果与持久布局逐项一致时不得产生 CAS 写入。
|
||||
- 当某栏目全部记录都是 `manuallyPlaced=true`(即用户已经排过序)时,新资源追加到该栏目末尾:协调阶段把新资源放到该栏目现有最大 `y` 之后,`x = 0`,`y` 按 `144` 递增,并保持 `manuallyPlaced=false`。没有任何用户排序记录的栏目继续按上面的派生规则落位。
|
||||
- 搜索或筛选只隐藏卡片,不删除、压缩或重排顺序;清空搜索后恢复原顺序。
|
||||
- 窗口尺寸变化只改变当前栏目的可视列宽与滚动范围,不回写或裁切持久记录。当前客户端继续以 `1280×800` 横屏合同验收。
|
||||
- 任一资源出现后,普通用户资源管理固定使用 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本` 七个栏目。前六栏是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`),项目版本不是资源资产、6 类资产分类轴对它不适用,因此固定单独成栏;已投影资源都进入栏目导航、分页、卡片、搜索与详情入口,不再有被分区轴排除的内部栏目。每个栏目是一块纵向滚动网格,列数由页面顶部的列数控件决定(自动或固定 `1..6` 列);列数是本机偏好,按全局单值保存在本机 `localStorage` 的 `agc.resource-canvas.columns.v1`,两个 mode 与所有项目共享,不写入布局 sidecar,切换列数不产生任何 Tauri 写入,也不改写任何记录顺序。页面不提供平移、缩放、适应内容、框选和依赖连线;搜索和详情开关不得改变滚动位置或顺序,项目、mode 或栏目切换只恢复各自的会话状态。
|
||||
- dependency 模式不再绘制连线或 marker,但仍把当前可见的精确资源引用以关联的视觉隐藏文本逐条说明(`aria-describedby` 挂在栏目网格容器上);说明与可见引用同源,搜索隐藏任一端点时该条说明同步消失。task-flow 不进入该说明。type 模式不产生该说明。
|
||||
- 资源卡容器负责拖动,内部“打开详情”和媒体控制仍可点击。Pointer 距离小于 `5px` 时保持点击语义;超过阈值后按当前 viewport scale 换算世界坐标,乐观更新卡片 `x / y`、`is-dragging` class 与依赖线拖动预览。拖动可越过所属 section 原点,坐标允许落在 `-1_000_000..=1_000_000`。
|
||||
- 指针释放且拖动已改变时提交一次 `manuallyPlaced=true` 的布局 CAS;pointer cancel 恢复起始坐标且不提交。拖动后的释放点击被抑制,不打开资源详情。
|
||||
- dependency 默认布局按 Rust 返回的 `dependencyDepth` 形成横向层级;同层仅在所属固定分区内按精确引用、聚合 task-flow 的稳定邻接、连通簇和固定两轮中位数扫描确定纵向次序,随后寻找第一个不重叠位置。task-flow 只作为聚合超边参与分组和排序,不能生成资源两两边。dependency 自动布局使用专用 `48px` 列间走线区和 `40px` 行间走线区;相关簇以本簇最大层行数为高度,资源较少的层在该高度中居中,避免菱形 / 分叉关系一侧极短、另一侧过长。type 默认布局继续使用原有 `16px` 行列间距,并固定按“资源子类型 -> 媒体类型 -> 名称 -> 资源 ID”稳定排序。布局模型的 `subtype` 必填:manifest 资产使用 `asset.kind`,任务产物、导入附件与 Agent 文本成果分别使用稳定的 `task-artifact`、`attachment`、`agent-result`,不得以缺失值或显示文案兜底;资源协调签名必须包含 subtype。卡片尺寸、间距和 `-1_000_000..=1_000_000` 坐标范围由前后端同名合同维护。超深依赖仍保留原始 `dependencyDepth` 业务真相,但显示坐标在上限列确定性饱和并纵向避让;自动布局在 IPC 前必须保证全部 `x / y` 合法,不能向 Tauri 永久重放必然失败的坐标。
|
||||
- type 模式资源集合变化时保留全部仍存在的坐标,只为新 ID 计算默认位置,并删除已确认失效的旧 ID。dependency 模式只永久保留 `manuallyPlaced=true` 的历史坐标;`manuallyPlaced=false` 属于可派生自动位置,在 Rust 关系图首次就绪、`dependencyDepth` 或资源拓扑身份签名(精确引用端点和聚合 task-flow 成员)变化后按最终拓扑确定性重算。签名以稳定资源 ID 的规范端点 / 成员序列生成固定大小摘要,不使用显示名称或浏览器测量值;自动重算不得移动手动坐标,协调结果与持久布局逐项一致时不得产生 CAS 写入。
|
||||
- 搜索或筛选只隐藏卡片,不删除、压缩或重排其坐标;清空搜索后恢复原位置。
|
||||
- 窗口尺寸变化只改变当前栏目的可视范围,不回写或裁切持久坐标,也不因资源 extent 或 resize 把已平移的 viewport 拉回内容边界。当前客户端继续以 `1280×800` 横屏合同验收。
|
||||
- 任一资源出现后,普通用户资源管理固定使用 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本` 七栏目分页画布。前六栏是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`),项目版本不是资源资产、6 类资产分类轴对它不适用,因此固定单独成栏;已投影资源都进入画布导航、分页、卡片、搜索与详情入口,不再有被分区轴排除的内部栏目。每个栏目按 `projectId + mode + category` 保留独立 viewport,普通 wheel 平移当前视图,`Ctrl/Cmd + wheel` 以指针位置为锚点缩放当前无限画布,空白拖动只平移当前栏目;非空状态不提供分区高度、分区内部滚动或分区内容倍率。搜索和详情开关不得重置 viewport,项目、mode 或栏目切换只恢复各自会话状态,显式复位才重新适配当前栏目内容。
|
||||
- 布局 sidecar 的 `section` 允许继续出现旧四 / 五栏目取值(`code / document / version / art / audio`),读取时按资源当前分区做一次读时归并:`document / audio / version` 与新分区同名,1:1 保留;旧 `art` 是「扩展名 + mediaType」口径,按资源当前分类落入 `ui-interaction / character / scene / unclassified`;旧 `code` 落入 `unclassified`;无法精确归并时回落到 `unclassified`。归并只改写 `section`,`x`、`y` 与 `manuallyPlaced` 原样保留,不写迁移脚本、不删除、不重置。
|
||||
- 首次载入项目中的既有资源不显示未读标识。当前会话内,非当前栏目出现稳定 ID 的新资源时,在对应栏目名称右上角显示红点;当前栏目新增资源不显示红点,用户通过点击或程序跳转进入该栏目后立即清除。未读状态只属于当前前端会话,并按 `projectPath + projectId` 隔离,切换项目时清空,不写入 manifest、布局 sidecar 或后端。
|
||||
- 打开项目、切换 mode 或当前 mode 首次出现新资源时执行“读取 -> 协调 -> 必要时 CAS 写入”;dependency 模式必须先等待与当前 `projectPath + projectId + resource inputs` 匹配的 Rust 图进入 `ready` 或 `failed` 终态,等待期间不得创建 fallback、读取 sidecar、协调资源或入队保存。`failed` 只允许以空图降级初始化一次。项目或 mode 已切换后返回的旧异步结果必须丢弃。
|
||||
@@ -333,16 +329,33 @@ type UpdateProjectResourceCanvasLayoutResult =
|
||||
- 自动协调 CAS 冲突时直接载入返回的最新布局;仍需协调时可以基于权威 revision 最多追加 `2` 次重试,持续跨窗口写入时不得无限自旋。当前提示只说明“布局已在其他窗口更新”,不得要求用户重新拖动。
|
||||
- 缺少 Tauri bridge 的浏览器开发态可以保留当前会话内布局用于界面测试,但不得宣称已经持久保存。
|
||||
|
||||
#### 5.2.5 拖拽排序合同
|
||||
#### 5.2.5 手动拖动合同
|
||||
|
||||
- Pointer Events 使用 `5px` 移动阈值,拖动期间指针捕获到卡片;未超过阈值保持点击语义,不进入排序。排序拖拽只在本栏目网格内生效,跨栏目悬停既不预览也不写入。
|
||||
- 拖动期间只更新当前会话内的插入位预览,不改坐标;释放时把该栏目全部记录重编码为顺序阶梯(`x = 0`,`y = 序号 × 144`)并一次性提交布局 CAS。取消、未超过阈值或顺序未变化都不写入。
|
||||
- 重编码会重写该栏目全部记录的坐标,因此同栏目其它卡片的记录位置也随阶梯一起变化;这是顺序本身被重写的必然结果,不是自动补位,也不会把顺序拉回排序前状态。
|
||||
- 排序提交沿用现有单写者 FIFO 与 CAS 冲突重试;冲突提示沿用“布局已在其他窗口更新”,用户可重新拖动。其它栏目的记录、以及本栏目中 `manuallyPlaced=false` 的新资源追加位都保持原样。
|
||||
- Pointer Events 使用 `5px` 移动阈值,拖动期间指针捕获到卡片,画布普通滚轮只平移当前视图,`Ctrl/Cmd + wheel` 仍以真实画布指针位置缩放。
|
||||
- 拖动提交沿用现有单写者 FIFO 与 CAS 冲突重试;冲突提示沿用“布局已在其他窗口更新”,用户可重新拖动。历史 `manuallyPlaced=true` 坐标仍优先保留。
|
||||
- 资源卡拖动统一调用现役 Hook 手动意图、命令式 SVG preview、sidecar 字段和 Rust CAS:拖动期间只更新当前会话预览,成功释放后提交一次手动布局 CAS,取消或未超过阈值不写入。
|
||||
|
||||
#### 5.2.6 资源依赖关系图层
|
||||
|
||||
- 阶段五实现状态(2026-08-03):dependency 自动排列同时消费任务 DAG 与精确资源引用。Rust 把可信 producer 的任务深度作为资源深度下限,再对 `asset-reference` 图做迭代式 SCC 压缩与确定性层级传播;被引用资源位于引用资源之前,同一引用环共享稳定深度,环后资源继续递增,没有引用关系的资源保持默认不重叠位置。布局深度通过独立 `dependencyDepths` 返回,不能把 producer assignment 冒充全部资源的布局结果。
|
||||
- `producerMappingTruncated=true` 只关闭依赖有界 Agent DB 审计的 `producerAssignments`、`taskFlows` 和 `cyclicTaskIds`。Rust 返回的 `dependencyDepths` 仍是 manifest / 精确引用 read model 的权威结果,前端必须过滤未知资源、负数、非整数和非安全整数后继续消费;不得因 producer 截断清空全部深度,也不得在前端重算替代深度。`referenceEdges`、connection index 中的 reference 关系、`cyclicResourceIds` 和 unresolved reference 继续保持可信。
|
||||
- 图层只在 dependency 模式挂载;type 模式不得渲染 SVG、连线或 marker。切换 mode、切换项目或卸载工作台时必须销毁旧图层,并清理尺寸观察和窗口事件监听。
|
||||
- 输入固定为当前栏目资源投影的卡片身份 / 栏目内坐标与 Tauri Rust 返回的 `ProjectResourceGraph` 只读 DTO;Rust 负责资源过滤、去重、迭代式环检测、SCC 压缩后的确定性依赖深度、任务流聚合和一跳连接索引,前端只负责 DTO 防御归一化、当前栏目 viewport 变换与原生 SVG path / marker。dependency 当前栏目持有一个覆盖该栏目内容的 SVG,SVG 与卡片直接消费同一坐标和 viewport transform;端点主路径不得通过 `getBoundingClientRect` 拼接屏幕坐标。SVG 位于资源卡底层并设置 `pointer-events: none`,不得引入 D3、React Flow 等图表库,也不得阻断卡片点击。
|
||||
- `asset-reference` 表示精确资源引用;当前可见且两端均属于当前栏目的引用进入该栏目 SVG 和无障碍关系说明,使用明亮橙色实线与实心箭头。只有同分类引用参与自动布局邻接,跨分类引用不形成布局簇、边界偏置或分栏连线。`GameCreationAppAssetManifestEntry.source.referenceResourceIds` 中的外部资源 ID 必须先唯一匹配另一项资产的 `source.resourceId`,再映射为当前资源卡 ID;缺失、重复或已删除的目标均不得渲染幽灵连线。
|
||||
- `task-flow` 表示同一资源类型内的任务产物流转,但资源画布不再绘制灰色虚线或任务流 marker,也不把它加入 `aria-describedby` 关系说明。它只按 `sourceTaskId -> targetTaskId + section` 作为聚合超边参与 dependency 连通簇和中位数排序,禁止对上下游资源生成笛卡尔积邻接;详情仍可展示 Rust read model 已证明的任务信息。
|
||||
- 画布资产 producer 只能来自 `agent.runtime.canvas.asset_generate` 的 `assetId -> agentId` 审计且 `agentId` 必须存在于当前 manifest;External Editor `source.taskId` 属于平台生成任务命名空间,禁止当作 manifest task ID。证据缺失、冲突或有界审计读取未覆盖时不生成对应 task flow,不猜测归属。
|
||||
- 图模型必须对资源引用图和完整任务依赖图做迭代式环检测,不得用无界递归遍历;参与环的可见边保留渲染并标记 cyclic,环本身不能造成重复生成或死循环。
|
||||
- 资源自引用的起点与终点为同一张卡片时,必须绘制在卡片外侧的可见闭环并保留箭头,不得让路径穿过卡片后被底层 SVG 层级遮挡。
|
||||
- 搜索只允许为当前可见端点生成几何;任一精确引用端点隐藏时该橙线隐藏。task-flow 不产生显示几何,搜索也不因此重排布局。
|
||||
- 同类型精确引用按稳定边 ID 和对端次序为同一卡片同侧的多条边分配独立端口;横向层级可用时优先左右连接,同列或横向间隙不足时才上下连接。同轴端点直接用直线,需转向时使用正交线段与最大 `10px` 的小圆角,不使用大范围贝塞尔控制柄。端口顺序不使用显示名称、随机数或浏览器枚举顺序,相同输入必须产生相同路径。资源点击只进入中央聚焦并保留当前选中卡片,不改变依赖卡片或连线的颜色、线宽与透明度;关系线始终直接展示,不提供点击后的上下游高亮或无关线弱化。
|
||||
- 资源卡 Pointer Move 不改变基础 positions 或 SVG 几何。连线只随布局读取、资源自动协调、搜索、项目切换或 section origin 变化而更新。
|
||||
- 当前 dependency 栏目平面最多构造一个 `ResizeObserver`;observer 只维护当前栏目 viewport,不测量或重建卡片屏幕端点。栏目 extent 必须为最右侧自环和箭头保留视觉 gutter,但不得修改卡片坐标或布局 sidecar。
|
||||
- 非空资源页的普通滚轮只平移当前视图;拖拽平移、Ctrl/Meta + 滚轮缩放和窗口 resize 共用当前栏目 viewport 变换。原生 `{ passive: false }` wheel 监听必须阻止 WebView 默认滚动或缩放。完全空项目的分区展览保留原生分区滚动。项目 / mode 切换或卸载时必须清理 observer、wheel 与 window resize 监听。
|
||||
- 阶段五保留已有 `manuallyPlaced=true` 坐标;资源卡拖动成功释放会将该卡片写为 `manuallyPlaced=true`,后续资源引用新增或变化只重新派生 `manuallyPlaced=false` 的自动坐标。任务流继续按任务对与资源分区聚合,禁止为了布局分组生成资源笛卡尔积,且不进入 SVG。
|
||||
|
||||
### 5.3 资源类型与替换兼容性(P1)
|
||||
|
||||
实现状态(2026-09-10):资源栏目分区口径是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`)加末尾独立的「项目版本」栏目,不再按扩展名或 mediaType 派生分区。`icon / icon-spritesheet / icon-spec / ui-design` 进入 UI 交互,`character / character-animation` 进入角色与对象,`scene` 进入场景与环境,`sound-effect / background-music / audio` 进入音频,`spec` 与合法 Agent 文本回执进入文档;`image / video / code / publication-material` 以及任务产物、导入附件进入待归类,只登记游戏代码的项目因此有可见栏目与卡片,不再出现四栏全空;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本。扩展名分类器只保留准入与卡片显示类型职责:无法识别的二进制任务产物和附件不进入资源管理页。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。既有布局 sidecar 的旧栏目坐标按读时归并继续生效,`x / y / manuallyPlaced` 原样保留。
|
||||
实现状态(2026-09-10):资源画布分区口径是 manifest 资产的功能分类 `category`(`ui-interaction / character / scene / audio / document / unclassified`)加末尾独立的「项目版本」栏目,不再按扩展名或 mediaType 派生分区。`icon / icon-spritesheet / icon-spec / ui-design` 进入 UI 交互,`character / character-animation` 进入角色与对象,`scene` 进入场景与环境,`sound-effect / background-music / audio` 进入音频,`spec` 与合法 Agent 文本回执进入文档;`image / video / code / publication-material` 以及任务产物、导入附件进入待归类,只登记游戏代码的项目因此有可见栏目与卡片,不再出现四栏全空;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本。扩展名分类器只保留准入与卡片显示类型职责:无法识别的二进制任务产物和附件不进入资源画布。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。既有布局 sidecar 的旧栏目坐标按读时归并继续生效,`x / y / manuallyPlaced` 原样保留。
|
||||
|
||||
分类取值优先级(2026-09-10 收口):落盘 `category` 是权威值(用户可在「分类与标签」面板手动设置),缺失或非法时按 `assets[].kind` 派生。唯一例外是读时自愈——落盘值为 `unclassified` 而该资产 `kind` 能派生出明确的非 `unclassified` 分类时采用派生值,用于修复历史上被系统误写成 `unclassified` 的存量数据(无需迁移脚本、永久自愈);`kind` 派生结果本身就是 `unclassified` 的(`image / video / code / publication-material`)仍信任落盘值。该例外的已知盲区是「用户手动把 kind 已能明确分类的资产设为待归类」会被覆盖,属有意接受的最小覆盖窗口。写入侧必须只产出 canonical kind(画板导出推断同样如此),别名表仅用于兼容存量数据。
|
||||
|
||||
@@ -449,7 +462,7 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
- 复用现有四区工作台壳。
|
||||
- 资源/运行切换与客户端内 loopback 预览。
|
||||
- 固定四类资源投影、只读资源管理页与中央主视窗资源聚焦;资源聚焦工具栏 / 工具侧边栏及后续媒体能力暂缓。
|
||||
- 固定四类资源投影、只读资源画布与中央主视窗资源聚焦;资源聚焦工具栏 / 工具侧边栏及后续媒体能力暂缓。
|
||||
- Supervisor 正式会话、上传、Runtime 确认与安全错误。
|
||||
- 当前 run 专业状态与项目历史成果分离。
|
||||
- 默认三专业组,并可展开另外三组。
|
||||
@@ -458,8 +471,9 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
### P1
|
||||
|
||||
- 已实施依赖/类型两套顺序持久化、首次默认排版、历史记录跨重启恢复、自动协调 CAS 冲突处理与栏目内拖拽排序。
|
||||
- 已实施资源关系文本说明(`aria-describedby`,不绘制连线)、正式版本不可变模型、版本卡、父子关系与引用资源高亮;“编辑资源”可追加继承源绑定并记录提示词的子版本,资源直接替换、运行版本切换和兼容性迁移仍待后续切片。
|
||||
- 已实施依赖/类型两套坐标持久化、首次默认不重叠布局、历史坐标跨重启恢复、自动协调 CAS 冲突处理与资源卡手动拖动。
|
||||
- 资源关系线在布局持久化验收通过后单独实施,不与本切片捆绑伪造完成。
|
||||
- 已实施正式版本不可变模型、版本卡、父子关系与引用资源高亮;“编辑资源”可追加继承源绑定并记录提示词的子版本,资源直接替换、运行版本切换和兼容性迁移仍待后续切片。
|
||||
- 素材创作无限画布阶段一按权威专题一次交付图片导入、编辑、生成、导出、草稿恢复、正式本地回写、即时投影和焦点竞态闭环。
|
||||
- 高级抠图、图集、角色动画、视频时间线编辑和音频波形级编辑按后续切片实施;当前视频走源引用派生,音频只做语义重制。
|
||||
|
||||
@@ -487,15 +501,29 @@ type ProjectAgentMudPointAttribution = {
|
||||
10. 实时 manifest 验收必须捕获真实 App Tauri listener,并让 `get_local_game_manifest` 在非 Supervisor Agent 的 Runtime / manifest 失效事件后返回新快照;测试不得直接调用 `onManifestChange` 冒充数据源。读取合并、旧项目迟到响应和项目切换隔离必须分别有回归证据。
|
||||
11. 音频或视频控件获得焦点后,同一资源 ID 的 manifest 更新不得把焦点移回详情 region;当前资源被删除后详情关闭、focused / selected ID 清理且焦点落到资源搜索框。显式收起与 Escape 的原卡片焦点和滚动恢复继续成立。
|
||||
|
||||
### 7.2 P1 资源栏目布局持久化验收
|
||||
### 7.2 P1 资源画布布局持久化验收
|
||||
|
||||
1. 同一项目在 dependency 与 type mode 分别读取并协调布局,关闭并重启客户端后恢复各自 sidecar 顺序;历史 `manuallyPlaced=true` 记录保持不变。
|
||||
2. 新资源进入任一 mode 时获得默认记录,现存记录保持逐项不变;用户已排序的栏目里新资源追加到该栏目末尾;删除资源后,下一次成功写入不再包含已确认失效的 ID。
|
||||
3. 栏目内卡片 Pointer Down / Move / Up / Cancel 支持移动阈值与插入位预览,释放时把该栏目全部记录重编码为顺序阶梯并提交一次布局 CAS;未超过阈值或点击媒体控制时保留原点击语义。
|
||||
1. 同一项目在 dependency 与 type mode 分别读取并协调布局,关闭并重启客户端后恢复各自 sidecar 坐标;历史 `manuallyPlaced=true` 坐标保持不变。
|
||||
2. 新资源进入任一 mode 时获得不重叠默认位置,现存资源坐标保持逐项不变;删除资源后,下一次成功写入不再包含已确认失效的 ID。
|
||||
3. 资源卡 Pointer Down / Move / Up / Cancel 支持移动阈值、拖动预览、依赖线同步与结束时一次手动布局 CAS;未超过阈值或点击媒体控制时保留原点击语义。
|
||||
4. 两个窗口基于同一 revision 执行资源自动协调写入时最多一个成功;失败方收到 `conflict` 与最新完整布局,界面不静默覆盖成功方结果。
|
||||
5. 布局文件缺失的旧项目可以无迁移打开;损坏、未知 schema、身份冲突、超限和链接文件失败关闭,且原文件不被空布局覆盖。
|
||||
6. 布局读写不改变 manifest、游戏项目 mutation revision、Runtime verification、Agent 权限与预览状态。
|
||||
7. `1280×720` 最小横屏下完全空项目可通过分区展览查看全部栏目,非空项目可通过栏目大纲和纵向滚动访问全部栏目(包括空栏目),不出现页面级横向或纵向溢出,右侧对话和底部 Agent 状态栏保持可见;`1280×800` 继续作为默认窗口基线。
|
||||
7. `1280×720` 最小横屏下完全空项目可通过分区展览查看全部栏目,非空项目可通过栏目大纲和滚轮访问全部栏目(包括空栏目),不出现页面级横向或纵向溢出,右侧对话和底部 Agent 状态栏保持可见;`1280×800` 继续作为默认窗口基线。
|
||||
|
||||
### 7.3 P1 资源依赖关系图验收
|
||||
|
||||
1. dependency 模式在当前栏目中显示两端均属于该栏目且当前可见的合法精确引用,并使用对画布背景至少 `3:1` 对比度的橙色实线箭头;聚合任务流只参与同类型布局,不绘制灰色虚线或 marker。type 模式没有图层或连线。
|
||||
2. 精确引用只接受唯一有效的外部资源 ID 映射,删除或不存在的资源不产生幽灵连线。
|
||||
3. 多资源任务依赖按资源类型拆分后只形成布局超边,以 `O(S+T)` 成员关系参与聚类,不产生 `S×T` 邻接、SVG 主线或端点分支;跨分类精确引用不参与布局聚类,也不在分栏画布中显示。
|
||||
4. 资源引用环和无资源产物参与的任务环都可被有限遍历识别,界面不死循环。
|
||||
5. 搜索触发端点过滤;资源点击不改变上下游卡片或任何连线的视觉状态,资源卡指针移动不更新线段,点击与中央聚焦行为不回归。
|
||||
6. 切换布局模式或项目后旧 SVG、ResizeObserver 与窗口监听全部清理;图层从不写入 layout sidecar、manifest 或其它持久化。
|
||||
7. 4096 资源链式 fixture 继续验证拓扑、聚合复杂度和自动布局性能;拖动局部更新只影响当前卡片和依赖线,不重算全图布局。最右侧自环与箭头仍需完整显示。
|
||||
8. Rust 图读取延迟时,dependency sidecar 在图进入 `ready / failed` 前没有读取或写入;首次布局直接使用 Rust 返回的最终 producer 与 dependency depth。Agent DB 有界读取截断时 producer、task flow 与 `cyclicTaskIds` 失败关闭,精确 manifest 引用及 Rust 返回的合法 `dependencyDepths` 继续到达布局层。重新打开包含深度 `0 / 1 / 2` 自动坐标的旧布局时不得降成全 `0` 或持久化扁平布局;手动位置逐项不变,自动位置按最终拓扑协调且相同结果不增加 revision。
|
||||
9. 依赖 SVG 作为装饰层不可聚焦并对辅助技术隐藏;画布通过关联的视觉隐藏文本逐条说明当前可见的橙色精确引用,搜索过滤或模式切换后文本与可见关系同步变化。task-flow 不进入该关系说明。
|
||||
10. 精确引用使用橙色实线和可见箭头;dependency 自动卡片之间保留横向 `48px`、纵向 `40px` 走线区,相关簇的窄层按本簇最大层高度居中。不同横向层级且卡片间有空间时使用左右端口优先的横线,同列、同深度或横向空间不足时才使用上下端口纵向降级。同行 / 同列直接连线,需要转向时只使用正交线段与最大 `10px` 小圆角。同一卡片同侧的多条精确引用按稳定边 ID 与对端位置分配不同端口;自引用继续在卡片右侧外绕,箭头端点与卡片保留固定显示间隙。
|
||||
11. 当前栏目 SVG 必须位于拥有该栏目 dependency 资源卡的内容平面内,以同一栏目坐标随 viewport 平移和缩放同步移动;滚动 / 缩放不得通过异步 DOM 屏幕测量重新绑定端点。栏目 viewport 裁剪 SVG 与卡片,搜索隐藏精确引用任一端点时整边及对应无障碍说明同时隐藏。
|
||||
|
||||
### 7.4 P1 正式项目版本阶段六验收
|
||||
|
||||
@@ -504,17 +532,16 @@ type ProjectAgentMudPointAttribution = {
|
||||
3. 点击版本卡后,当前 manifest 中仍存在的绑定资产卡被高亮;历史已删除资产只在版本详情保留 ID,不创建幽灵卡,也不把 External Editor resource ID 猜成 manifest asset ID。
|
||||
4. 版本聚焦态展示身份、修订、创建原因、父子关系、创建时间和 slot 绑定;“编辑资源”只追加继承源绑定并记录提示词的子版本,不提供原地替换、切换、回滚或运行按钮。
|
||||
5. 任意现有 manifest 写入只能保留磁盘版本前缀并追加新记录;存储边界以跨进程专用锁串行覆盖旧状态读取、前缀校验、安装和回读,修改、删除、重排或并发旧快照覆盖已有版本时写入失败。
|
||||
6. 版本选择和高亮不写 manifest、布局 sidecar 或 project revision;dependency / type 两种布局都可显示绑定高亮,页面不渲染依赖关系 SVG。
|
||||
6. 版本选择和高亮不写 manifest、布局 sidecar 或 project revision;dependency / type 两种布局都可显示绑定高亮,既有依赖关系 SVG 语义不变。
|
||||
|
||||
### 7.5 资源栏目分页纵向网格验收
|
||||
### 7.5 资源栏目分页无限画布验收
|
||||
|
||||
1. 完全空项目继续显示全部栏目的分区展览;任一栏目出现资源后,dependency / type 都切换为固定七栏目分页的纵向滚动网格,悬浮 Dock、底部下一页标题和纵向滚动可访问全部栏目,空栏目也可打开空网格;栏目为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`,游戏代码资产落在「待归类」并正常显示卡片。
|
||||
2. 栏目页只纵向滚动:卡片按列排布,页面无横向溢出、不需要左右拖动,也不出现画布平移、缩放、适应内容或空白拖动;滚轮和触控板只滚动当前栏目,不切换栏目,也不被 `preventDefault` 拦成画布手势。
|
||||
3. 旧栏目(`art / code` 等)的历史坐标按读时归并进入对应新栏目,`x / y / manuallyPlaced` 原样保留、不被删除或重写;栏目内初始顺序即该栏目记录按 `(y, x, resourceId)` 的字典序,历史手动摆放以“顺序”形式保留下来。切换栏目、模式、项目和打开 / 关闭详情后顺序不变,窗口 resize、媒体测量都不改写记录。
|
||||
4. 顶部列数控件提供“自动”与固定 `1..6` 列;自动按容器宽度计算列数。选择结果在本机记住,重开客户端后保持;切换列数只改变网格列数,不产生任何 Tauri 写入,也不改变任何记录顺序。
|
||||
5. 卡片超过 `5px` 阈值后进入排序拖动,跟随指针显示插入位;成功释放只提交一次 `manuallyPlaced=true` CAS 并把该栏目重编码为顺序阶梯,取消、跨栏目悬停、媒体控制点击和未超过阈值均不写布局。重开客户端后顺序保持。
|
||||
6. 栏目分页、列数和拖拽排序只修改工作台会话状态、本机列数偏好或资源布局 sidecar,不改 manifest、项目 mutation revision、Runtime verification、Agent 权限和预览状态;图片、SVG、视频、音频、文档、任务产物、Agent 回执、项目版本卡片及非模态详情回归全部通过,游戏代码资产落在「待归类」栏目并正常显示卡片。
|
||||
7. 点击卡片仍保留选中态与浮出工具条,快速编辑仍可用;dependency 模式仍通过 `aria-describedby` 关联的视觉隐藏文本逐条说明当前可见的精确资源引用,搜索隐藏任一端点时该条说明同步消失,页面不渲染连线或 marker。
|
||||
1. 完全空项目继续显示全部栏目的分区展览;任一栏目出现资源后,dependency / type 都切换为固定七栏目分页画布,悬浮 Dock、底部下一页标题和普通 wheel 可访问全部栏目,空栏目也可打开空画布;栏目为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`,游戏代码资产落在「待归类」并正常显示卡片。
|
||||
2. 每个 `projectId + dependency|type + ui-interaction|character|scene|audio|document|unclassified|version` 组合保留独立 viewport;旧栏目(`art / code` 等)的历史坐标按读时归并进入对应新栏目,`x / y / manuallyPlaced` 原样保留、不被删除或重写,切换栏目、模式、项目和打开 / 关闭详情后恢复对应栏目平移与缩放,窗口 resize、媒体测量和资源 extent 变化不得重置用户 viewport。
|
||||
3. 当前栏目允许空白拖动无限平移;`Ctrl/Cmd + wheel` 以指针为锚点缩放,显式复位按包含负坐标资源在内的完整 bounds 适配内容。非空状态不显示分区高度、分区内部滚动或分区内容倍率操作。
|
||||
4. 资源卡超过 `5px` 阈值后进入拖动,预览和 dependency 线同步移动;成功释放只提交一次 `manuallyPlaced=true` CAS,取消、移出释放、媒体控制点击和未超过阈值均不写布局。
|
||||
5. dependency 引导线消费当前栏目的同类型精确引用,并与卡片共享同一 viewport transform;平移、缩放、拖动预览、搜索和 resize 后端点保持对齐,type 模式不渲染引导线。
|
||||
6. 栏目分页、viewport 和资源卡拖动只修改工作台会话状态或资源布局 sidecar,不改 manifest、项目 mutation revision、Runtime verification、Agent 权限和预览状态;图片、SVG、视频、音频、文档、任务产物、Agent 回执、项目版本卡片及非模态详情回归全部通过,游戏代码资产落在「待归类」栏目并正常显示卡片。
|
||||
|
||||
### 7.6 阶段七完整验收
|
||||
|
||||
@@ -558,10 +585,9 @@ type ProjectAgentMudPointAttribution = {
|
||||
## 8. 非目标
|
||||
|
||||
- 当前不开放“新增资源”产品入口。图片精修候选保持私有且不污染资源总览;“设为最终图”只允许保持原 asset ID、以新正式文件和事务方式切换 manifest 指针,不原地覆写旧文件。其他资源编辑继续追加派生 asset 或子版本。
|
||||
- 资源总览不实现通用聚焦工具栏/侧边栏、运行版本切换、版本回滚、运行模块扩展、测试切片、运行态消费版本、数值参数或泥点归因;资源详情仅为不影响背景页面的非模态卡片。
|
||||
- 资源总览不实现通用聚焦工具栏/侧边栏、运行版本切换、版本回滚、运行模块扩展、测试切片、运行态消费版本、数值参数或泥点归因;资源详情仅为不影响背景画板的非模态卡片。
|
||||
- 素材创作不实现高级蒙版/毛发级抠图、图集、角色动画、视频时间线或音频波形级编辑;图片画布提供现役编辑闭环,其他类型使用统一非破坏性派生面板。
|
||||
- 资源聚焦、搜索、筛选、dependency/type mode 与栏目滚动位置均为当前会话态;列数偏好为本机持久偏好。打开详情不得改变这些状态。素材创作 viewport、候选图层、generation 占位和最后最终图提交投影使用独立草稿 schema,不能混用资源总览 sidecar。
|
||||
- 资源管理页不开放画布平移、缩放、自由摆放、框选、依赖连线和排序撤销/重做。
|
||||
- 资源聚焦、搜索、筛选、dependency/type mode 与分区滚动/倍率均为当前会话态;打开详情不得改变这些状态。素材创作 viewport、候选图层、generation 占位和最后最终图提交投影使用独立草稿 schema,不能混用资源总览 sidecar。
|
||||
- 不修改 SpacetimeDB schema。
|
||||
- 不开放普通用户 Agent.md/Skill。
|
||||
- 不自动确认 Agent 动作,不自动触发可能扣费的生成。
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
- 关联文档:相关 PRD、技术文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 2026-09-11 AGC 资源管理页从自由定位画布改为栏目分页纵向滚动网格,顺序由 sidecar 读序承载
|
||||
|
||||
- 背景:资源管理页原形态是"主画布 + 子画布"的自由定位画布:卡片按 sidecar 的 `x / y` 自由摆放,视口可无限平移、`Ctrl/Cmd + wheel` 缩放,dependency 模式叠加 SVG 连线,空白处左键框选,拖动卡片改写自己的坐标。用户实际使用后明确判断"按栏目纵向滚动的网格 + 可拖拽排序"更好用(移动端尤甚),并明确知道该判断与 PRD 既有条文冲突、授权按更好用的形态落地。历史手动坐标是用户资产,PRD 硬约束是"只读恢复,不删除、不重置、不迁移",换形态不能把它丢掉。
|
||||
- 决策:资源管理页改为栏目分页的纵向滚动网格,不提供平移、缩放、自由摆放、框选和依赖连线;保留栏目分组、点卡选中、浮出工具条、快速编辑、非模态详情、搜索与平台资源筛选。顺序**不新增持久化字段**:沿用 `reconcileResourceCanvasLayout` 已有的"该 `section` 记录按 `(y, x, resourceId)` 字典序"读序,把历史坐标无损地变成"顺序",既有 sidecar 打开即正确、首次渲染零写入。用户拖动排序时把该栏目全部记录重编码为顺序阶梯(`x = 0`,`y = 序号 × 144`)并一次性 CAS 落盘、全部写 `manuallyPlaced=true`;用户已排序的栏目里新资源追加到末尾。列数(自动或固定 `1..6`)是本机 `localStorage` 的全局偏好 `agc.resource-canvas.columns.v1`,只改 CSS、不回写坐标、两个 mode 共享。转场从真实元素 FLIP 降级为 CSS 淡入;本页不进撤销栈,Dock 上的撤销 / 重做按钮删除,不留假按钮。dependency 模式不再绘制 SVG,但保留 `aria-describedby` 关联的关系说明文本。
|
||||
- 有意推翻:既有提交 `1da7dc62a`("拖拽写入不再触发其它自动卡重派生补位")在本页不再成立——排序本身就要重写该栏目其它卡片的记录位置。后续 Agent 不得把它当回归修回去。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/` 的页面结构与指针段、新增 `resourceCanvasOrderModel.ts`、`resourceCanvasLayoutModel.ts` 的协调与追加规则、`resourceCanvasChrome.css`;`resourceBookController.ts`(FLIP)、`resourceBookViewport.ts`、`resourceBookLayout.ts` 的场景计划与 `ResourceDependencyOverlay` 随本形态退役。`game-creator-resource-layout.v1` schema 不升版,Rust 命令与字段一字不改,`packages/shared` 与 manifest 不变。
|
||||
- 验证方式:`resourceCanvasOrderModel.test.ts` 覆盖读序、阶梯重编码与列数偏好读写;`resourceCanvasLayoutModel.test.ts` 覆盖已排序栏目的新资源追加;`appSurface` 的 project-development suite 覆盖"页面不再有平移 / 缩放 / 框选"与"列数被记住"。变异验证要求拆掉落盘或改坏读序时对应断言必须失败。
|
||||
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AGC资源管理页纵向网格与排序-2026-09-11.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 2026-09-10 AGC 资源画布分区改为 6 类资产分类加项目版本栏目,旧栏目坐标读时归并
|
||||
|
||||
- 背景:资源画布原分区轴是「扩展名 + mediaType」派生的 `document / art / audio / code / version`,普通画布只显示其中四栏并隐藏游戏代码,与 `@` 面板、资源详情筛选已经收敛的单一权威 `category`(`ui-interaction / character / scene / audio / document / unclassified`)不一致。只登记游戏代码的项目会因资源签名非空进入分页画布,但 code 不在可见栏目里,于是四栏全空、一张卡都不显示。旧布局 sidecar 已存有旧 `section` 值,而 PRD 要求历史手动坐标只读恢复,不删除、不重置、不迁移。
|
||||
|
||||
@@ -32,7 +32,7 @@ AI 游戏创作 / DirectProject / UI workflow:
|
||||
5. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`
|
||||
6. `docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`
|
||||
7. `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`
|
||||
8. `docs/technical/【技术方案】AGC资源管理页纵向网格与排序-2026-09-11.md`
|
||||
8. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
|
||||
9. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
|
||||
10. UI 编辑器、宿主壳和当前测试专题文档
|
||||
|
||||
|
||||
@@ -5125,18 +5125,17 @@
|
||||
- 顺带两条死代码,读代码时别被误导:`persistingAssetKindLayerIds` 在编辑器里从未被传值(`isPersistingAssetKind` 恒 false);`ImageCanvasContextMenusView` 的 `canvasClipboard` 是必填 prop,但组件内解构为 `_canvasClipboard` 后完全不用。
|
||||
- 关联:`src/components/image-editor/ImageCanvasStageView.tsx`、`ImageCanvasContextMenusView.tsx`、`ImageCanvasEditorShellView.tsx`、`ImageCanvasEditorView.tsx`。
|
||||
|
||||
## AGC 资源管理页真正自制的交互面(改这一页前先看这里)(2026-09-11)
|
||||
## AGC 资源画布真正自制的交互面(改画布 bug 前先看这里)(2026-09-10)
|
||||
|
||||
- 这一页是**栏目分页的纵向滚动网格**,不是画布。页面没有平移、缩放、自由摆放、框选和依赖连线;改这一页时不要再按"画布"思路补 viewport、缩放或坐标测量。
|
||||
- 已经接成共享件的部分:`view/project-development/index.tsx` 用了 `@genarrative/image-canvas-core` 的 `getPointerClient` / `resolveSelectedToolbarStyle` / `resolveQuickEditPanelStyle`,以及 `@genarrative/image-canvas-react` 的 `CanvasChromeButton`;工具条与快速编辑浮层直接复用 `ImageCanvasSelectedLayerToolbarView` / `ImageCanvasQuickEditPanelView`。
|
||||
- 已经接成共享件的部分:`view/project-development/index.tsx` 用了 `@genarrative/image-canvas-core` 的 `createCanvasMarqueeState` / `getPointerClient` / `selectLayersInsideMarquee` / `resolveSelectedToolbarStyle` / `resolveQuickEditPanelStyle` / `MIN_SCALE` / `MAX_SCALE` / `resolveViewportFromWheel`,以及 `@genarrative/image-canvas-react` 的 `CanvasChromeButton` / `SelectionOverlay`;工具条与快速编辑浮层直接复用 `ImageCanvasSelectedLayerToolbarView` / `ImageCanvasQuickEditPanelView`。
|
||||
- **仍然自制的部分**(都有自己的单测,改前先跑对应的 `apps/ai-game-creator-shell/tests/*`):
|
||||
- `view/project-development/resourceCanvasOrderModel.ts`:栏目内顺序(该 section 记录的 `(y, x, resourceId)` 字典序)、拖拽插入位计算、顺序阶梯重编码(`x = 0`,`y = 序号 × 144`)与列数偏好的读写。
|
||||
- `view/project-development/resourceCanvasLayoutModel.ts`:`reconcileResourceCanvasLayout`、dependency 聚类与 type 默认排版。在本页它们只作为"未排序栏目的初始顺序种子",不再当作卡片几何。
|
||||
- `index.tsx` 的栏目内指针段:排序拖拽的 pointer down / move / up / cancel、`5px` 阈值、"跨栏目悬停不写入"的拒绝逻辑。
|
||||
- **列数偏好**:本机 `localStorage` 的 `agc.resource-canvas.columns.v1`,全局单值、两个 mode 共享、不进 sidecar。切换列数只改 CSS;如果为了"记住列数"去回写坐标,就是把布局 sidecar 当样式缓存,也会让多窗口互相覆盖。
|
||||
- **拖拽排序会重写整个栏目的 `x / y`**:这是顺序落盘的必然代价,同栏目其它卡片的记录位置随之变化不属于"自动补位"回归。
|
||||
- **样式缺口量化**:AGC 的宿主 chrome 样式集中在 `apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasChrome.css`(`main.tsx` 引入),只覆盖实际用到的 `image-canvas-editor__*` 类;网页端 `src/index.css` 里这套类有数百个、上千处。样式靠手抄是"样式不齐 / 工具条折行"反复复发的根源,补样式时只补实际用到的那几个类。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasOrderModel.ts`、`resourceCanvasLayoutModel.ts`、`index.tsx`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasChrome.css`、`apps/ai-game-creator-shell/tests/resourceCanvasOrderModel.test.ts`、`apps/ai-game-creator-shell/tests/resourceCanvasLayoutModel.test.ts`。
|
||||
- `view/project-development/resourceBookViewport.ts`:`normalizeResourceBookViewport`、`zoomResourceBookViewport`、`panResourceBookViewport`、`keepResourceBookViewportAtReadableScale`、`ensureResourceBookContentVisible`、`resolveResourceBookWheelViewport`(最后一个只是给共享 `resolveViewportFromWheel` 加了一层 normalize)。
|
||||
- `view/project-development/resourceCanvasLayoutModel.ts` 的 `normalizeInfiniteResourceCanvasViewport`(与 `resourceBookViewport.ts` 的 `normalizeResourceBookViewport` **是同一套语义的两份实现**)与 `fitResourceCanvasViewportToContent`:后者的 `maxScale` 默认放到 1.5,**不**像 core 的 `fitViewportToBounds` 那样封顶 1:1——这是刻意差异,别照着 core 去"对齐"。
|
||||
- `index.tsx` 的指针/滚轮/缩放段(约 `:3656-4090`):`resetResourceCanvasViewport`、`fitResourceBook`、`zoomResourceBookBy`、`handleResourceBookMainPointerMove`、`handleResourceCanvasPointerDown` / `PointerMove`、`stopResourceCanvasPan`。
|
||||
- 一处**行为不一致**(改平移相关 bug 时优先怀疑这里):主画布平移走 `panResourceBookViewport`(内部会 normalize),而分栏画布平移在 `index.tsx:4079-4087` 用**内联算式**直接算 `startViewport + delta`,不过 normalize。两条平移路径的守卫不一致。
|
||||
- **右键菜单**:AGC 全仓没有任何画布右键菜单(`ContextMenu` 只出现在 UI 编辑器里)。接共享 `ImageCanvasContextMenusView` 会撞两件事:① 该组件**没有** `supportedActions` 一类的 opt-in(门控只有 `canCopyContextLayers` / `isCanvasClipboardCopyBlocked` / `contextShouldShowLayer` / `contextShouldUnlockLayer` / `contextMenuLayer.mediaType === 'image-sequence'` / `imageContextMenuLayer.assetKind === 'character'`),宿主无法声明"哪些动作我真实支持",要新增能力只能按可选 prop opt-in 的既定口径给共享组件加;② `.image-canvas-editor__context-menu*` / `__context-submenu*` 这些类 AGC **一条都没有**。
|
||||
- **样式缺口量化**:AGC 的宿主 chrome 样式集中在 `apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasChrome.css`(395 行,`main.tsx:3` 引入),目前只覆盖 **17 个** `image-canvas-editor__*` 类;而网页端 `src/index.css`(15542 行)里这套类有 **451 个、910 处**。样式靠手抄是"样式不齐 / 工具条折行"反复复发的根源,补样式时只补实际用到的那几个类。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/resourceBookViewport.ts`、`resourceCanvasLayoutModel.ts`、`index.tsx`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasChrome.css`、`apps/ai-game-creator-shell/tests/resourceBookViewport.test.ts`、`apps/ai-game-creator-shell/tests/resourceCanvasLayoutModel.test.ts`。
|
||||
|
||||
## 资源画布工具条的「改造」「角色动画」按钮点了没反应(2026-09-10)
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
# 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,28 +591,29 @@ game-project/
|
||||
|
||||
- 页面骨架固定为左侧现有全局导航、中间主视窗、右侧陶泥儿对话和底部子 Agent 状态栏;不新建第二套客户端或平行项目页。
|
||||
- 中间主视窗提供 `资源管理 / 运行` 切换。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源管理只修改前端展示态,不伪造后端预览暂停结果。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执和已导入附件派生资源,固定按 manifest 资产功能分类 `category`(`UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类`)加末尾独立的「项目版本」栏目分区;未知任务产物不再兜底为版本,任务声明中的未登记音频也不冒充正式音频。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外把当前 manifest 与资源投影可证明的精确引用挂成关联的视觉隐藏说明,页面不绘制连线。排列结果只作为未排序栏目的初始顺序种子,不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。资源管理页是栏目分页的纵向滚动网格:栏目内顺序由布局 sidecar 记录按 `(y, x, resourceId)` 字典序推出,用户可拖动卡片调整本栏目顺序并一次性落盘;顶部可选自动或固定 `1..6` 列,列数是本机 `localStorage` 偏好,不写入 sidecar。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、排序模式、滚动位置与选中资源;不提供工具栏、工具侧边栏或可拖动标题栏。2026-08-10 起聚焦态以资源元数据、Rust 权威深度、同类型上下游 / 任务流和版本信息为首屏;美术图片与视频只保留卡内本体,不在详情重复放大。安全文档正文与按意图读取的音频控制位于元数据之后;美术编辑、音频编辑 / 替换、版本替换或运行模块仍不在本阶段。页面不提供画布平移、缩放、自由摆放、框选与依赖连线,可见性预取以栏目滚动容器为交叉根。
|
||||
- 资源管理从当前 `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 权威深度、同类型上下游 / 任务流和版本信息为首屏;美术图片与视频只保留卡内本体,不在详情重复放大。安全文档正文与按意图读取的音频控制位于元数据之后;美术编辑、音频编辑 / 替换、版本替换或运行模块仍不在本阶段。
|
||||
- `@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` 作为默认排版的唯一权威值;网格单元尺寸与列数由 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 模式的精确引用关系改为挂在栏目网格容器上的关联视觉隐藏文本,与可见引用同源变化。
|
||||
- 卡片宽高从 `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 与监听。
|
||||
- 阶段三的聚类已落在 `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,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。资源管理页是栏目分页的纵向滚动网格,卡片按列排布并只纵向滚动。栏目内顺序由布局 sidecar 记录按 `(y, x, resourceId)` 字典序推出:用户拖动卡片排序时把该栏目全部记录重编码为顺序阶梯(`x = 0`,`y = 序号 × 144`)并一次性落盘,用户已排序的栏目里新资源追加到末尾。顶部列数控件提供自动与固定 `1..6` 列,选择按全局单值记在本机 `localStorage`,不写入布局 sidecar。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、排序模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源页边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 资源管理从当前 `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` 倍缩放卡片,避免单个低尺寸卡片被插值放大成糊图;用户主动缩放仍沿用通用画布倍率,并按“排序模式 + 栏目”保留当前会话内的平移和缩放。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 运行表现层首版直接嵌入当前项目的 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 与回滚合同完备前不向普通用户开放。
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# 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