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 = {