16 KiB
16 KiB
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}/ledger/+page.svelte ← parent only (rewards/chores/todos)
├── [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 viacolspropCard— 1/2/3 col span, micro-layout per pageAccordion— for settings / log sectionsButton— consistent CTAs withvariant(primary/secondary/ghost/danger) andsize(sm/md/lg)
Components (frontend/src/lib/components/)
Sidebar.svelte,TopNav.svelte,Footer.svelteViewHeader.svelte,Card.svelte,CardGrid.svelteButton.svelte,Accordion.svelteicons.ts— SVG icon strings (no icon library dep)
Role-Based Auth (Jul 2026)
- Members have
rolefield ('parent' | 'child', added tomemberscollection) - Parents authenticate via email/password (PB session JWT), children via device token
/api/admin/signupnow creates a member record for the parent withrole: 'parent'/api/admin/loginreturnsmemberName,memberColor,rolealongside session info/api/members/verify-tokenreturnsrolefor the frontendrequireAdminmiddleware unchanged (still checksfam_admins)- Frontend sidebar is role-aware:
isParent = session !== null - No more
/adminprefix — admin pages live under/{fam}/{parent-username}/choresetc.
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}/ledger Parent: rewards / chores / todos 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. Port3000reserved/conflict. - Shared config:
config.tsat root for dev/build-time values (e.g.PROXY_PORT). Runtime config via env vars..envtracks ports,.env.examplecommitted. - Docker: 2 Dockerfiles —
Dockerfile(prod, multi-stage with nginx) andDockerfile.dev(PocketBase for dev). - Nginx: Prod container uses nginx to route
/api/*→ Hono (:3456),/*→ SvelteKit (:2080). - Dev workflow:
pnpm devat root runs SvelteKit + Hono in parallel. PocketBase viaDockerfile.dev. - Proxy runtime: Uses
process.env.PROXY_PORTinstead of importingconfig.ts(avoidsrootDirissues intsc).
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
/debugpage's PB SDKsubscribe()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.tsandjoin/[code]/[member]/+page.server.tscalledsetSessionCookie()withuserId: memberId(the child's PB record ID). This madeevent.locals.sessiontruthy for children, causing[username]/+page.server.tsto enter the admin branch and callhono.admin.*endpoints. The proxy'srequireAdmincheckedfam_adminsfor the child's member ID (which doesn't exist) and returned 401. - Fix: Removed
setSessionCookie()from both join pages. Children only get adevice_tokencookie. The session cookie is only for email/password-authenticated parents, set by/loginand/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 viamigrate.ts5d/5e (field + backfill"auto"). Exposed in settings Payday card (dropdown fromCOMMON_TIMEZONES, ~40 entries, + "Auto (detected)"). Set viaPATCH /api/admin/:famId/famalongside 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/daysLeftused localsetDate+toISOString(). On BST Sunday Aug 9 local midnight → UTC Aug 8, so Monday showed 5 days left instead of 6. Now the child page computesweekStart/todayIso/daysLeftviaweekStart(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) → target2026-08-03T19:00:00Z;autoresolves to server tz. Returns{settled:false, notYet:true, weekStart, target}before the time. - Proxy threads
tzthrough weekly-summary, eow-preview, bonus-configs/progress,evaluateFam, tallies, manual trigger, complete-week,releaseWeek.my-choresreturnstimezone; child+page.server.tspasses it todata.timezone; parent passesdata.fam.timezone. - Frontend child page:
rawFamTz = data.timezone || data.fam?.timezone || 'auto',famTz = resolveTz(rawFamTz).todayChild,todayIso,mondayOf,addDays,isPaydayToday(viaweekdayInTz),paydayTarget(viawallClockToUtc),todayall tz-aware.mondayOfnow delegates totzWeekStart(1, famTz). - Smoke-tested live (fam
v56f0f8o147kj1x): GET fam returns timezone, PATCH sets Europe/London, time-gate returns correct target,autoresolves 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 --noEmit28 errors (all pre-existing rootDir/.ts-import/key: never/implicit-any baseline); frontendsvelte-check9 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, CSSeow-*) left as-is. - daysLeft now counts to settlement day:
daysLefton the child dashboard counts toweekStart + 7(the next payday day) instead ofweekEnd = 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 cardSimulate End-of-Week→Simulate Payday; errorFailed 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.
040826for 4 Aug 2026). Shared helperformatDDMMYY()infrontend/src/lib/format.ts(extracted from the local copy inchores/+page.svelte). Never render rawYYYY-MM-DDto 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-previewreward queries (rewardPointsList/rewardCashList) had NO date filter, summing every claimed reward ever.complete-week(the real settlement snapshot) scopes withdate >= ws. - Fix: added
&& date >= '${ws}'to both reward queries ineow-preview(proxy/src/index.ts) so the preview matches what the rollover actually records. Verified live: zooney now shows this-week220 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-checkstill at 12 baseline errors (no new); proxytscunchanged 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.
?/previewEowaction callseowPreviewthenhono.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 returnssimulateEow: !!settings.simulateEow; child+page.server.tspasses it asdata.simulateEow. Child kanban renders a.preview-noticebanner ("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-onnote + "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
?/previewEowsetssettings.simulateEow=true(GET settings confirms); child SSR page data carriessimulateEow: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()inproxy/src/index.ts— periodsweekly/monthly→{ claimable: 'payday', settleDate: nextPaydayAfter(periodEnd) }; elseimmediate. Manual bonus triggers (/bonus-configs/:id/trigger) stampimmediate(parent-initiated, not a scheduled payout). All 3evaluateFamcreate sites (individual/collaborative/competitive) useclaimableStamp.nextPaydayAfter()helper added totimezone.ts. - Enforcement: member
claimendpoint checksassertPaydayUnlocked()(throws"This bonus pays out on payday (…settleDate) — hang tight!", returned as HTTP 400);request-allskips payday-gated rewards not yet settled. AdminIssue/Issue Allare parent discretion and unaffected. - UI: child wallet renders locked badge for pre-settle payday rewards; admin "Claims → Outstanding" shows a
🔒 {DDMMYY}hint. Both reuseformatDDMMYY(). (Later: switched both toformatShortDate()→ "🔒 pays out 9 Aug"; childowedCashbanner 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 inproxy/src/migrate.ts+proxy/scripts/seed.ts. PB'srequiredselect rejects empty on write; legacy null-claimable rewards are treated asimmediateby both proxy and frontend, so no data backfill was needed. - Verified live:
complete-week→evaluateFamrecreated the weekly Pocket Money reward withclaimable=payday, settleDate=2026-08-09(Sunday payday after Sun→Sat week); member claim pre-payday → 400 with friendly message + status staysunclaimed;request-allreturns{count:0}; claim succeeds after settleDate. - Ops note: the dev proxy's
tsx watchhad 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 relaunchingpnpm dev(nohup →/tmp/proxy_dev.log).pkill -f "tsx watch src/index.ts"hangs the shell — usekill <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 frontendlocalhost:2080(vite HMR). Don't spawnnohup 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 authTOKENacross 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 templatebutton (.add-inline) at the top of the Todos accordion content and the Templates list respectively. - Accordions default open:
accordionStatelookup defaults to{ chores: true, todos: true }(?? truein 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-ctaCSS removed. - Check: frontend
svelte-checkstays 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()tofrontend/src/lib/format.ts— renders5 Aug(adds26when the year isn't the current one). Chores todo card now showsdue 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. ViewHeaderhero variant: addedheroprop tofrontend/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
heroto: 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/.tilepattern + new.tile-members/.tile-chorescolors, from a newadminTilesderived summingsummary.summaries. Also fixed admin subtitleWeek of {YYYY-MM-DD}→Week of {DDMMYY}. - Check: frontend
svelte-checkstays at 12 baseline errors. Note: frontend dev server on :2080 was not running when verified (proxy :3456 up).