继续收敛活动回合读取错误

保留 Direct 活动回合快照 IPC 失败正文

补充窗口标题栏显示和回归测试
This commit is contained in:
kdletters
2026-10-05 21:24:12 +08:00
parent 7d73f225b5
commit 0850195d1d
8 changed files with 38 additions and 16 deletions
@@ -178,6 +178,7 @@ export function WindowChrome({ children }: WindowChromeProps) {
activeTurns={activeProjectRuns.activeTurns}
currentProjectPath={activeProjectRuns.currentProjectPath}
readFailed={activeProjectRuns.readFailed}
readError={activeProjectRuns.readError}
onOpenProject={activeProjectRuns.onOpenProject}
placement="titlebar"
/>
@@ -19,6 +19,7 @@ export type WindowChromeActiveProjectRuns = {
activeTurns: GameCreatorDirectActiveTurn[];
currentProjectPath?: string | null;
readFailed?: boolean;
readError?: string;
onOpenProject?: (projectPath: string) => void;
};
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import type { GameCreatorDirectActiveTurn, TauriInvoke } from '../../app/types';
import { visibleClientErrorMessage } from '../../services/errorReporting';
import {
canSubscribeTauriEvents,
subscribeTauriEvent,
@@ -35,6 +36,7 @@ export function useDirectActiveTurns({
[],
);
const [snapshotReadFailed, setSnapshotReadFailed] = useState(false);
const [snapshotReadError, setSnapshotReadError] = useState('');
const mountedRef = useRef(true);
const inFlightRef = useRef<Promise<void> | null>(null);
/**
@@ -93,10 +95,12 @@ export function useDirectActiveTurns({
setActiveTurns(nextTurns);
}
setSnapshotReadFailed(false);
setSnapshotReadError('');
inFlightRef.current = null;
return;
} catch {
} catch (error) {
if (!isCurrent()) return;
setSnapshotReadError(visibleClientErrorMessage(error));
if (attempt < DIRECT_ACTIVE_TURNS_READ_ATTEMPTS) {
await new Promise<void>((resolve) => {
retryTimerRef.current = window.setTimeout(() => {
@@ -132,6 +136,7 @@ export function useDirectActiveTurns({
// 空态也要保持引用稳定:已经空了就不要再换一个新数组。
setActiveTurns((current) => (current.length === 0 ? current : []));
setSnapshotReadFailed((current) => (current ? false : current));
setSnapshotReadError((current) => (current ? '' : current));
return;
}
let disposed = false;
@@ -172,5 +177,10 @@ export function useDirectActiveTurns({
};
}, [enabled, invoke, refreshActiveTurns]);
return { activeTurns, refreshActiveTurns, snapshotReadFailed };
return {
activeTurns,
refreshActiveTurns,
snapshotReadFailed,
snapshotReadError,
};
}
@@ -16,6 +16,7 @@ export type ActiveProjectRunsPanelProps = {
activeTurns: GameCreatorDirectActiveTurn[];
currentProjectPath?: string | null;
readFailed?: boolean;
readError?: string;
onOpenProject?: (projectPath: string) => void;
placement?: 'panel' | 'titlebar';
};
@@ -59,6 +60,7 @@ export function ActiveProjectRunsPanel({
activeTurns,
currentProjectPath = null,
readFailed = false,
readError = '',
onOpenProject,
placement = 'panel',
}: ActiveProjectRunsPanelProps) {
@@ -97,7 +99,7 @@ export function ActiveProjectRunsPanel({
className="launcher-runs-titlebar launcher-runs-titlebar--error"
role="status"
>
正在运行的项目读取失败
{readError || '正在运行的项目读取失败'}
</span>
);
}
@@ -105,7 +107,7 @@ export function ActiveProjectRunsPanel({
return (
<aside className="launcher-runs-panel" aria-label="正在运行的项目">
<span className="launcher-runs-panel-note" role="status">
未能读取正在运行的项目
{readError || '未能读取正在运行的项目'}
</span>
</aside>
);
@@ -166,10 +168,10 @@ export function ActiveProjectRunsPanel({
const elapsed = formatActiveTurnElapsed(turn.startedAt, now);
const isCurrent = Boolean(
currentProjectPath &&
projectPathsMatchForInvalidation(
turn.projectPath,
currentProjectPath,
),
projectPathsMatchForInvalidation(
turn.projectPath,
currentProjectPath,
),
);
return (
<li key={`${turn.projectPath}:${turn.turnId}`}>
@@ -215,10 +217,10 @@ export function ActiveProjectRunsPanel({
const elapsed = formatActiveTurnElapsed(turn.startedAt, now);
const isCurrent = Boolean(
currentProjectPath &&
projectPathsMatchForInvalidation(
turn.projectPath,
currentProjectPath,
),
projectPathsMatchForInvalidation(
turn.projectPath,
currentProjectPath,
),
);
return (
<li key={`${turn.projectPath}:${turn.turnId}`}>
@@ -174,10 +174,11 @@ export function WorkspaceLauncherShell({
: null,
);
const directInvoke = resolveTauriInvoke();
const { activeTurns, snapshotReadFailed } = useDirectActiveTurns({
invoke: directInvoke,
enabled: true,
});
const { activeTurns, snapshotReadFailed, snapshotReadError } =
useDirectActiveTurns({
invoke: directInvoke,
enabled: true,
});
const switchedToGameRuntime =
gameRuntimeSwitch !== null &&
currentProjectContext !== null &&
@@ -304,6 +305,7 @@ export function WorkspaceLauncherShell({
activeTurns,
currentProjectPath: currentProjectContext?.projectPath ?? null,
readFailed: snapshotReadFailed,
readError: snapshotReadError,
onOpenProject: (projectPath: string) =>
openActiveProjectRef.current(projectPath),
});
@@ -312,6 +314,7 @@ export function WorkspaceLauncherShell({
currentProjectContext?.projectPath,
setActiveProjectRuns,
snapshotReadFailed,
snapshotReadError,
]);
useEffect(() => () => setActiveProjectRuns(null), [setActiveProjectRuns]);
@@ -152,6 +152,9 @@ describe('useDirectActiveTurns', () => {
});
expect(result.current.activeTurns).toBe(firstSnapshot);
expect(result.current.snapshotReadFailed).toBe(true);
expect(result.current.snapshotReadError).toContain(
'snapshot unavailable',
);
} finally {
vi.useRealTimers();
}