补充 UI 节点移动与隐藏状态文档

明确跨树移动仅保留局部 transform,不承诺页面位置稳定。

记录同树重挂的位置换算语义与全局 NodeId 隐藏集合。
This commit is contained in:
2026-08-18 15:17:16 +08:00
parent 847353e4f0
commit e88cb6ed9e
2 changed files with 12 additions and 7 deletions
@@ -16,7 +16,6 @@
- 验证方式:覆盖 Codex 分类与敏感诱饵、失败事件公共摘要、最近任务与各正式卡片、game-chat 阶段记录、待核对状态、final-reply fallback 白名单及 malformed 响应完整重试;运行 Rust 定向测试、前端模型/AppSurface 定向测试、Shell typecheck、编码和 diff 门禁。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-10 资源管理评审阻塞项按第二轮正式合同修复
- 背景:资源管理第一轮实现后,人工验证继续暴露 WebView 默认缩放、预览队列饥饿、过滤后媒体残留播放、外层滚动串 scope、超深依赖坐标越过 Rust 上限和暂时错误无法重试等问题。部分 PRD / 技术方案仍描述第一轮的中央媒体预览、单全局 Overlay 和统一 section scope,已经与第二轮代码及验收结论冲突。
@@ -60,6 +59,7 @@
- 替代关系:本条替代下方 2026-08-07 阶段二中“缩放只等于可视高度”和“只为完整可见卡片建立端点”的显示口径;其中曾采用的全局 SVG、四 viewport 联合 clip 和单全局 Observer 又由上方“依赖 SVG 改为分区 plane 所有”决定替代。高度模型、四分区、会话隔离、内外滚动和无布局 CAS 等其它决定继续有效。阶段一媒体卡、阶段三确定性聚类、Rust `dependencyDepths`、producer 截断降级、历史手动坐标与 type sidecar 均不变。
- 验证方式:纯倍率模型覆盖按钮 / wheel 边界;AppSurface 覆盖项目、mode、section 隔离、普通 wheel、Ctrl wheel、WebKit gesture 与零布局写入;SVG 覆盖同 plane 倍率、橙色 marker、直线 / 小圆角横纵路由、自环间隙、双向边界继续线、分区原生裁剪、每区单 Observer、task-flow 零渲染和 4096 精确关系有界输出。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-11 Tauri 无限画布以可证明事务和分类恢复收口
- 终态与队列:资源编辑账本正式区分可继续阶段、`reconciliation-required`、`remote-failed` 和 `archived`。远端明确失败只保存稳定分类与终态时间,不得再 POST、轮询或重新扣费;只有该终态能由用户显式归档并移出活动恢复队列,归档保留账本且不伪装 `committed`。结果未知和需对账项继续失败关闭。
@@ -7169,8 +7169,9 @@
## 2026-08-18 UI Editor 左侧节点树跨界面移动
- 决策:左侧 UI 节点树使用 UI-only 的虚拟超级节点统一承载现有 `ui_trees` 页面根节点;超级节点和页面根节点不可拖动,普通节点可以在不同页面根节点之间移动,整个子树随节点移动并保留原 `transform`。
- 状态边界:虚拟超级节点只改变树视图,不写入 UI Editor State 或持久化契约;跨界面移动不自动切换当前 active image。跨界面坐标换算不在本次范围内,后续需单独设计。
- 决策:左侧 UI 节点树使用 UI-only 的虚拟超级节点统一承载现有 `ui_trees` 页面根节点;超级节点和页面根节点不可拖动,普通节点可以在不同页面根节点之间移动,整个子树随节点移动并保留原 `transform`。跨界面“保留”只指复制原局部布局参数,不代表源、目标画布之间存在可比较的页面位置;不同界面图的画布尺寸、像素密度和父节点坐标空间独立,跨界面坐标换算不在本次范围内。
- 同界面重新挂载与跨界面移动分开处理:同界面重新挂载必须先做页面矩形到目标父节点局部坐标的换算,保持页面位置稳定;跨界面移动不做该换算,仅保留原局部 `transform`。
- 状态边界:虚拟超级节点只改变树视图,不写入 UI Editor State 或持久化契约;跨界面移动不自动切换当前 active image。`NodeId` 在 State 内全局唯一,节点显示 / 隐藏直接使用页面会话级 `Set<NodeId>`,跨界面移动时隐藏状态随节点保留;清空、重新加载或节点失效时清理。跨界面坐标换算不在本次范围内,后续需单独设计。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-08-18 UI Editor 左侧节点树跨界面移动”。
## 2026-08-17 UI Editor 文本字体来源显式化
@@ -7191,7 +7192,6 @@
- 决策:只有同一可信根 Supervisor 能调用 `agent.acceptance_update`,且该控制面动作必须独占一轮,不能夹带 `plan_update`、legacy plan 或回复。requiredEvidence 采用 `tool:<Runtime 工具名>`,合同冻结前必须命中 Runtime 允许的持久证据工具集合并拒绝拼错、控制面和纯协调工具;动态 MCP catalog 不得冻结为不可变 requiredEvidence。passed 节点必须引用当前根任务树中对应工具的真实成功动作回执;回执同时记录动作执行边界的 `projectRevisionBefore / projectRevisionAfter`,非 mutation evidence 必须满足 before、after 与验收时 current revision 完全相同,mutation evidence 的 after 必须等于 current revision,旧 revision 或执行后延迟落账的回执不可重放。failed、not-observed、缺失节点以及落后当前 project revision 的整图确认状态均阻断普通完成、finalization 与恢复。项目 revision 变化后由 Supervisor 只更新受影响节点并确认当前图,未提交的 passed 节点保持不变;Runtime 不替 Supervisor 推断影响范围、选择具体 Agent 或实现方式。
- 决策:不可变 Goal Contract 的根 Run 收到 steer 时,必须按旧 rootRunId 串行化整个转换,并在持锁后重新确认该旧根仍是 Session 当前权威 Run,避免不同 steerId 并发创建多个 replacement。随后为绑定旧 rootRunId 的全部非终态静态、ready 和 isolated 后代写入取消栅栏、打断 Provider 并逐个收束,再终止旧根;所有项目修改入口在看到取消栅栏后立即失败关闭。Runtime 必须确认旧树所有成员都已进入终态或 `needs-reconciliation`,超时则保持等待并拒绝启动 replacement;只有旧树停稳后,才在同一 Session、source 和 Run Profile 启动新根 Run 重新理解完整目标。
## 2026-08-08 External v1 图片编辑来源字段允许原地收紧
- 背景:图片编辑主来源已经从可由客户端提交 objectKey 和类型提示的 `sourceImageSrc / sourceResourceId / assetKind`,收紧为服务端按项目资源 ID 或素材 ID 解析权威对象与类型的必填 `sourceReferenceId`。这会让严格 External v1 客户端立即失败,属于现役版本策略明确列出的 breaking change;2026-07-31 的历史豁免不能自动覆盖本次变更。
@@ -8660,6 +8660,7 @@
- 2026-06-19 桌面壳外链打开 helper 共用:Tauri WebView 外域拦截和 HostBridge `app.openExternalUrl` 都必须复用 `open_normalized_desktop_external_url` 执行系统外链打开动作;HostBridge 分支仍先用 `normalize_external_url` 保留 payload 错误语义并把 opener 错误回传给 H5,WebView 拦截保持 best-effort 静默处理。桌面壳配置检查会拒绝 `dispatch.rs` 直接调用 `app.opener().open_url` 绕过该 helper,避免两条离壳路径漂移。
> 2026-07-18 覆盖说明:本段后续关于微信 `navigation.openNativePage`、生成结果订阅页、`[subscribe-message]` 日志和订阅页路由门禁的 2026-06 决策均已由旧创作模板退役决策废止,只作为历史记录。Expo / Tauri 的同源 H5 受控导航及微信登录、支付、分享能力继续有效。
- 2026-06-20 H5 原生导航预校验:`navigateHostNativePage()` 在 `native_app` 下发送 `navigation.openNativePage` 前必须先拒绝空值、控制字符、协议相对 URL、外域绝对 URL 和非 `http:` / `https:` 协议目标;同源绝对 URL、`/path` 和保留给桌面壳兼容的相对 route 继续交给 Expo / Tauri 壳二次归一并补写宿主上下文。微信小程序分支仍按小程序页面 URL 语义走 `wx.miniProgram.navigateTo`,不套原生 App 同源 H5 预校验。根级 `npm run check:native-shells` 会反查 H5 facade 仍使用 `normalizeNativeAppPageUrl(...)` 且发送归一后的 URL,避免明显不安全目标触达原生壳。
- 2026-06-20 微信受控原生页能力声明:微信小程序壳真实 capability profile 声明 `navigation.openNativePage`,用于承接已经登记并测试的小程序原生页 flow;当前订阅生成结果通知页通过 H5 `requestGenerationResultSubscribePermission()` 调用 `navigateHostNativePage()` 打开 `/pages/subscribe-message/index`,小程序页再调用真实 `wx.requestSubscribeMessage` 并按既有结果协议回灌。根级 `npm run check:native-shells` 必须把该能力反查到共享 profile、微信 `WECHAT_HOST_CAPABILITIES` 镜像、订阅页协议常量、H5 入口、小程序 host-bridge / shell / page 文件和相关测试;该能力不代表开放任意小程序页面跳转。
- 2026-06-18 能力声明收紧:`packages/shared/src/contracts/hostBridge.ts` 提供 HostBridge method / capability 白名单,H5 的 `getHostRuntime()` 会解析并过滤 `hostCapabilities`;`openHostShare`、`writeHostClipboardText`、`requestHostHapticsImpact`、`setHostAppTitle`、`exportHostTextFile` 等 native 能力只在宿主声明对应 capability 后调用。发布分享弹窗只有声明 `share.open` 时才显示受控分享动作,并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达,避免旧壳或裁剪壳露出不可用入口。
@@ -14072,6 +14073,7 @@
- 分类:内部稳定 reason code 固定为 `translation_invalid / translation_upstream_failed / translation_budget_exhausted / elevenlabs_http_failed / invalid_audio / duration_probe_failed / oss_failed / writeback_failed`。MIME、空 body 和大小归 `invalid_audio`;MP3 识别、帧读取和时长门禁归 `duration_probe_failed`。普通用户继续只读稳定短文案,不暴露 endpoint、上游正文或凭据。
- 跨入口:画布 Agent `generate-sound-effect` 与站内 / External v1 共用 canonical Prompt、固定模型、nullable `0.5-30` 小数时长和 Loop;省略 duration 为手动 `5s`,显式 null 为自动。SFX 参数解析必须保留该 null,不能被通用 null-default 兼容层改写。最终仍进入相同 `editor_sound_effect_generation` queue payload,不新增 Agent 专属链路。
- 发布边界:T6 工程实施和 mock / loopback 门禁不等于真实 provider 或生产验收。发布前关闭 SFX 入队,使用显式 `--server` / `--server-url` 只读查询 `external_generation_job` 中 pending / running 的 `editor_sound_effect_generation`,清零后按 api-server / Worker → Web 顺序部署并灰度;禁止 `--root-dir`、删除任务伪造 drain 或自动回退 Vidu。本次没有 SpacetimeDB schema、migration 或 bindings 变更。
## 2026-08-06 编辑器生成结果使用 durable receipt 与统一原子提交
- 背景:图片、改图、去背景、图集 / UI 多产物、角色动作、视频、音效和背景音乐在 OSS 结果可用后,仍分段 confirm object、创建 project resource / account asset、保存 canvas 和 complete job。任一中间失败都会留下部分业务事实;只把 `external_generation_job` 当 operation journal 又无法覆盖无 job 的 inline,也无法独立证明某批 resource/asset/canvas 已作为一笔提交完成。
@@ -2,7 +2,9 @@
## 2026-08-18 UI Editor 左侧节点树跨界面移动
左侧 UI 节点树使用 UI-only 的虚拟超级节点统一承载现有 `ui_trees` 的页面根节点,仅改变树视图,不写入 State 或持久化契约。页面根节点和超级节点不可拖动;普通节点可在不同页面根节点之间拖动,整个子树随节点移动并保留原 `transform`。移动不会自动切换当前 active image;后续如需跨界面坐标换算,另行设计。
左侧 UI 节点树使用 UI-only 的虚拟超级节点统一承载现有 `ui_trees` 的页面根节点,仅改变树视图,不写入 State 或持久化契约。页面根节点和超级节点不可拖动;普通节点可在不同页面根节点之间拖动,整个子树随节点移动并保留原 `transform`。这里的“保留”只表示复制节点原本的局部布局参数,不承诺跨界面后的页面像素位置稳定:不同界面图拥有独立的画布尺寸、像素密度和父节点坐标空间,跨树坐标换算没有可靠的默认语义,因此本次不做换算,也不把跨树移动后的视觉位置描述为“保持不变”。
同一界面图内的重新挂载沿用原有页面几何语义:先计算节点和目标父节点在页面坐标中的矩形,再换算出目标父节点下的新局部 `transform`,以保持节点在页面中的位置稳定。跨界面移动与同界面重新挂载是两条不同的行为;前者只保留原局部 `transform`,后者才做页面矩形换算。两种移动都不会自动切换当前 active image;后续如需跨界面坐标换算,另行设计。
## 2026-08-17 UI Editor 项目字体导入与预览
@@ -993,13 +995,14 @@ game-project/
- 根 Supervisor 只对 manifest 中已经 Completed 的 seed task执行正式产物、素材和 Canvas 深验;未完成状态本身已经阻塞根完成,禁止在 code-prototype 尚未开始时提前扫描四类素材。自动唤醒连续耗尽 200 次瞬态预算后进入 `needs-reconciliation`,不得静默保留 running。
- Canvas 可见性区间证明按 Oxc semantic symbol 与当前绘图 context 的 Canvas owner 解析:函数 alias 重绑定、可达调用参数、计数循环和作用域内数值常量都必须保持身份。无法化简的格子索引只在唯一 `COLS / ROWS / CELL` 合同下覆盖完整棋盘轴;已知越界实参继续拒绝。动态 clamp 只接受未遮蔽全局 `Math.min(currentCanvas.width|height - size, Math.max(0, dynamic))`,错误 Canvas、遮蔽 Math 和普通未知坐标失败关闭。
- parent wake 达到瞬态预算上限时先形成 durable reconciliation signal;lane 忙保留 deferred 状态,随后在同一 execution lane 与项目写锁内重读原始 state、最新 durable task、取消墓碑和 DAG 进展。manifest 结构损坏时使用不依赖 manifest hydration 的专用 CAS journal 与原子 state 写入,不能吞掉持久化错误,也不能用过期终态覆盖并发取消或 child 进展。durable terminal reconciliation task 是后续 state / queue / event / audit 的提交标记;重启恢复必须在其它 resume 动作前幂等补齐这些投影,task journal 读取损坏不得当作任务不存在。恢复只在 raw state 具有完整 Agent/task/Session/run/source/profile/binding/task 身份时选择其当前 run;state 缺失、JSON 损坏、空对象或 runId 等关键身份为空时只考虑 journal 最后一个 logical run,完整有效的新 Run 继续阻止历史 marker 覆盖。event 与 Agent DB audit 只有完整稳定 payload 唯一匹配时才视为已投影,同键冲突或重复必须失败关闭;旧 task 已 completed/cancelled、Runtime 已离开 waiting 或新 Run 已接管时,deferred signal 必须追加 run-scoped resolved/superseded 终局。
## 2026-08-04 manifest 与工作台一致性收口
- `.agent/manifest.json` 的存储写边界使用同目录持久文件锁跨线程、跨进程串行化;锁必须覆盖旧 manifest 读取、不可变版本前缀校验、临时文件安装和安装后回读一致性校验。锁文件拒绝符号链接、非普通文件和异常所有权 / 硬链接;Windows 使用不共享写句柄,Unix 使用 `O_NOFOLLOW + flock`。旧快照在新版本安装后只能被拒绝,不能覆盖已追加版本。
- 后台 Agent 的 manifest 变化以共用 Runtime 状态投影 / 终态 emitter 作为失效因果点:`game-creator-agent-runtime-update` 的 Rust / TypeScript DTO 固定携带 `manifestInvalidated`,且 App 必须在 Supervisor、selected agent、session 和 run 身份的任何 early return 之前处理失效。GUI 进程内 Runtime 直接发该事件;External Runner 是独立进程、没有 GUI `AppHandle`,因此 Runner 协议 v5 的 `runner.attach_gui_owner` 必须登记 GUI 创建的随机 loopback 端口和 64 位随机令牌,Runner 的同一 emitter 通过受令牌保护的短连接转发 `game-creator-manifest-invalidated`。两条路径都只传项目路径与 Agent 身份,不复制 manifest,也不靠轮询补偿。
- App 收到当前项目的 Runtime / relay 失效后重新调用 `get_local_game_manifest`。重读按项目 single-flight 合并事件风暴;读取中再到达失效只追加一轮串行重读,不并发提交同项目响应。应用结果同时校验组件仍挂载、当前项目路径和项目 scope version;项目切换、组件卸载或旧 scope 的迟到响应不得覆盖新项目。Project Supervisor 对外发布前以“revision 前读 -> manifest -> revision 后读”取得一致快照,再通过 `onManifestChange(projectPath, manifest, metadata)` 携带 `projectId + revision + source`;启动器按 `projectPath + projectId` 只接受更高 revision,同 revision 只接受内容一致的重复,旧轮询和同 revision 分叉都不得覆盖。资源列表、依赖图输入、任务状态、运行入口和正式版本卡必须在当前页面实时重投影,不要求关闭或重开项目。集成测试记录“事件未重新打开项目”的调用基线前,必须先等待项目写入最近列表后触发的只读目录状态刷新完成,不能把这项合法后台检查误算成失效事件副作用。
- `.agent/agent.db` 有界尾部读取报告截断时,审计 producer 映射失败关闭,不生成基于不完整审计的 producer、task flow 或对应任务环。前端收到截断 DTO 时只剔除 `producerAssignments`、`taskFlows` 与对应 `cyclicTaskIds`;Rust 根据当前 manifest、精确资源引用和仍可信任务深度下限返回的 `dependencyDepths` 继续保留,前端只校验资源仍存在且深度为非负安全整数,不得自行重算或压平权威深度。精确引用边、reference connection index、`cyclicResourceIds` 与 unresolved references 同样继续保留。
-- 资源依赖 SVG 继续作为不可交互装饰层隐藏,但 dependency 画布通过 `aria-describedby` 提供当前可见精确引用和任务流的文本等价列表。中央资源聚焦按稳定 `resourceId` 驱动焦点状态:仅 `null -> id` 或 `idA -> idB` 聚焦详情 region,同一 ID 的 manifest 重投影不得抢走音频、视频、链接或关闭按钮焦点;显式收起和 Escape 恢复画布滚动并优先聚焦原触发卡片。聚焦资源被删除时清理 stale focused / selected ID,关闭详情并把焦点落到资源搜索框;项目切换或运行视图切换清除旧恢复意图,不得恢复旧项目卡片。橙色引用线及箭头使用对 `#fffdfa` 画布达到至少 `3:1` 的颜色。
-- 资源依赖 SVG 继续作为不可交互装饰层隐藏,但 dependency 画布通过 `aria-describedby` 提供当前可见精确引用和任务流的文本等价列表。中央资源聚焦按稳定 `resourceId` 驱动焦点状态:仅 `null -> id` 或 `idA -> idB` 聚焦详情 region,同一 ID 的 manifest 重投影不得抢走音频、视频、链接或关闭按钮焦点;显式收起和 Escape 恢复画布滚动并优先聚焦原触发卡片。聚焦资源被删除时清理 stale focused / selected ID,关闭详情并把焦点落到资源搜索框;项目切换或运行视图切换清除旧恢复意图,不得恢复旧项目卡片。橙色引用线及箭头使用对 `#fffdfa` 画布达到至少 `3:1` 的颜色。
## 2026-08-05 客户端素材创作无限画布阶段一合同
@@ -1095,6 +1098,6 @@ game-project/
## 2026-08-18 UI Editor 节点预览可见性
- 节点显示 / 隐藏是编辑器页面级临时状态,不新增 Rust `Node` 字段,不进入 UI State 保存快照、运行时数据或后端契约。状态按界面图 ID 和节点 ID 维护;切换界面图保留,清空或重新加载时重置,删除节点或界面图时清理失效 ID。
- 节点显示 / 隐藏是编辑器页面级临时状态,不新增 Rust `Node` 字段,不进入 UI State 保存快照、运行时数据或后端契约。`NodeId` 在 State 内全局唯一,隐藏状态直接使用页面会话级 `Set<NodeId>`;切换界面图保留,跨界面移动随节点保留,清空、重新加载、删除节点或删除界面图时清理失效 ID。
- 同一可见性状态同时作用于 `编辑叠加` 和 `最终预览`。隐藏节点会截断整个子树,移除节点组件、编辑边框、名称、缩放控制点和画布命中区域;参考图和预览画布本身保持存在。父节点恢复时,子节点此前独立设置的隐藏状态保持不变,根节点不享受特殊限制,也允许隐藏。
- UI Tree 行提供 `Eye` / `EyeOff` 按钮,按钮只切换状态、不改变选择;节点 Inspector 提供“在预览中显示”复选框,两者共享页面控制器状态。隐藏节点仍可通过 UI Tree 选择并在 Inspector 中编辑,但不会重新出现在画布上,直到恢复显示。