diff --git a/MEMORY/WORK/20260317-000000_humor-units-7-improvements/PRD.md b/MEMORY/WORK/20260317-000000_humor-units-7-improvements/PRD.md new file mode 100644 index 0000000..83019aa --- /dev/null +++ b/MEMORY/WORK/20260317-000000_humor-units-7-improvements/PRD.md @@ -0,0 +1,119 @@ +--- +task: Implement 7 humor-units admin improvements +slug: 20260317-000000_humor-units-7-improvements +effort: comprehensive +phase: execute +progress: 0/64 +mode: interactive +started: 2026-03-17T00:00:00Z +updated: 2026-03-17T00:01:00Z +--- + +## Context + +Implementing 7 ordered features for the humor-units SvelteKit admin page. Files: src/lib/types.ts, src/routes/admin/+page.svelte, src/routes/+page.svelte, src/components/ConversionResult.svelte, new src/routes/admin/api/order/+server.ts. Each feature must pass `npm run build` before committing and proceeding. + +### Risks +- TypeScript strict mode may reject certain Svelte 5 patterns (e.g., action typing) +- Drag-and-drop with Svelte 5 runes requires careful state management +- Form dirty-check needs accurate initial-value tracking +- Auto-ID edge cases: user clears field, then types again +- The `description` field must be passed through all API layers (POST/PUT) without breaking existing validation +- CSS override of Pico hover must handle both light and dark mode correctly + +### Plan +1. Feature 1: Add `description?: string` to Unit type; add textarea in admin unit form; add `use:tooltip` action in ConversionResult.svelte +2. Feature 2: Track form initial values; dirty detection; auto-save on "+ Add" when dirty; highlight invalid fields with `field-error` class +3. Feature 3: HTML5 DnD on group rows and unit rows; new PUT /admin/api/order endpoint +4. Feature 4: Add `sortOrder?` to Group type; select in group form; sort logic in orderedResults derived +5. Feature 5: Auto-ID from label (kebab) in create forms; ID field with manual-edit tracking +6. Feature 6: Add `text-decoration: underline` to `.bulk-clear:hover` and ensure color override +7. Feature 7: Add "delete" to bulkAction dropdown; confirmation UI; DELETE each checked unit + +## Criteria + +### Feature 1: Unit descriptions / tooltips +- [ ] ISC-1: `description?: string` optional field added to Unit interface in types.ts +- [ ] ISC-2: Unit form has textarea labeled "Description" below existing fields +- [ ] ISC-3: Description value saved via POST /admin/api/units (new unit) +- [ ] ISC-4: Description value saved via PUT /admin/api/units/:id (edit unit) +- [ ] ISC-5: Description value loaded into form when unit is selected for editing +- [ ] ISC-6: `use:tooltip` Svelte action defined in frontend converter page +- [ ] ISC-7: Tooltip action sets `title` attribute after 500ms mouseenter delay +- [ ] ISC-8: Tooltip action clears timeout and removes `title` on mouseleave +- [ ] ISC-9: Tooltip applied to unit label/symbol span in ConversionResult +- [ ] ISC-10: Tooltip NOT set when description is empty or undefined + +### Feature 2: Auto-save partial forms + error blocking +- [ ] ISC-11: `isDirty()` function detects changes from initial form values +- [ ] ISC-12: Initial form snapshot captured when form is opened (select/new) +- [ ] ISC-13: Clicking "+ Add Unit" while dirty unit form open attempts auto-save +- [ ] ISC-14: Clicking "+ Add Group" while dirty group form open attempts auto-save +- [ ] ISC-15: Successful auto-save opens new form as intended +- [ ] ISC-16: Failed auto-save displays error inline; does NOT open new form +- [ ] ISC-17: `field-error` CSS class defined with invalid border-color var +- [ ] ISC-18: First invalid/empty required field highlighted with `field-error` on save failure +- [ ] ISC-19: Required unit fields validated: label, labelPlural, symbol, toBase>0, id (kebab) +- [ ] ISC-20: Required group fields validated: label, id (kebab) +- [ ] ISC-21: Form not dirty if user clicked "+ Add" but typed nothing yet + +### Feature 3: Drag-to-reorder groups and units +- [ ] ISC-22: Group header rows have `draggable="true"` attribute +- [ ] ISC-23: Group drag cursor is `grab` (CSS) +- [ ] ISC-24: Drag handle `⠿` icon added to unit rows (left side, cursor grab) +- [ ] ISC-25: Groups can be reordered by drag-and-drop within the list +- [ ] ISC-26: Units can be reordered within their group by drag-and-drop +- [ ] ISC-27: Units cannot be dragged between groups via DnD +- [ ] ISC-28: After drop, PUT /admin/api/order called with new order arrays +- [ ] ISC-29: New endpoint `src/routes/admin/api/order/+server.ts` created +- [ ] ISC-30: Order endpoint is auth-gated via authRequest +- [ ] ISC-31: Order endpoint accepts `groups?: string[]` and reorders data.groups +- [ ] ISC-32: Order endpoint accepts `units?: Record` and reorders units within groups +- [ ] ISC-33: Order endpoint saves data and returns `{ ok: true }` + +### Feature 4: Per-group sort order setting +- [ ] ISC-34: `sortOrder?: 'defined' | 'alpha'` added to Group interface in types.ts +- [ ] ISC-35: Group edit form has select for "Display order" (Defined order / Alphabetical) +- [ ] ISC-36: sortOrder value saved via PUT /admin/api/groups/:id +- [ ] ISC-37: sortOrder value loaded into form when group is selected for editing +- [ ] ISC-38: Frontend orderedResults sorts by `label` when group.sortOrder === 'alpha' +- [ ] ISC-39: Frontend uses defined order when sortOrder is undefined or 'defined' + +### Feature 5: Auto-ID generation from label +- [ ] ISC-40: `toKebab(s)` helper function defined +- [ ] ISC-41: `idWasManuallyEdited` boolean state tracked for unit create form +- [ ] ISC-42: `idWasManuallyEdited` boolean state tracked for group create form +- [ ] ISC-43: Unit create form ID field live-updates from label while not manually edited +- [ ] ISC-44: Group create form ID field live-updates from label while not manually edited +- [ ] ISC-45: Typing in ID field sets `idWasManuallyEdited = true`, stops auto-update +- [ ] ISC-46: Clearing ID field sets `idWasManuallyEdited = false`, resumes auto-generation +- [ ] ISC-47: On form blur or save attempt: ID generated from label if ID empty and label non-empty + +### Feature 6: Clear button hover highlight +- [ ] ISC-48: `.bulk-clear:not(:disabled):hover` CSS has `text-decoration: underline` +- [ ] ISC-49: Hover color uses `--pico-contrast` for dark text in light/Dan mode +- [ ] ISC-50: Background stays transparent on hover +- [ ] ISC-51: Dark mode hover effect not broken (text lightens appropriately) +- [ ] ISC-52: All three Pico vars overridden: `--pico-color`, `--pico-background-color`, `--pico-border-color` + +### Feature 7: Bulk delete action +- [ ] ISC-53: "Delete" option added to bulkAction select (value: `'delete'`) +- [ ] ISC-54: Group target row hidden when bulkAction is 'delete' +- [ ] ISC-55: Confirmation section shown: "Delete N units? [Confirm] [Cancel]" +- [ ] ISC-56: N in confirmation reflects current checkedUnitIds.size +- [ ] ISC-57: Confirm calls DELETE /admin/api/units/:id for each checked unit +- [ ] ISC-58: After all deletions, selection cleared and data reloaded +- [ ] ISC-59: Failed deletions shown as formError with count of failures +- [ ] ISC-60: "Cancel" resets bulkAction to '' and clears confirmation state +- [ ] ISC-61: Apply button disabled when bulkAction is 'delete' (confirm/cancel separate UI) + +### Anti-criteria +- [ ] ISC-A1: data/units.json and data/config.json not directly modified by feature code +- [ ] ISC-A2: Auth system, session handling unchanged +- [ ] ISC-A3: No global CSS added outside scoped style blocks +- [ ] ISC-A4: No bare :root var overrides for Pico +- [ ] ISC-A5: npm run build passes after each feature implementation + +## Decisions + +## Verification diff --git a/src/components/UnitInput.svelte b/src/components/UnitInput.svelte index 6632c85..5056c2b 100644 --- a/src/components/UnitInput.svelte +++ b/src/components/UnitInput.svelte @@ -1,7 +1,16 @@ diff --git a/src/lib/types.ts b/src/lib/types.ts index 3633c1f..6118920 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -24,6 +24,9 @@ export interface Unit { /** Group this unit belongs to, or null if orphaned. */ group: string | null; + + /** Optional human-readable description shown as a tooltip in the converter. */ + description?: string; } /** A group of units sharing a base unit and a universal scale factor. */