Map design IDs to Gitea #1-#37, add tasks 80-88, stub ADRs 0004-0006, and point STATUS at the P1 batch order.
21 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. -
40 — Web Push (VAPID). Installed
web-push+@types/web-push. Addedpnpm vapid:generatescript (scripts/vapid-generate.mjs) that prints all three env vars to stdout. Addedpush_subscriptionstable to_core/schema.ts+ migration0013_push_notify_reminders.sql. Created_core/push.tswithsendPush(userId, payload)— iterates subscriptions, removes 404/410 stale entries. Addedpushandnotificationclickevent handlers to the generatedpublic/sw.jstemplate. Created<PushOptIn />client component on/settings(opt-in button →subscribeToPushserver action, disable button →unsubscribeFromPush, test button →sendTestNotification). DocumentedNEXT_PUBLIC_VAPID_PUBLIC_KEYin.env.example.pnpm typecheck,pnpm lint,pnpm buildpass. -
42 — Notification bus + ntfy adapter. Added
notificationstable andnotif_push/notif_inapp/notif_ntfycolumns onusers(migration0013_push_notify_reminders.sql). Created_core/notify.tswithnotify(userId, { title, body, url, channels? })— fans out to push (if VAPID configured), in-app DB insert, and ntfy POST (ifNTFY_URL+NTFY_TOPICset). Added<NotificationBell />async server component inAppNav: queries last 20 notifications, shows unread badge, dropdown inbox with mark-read and mark-all-read. Added<NotifyChannelToggles />client component with per-channel checkboxes in/settings.pnpm typecheck,pnpm lint,pnpm buildpass. -
41 — Reminders engine. Added
fired_atandcreated_bycolumns toreminderstable (migration0013_push_notify_reminders.sql); default channel changed to'auto'. Created_core/reminders.tswithscheduleReminder(upsert by entity),cancelReminder,listReminders, andtickReminders(30 s tick,pg_try_advisory_xact_lockguard).startReminderWorker()started viasrc/instrumentation.tson the Node.js runtime. Notes actions updated to usescheduleReminder/cancelReminderinstead of raw SQL. CalendarcreateEventaccepts optionalremindMinutesBeforeand schedules a reminder;deleteEventcallscancelReminder. Calendar-shell event dialog shows "Remind me 30 min before" checkbox (new events only, checked by default). Reminder worker confirmed starting on server boot (logged in dev server output).pnpm typecheck,pnpm lint,pnpm buildpass. -
60 — Postgres backups.
famapp-backupAlpine service added todeploy/compose.yaml; scripts indeploy/backups/:backup.sh(pg_dump -Fcfor famapp-db + authentik-db nightly at 02:00),retain.sh(14 daily / 8 weekly / 6 monthly),restore.sh(restore from any dump file),entrypoint.sh(installs postgresql-client, sets up crontab, starts crond). Backup files stored inbackupsnamed Docker volume. Restore procedure indeploy/backups/README.md.pnpm typecheck,pnpm lint,pnpm buildpass. -
61 — Rate limiting on share links.
src/lib/rate-limit.ts: pure-JS Edge-compatible sliding-window counter (50 req/min, 1-min window, 10k-key LRU eviction) withconsume(),isRateLimited(), andrecordFailure()exports.src/middleware.tscallsconsume(ip:prefix)for every/s/[token]request and returns 429 withRetry-After: 60when the bucket is exceeded.src/app/s/[token]/page.tsxadditionally tracks only failedresolveShareTokenlookups viarecordFailure()in the Node.js runtime (separate module instance from middleware; Redis would unify them for multi-replica deployments).pnpm typecheck,pnpm lint,pnpm buildpass. -
62 — Structured logging. Installed
pino+pino-pretty(dev).src/lib/logger.ts: pino instance — JSON in production (stdout), pretty-printed in dev; level fromLOG_LEVELenv (defaultinfo);pid/hostnamestripped, ISO timestamps.src/middleware.tslogs every request as structured JSON viaconsole.log(Edge-compatible; pino not available in Edge runtime) withmethod,path,status,ms,authenticated. Allconsole.error/console.logcalls in_core/push.ts,_core/notify.ts,_core/reminders.tsreplaced withlogger.error/logger.info; sensitive fields (endpoint URLs, keys) are never logged as named fields.next.config.tsaddsserverExternalPackages: ["pino","pino-pretty"]so webpack does not bundle them.pnpm typecheck,pnpm lint,pnpm buildpass.
Next up
Phase 9 — Post-v0.1 (see docs/superpowers/specs/2026-07-03-backlog-triage-design.md, docs/issues-map.md).
Batch order:
- Bugs: tasks 80–84 (quick-add, dashboard edit, garden count, bangs, back-nav)
- API foundation: task 87 (+ ADR 0006)
- Shared rich-text + notes overhaul: task 85 (+ ADR 0004); closes notes mobile overflow
- Journal: task 86 (+ ADR 0005), including journal API endpoints
- LLM agent chat: task 88
P2/P3 backlog is filed on Gitea only (no task briefs yet) — see docs/issues-map.md designs 7–9, 11–12, 15–19.
How to resume: Read AGENTS.md / CLAUDE.md / STATUS.md, open the next unchecked task in docs/tasks/80–88, stop at acceptance criteria.
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\.