Added implementation summary showing: - All 5 forms now use runes-based autosave - New reactive API without subscriptions - Key improvements (prime, auto-idle, smart guards) - Marked as completed January 2025 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Admin Autosave Completion Guide
Status: ✅ COMPLETED (January 2025)
All objectives have been achieved. This document is preserved for historical reference and implementation details.
Implementation Summary
All admin forms now use the modernized runes-based autosave system (createAutoSaveStore):
- ✅ ProjectForm - Migrated to runes with full lifecycle management
- ✅ Posts Editor - Migrated with draft recovery banner
- ✅ EssayForm - Added autosave from scratch
- ✅ PhotoPostForm - Added autosave from scratch
- ✅ SimplePostForm - Added autosave from scratch
New API (Svelte 5 Runes)
import { createAutoSaveStore } from '$lib/admin/autoSave.svelte'
const autoSave = createAutoSaveStore({
debounceMs: 2000,
idleResetMs: 2000,
getPayload: () => buildPayload(),
save: async (payload, { signal }) => {
const response = await fetch('/api/endpoint', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
credentials: 'same-origin',
signal
})
if (!response.ok) throw new Error('Failed to save')
return await response.json()
},
onSaved: (saved, { prime }) => {
updatedAt = saved.updatedAt
prime(buildPayload())
clearDraft(draftKey)
}
})
// Reactive state - no subscriptions needed!
autoSave.status // 'idle' | 'saving' | 'saved' | 'error' | 'offline'
autoSave.lastError // string | null
Key Improvements
- No autosaves on load:
prime()sets initial baseline - Auto-idle transition: Status automatically resets to 'idle' after save
- Smart navigation guards: Only block if unsaved changes exist
- Draft-on-failure: localStorage only used when autosave fails
- Proper cleanup:
destroy()called on unmount - Reactive API: Direct property access instead of subscriptions
Original Objectives
- Eliminate redundant save requests triggered on initial page load.
- Restore reliable local draft recovery, including clear-up of stale backups.
- Deliver autosave status feedback that visibly transitions back to
idleafter successful saves. - Ensure navigation/unload flows wait for pending autosaves instead of cancelling them mid-flight.
Key Problem Areas
Missing Draft Handlers
src/routes/admin/posts/[id]/edit/+page.svelte:425referencesrestoreDraftanddismissDraft, but the functions are never defined. Draft recovery buttons therefore break compilation and runtime behavior.
Immediate Autosaves on Load
- Effects in
src/routes/admin/posts/[id]/edit/+page.svelte:307andsrc/lib/components/admin/ProjectForm.svelte:157callautoSave.schedule()as soon as the component mounts. Because the payload hash includesupdatedAt, each mount triggers redundant PUTs until the server response realigns the hash.
Ineffective Navigation Guard
beforeNavigate(() => autoSave.flush())(posts + project form) does not cancel the outbound navigation, so the flush typically aborts when the route unloads. Result: unsaved work if the user navigates away during a pending autosave.
Controller Lifecycle Gaps
createAutoSaveControllertimers/AbortController persist after leaving the page because callers never invokedestroy().- Post editor imports
clearDraftbut never clears the draft after successful saves or when dismissing the prompt, so stale backups reappear.
Controller Enhancements (src/lib/admin/autoSave.ts)
- Baseline priming: Add a
prime(initialPayload)(or allowonSavedto pass the response payload) to setlastSentHashimmediately after fetching server data. This prevents an automatic save when the user has not made changes. - Auto-idle transition: When status becomes
'saved', set a timeout (e.g., 2s) that reverts status to'idle'. Cancel the timeout on any new state change. - Robust destroy: Ensure
destroy()clears pending timers and aborts the current request; expose and require callers to invoke it on component teardown. - Consider optional helper flags (e.g.,
autoResetStatus) so forms do not reimplement timing logic.
Shared Lifecycle Helper
Create a utility (e.g., initAutoSaveLifecycle) that accepts the controller plus configuration:
- Registers keyboard shortcut (
Cmd/Ctrl+S) toflush()once the page has loaded. - Provides a real navigation guard that cancels the navigation event, awaits
flush(), then resumes or surfaces an error. - Hooks into
onDestroyto remove listeners and callcontroller.destroy(). - Optionally wires window unload handling if needed.
Form Integration Checklist
Posts Editor (src/routes/admin/posts/[id]/edit/+page.svelte)
- Implement
restoreDraft/dismissDraftand handleclearDraftafter autosave or manual save success. - Introduce a
hasLoadedflag set afterloadPost()(and controllerprime) before scheduling autosave. - Adopt the shared lifecycle helper for navigation, keyboard shortcuts, and cleanup.
Project Form (src/lib/components/admin/ProjectForm.svelte)
- Mirror baseline priming and
hasLoadedgating before scheduling. - Clear drafts on success or dismissal, and reuse the lifecycle helper.
- Ensure autosave only starts after the initial project data populates
formData.
Other Forms (Simple Post, Essay, Photo, etc.)
- Audit each admin form to ensure they use the shared lifecycle helper, seed baselines, clear drafts, and transition status back to
idle.
Testing & Verification
- Unit Tests: Cover controller state transitions, baseline priming, abort handling, and auto-idle timeout (
tests/autoSaveController.test.ts). Run withnode --test --loader tsx tests/autoSaveController.test.ts. - Component Tests: Verify autosave does not fire on initial mount, drafts restore/clear correctly, and navigation waits for flush.
- Manual QA: Confirm keyboard shortcut behavior, offline fallback, and that UI returns to
idleafter showing “saved”.
Structural Considerations
- Factor shared autosave wiring into reusable modules to avoid copy/paste drift.
- Ensure server response payloads used in
prime()reflect the canonical representation (including normalized fields) so hashes stay in sync. - Document the lifecycle helper so new admin screens adopt the proven pattern without regression.