diff --git a/apps/ai-game-creator-shell/src/view/project-development/export/state/useXhsMinitoolExport.ts b/apps/ai-game-creator-shell/src/view/project-development/export/state/useXhsMinitoolExport.ts index f47257810..05938b2bc 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/export/state/useXhsMinitoolExport.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/export/state/useXhsMinitoolExport.ts @@ -241,11 +241,12 @@ export function useXhsMinitoolExport({ throwToErrorPool(String(error), error); return null; } - setFailure(structured); - // 宿主侧事实故障:先让面板显示现场,再抛进错误池(策略拒绝等预期拒绝不走这一步)。 if (structured.type === 'exportUnavailable') { + // 宿主侧事实故障:面板不接管(不留失败卡片),直接抛给全局错误池上报;下一次轮询会自然重试。 throwToErrorPool(JSON.stringify(structured), error); + return null; } + setFailure(structured); return null; } finally { if (isInitialRead) { @@ -303,12 +304,10 @@ export function useXhsMinitoolExport({ } if (structured.type === 'formInvalid') setFieldError(structured); else if (structured.type === 'saveConflict') setConflict(structured); - else { - setFailure(structured); - if (structured.type === 'exportUnavailable') { - throwToErrorPool(JSON.stringify(structured), error); - } - } + else if (structured.type === 'exportUnavailable') { + // 宿主侧事实故障:不落面板,直接抛给全局错误池上报。 + throwToErrorPool(JSON.stringify(structured), error); + } else setFailure(structured); return false; } }, @@ -367,10 +366,12 @@ export function useXhsMinitoolExport({ throwToErrorPool(String(error), error); return; } - setFailure(structured); if (structured.type === 'exportUnavailable') { + // 宿主侧事实故障:不落面板,直接抛给全局错误池上报。 throwToErrorPool(JSON.stringify(structured), error); + return; } + setFailure(structured); } }, [projectPath, throwToErrorPool]); diff --git a/apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx b/apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx index 3876b4a29..56c5315ee 100644 --- a/apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx +++ b/apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx @@ -1017,7 +1017,7 @@ describe('状态 hook', () => { await expect(result.current.packageAll()).resolves.toBeUndefined(); }); - it('宿主侧事实故障先给卡片、再把同一个故障抛一次', async () => { + it('宿主侧事实故障原样抛出进错误池,面板不留卡片', async () => { const invoke = vi.fn(async (command: string) => { if (command === 'read_xhs_minitool_export') { return { form: FORM, hasScript: true }; @@ -1036,18 +1036,16 @@ describe('状态 hook', () => { ); await waitFor(() => expect(result.current.form).toEqual(FORM)); - // 卡片照常给现场,同时原样抛出(真实 WebView 里由全局 handler 结算进错误池)。 + // 宿主故障交给全局错误池上报,面板不接管:原样抛出,failure 保持为空。 await act(async () => { await expect(result.current.packageAll()).rejects.toBeInstanceOf(Error); }); - // 面板留下的就是宿主那个原始错误,用户那句话是渲染时从变体推的。 - expect(result.current.failure).toEqual({ - type: 'exportUnavailable', - cause: '项目根不可用:找不到目录', - }); + expect(result.current.failure).toBeNull(); + // 同一个故障只抛一次(轮询会反复读到它,不能让错误池的 count 变成轮询次数)。 await act(async () => { await expect(result.current.packageAll()).resolves.toBeUndefined(); }); + expect(result.current.failure).toBeNull(); }); it('帮我填:先把没保存的输入落盘,再把改注册表这件事交给同一个会话', async () => {