Add generationStatus and match3d/runtime fixes

Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
This commit is contained in:
2026-05-16 22:59:02 +08:00
parent bb60ca91ef
commit a45e358e83
42 changed files with 3872 additions and 443 deletions

View File

@@ -24,9 +24,9 @@ import type {
PuzzleResultDraft,
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import { resolvePuzzleUiBackgroundSource } from '../../services/puzzle-runtime/puzzleUiBackgroundSource';
import { updatePuzzleWork } from '../../services/puzzle-works';
import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works/puzzleHistoryAsset';
import { resolvePuzzleUiBackgroundSource } from '../../services/puzzle-runtime/puzzleUiBackgroundSource';
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
import { useAuthUi } from '../auth/AuthUiContext';
import PuzzleHistoryAssetPickerDialog from '../puzzle-agent/PuzzleHistoryAssetPickerDialog';
@@ -1833,13 +1833,21 @@ export function PuzzleResultView({
Record<string, PuzzleLevelGenerationRuntime>
>({});
const [generationNowMs, setGenerationNowMs] = useState(() => Date.now());
const latestEditStateRef = useRef<DraftEditState | null>(
draft ? createDraftEditState(draft) : null,
);
const savedEditStateRef = useRef<DraftEditState | null>(
draft ? createDraftEditState(draft) : null,
);
useEffect(() => {
latestEditStateRef.current = editState;
}, [editState]);
useEffect(() => {
if (!draft) {
setEditState(null);
latestEditStateRef.current = null;
setActiveLevelId(null);
setAutoSaveState('idle');
setAutoSaveError(null);
@@ -1847,17 +1855,16 @@ export function PuzzleResultView({
return;
}
const nextState = createDraftEditState(draft);
setEditState((currentState) => {
const mergedState = mergeDraftEditStateWithIncomingState(
currentState,
nextState,
);
savedEditStateRef.current = nextState;
return mergedState;
});
const mergedState = mergeDraftEditStateWithIncomingState(
latestEditStateRef.current,
nextState,
);
latestEditStateRef.current = mergedState;
savedEditStateRef.current = nextState;
setEditState(mergedState);
setGenerationRuntimeByLevelId((current) => {
const nextRuntimes: Record<string, PuzzleLevelGenerationRuntime> = {};
nextState.levels.forEach((level) => {
mergedState.levels.forEach((level) => {
if (level.generationStatus === 'generating') {
nextRuntimes[level.levelId] =
current[level.levelId] ?? {
@@ -1870,7 +1877,7 @@ export function PuzzleResultView({
});
setActiveLevelId((currentLevelId) =>
currentLevelId &&
nextState.levels.some((level) => level.levelId === currentLevelId)
mergedState.levels.some((level) => level.levelId === currentLevelId)
? currentLevelId
: null,
);