diff --git a/src/hooks/useMigrationOrchestrator.test.ts b/src/hooks/useMigrationOrchestrator.test.ts index 05b8a981..c336ad84 100644 --- a/src/hooks/useMigrationOrchestrator.test.ts +++ b/src/hooks/useMigrationOrchestrator.test.ts @@ -129,6 +129,40 @@ describe('useMigrationOrchestrator', () => { expect(rewriteFrontendStoreKeysMock).not.toHaveBeenCalled(); }); + it('keeps startup non-blocking while genre-tags inspect is pending (no gate flash)', async () => { + localStorage.setItem(DONE_FLAG, '1'); + migrationInspectMock.mockResolvedValue({ + needsMigration: false, + hasSkippedUnknownServerRows: false, + canRun: true, + warnings: [], + unmappedEmptyBucket: false, + library: { totalLegacyRows: 0, skippedUnknownServerRows: 0, tables: {} }, + analysis: { totalLegacyRows: 0, skippedUnknownServerRows: 0, tables: {} }, + mappings: [{ legacyId: 'legacy-a', indexKey: 'a.test' }], + }); + let resolveGenre: ((value: any) => void) | undefined; + libraryGenreTagsInspectMock.mockImplementation( + () => new Promise(resolve => { resolveGenre = resolve; }), + ); + + renderHook(() => useMigrationOrchestrator()); + + // Server-index precheck resolved; genre inspect still pending. The gate must + // not be blocking (phase stays 'idle', never 'inspecting'/'running'). + await waitFor(() => { + expect(libraryGenreTagsInspectMock).toHaveBeenCalled(); + }); + expect(useMigrationStore.getState().phase).toBe('idle'); + + if (!resolveGenre) throw new Error('genre inspect resolver not captured'); + resolveGenre({ needed: false, totalTracks: 100, doneTracks: 100 }); + + await waitFor(() => { + expect(useMigrationStore.getState().phase).toBe('completed'); + }); + }); + it('keeps startup non-blocking while done-flag precheck is pending', async () => { localStorage.setItem(DONE_FLAG, '1'); let resolveInspect: ((value: any) => void) | undefined; diff --git a/src/hooks/useMigrationOrchestrator.ts b/src/hooks/useMigrationOrchestrator.ts index d1bf1931..c3b7c8e3 100644 --- a/src/hooks/useMigrationOrchestrator.ts +++ b/src/hooks/useMigrationOrchestrator.ts @@ -33,11 +33,11 @@ function buildMappings(): ServerIndexMapping[] { async function runGenreTagsPhase(): Promise { const state = useMigrationStore.getState(); - state.setStep('genreTags'); state.setGenreTagsProgress(null); - state.setError(null); - state.setPhase('inspecting'); + // Inspect first WITHOUT entering a blocking phase. An already-migrated launch + // must not flash the gate while this inspect IPC round-trips (regression: the + // modal briefly appeared on every startup once the backfill was complete). const inspect = await libraryGenreTagsInspect(); state.setGenreTagsInspect(inspect); if (!inspect.needed) { @@ -45,6 +45,8 @@ async function runGenreTagsPhase(): Promise { return; } + state.setStep('genreTags'); + state.setError(null); state.setPhase('running'); const maxAttempts = 3; for (let attempt = 0; attempt < maxAttempts; attempt++) {