feat(PBI-76): bridge runs one-shot localStorage migration

After hydrate, scans legacy localStorage keys via buildMigrationPatch
and, if any data is found, pushes one bulk patch to the server,
applies it locally, then removes the legacy keys. Demo accounts skip
the migration entirely. Cancellable on unmount to avoid setState on
unmounted component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-10 12:50:50 +02:00
parent e2ecb788e5
commit bf6bdf366f

View file

@ -1,8 +1,13 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect } from 'react'
import { updateUserSettingsAction } from '@/actions/user-settings'
import { useUserSettingsStore } from '@/stores/user-settings/store' import { useUserSettingsStore } from '@/stores/user-settings/store'
import type { UserSettings } from '@/lib/user-settings' import type { UserSettings } from '@/lib/user-settings'
import {
buildMigrationPatch,
clearLegacyLocalStorage,
} from '@/lib/user-settings-migration'
interface Props { interface Props {
initial: UserSettings initial: UserSettings
@ -23,6 +28,29 @@ export function UserSettingsBridge({ initial, isDemo }: Props) {
hydrate(initial, isDemo) hydrate(initial, isDemo)
}, [hydrate, initial, isDemo]) }, [hydrate, initial, isDemo])
// One-shot migration: read legacy localStorage prefs, push to server, clear.
// Idempotent via marker; demo accounts skip (no server-write).
useEffect(() => {
if (isDemo) return
const result = buildMigrationPatch()
if (!result.hasData) {
clearLegacyLocalStorage([])
return
}
let cancelled = false
void (async () => {
const res = await updateUserSettingsAction(result.patch)
if (cancelled) return
if ('success' in res && res.success) {
applyServerPatch(result.patch)
clearLegacyLocalStorage(result.legacyKeys)
}
})()
return () => {
cancelled = true
}
}, [isDemo, applyServerPatch])
useEffect(() => { useEffect(() => {
if (isDemo) return if (isDemo) return
const es = new EventSource('/api/realtime/user-settings') const es = new EventSource('/api/realtime/user-settings')