diff --git a/src/lib/admin/autoSave.svelte.ts b/src/lib/admin/autoSave.svelte.ts index 6e5bf3c..d4b50e6 100644 --- a/src/lib/admin/autoSave.svelte.ts +++ b/src/lib/admin/autoSave.svelte.ts @@ -8,7 +8,7 @@ export interface AutoSaveStoreOptions { onSaved?: (res: TResponse, ctx: { prime: (payload: TPayload) => void }) => void } -export interface AutoSaveStore { +export interface AutoSaveStore { readonly status: AutoSaveStatus readonly lastError: string | null schedule: () => void @@ -36,7 +36,7 @@ export interface AutoSaveStore { */ export function createAutoSaveStore( opts: AutoSaveStoreOptions -): AutoSaveStore { +): AutoSaveStore { const debounceMs = opts.debounceMs ?? 2000 const idleResetMs = opts.idleResetMs ?? 2000 let timer: ReturnType | null = null diff --git a/src/lib/admin/useDraftRecovery.svelte.ts b/src/lib/admin/useDraftRecovery.svelte.ts index 77d6f75..c8f84df 100644 --- a/src/lib/admin/useDraftRecovery.svelte.ts +++ b/src/lib/admin/useDraftRecovery.svelte.ts @@ -1,7 +1,7 @@ import { loadDraft, clearDraft, timeAgo } from '$lib/admin/draftStore' export function useDraftRecovery(options: { - draftKey: string | null + draftKey: () => string | null onRestore: (payload: TPayload) => void enabled?: boolean }) { @@ -17,9 +17,10 @@ export function useDraftRecovery(options: { // Auto-detect draft on mount using $effect $effect(() => { - if (!options.draftKey || options.enabled === false) return + const key = options.draftKey() + if (!key || options.enabled === false) return - const draft = loadDraft(options.draftKey) + const draft = loadDraft(key) if (draft) { showPrompt = true draftTimestamp = draft.ts @@ -43,19 +44,21 @@ export function useDraftRecovery(options: { draftTimeText, restore() { - if (!options.draftKey) return - const draft = loadDraft(options.draftKey) + const key = options.draftKey() + if (!key) return + const draft = loadDraft(key) if (!draft) return options.onRestore(draft.payload) showPrompt = false - clearDraft(options.draftKey) + clearDraft(key) }, dismiss() { - if (!options.draftKey) return + const key = options.draftKey() + if (!key) return showPrompt = false - clearDraft(options.draftKey) + clearDraft(key) } } } diff --git a/src/lib/admin/useFormGuards.svelte.ts b/src/lib/admin/useFormGuards.svelte.ts index 0d84759..3b3ac9f 100644 --- a/src/lib/admin/useFormGuards.svelte.ts +++ b/src/lib/admin/useFormGuards.svelte.ts @@ -2,7 +2,9 @@ import { beforeNavigate } from '$app/navigation' import { toast } from '$lib/stores/toast' import type { AutoSaveStore } from '$lib/admin/autoSave.svelte' -export function useFormGuards(autoSave: AutoSaveStore | null) { +export function useFormGuards( + autoSave: AutoSaveStore | null +) { if (!autoSave) return // No guards needed for create mode // Navigation guard: flush autosave before route change @@ -21,8 +23,12 @@ export function useFormGuards(autoSave: AutoSaveStore | null) // Warn before closing browser tab/window if unsaved changes $effect(() => { + // Capture autoSave in closure to avoid non-null assertions + const store = autoSave + if (!store) return + function handleBeforeUnload(event: BeforeUnloadEvent) { - if (autoSave!.status !== 'saved') { + if (store.status !== 'saved') { event.preventDefault() event.returnValue = '' } @@ -34,13 +40,17 @@ export function useFormGuards(autoSave: AutoSaveStore | null) // Cmd/Ctrl+S keyboard shortcut for immediate save $effect(() => { + // Capture autoSave in closure to avoid non-null assertions + const store = autoSave + if (!store) return + function handleKeydown(event: KeyboardEvent) { const key = event.key.toLowerCase() const isModifier = event.metaKey || event.ctrlKey if (isModifier && key === 's') { event.preventDefault() - autoSave!.flush().catch((error) => { + store.flush().catch((error) => { console.error('Autosave flush failed:', error) toast.error('Failed to save changes') }) diff --git a/src/lib/components/admin/AlbumForm.svelte b/src/lib/components/admin/AlbumForm.svelte index ba68d09..20218ca 100644 --- a/src/lib/components/admin/AlbumForm.svelte +++ b/src/lib/components/admin/AlbumForm.svelte @@ -104,10 +104,15 @@ } // Autosave store (edit mode only) + // Initialized as null and created reactively when album data becomes available let autoSave = $state, Album>> | null>(null) + // INITIALIZATION ORDER: + // 1. This effect creates autoSave when album prop becomes available + // 2. useFormGuards is called immediately after creation (same effect) + // 3. Other effects check for autoSave existence before using it $effect(() => { - // Create or update autoSave when album becomes available + // Create autoSave when album becomes available (only once) if (mode === 'edit' && album && !autoSave) { const albumId = album.id // Capture album ID to avoid null reference autoSave = createAutoSaveStore({ @@ -180,6 +185,8 @@ }) // Trigger autosave when form data changes + // Using `void` operator to explicitly track dependencies without using their values + // This effect re-runs whenever any of these form fields change $effect(() => { void formData.title void formData.slug