SW is now generated by next.config.ts on every build with a versioned cache name (timestamp in prod, "dev" in dev). Strategies: stale-while- revalidate for static chunks and navigation, network-first with 2s timeout for API GETs, network-only for mutations. Offline mutations postMessage to clients; pwa-register.tsx shows amber offline banner, red mutation-failed toast, and indigo "new version" refresh prompt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
Status
Living progress tracker. Update at the end of each task. Codex and Claude Code both work on this project, so write status notes and next-step instructions for either agent to resume. Canonical briefs are AGENTS.md and CLAUDE.md; keep them synchronized. Task briefs live in docs/tasks/.
Done
-
01 — Repo init & tooling (commit
b89690a). pnpm 10 + TS strict + ESLint flat + Prettier. All acceptance criteria green. -
02 — Next.js app skeleton. Next.js 15 + React 19 + Tailwind v4 + shadcn/ui (button, card, input, dialog).
pnpm devserves placeholder,pnpm buildproduces.next/standalone/,pnpm lintclean. Added.npmrcwithnode-linker=hoistedfor Windows symlink compatibility. -
03 — Drizzle + Postgres setup. drizzle-orm + postgres driver + drizzle-kit wired up.
src/modules/_core/schema.tsdeclaresusers,households,household_members.docker-compose.dev.yamlstarts Postgres 16.drizzle/0000_silent_magma.sqlgenerated and applied.tsc --noEmitpasses. -
04 — Module loader & registry.
src/modules/_core/module.tstypes (ModuleManifest,EntityTypeRegistration,DashboardWidget, etc.),registry.tssingleton withregisterModule/getRegistry/getEntityType/getWidget, barrel_core/index.ts. Stub manifests forcalendar,lists,notes.src/modules/index.tsloader. Root layout imports loader;AppNavreads registry for nav links./debug/registrydumps full registry JSON (dev only). Uses zod v4 + built-inz.toJSONSchema().tsc --noEmit,pnpm build,pnpm lintall clean. -
05 — Compose + Caddy.
Dockerfile(3-stage: deps/builder/runner, pnpm fetch + offline install, non-rootnextjsuser,output: standalone),deploy/compose.yaml(famapp + famapp-db + full Authentik stack onfamapp_net),deploy/Caddyfile.snippet,.env.production.example.docker build -t famapp .succeeds (~311 MB);docker compose -f deploy/compose.yaml configvalidates clean. Added.dockerignoreandpublic/.gitkeep. -
06 — Authentik OIDC.
next-auth@beta+@auth/drizzle-adapterwired up.src/lib/auth.tsconfigures OIDC provider (Authentik), database sessions,authorizedcallback guarding all routes except/login,/s/*,/api/auth/*.src/middleware.tsexportsauthas middleware.src/app/api/auth/[...nextauth]/route.tsmounts the handlers.src/app/login/page.tsxhas a single "Sign in with SSO" server action.getCurrentUser()available for server components/actions. Schema extended:users→ addedname/emailVerified/image; newaccounts,sessions,verificationTokenstables; migration0001_auth_tables.sqlgenerated.deploy/authentik/README.mddocuments the manual Authentik bootstrap.tsc --noEmitandpnpm lintpass clean. -
07 — Household seed.
pnpm db:seedinserts household "Home" idempotently (powered bytsx).signIncallback assignsownerto the first member of the household andmemberto all subsequent users.getCurrentSession()insrc/lib/session.tsreturns{ user, household, role }and throws if unauthenticated or unmembered./settings/householdrenders member list for all roles and a rename form for owner only.tsc --noEmitandpnpm lintboth clean. -
08 — Theming infrastructure. CSS-variable multi-theme system (
default+warm) ×{light, dark, system}.users.theme+users.themeModecolumns in migration0002_naive_starbolt.sql. Theme registry insrc/modules/_core/themes.ts(THEMES/THEME_MODES arrays — adding a third theme is one CSS block + one registry entry). Root layout reads session and setsdata-theme/darkon<html>server-side; inline pre-paint<script>covers system mode and signed-out pages (no flash).useTheme()hook optimistically flips attributes, writeslocalStorage, and callssetUserThemeserver action.<ThemePicker />component mounts on/settings(select for theme, segmented buttons for mode).tsc --noEmit,pnpm lint,pnpm buildall clean. -
10 — Calendar module. Added
calendarsandcalendar_eventsschema + migration0003_rainy_ravenous.sql, default Home/Personal calendar seeding, first-login default calendar creation, visibility-safe calendar/event queries, CRUD server actions, FullCalendar-backed/calendarUI with sidebar calendar management and event create/edit/delete/drag updates. Calendar manifest now registers share/reminder/search capabilities, two configurable widgets, and quick-add entries. Added Playwright happy-path spec intests/e2e/calendar.spec.ts.pnpm db:generate,pnpm typecheck,pnpm lint, andpnpm buildpass. -
11 — Lists module. Added
listsandlist_itemsschema + migration0004_opposite_wraith.sql, default Shopping/Tasks seeding on first access/sign-in/seed, household-gated list and item CRUD server actions, reorder support, and PostgresLISTEN/NOTIFYto SSE bridge documented in ADR0002. Added/listsgrouped index,/lists/[id]keyboard-first item entry with checkbox toggles and swipe/delete, manifest entity/search/widget/quick-add registrations, and Playwright happy-path spec intests/e2e/lists.spec.ts.pnpm typecheck,pnpm lint, andpnpm buildpass. -
12 — Notes module. Added generic core
reminderstable plus household-scopednotesschema in migration0006_new_hannibal_king.sql, notes CRUD server actions, reminder synchronization fornotes.note,/notesindex,/notes/new,/notes/[id]editor with safe markdown preview, manifest entity/search/reminder/share registration,notes.filteredwidget registration, quick-add placeholder, and Playwright happy-path spec intests/e2e/notes.spec.ts.pnpm typecheck,pnpm lint,pnpm build, and notes E2E pass. -
20 — Dashboard composition (single-dashboard MVP). Added
default_dashboard_layoutjsonb column tousers+ migration0007_uneven_living_lightning.sql. Createdsrc/modules/_core/manifest.tsx(core.activityplaceholder widget) and registered it. Updated all three module manifests (calendar, lists, notes) with real async server component widget renders (data-fetching, empty states). Createdsrc/lib/dashboard.ts(layout parsing +computeDefaultLayoutgreedy packer). Builtsrc/app/page.tsx— 12-col CSS Grid, staticsmColSpanlookup for Tailwind class safety, per-widget<Suspense>for parallel loading, graceful skip for unknown widget IDs.pnpm typecheck,pnpm lint,pnpm build, and all 4 E2E specs pass. -
21 — Quick-add registry. Added
url: stringtoQuickAddActiontype (action is now optional). AddedgetQuickAdds()/SerializedQuickAddItemto registry (strips non-serializableactionfn before crossing server→client boundary). Updated all three module manifests with navigation URLs. BuiltQuickAddProvider(context + cmd+k global shortcut),QuickAddFab(opens sheet, replaces plain button in dashboard),QuickAddSheet(bottom drawer / desktop popover grouped by module), andCommandPalette(cmdk-powered modal with arrow + enter + esc keyboard nav). Provider in root layout receives actions fromgetQuickAdds()at render time — adding a module'squickAddsautomatically appears in both surfaces. Also added.claude/**to ESLint ignores to prevent stale worktree build artifacts from failing lint.pnpm typecheck,pnpm lint,pnpm build, and all 4 E2E specs pass. -
22 — Activity log. Added
activity_logtable to_core/schema.tswith index on(household_id, created_at desc). Migration0008_activity_log.sqlapplied.logActivity()server function in_core/activity.tsreads current session and inserts a row. AddedActivityLogEntrytype and optionalrenderActivity?(entry): stringtoEntityTypeRegistrationin_core/module.ts. All three module manifests implementrenderActivityfor each entity type (human-readable, no hardcoded branches in the widget). Replacedcore.activitywidget stub with a real async server component that queries the last 20 rows viagetEntityType(entry.entityType)?.renderActivity(entry). WiredlogActivity()into every create/update/delete in calendar, lists, and notes server actions. Also addedtexttogetAuthorizedItemselect so toggle/delete log the item text.pnpm typecheck,pnpm lint,pnpm build, and all 4 E2E specs pass. -
30 — Share-link service. Added
share_linkstable to_core/schema.ts+ migration0009_share_links.sql. Created_core/share.tswithcreateShareLink,resolveShareToken,revokeShareLink, andgetActiveShareLinks. Token is 32 random bytes (URL-safe base64), stored as SHA-256 hash — raw token only returned at creation.createShareLinkguards that the entity type is registered withcanShare === true.resolveShareTokenreturns null for expired or revoked tokens. All three functions exported from_core/index.ts./settingspage gained a Share links card: lists active links (entity label, read/write capabilities, expiry) with a Revoke button per link (server action insettings/actions.ts).pnpm typecheck,pnpm lint,pnpm build, and all 4 E2E specs pass. -
25 — Multiple dashboards per user. Added
dashboardstable (migration0012_dashboards.sql). Migrated each user'sdefault_dashboard_layoutinto a "Home" dashboard row withis_default = true; dropped the interim column. Server actions:listDashboards,createDashboard,renameDashboard,deleteDashboard,setDefaultDashboard,reorderDashboards,saveDashboardLayout,resetDashboardLayout,resolveWidgetConfigOptions./redirects to the user's default/d/<slug>. Dashboard switcher in AppNav renders tabs (active highlighted client-side) with a+button to create new dashboards and a kebab menu on the active tab for rename / set-default / delete.pnpm typecheck,pnpm lint,pnpm buildpass. -
26 — Customizable layout + widget configuration. Installed
react-grid-layoutv2. Dashboard pages check?edit=1to enter edit mode, rendering a clientDashboardEditorinstead of the static grid. Editor usesreact-grid-layoutwithgridConfig/dragConfigv2 API; each widget shell shows a drag handle, configure button (⚙), and remove button (🗑).WidgetPickeris a two-step modal: step 1 lists all registry widgets grouped by category; step 2 is aWidgetConfiguratorauto-generated from the widget's default config — handles"all"|string[]multi-selects, booleans, numbers, and enums.resolveWidgetConfigOptionsserver action fetches dynamic options (calendars, lists). Save validates each config against its registered Zod schema. Reset to defaults callscomputeDefaultLayout().pnpm typecheck,pnpm buildpass. -
31 — Public share viewer. Made
actorIdnullable inactivity_log(migration0010_nullable_actor_id.sql,onDelete: "set null") for anonymous share-page mutations. AddedlogShareActivityto_core/activity.ts(no session, explicithouseholdId). AddedhouseholdIdtoresolveShareTokenreturn. AddedrenderSharedViewtoEntityTypeRegistrationtype. Each module implementsloadForShare(bare DB queries, no session) andrenderSharedView: calendar shows upcoming 90-day events or single-event details, lists shows items with optional toggle, notes shows title + body.toggleShareListItemserver action lives inlists/server/share-actions.ts— validates token write capability, verifies item→list→household chain, logsshare.togglewithactorId = null./app/s/[token]/page.tsxresolves token, dispatches toloadForShare+renderSharedView, returns friendly error for invalid/expired tokens, setsnoindex. Middleware/s/*exemption confirmed present.pnpm typecheck,pnpm lint,pnpm build, and all 4 E2E specs pass. -
50 — PWA shell.
public/manifest.webmanifest(name, short_name, icons, theme_color, display: standalone, start_url/). Placeholder PNG icons at 180, 192, 384, 512 (regular + maskable) generated byscripts/generate-icons.mjs(pnpm gen:icons);public/icon.svgcommitted as source. Hand-rolled service worker atpublic/sw.js: precachesoffline.htmlon install, cache-first for/_next/static/, network-first for navigation with offline fallback, network-only for API routes.src/components/pwa-register.tsxregisters the SW client-side.src/components/install-prompt.tsxshows a dismissible banner:beforeinstallprompton Android/Chrome, a one-time "Add to Home Screen" hint on iOS (detected via UA +navigator.maxTouchPoints, suppressed in standalone mode). Root layout exportsviewport(themeColor), updatedmetadata(manifest, appleWebApp, apple-touch-icon), and mounts both new components.pnpm typecheck,pnpm lint,pnpm buildall clean. -
51 — Offline shell + service worker caching.
next.config.tsgeneratespublic/sw.jsas a side effect on everynext build/next devinvocation, embedding a build timestamp asCACHE_VERSION(stable"dev"string in development to avoid hot-reload cache churn; epoch milliseconds in production). SW strategies: stale-while-revalidate for/_next/static/chunks and navigation HTML (cached page served instantly, network update fires in background); network-first with 2-second abort timeout for API GETs falling back to cache; network-only for mutations (POST/PATCH/DELETE/PUT) — if offline, all controlled clients receive{ type: "OFFLINE_MUTATION" }viapostMessageand a synthetic 503 is returned. Activate handler evicts allfamapp-*caches whose suffix doesn't match the current version, then claims clients.pwa-register.tsxextended with three inline toasts: amber "offline" banner (persistent, driven bynavigator.onLine+online/offlineevents), red "changes can't be saved" toast (auto-dismisses in 4 s, driven by SW postMessage), and indigo "new version available — refresh" bottom toast (driven bycontrollerchangewithhadControllerguard).pnpm typecheck,pnpm buildpass.
Next up
- Next task in
docs/tasks/.
Development login/testing notes
- Local development can use the documented Dev login flow in
docs/dev-login.md. It creates a database-backed Auth.js session forDEV_LOGIN_EMAILwhenENABLE_DEV_LOGIN=trueandNODE_ENV !== "production". - The production cleanup gate is tracked in
docs/tasks/09-production-dev-login-removal.md. Complete it before first production deployment.
Phase 1 remaining
- 03 Drizzle + Postgres → 04 module loader → 05 compose/Caddy → 06 Authentik OIDC → 07 household seed → 08 theming infrastructure (newly added; multi-theme + per-user dark/light, must land before module work so module UIs adopt the token system from day one).
Recent architectural decisions
- Calendars are first-class entities. Like lists, users can create as many as they want with
privateorhouseholdvisibility. Both calendar entities are independently shareable. Updated CLAUDE.md data model + task 10. - Uniform widget contract — no singletons. Every dashboard widget declares a
configSchemaanddefaultConfig; every placement is an independent instance. Same widget can appear N times on a dashboard pointed at different things. Updated task 04; tasks 11/12 widget sections updated to match. - Per-user customizable dashboards. Each user can have any number of named dashboards, switch between them, drag/resize widgets, and configure each placement. Tasks 25 (multiple dashboards) and 26 (customizable layout + widget configuration) added; phase 3 index updated.
How to resume in a fresh session
- Open the repo root in VS Code.
- Tell Codex or Claude Code: "Read AGENTS.md, CLAUDE.md, and STATUS.md, then complete the next task in docs/tasks/. Stop at the acceptance criteria."
- After it lands, append the result to the Done section here, bump Next up, and commit.
Environment notes
- Repo: https://github.com/ginnoir/famapp (HTTPS remote on
origin). - Local dev tooling installed: Node 22+, pnpm 10.33.3.
.envis not committed; copy.env.example→.envwhen needed.- VS Code recommended extensions in
.vscode/extensions.json; copy.vscode/settings.json.example→.vscode/settings.jsonfor the workspace defaults. - Memory files (cross-session, only seen by Claude):
C:\Users\MattC\.claude\projects\C--Users-MattC-Documents-famapp\memory\.