fix: preserve analysis results and flag stale user data
Publish GitHub Pages output / publish (push) Successful in 51s

This commit is contained in:
2026-09-09 18:00:33 +09:00
parent eb294ff5e3
commit 28035110f9
5 changed files with 234 additions and 46 deletions
+63 -20
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onMount, tick } from 'svelte';
import { page } from '$app/stores';
import cardSummary from '$lib/data/cards-summary.json';
import { leaderOutfitCards } from '$lib/engine/leader-outfits.ts';
@@ -168,6 +168,43 @@
let screeningProgress = 0;
let errorMessage = '';
let ready = false;
let loadedUserData: string | null | undefined;
let userDataChanged = false;
function loadUserData(): void {
const raw = localStorage.getItem(USER_DATA_STORAGE_KEY);
const stored = JSON.parse(raw ?? 'null');
collection = stored?.collection && typeof stored.collection === 'object' ? stored.collection : {};
memberBoards = stored?.memberBoards && typeof stored.memberBoards === 'object' ? stored.memberBoards : {};
boardCards = stored?.boardCards && typeof stored.boardCards === 'object' ? stored.boardCards : {};
memoryCount = Number.isFinite(stored?.memoryCount) ? stored.memoryCount : 0;
memberPowerUpPercent = Number.isFinite(stored?.memberPowerUpPercent) ? stored.memberPowerUpPercent : 0;
loadedUserData = raw;
userDataChanged = false;
}
function resetBaseline(): void {
baselineWorker?.terminate();
const nextWorker = createScoreOptimizerWorker();
baselineWorker = nextWorker;
baselineResult = null;
baselineAchievementProbabilities = undefined;
baselineRunning = false;
baselineError = '';
nextWorker.onmessage = (event) => {
if (baselineWorker !== nextWorker) return;
if (event.data.type === 'progress') baselineProgress = event.data.progress;
if (event.data.type === 'result') {
baselineResult = event.data.result;
baselineAchievementProbabilities = event.data.achievementProbabilities;
baselineRunning = false;
}
if (event.data.type === 'error') {
baselineError = event.data.message;
baselineRunning = false;
}
};
}
$: charts = releasedSongCharts(allCharts, releaseNow);
$: currentEvent = eventMode
@@ -259,12 +296,7 @@
releaseNow = currentReleaseClock.now();
}, 1_000);
try {
const stored = JSON.parse(localStorage.getItem(USER_DATA_STORAGE_KEY) ?? 'null');
collection = stored?.collection && typeof stored.collection === 'object' ? stored.collection : {};
memberBoards = stored?.memberBoards && typeof stored.memberBoards === 'object' ? stored.memberBoards : {};
boardCards = stored?.boardCards && typeof stored.boardCards === 'object' ? stored.boardCards : {};
memoryCount = Number.isFinite(stored?.memoryCount) ? stored.memoryCount : 0;
memberPowerUpPercent = Number.isFinite(stored?.memberPowerUpPercent) ? stored.memberPowerUpPercent : 0;
loadUserData();
} catch {
collection = {};
}
@@ -296,19 +328,7 @@
searchPhase = 'idle';
}
};
baselineWorker = createScoreOptimizerWorker();
baselineWorker.onmessage = (event) => {
if (event.data.type === 'progress') baselineProgress = event.data.progress;
if (event.data.type === 'result') {
baselineResult = event.data.result;
baselineAchievementProbabilities = event.data.achievementProbabilities;
baselineRunning = false;
}
if (event.data.type === 'error') {
baselineError = event.data.message;
baselineRunning = false;
}
};
resetBaseline();
try {
baselineState = readAnalysisState(localStorage.getItem(ANALYSIS_STORAGE_KEY));
} catch {
@@ -316,8 +336,16 @@
baselineError = '배치 분석의 저장 데이터를 읽지 못했습니다.';
}
ready = true;
const syncStorage = (event: StorageEvent) => {
if (event.storageArea !== localStorage || (event.key !== USER_DATA_STORAGE_KEY && event.key !== null)) return;
// Keep the calculation's growth snapshot and all existing results intact.
// Read the current value because queued storage events may already be outdated.
userDataChanged = localStorage.getItem(USER_DATA_STORAGE_KEY) !== loadedUserData;
};
window.addEventListener('storage', syncStorage);
return () => {
mounted = false;
window.removeEventListener('storage', syncStorage);
window.clearInterval(releaseClock);
worker?.terminate();
terminateParallelWorkers();
@@ -821,6 +849,7 @@
}
async function analyzeBaseline(): Promise<void> {
const targetWorker = baselineWorker;
const state = baselineState;
const chartMetadata = baselineChartMetadata;
if (!state || !chartMetadata || !baselineWorker || !baselineMatchesConditions) return;
@@ -872,6 +901,7 @@
};
});
const chart = await loadExactSongChart(chartMetadata.path);
if (baselineWorker !== targetWorker) return;
baselineOverallPower = power.overallPower;
baselineProgress = { evaluatedCandidates: 0, totalCandidates: countOptimizationCandidates(optimizerCards) };
baselineWorker.postMessage({
@@ -883,6 +913,7 @@
includeAchievementProbabilities: true
});
} catch (error) {
if (baselineWorker !== targetWorker) return;
baselineError = error instanceof Error ? error.message : '기준 조합을 분석하지 못했습니다.';
baselineRunning = false;
}
@@ -1007,7 +1038,18 @@
}
async function search(): Promise<void> {
if (running) return;
errorMessage = '';
try {
const previousUserData = loadedUserData;
loadUserData();
if (previousUserData !== loadedUserData) resetBaseline();
// Rebuild reactive connection cards and candidate ownership before searching.
await tick();
} catch {
errorMessage = '사용자 데이터를 읽지 못했습니다. 사용자 데이터 페이지에서 저장 상태를 확인해 주세요.';
return;
}
result = null;
if (!worker || !selectedChart) return;
if (ownedCardsOnly && new Set(candidateCards.map((card) => card.characterId)).size < 5) {
@@ -1259,6 +1301,7 @@
<section class="panel" aria-labelledby="result-title">
<div class="section-title"><div><span>02</span><h2 id="result-title">탐색 결과</h2></div>{#if result}<strong>{screenedCombinationCount.toLocaleString('ko-KR')}개 선별 · {exactCombinationCount}개 정밀 분석 · {comparableToBaseline ? '기준 조합 포함 ' : ''}상위 {displayedRankings.length}개</strong>{/if}</div>
{#if userDataChanged}<p class="baseline-guide" role="status">사용자 데이터가 변경되었습니다. 진행 중인 탐색과 아래 결과는 이전 데이터 기준이며, 다시 탐색하면 최신 데이터를 적용합니다.</p>{/if}
{#if displayedRankings.length > 0}
<div class="ranking-list">
{#each displayedRankings as ranking, rank}