diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/stateTransition.ts b/apps/ai-game-creator-shell/src/features/ui-editor/stateTransition.ts index c4338e0ca..a559e74ed 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/stateTransition.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/stateTransition.ts @@ -93,7 +93,8 @@ export function applyUiEditorCommand( if (!tree) return { ok: false, reason: 'missing' }; if (command.type === 'set-tree-offset') { - if (!command.min.every(Number.isFinite)) + // 只校验元素有限不够:`[]` 会让 every 空真通过,缺第二个坐标时 max 会算出 NaN。 + if (command.min.length !== 2 || !command.min.every(Number.isFinite)) return { ok: false, reason: 'invalid' }; const size = imageLogicalSize(next, command.treeId); if (!size) return { ok: false, reason: 'invalid' }; diff --git a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts index 7a117eb69..37c8ba33d 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorState.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorState.test.ts @@ -138,6 +138,23 @@ describe('useUiEditorState', () => { } }); + it('rejects a tree offset that is not a two-element tuple', () => { + const state: State = { + ...structuredClone(EMPTY_UI_EDITOR_STATE), + ui_design_images: { page: image('Page') }, + ui_trees: [{ src_ui_design: 'page', root: pageRoot('root') }], + }; + for (const min of [[12], []] as unknown as [number, number][]) { + expect( + applyUiEditorCommand(state, { + type: 'set-tree-offset', + treeId: 'page', + min, + }), + ).toEqual({ ok: false, reason: 'invalid' }); + } + }); + it('reports persistable state invariant failures before save', () => { const state = structuredClone(EMPTY_UI_EDITOR_STATE); state.ui_trees = [{ src_ui_design: 'missing', root: pageRoot('root') }];