jedmund-svelte/docs/autosave-completion-guide.md
Justin Edmund 39e82146d9 docs: update autosave completion guide with new API
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>
2025-10-07 16:16:32 -07:00

117 lines
6.2 KiB
Markdown

# 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)
```typescript
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
1. **No autosaves on load**: `prime()` sets initial baseline
2. **Auto-idle transition**: Status automatically resets to 'idle' after save
3. **Smart navigation guards**: Only block if unsaved changes exist
4. **Draft-on-failure**: localStorage only used when autosave fails
5. **Proper cleanup**: `destroy()` called on unmount
6. **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 `idle` after 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:425` references `restoreDraft` and `dismissDraft`, 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:307` and `src/lib/components/admin/ProjectForm.svelte:157` call `autoSave.schedule()` as soon as the component mounts. Because the payload hash includes `updatedAt`, 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
- `createAutoSaveController` timers/AbortController persist after leaving the page because callers never invoke `destroy()`.
- Post editor imports `clearDraft` but 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 allow `onSaved` to pass the response payload) to set `lastSentHash` immediately 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`) to `flush()` 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 `onDestroy` to remove listeners and call `controller.destroy()`.
- Optionally wires window unload handling if needed.
## Form Integration Checklist
### Posts Editor (`src/routes/admin/posts/[id]/edit/+page.svelte`)
1. Implement `restoreDraft` / `dismissDraft` and handle `clearDraft` after autosave or manual save success.
2. Introduce a `hasLoaded` flag set after `loadPost()` (and controller `prime`) before scheduling autosave.
3. Adopt the shared lifecycle helper for navigation, keyboard shortcuts, and cleanup.
### Project Form (`src/lib/components/admin/ProjectForm.svelte`)
1. Mirror baseline priming and `hasLoaded` gating before scheduling.
2. Clear drafts on success or dismissal, and reuse the lifecycle helper.
3. 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 with `node --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 `idle` after 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.