Files
famdone/MEMORY.md
T

15 KiB
Raw Blame History

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.
  • 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 leftdays until payday; debug card Simulate End-of-WeekSimulate Payday; error Failed to preview EOWFailed 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-weekevaluateFam 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.