Merge branch 'master' into fix/preview-iframe-resize-loop
Project CI / Repository checks (pull_request) Successful in 4m1s
Project CI / Frontend tests (pull_request) Successful in 4m52s
Project CI / Backend tests (pull_request) Successful in 7m31s
Project CI / Native shell tests (pull_request) Successful in 15m43s

This commit is contained in:
2026-09-02 17:29:17 +08:00
31 changed files with 1394 additions and 59 deletions
@@ -14,3 +14,9 @@
- `save`:加载 / revision 状态、脏状态、保存警告和保存意图。
预览树在会话边界归一化为 `UITree | null`:尚未选择界面图、正在加载或尚未识别树时均以 `null` 表示,不能把 `Array.find``undefined` 传播到视图接口。
保存与代码生成共享同一份持久化 State/revision。会话层在保存或生成进行期间互斥拦截,且代码生成必须基于已加载的持久化 revision;视图层的保存按钮和“保存并返回”按钮同步遵守该互斥状态。
`UiDesignStateStore``generateCode(assetId)` 是必需能力,返回成功结果时不得为 nullable;所有注入的 adapter 与测试替身都必须实现该方法。
资源切换时,会话必须清理上一资源的保存/生成错误和生成中状态;普通保存开始时也清理代码生成错误。生成请求若因加载、锁定或 revision 等前置条件被拦截,必须向视图提供可见错误,而不是静默返回。
@@ -1199,6 +1199,7 @@ game-project/
- UI 编辑器复用现有 manifest `kind: "UI"``mediaType: "application/json"` 资源,不增加平行 asset kind。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope`projectId``assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets``Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。
- Tauri 专用 load/save command 只接受项目路径、期望项目 ID、manifest asset ID 和(保存时)资源 revisionRust 按 manifest 解析受控本地路径并在项目写锁内做 CAS。相同 `State` 返回 unchanged 且不推进 project revision;不同内容安装并回读一致后才推进 revision,后续推进失败返回 `reconciliation-required`,不伪装为完整保存。
- UI 编辑器代码导出仅写入用户项目目录下的 `ui/generated-*.js` 派生文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不得把生成文件写入冒充为项目 mutation。
- UI State 原子安装保留最近一个可解析、canonical 的 `.previous` 恢复候选,作为最佳努力恢复来源;写入主文件前不把完整 State 语义校验重复执行一遍。主文件损坏时,恢复候选仍必须通过同一严格 schema、project/asset identity、revision、引用和 State 校验后才能安装;恢复安装与保存共用项目写锁,并在持锁后重新读取主文件,已有并发保存的有效新版本时直接返回而不安装旧副本。任一候选均不可信则停在加载错误,前端禁编辑和保存。新建 UI 资源先登记并安装合法 envelope,任一步失败补偿 manifest/文件,避免把空 JSON 留给资源卡。
- 图片路径只需是安全项目相对路径,不要求外部图片仍存在或已登记为 manifest asset;缺失媒体只导致 preview 占位。`imageOrder`、当前选择、缩放、面板开关和 preview URL 不写入 State,加载后由 State 派生。保存冻结提交快照,保存期间的新编辑继续保持 dirty;AI state lock 和加载期间禁保存。
@@ -35,6 +35,7 @@ Container 专属数据是互斥 tagged unionHBox/VBox 存 `alignment + separa
- 不支持 Flex/Flow wrapGrid 按行优先排列。
- Margin 的 children 填满内侧矩形;Center 的 children 居中并可重叠。
- v1 minimum size 只使用 `custom_minimum_size`。TODO:为文本、图片等组件提供 minimum-size,再与 custom minimum 逐轴取最大值。
- `ImageType::Tiled.fill_center=false` 当前暂不支持,前端预览与 Rust HTML 生成均保持整块 `repeat` fallback;如需实现仅铺边框,必须先补充 CSS 输出与既有项目迁移契约。
## 编辑器交互
@@ -1673,6 +1673,7 @@ type PlanningBaselineInput =
- 持有 Runtime 提供的 approvalRequestId;一次点击生成 responseIdbusy/超时重试复用。
- command 进行中禁用重复点击;另一个窗口先决定后,当前卡刷新为 `already-decided`,不能覆盖。
- `recoveryPending=true` 时显示可恢复状态,只允许重试同一 ID,不允许提交新版本或启动构建。
- 已渲染审批卡的后台 hydrate 仅更新权威投影,不因短暂的 `hydrateBusy` 竞态禁用当前决定或已打开的评论弹层;只有 hydrate 返回的 `recoveryPending=true`(或决定请求自身 busy)才阻止提交,并保留用户已输入的原因。
- 待审版本的 receipt 已存在时隐藏对应 stale pending 卡;hydrate 只恢复精确 project/session/run/action identity。
- approved 后只有在所有必需投影恢复完成时启用“做成游戏”;恢复态不提供该出口。