From 9e49db419f6a53a36d0af98556f413f41c3fc740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 16:01:14 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A9=E7=8A=B6=E6=80=81=E4=B8=8D=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=A0=A1=E9=AA=8C=E5=85=88=E5=88=A4=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=86=8D=E5=8F=96=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/features/ui-editor/stateInvariants.ts 在取 path.trim 与 pixel_size.every 前先判 string / Array.isArray,残缺持久化文档此前会在 firstUiDesignInvariantMessage 里抛 TypeError,被保存流程吞成通用失败文案 tests/uiEditorState.test.ts 补三组残缺界面图都回落到 invalid-image 的用例 --- .../src/features/ui-editor/stateInvariants.ts | 4 ++++ .../tests/uiEditorState.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/stateInvariants.ts b/apps/ai-game-creator-shell/src/features/ui-editor/stateInvariants.ts index 9d14e1f44..cd792d2b1 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/stateInvariants.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/stateInvariants.ts @@ -14,8 +14,12 @@ export function validateUiDesignState(state: State): UiDesignInvariantIssue[] { const issues: UiDesignInvariantIssue[] = []; const nodeIds = new Set(); for (const [id, image] of Object.entries(state.ui_design_images)) { + // 这个 seam 专门接住可能残缺的持久化 State,取字段前先判类型,别把 TypeError + // 漏给调用方,否则保存失败只会退化成通用文案。 if ( + typeof image.path !== 'string' || image.path.trim() === '' || + !Array.isArray(image.pixel_size) || !image.pixel_size.every((value) => Number.isFinite(value) && value > 0) || !Number.isFinite(image.pixels_per_unit) || image.pixels_per_unit <= 0 diff --git a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts index 37c8ba33d..737ce9d09 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts @@ -163,6 +163,22 @@ describe('useUiEditorState', () => { ]); }); + it('reports malformed images instead of throwing', () => { + const state = structuredClone(EMPTY_UI_EDITOR_STATE); + for (const malformed of [ + { pixel_size: [100, 80], pixels_per_unit: 1 }, + { path: 'assets/page.png', pixels_per_unit: 1 }, + { path: 'assets/page.png', pixel_size: [0, 80], pixels_per_unit: 1 }, + ]) { + state.ui_design_images = { + page: malformed as unknown as UIDesignImage, + }; + expect(validateUiDesignState(state)).toEqual([ + expect.objectContaining({ code: 'invalid-image' }), + ]); + } + }); + it('moves a subtree between UI trees without changing its transform', () => { const moved = nodeWithSprite('moved'); const initial: State = {