Files
famdone/MEMORY.md
T
2026-08-04 15:32:59 +01:00

162 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FamChore v2 — Development Memory
## UI Component Architecture (Jul 2026)
### Layout Hierarchy
```
+layout.svelte ← global styles, meta, favicon
├── /login, /signup, /join/* ← auth pages (no shell)
└── [fam]/+layout.svelte ← Shell: Sidebar + TopNav + Footer + claim toast
├── [fam]/+page.svelte ← fam dashboard
├── [fam]/{username}/+page.svelte ← parent=admin overview, child=kanban
├── [fam]/{username}/chores/+page.svelte ← parent only
├── [fam]/{username}/rewards/+page.svelte ← parent only
├── [fam]/{username}/bonuses/+page.svelte ← parent only
├── [fam]/{username}/settings/+page.svelte ← parent only
└── [fam]/{username}/preferences/+page.svelte ← both roles
```
### Sidebar (collapsible to mini-mode)
- Header: app name (FamChore)
- Admin CTAs: Dashboard, Chores, Rewards (badge count), Bonuses
- Member CTAs: Dashboard, Preferences
- Footer: family name, Settings (admin only), Log out
- Role-aware: items differ based on admin vs member route
### TopNav
- Slot `announcement` (center) — system/family messages
- Slot `actions` (right) — user status, claim/message
### Page Content
- `ViewHeader` — title + subtitle + tool bar (tabs, weeknav, sort)
- `CardGrid` — 3-column grid, Cards span columns via `cols` prop
- `Card` — 1/2/3 col span, micro-layout per page
- `Accordion` — for settings / log sections
- `Button` — consistent CTAs with `variant` (primary/secondary/ghost/danger) and `size` (sm/md/lg)
### Components (frontend/src/lib/components/)
- `Sidebar.svelte`, `TopNav.svelte`, `Footer.svelte`
- `ViewHeader.svelte`, `Card.svelte`, `CardGrid.svelte`
- `Button.svelte`, `Accordion.svelte`
- `icons.ts` — SVG icon strings (no icon library dep)
### Role-Based Auth (Jul 2026)
- Members have `role` field (`'parent' | 'child'`, added to `members` collection)
- Parents authenticate via email/password (PB session JWT), children via device token
- `/api/admin/signup` now creates a member record for the parent with `role: 'parent'`
- `/api/admin/login` returns `memberName`, `memberColor`, `role` alongside session info
- `/api/members/verify-token` returns `role` for the frontend
- `requireAdmin` middleware unchanged (still checks `fam_admins`)
- Frontend sidebar is role-aware: `isParent = session !== null`
- **No more `/admin` prefix** — admin pages live under `/{fam}/{parent-username}/chores` etc.
### Routes (Jul 2026)
```
/ Landing (SaaS marketing)
/signup Signup (creates parent user + member)
/login Login (returns member info, redirects to /{fam}/{memberName})
/join/:code Member invite (child)
/join/:code/:member Member invite with pre-selected name
/admin Super admin dashboard (unchanged)
/{fam} Fam dashboard
/{fam}/{username} Parent → admin overview, Child → kanban
/{fam}/{username}/chores Parent: chore management
/{fam}/{username}/rewards Parent: reward ledger
/{fam}/{username}/bonuses Parent: bonus configs
/{fam}/{username}/settings Parent: family settings
/{fam}/{username}/preferences Both: edit name/color
/api/* Hono proxy
```
### Architecture Decisions
### 2026-06-23 — Monorepo & Docker Setup
- **Ports**: Frontend = `2080`, Proxy = `3456`, Container ext = `3001`. Port `3000` reserved/conflict.
- **Shared config**: `config.ts` at root for dev/build-time values (e.g. `PROXY_PORT`). Runtime config via env vars. `.env` tracks ports, `.env.example` committed.
- **Docker**: 2 Dockerfiles — `Dockerfile` (prod, multi-stage with nginx) and `Dockerfile.dev` (PocketBase for dev).
- **Nginx**: Prod container uses nginx to route `/api/*` → Hono (`:3456`), `/*` → SvelteKit (`:2080`).
- **Dev workflow**: `pnpm dev` at root runs SvelteKit + Hono in parallel. PocketBase via `Dockerfile.dev`.
- **Proxy runtime**: Uses `process.env.PROXY_PORT` instead of importing `config.ts` (avoids `rootDir` issues in `tsc`).
### 2026-06-23 — Hono Proxy for All Data; Svelte Reactivity Only
- **All data operations** (reads and writes) go through the Hono proxy, never directly to PB SDK.
- **UI reactivity** is purely Svelte `$state` / `$derived` / `$effect` — no PB SDK `.subscribe()` / SSE.
- The `/debug` page's PB SDK `subscribe()` was experimental only; final apps fetch via Hono proxy and update Svelte state reactively.
### 2026-07-28 — Auth Bug: Join Flow Set Session Cookie for Children
- **Bug**: Both `join/[code]/+page.server.ts` and `join/[code]/[member]/+page.server.ts` called `setSessionCookie()` with `userId: memberId` (the child's PB record ID). This made `event.locals.session` truthy for children, causing `[username]/+page.server.ts` to enter the admin branch and call `hono.admin.*` endpoints. The proxy's `requireAdmin` checked `fam_admins` for the child's member ID (which doesn't exist) and returned 401.
- **Fix**: Removed `setSessionCookie()` from both join pages. Children only get a `device_token` cookie. The session cookie is only for email/password-authenticated parents, set by `/login` and `/signup`.
- **Lesson**: Children must never get a session cookie. The auth table in AGENTS.md says "Member → device token, no expiry" — the code must match.
### 2026-08-03 — Family Timezone Setting + Tz-Aware Week Math
- **Feature**: `fams.timezone` (IANA name or `"auto"`) added via `migrate.ts` 5d/5e (field + backfill `"auto"`). Exposed in settings Payday card (dropdown from `COMMON_TIMEZONES`, ~40 entries, + "Auto (detected)"). Set via `PATCH /api/admin/:famId/fam` alongside payday/paydayTime.
- **Shared module** `timezone.ts` (root, imported by both proxy and frontend): `resolveTz`, `dateStrInTz`, `weekdayInTz`, `todayInTz`, `addDaysStr` (pure UTC), `weekStart(payday, tz)`, `wallClockToUtc` (iterative 4-pass Intl, DST-safe), `COMMON_TIMEZONES`.
- **Bug fixed** ("5 days left on Monday"): old `mondayOf`/`addDays`/`daysLeft` used local `setDate` + `toISOString()`. On BST Sunday Aug 9 local midnight → UTC Aug 8, so Monday showed 5 days left instead of 6. Now the child page computes `weekStart`/`todayIso`/`daysLeft` via `weekStart(1, famTz)` + `addDaysStr` + `todayInTz`, giving 6. Verified: Mon Aug 3 → weekStart 2026-08-03, weekEnd 2026-08-09, daysLeft 6.
- **releaseWeek time gate**: now tz-aware. `weekStart(payday, tz)` for idempotency check; `target = new Date(wallClockToUtc(weekStartToday, paydayTime, tz))`. Verified: payday Mon 20:00 Europe/London (BST) → target `2026-08-03T19:00:00Z`; `auto` resolves to server tz. Returns `{settled:false, notYet:true, weekStart, target}` before the time.
- **Proxy threads `tz`** through weekly-summary, eow-preview, bonus-configs/progress, `evaluateFam`, tallies, manual trigger, complete-week, `releaseWeek`. `my-chores` returns `timezone`; child `+page.server.ts` passes it to `data.timezone`; parent passes `data.fam.timezone`.
- **Frontend child page**: `rawFamTz = data.timezone || data.fam?.timezone || 'auto'`, `famTz = resolveTz(rawFamTz)`. `todayChild`, `todayIso`, `mondayOf`, `addDays`, `isPaydayToday` (via `weekdayInTz`), `paydayTarget` (via `wallClockToUtc`), `today` all tz-aware. `mondayOf` now delegates to `tzWeekStart(1, famTz)`.
- **Smoke-tested live** (fam `v56f0f8o147kj1x`): GET fam returns timezone, PATCH sets Europe/London, time-gate returns correct target, `auto` resolves to server tz, settings page SSR shows the dropdown, child page 200. Fam restored to payday=0, paydayTime=18:00, timezone=auto, lastIssued=2026-08-02.
- **Typecheck**: proxy `tsc --noEmit` 28 errors (all pre-existing rootDir/`.ts`-import/`key: never`/implicit-any baseline); frontend `svelte-check` 9 errors (baseline; no new errors in edited files).
### 2026-08-04 — Terminology: "Payday" + Countdown to Settlement Day
- **Decision**: Standardize the user-facing term on **"payday"** for the weekly settlement event/day. "EOW" is ambiguous (window-close vs settlement day) and is now dropped from user-facing strings. Keep "week" for the Sun→Sat earning window. Internal identifiers (`eow*`, `simulateEow`, `eowPreview`, CSS `eow-*`) left as-is.
- **daysLeft now counts to settlement day**: `daysLeft` on the child dashboard counts to `weekStart + 7` (the next payday day) instead of `weekEnd = weekStart + 6`. So Tue Aug 4 with payday=Sun shows 5 days until payday. Hero label updated to "days until payday".
- **User-facing renames**: hero `days left``days until payday`; debug card `Simulate End-of-Week``Simulate Payday`; error `Failed to preview EOW``Failed to preview payday`; settings hint reworded to lead with "Payday:".
### 2026-08-04 — DDMMYY Date Rule + Debug Payday Preview Fix
- **Rule added (AGENTS.md)**: all user-facing dates are DDMMYY (compact, e.g. `040826` for 4 Aug 2026). Shared helper `formatDDMMYY()` in `frontend/src/lib/format.ts` (extracted from the local copy in `chores/+page.svelte`). Never render raw `YYYY-MM-DD` to users.
- **Bug**: the admin "Preview payday" card showed all-time totals (e.g. "280 pts £288.00 14 chores" for zooney) because the proxy's `eow-preview` reward queries (`rewardPointsList`/`rewardCashList`) had NO date filter, summing every claimed reward ever. `complete-week` (the real settlement snapshot) scopes with `date >= ws`.
- **Fix**: added `&& date >= '${ws}'` to both reward queries in `eow-preview` (`proxy/src/index.ts`) so the preview matches what the rollover actually records. Verified live: zooney now shows this-week `220 pts / £16.00 / 14 chores / bonus 10`.
- **UI**: removed the no-op **Simulation ON/OFF** toggle (settings.simulateEow flag drives no behavior) — it was the source of "simulation on/off vs preview rollover" confusion. Card is now just "Debug: Preview payday" → "Preview payday" button. Debug card dates now render via `formatDDMMYY`.
- **Typecheck**: frontend `svelte-check` still at 12 baseline errors (no new); proxy `tsc` unchanged pre-existing baseline.
### 2026-08-04 — Preview Payday Extends to Child Dashboard
- **Feature**: "Preview payday" now enables a family-wide preview mode that the child dashboard reacts to. `?/previewEow` action calls `eowPreview` then `hono.admin.updateSettings({ simulateEow: true })`, returning `{ preview, simulateEow: true }`. A "Turn off preview" button (`?/setEow`, `on=false`) clears it.
- **Child notice**: `my-chores` (`proxy/src/index.ts`) now returns `simulateEow: !!settings.simulateEow`; child `+page.server.ts` passes it as `data.simulateEow`. Child kanban renders a `.preview-notice` banner ("Payday preview — your parent is checking this week's payday. Nothing is paid out yet.") when the flag is set. Admin card shows a `.eow-mode-on` note + "Turn off preview" when active.
- **Note**: `simulateEow` (settings.simulateEow) was previously a no-op debug flag; it now meaningfully drives preview mode across admin + child views.
- **Verified live**: POST `?/previewEow` sets `settings.simulateEow=true` (GET settings confirms); child SSR page data carries `simulateEow:true`; control case (flag off) renders no notice. Test data restored afterwards (zooney deviceToken + flag reset to false).
### 2026-08-04 — Payday-Gated Bonus Payouts
- **Feature**: weekly/monthly period bonus rewards are now **claimable only on payday** (not the moment they're met). Rewards gain `claimable: 'immediate' | 'payday'` + `settleDate` (YYYY-MM-DD, server-side). The bonus-met notice stays exciting on the child dash — the reward line shows a locked "🔒 pays out {DDMMYY}" badge and skips the request button pre-payday.
- **Stamp logic**: `claimableStamp()` in `proxy/src/index.ts` — periods `weekly`/`monthly``{ claimable: 'payday', settleDate: nextPaydayAfter(periodEnd) }`; else `immediate`. Manual bonus triggers (`/bonus-configs/:id/trigger`) stamp `immediate` (parent-initiated, not a scheduled payout). All 3 `evaluateFam` create sites (individual/collaborative/competitive) use `claimableStamp`. `nextPaydayAfter()` helper added to `timezone.ts`.
- **Enforcement**: member `claim` endpoint checks `assertPaydayUnlocked()` (throws `"This bonus pays out on payday (…settleDate) — hang tight!"`, returned as HTTP 400); `request-all` skips payday-gated rewards not yet settled. Admin `Issue`/`Issue All` are parent discretion and unaffected.
- **UI**: child wallet renders locked badge for pre-settle payday rewards; admin "Claims → Outstanding" shows a `🔒 {DDMMYY}` hint. Both reuse `formatDDMMYY()`. (Later: switched both to `formatShortDate()` → "🔒 pays out 9 Aug"; child `owedCash` banner excludes payday-locked rewards so "You've earned £X — go get it!" no longer shows for rewards that aren't claimable yet.)
- **Schema**: `rewards.claimable` (select, required) + `rewards.settleDate` (text) added in `proxy/src/migrate.ts` + `proxy/scripts/seed.ts`. PB's `required` select rejects empty on write; legacy null-claimable rewards are treated as `immediate` by both proxy and frontend, so no data backfill was needed.
- **Verified live**: `complete-week``evaluateFam` recreated the weekly Pocket Money reward with `claimable=payday, settleDate=2026-08-09` (Sunday payday after Sun→Sat week); member claim pre-payday → 400 with friendly message + status stays `unclaimed`; `request-all` returns `{count:0}`; claim succeeds after settleDate.
- **Ops note**: the dev proxy's `tsx watch` had silently frozen (file edits at 09:49 weren't picked up by a child started 09:47). Fixed by killing the watcher tree with explicit PIDs and relaunching `pnpm dev` (nohup → `/tmp/proxy_dev.log`). `pkill -f "tsx watch src/index.ts"` hangs the shell — use `kill <pid>` instead.
### 2026-08-04 — Dev Servers: Always Reuse Existing 2080/3456
- **Rule**: NEVER start our own dev servers. Always use the already-running ones: proxy `192.168.1.225:3456` (tsx watch, reloads on edit) and frontend `localhost:2080` (vite HMR). Don't spawn `nohup pnpm dev`, `tsx watch`, or extra vite instances — it wastes time/tokens. Only kill/restart when the user explicitly asks (or a watcher is demonstrably stale, and then only after asking). Prefer short targeted curls and reuse one auth `TOKEN` across commands in the persistent shell.
### 2026-08-04 — Chores Page Accordion Quick Fixes
- **Add actions moved into sections**: removed the blue round `+` from the member swimlane header and the Templates column header. Both replaced by a shared full-width dashed ` Add a todo` / ` New template` button (`.add-inline`) at the top of the Todos accordion content and the Templates list respectively.
- **Accordions default open**: `accordionState` lookup defaults to `{ chores: true, todos: true }` (`?? true` in the template + toggle), so both sections load expanded on page load; still toggleable. Redundant empty-state "+ Add a todo" button and `.add-todo-btn`/`.empty-cta` CSS removed.
- **Check**: frontend `svelte-check` stays at 12 baseline errors.
### 2026-08-04 — Human Dates for Todo "Due" (Not DDMMYY)
- **Problem**: the chores todo card rendered `due 050826` (DDMMYY code) — ambiguous/terrible for a due date.
- **Fix**: added `formatShortDate()` to `frontend/src/lib/format.ts` — renders `5 Aug` (adds ` 26` when the year isn't the current one). Chores todo card now shows `due 5 Aug`. AGENTS.md date rule updated: DDMMYY for dense/range contexts, `formatShortDate()` for single human-readable dates like due dates.
### 2026-08-04 — Child-Dashboard Design Lead Applied to All Pages
- **Design principal** (from `[fam]/[username]` child dash): gradient hero lead (`linear-gradient(135deg, #6366f1, #8b5cf6 55%, #a855f7)`, radius 16px, glow shadow, white text), gradient stat tiles, rounded white cards.
- **`ViewHeader` hero variant**: added `hero` prop to `frontend/src/lib/components/ViewHeader.svelte` — renders the title/subtitle/tools on the gradient hero (tabs/nav/sort get tinted-on-white styling). Off by default, so no behavior change elsewhere.
- **Applied `hero` to**: admin dashboard (fam name), chores, bonuses, rewards, settings, preferences, platform admin `/admin`.
- **Admin dashboard stat tiles**: added 4 gradient tiles (members / points / cash / chores done) reusing the child `.tiles`/`.tile` pattern + new `.tile-members`/`.tile-chores` colors, from a new `adminTiles` derived summing `summary.summaries`. Also fixed admin subtitle `Week of {YYYY-MM-DD}``Week of {DDMMYY}`.
- **Check**: frontend `svelte-check` stays at 12 baseline errors. Note: frontend dev server on :2080 was not running when verified (proxy :3456 up).