From 0c5e9c8d138736d415cdcd8002aa272fb377cf16 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 7 Oct 2025 22:41:11 -0700 Subject: [PATCH] docs: add Task 3 project form refactor plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the planned refactoring of ProjectForm.svelte to use: - Store factory for form state management - Reusable draft recovery helper - Reusable form guards helper - Simplified component structure This will reduce ProjectForm from ~719 lines to ~200-300 lines and establish patterns for PostForm, EssayForm, and other admin forms. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/task-3-project-form-refactor-plan.md | 286 ++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 docs/task-3-project-form-refactor-plan.md diff --git a/docs/task-3-project-form-refactor-plan.md b/docs/task-3-project-form-refactor-plan.md new file mode 100644 index 0000000..c01d650 --- /dev/null +++ b/docs/task-3-project-form-refactor-plan.md @@ -0,0 +1,286 @@ +# Task 3: Project Form Modularization & Store Extraction + +## Overview + +Refactor `ProjectForm.svelte` (currently ~719 lines) to use composable stores and reusable helpers, reducing duplication and improving testability. + +## Current State Analysis + +### ✅ Already Modularized +- **Section components exist**: + - `ProjectMetadataForm.svelte` + - `ProjectBrandingForm.svelte` + - `ProjectImagesForm.svelte` + - `ProjectStylingForm.svelte` + - `ProjectGalleryForm.svelte` +- **Autosave integrated**: Uses `createAutoSaveStore` from Task 6 + +### ❌ Needs Extraction +- **No store abstraction**: All form state lives directly in the component (~50 lines of state declarations) +- **Draft recovery scattered**: Manual logic spread across multiple `$effect` blocks (~80 lines) +- **Navigation guards duplicated**: `beforeNavigate`, `beforeunload`, Cmd+S shortcuts (~90 lines total) +- **Form lifecycle boilerplate**: Initial load, populate, validation (~60 lines) + +### Issues with Current Approach +1. **Not reusable**: Same patterns will be copy-pasted to PostForm, EssayForm, etc. +2. **Hard to test**: Logic is tightly coupled to component lifecycle +3. **Unclear boundaries**: Business logic mixed with UI orchestration +4. **Maintenance burden**: Bug fixes need to be applied to multiple forms + +## Proposed Architecture + +### 1. Create Store Factory: `src/lib/stores/project-form.svelte.ts` + +**Purpose**: Centralize form state management and validation logic. + +**API Design**: +```typescript +export function createProjectFormStore(project?: Project) { + // Internal state + const fields = $state({ ...defaultProjectFormData }) + const validationErrors = $state>({}) + const isDirty = $derived(/* compare fields to original */) + + return { + // Read-only derived state + fields: readonly fields, + validationErrors: readonly validationErrors, + isDirty, + + // Actions + setField(key: keyof ProjectFormData, value: any): void + setFields(data: Partial): void + validate(): boolean + reset(): void + populateFromProject(project: Project): void + buildPayload(): ProjectPayload + } +} + +export type ProjectFormStore = ReturnType +``` + +**Benefits**: +- Type-safe field access with autocomplete +- Centralized validation logic +- Easy to unit test +- Can be used standalone (e.g., in tests, other components) + +### 2. Create Draft Recovery Helper: `src/lib/admin/useDraftRecovery.svelte.ts` + +**Purpose**: Extract draft restore prompt logic for reuse across all forms. + +**API Design**: +```typescript +export function useDraftRecovery(options: { + draftKey: string | null + onRestore: (payload: TPayload) => void + enabled?: boolean +}) { + const showPrompt = $state(false) + const draftTimestamp = $state(null) + const timeTicker = $state(0) + const draftTimeText = $derived.by(() => + draftTimestamp ? (timeTicker, timeAgo(draftTimestamp)) : null + ) + + // Auto-detect draft on mount + $effect(() => { /* ... */ }) + + // Update time display every minute + $effect(() => { /* ... */ }) + + return { + showPrompt: readonly showPrompt, + draftTimeText, + restore(): void + dismiss(): void + } +} +``` + +**Usage**: +```svelte + + +{#if draftRecovery.showPrompt} + +{/if} +``` + +**Benefits**: +- Reusable across ProjectForm, PostForm, EssayForm, etc. +- Encapsulates timing and state management +- Easy to test in isolation + +### 3. Create Form Guards Helper: `src/lib/admin/useFormGuards.svelte.ts` + +**Purpose**: Extract navigation protection logic. + +**API Design**: +```typescript +export function useFormGuards(autoSave: AutoSaveStore | null) { + // Navigation guard: flush before route change + beforeNavigate(async (navigation) => { /* ... */ }) + + // Browser close warning + $effect(() => { /* addEventListener('beforeunload') */ }) + + // Cmd/Ctrl+S shortcut + $effect(() => { /* addEventListener('keydown') */ }) + + // No return value - purely side effects +} +``` + +**Usage**: +```svelte + +``` + +**Benefits**: +- Single source of truth for form protection +- Consistent UX across all forms +- Easier to update behavior globally + +### 4. Simplify ProjectForm.svelte + +**Before**: ~719 lines +**After**: ~200-300 lines + +**New structure**: +```svelte + + + + + + + {#if activeTab === 'metadata'} + + {:else if activeTab === 'case-study'} + + {/if} + +``` + +## Implementation Steps + +### Phase 1: Create Store Factory +1. Create `src/lib/stores/project-form.svelte.ts` +2. Extract state, validation, and field mutation logic +3. Add unit tests for store +4. Export TypeScript types + +### Phase 2: Create Reusable Helpers +1. Create `src/lib/admin/useDraftRecovery.svelte.ts` +2. Create `src/lib/admin/useFormGuards.svelte.ts` +3. Document usage patterns + +### Phase 3: Refactor ProjectForm +1. Update `ProjectForm.svelte` to use new store and helpers +2. Remove duplicated logic +3. Test create/edit flows +4. Test autosave, draft recovery, navigation guards + +### Phase 4: Extract Draft Prompt UI +1. Create `DraftPrompt.svelte` component +2. Update ProjectForm to use it +3. Will be reusable by other forms + +## Testing Strategy + +### Unit Tests +- `project-form.svelte.ts`: Field updates, validation, payload building +- `useDraftRecovery.svelte.ts`: Draft detection, restore, dismiss +- Can use Vitest for rune-based stores + +### Integration Tests +- Full form lifecycle: load → edit → save +- Draft recovery flow +- Navigation guard behavior +- Autosave coordination + +### Manual QA +- Create new project +- Edit existing project +- Restore from draft +- Navigate away with unsaved changes +- Browser refresh warning +- Cmd+S immediate save + +## Success Criteria + +- [ ] ProjectForm.svelte reduced to <350 lines +- [ ] Store factory fully typed with generics +- [ ] Draft recovery reusable across forms +- [ ] Navigation guards work consistently +- [ ] All existing functionality preserved +- [ ] Unit tests pass +- [ ] Manual QA checklist completed + +## Future Work (Post-Task 3) + +Once this pattern is proven with ProjectForm: + +1. **Apply to PostForm** (essays, posts) +2. **Apply to MediaForm** (photo editing) +3. **Extract common form shell** (header, tabs, actions) into `FormShell.svelte` +4. **Add form-level error boundaries** for graceful failure handling + +## Dependencies + +- ✅ Task 6 (Autosave Store) - already complete +- ✅ Existing section components - already built +- ⏳ Need to ensure TypeScript strict mode compliance + +## Related Documents + +- [Admin Modernization Plan](./admin-modernization-plan.md) +- [Task 6 Autosave Plan](./task-6-autosave-store-plan.md) +- [Autosave Completion Guide](./autosave-completion-guide.md)