Files
famapp/STATUS.md
T

22 KiB
Raw Blame History

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

  • 80 — Quick-add create UI (commit 68a573c). Quick-add actions open create dialogs/sheets in-place via createKey + QuickAddCreateHost; calendar, lists, notes, garden, bangs wired. E2E in tests/e2e/quick-add.spec.ts.

  • 81 — Dashboard edit live widgets (commit 7eeb2f1). Edit mode server-pre-renders real widget content via DashboardWidgetContent + widgetContents map; no placeholder→content reflow on save. E2E in tests/e2e/dashboard.spec.ts.

  • 82 — Garden container plant count (commit 700ee29). Fixed broken Drizzle subquery in getContainer and refactored listContainers plant count aggregation. E2E in tests/e2e/garden.spec.ts.

  • 83 — Bang edit and delete (commit 753f653). updateBang/deleteBang server actions with activity log; edit/delete UI on bang widget recent rows. E2E in tests/e2e/bangs.spec.ts.

  • 84 — Back navigation (commit d090200). Shared DetailBackLink on garden/notes/lists detail, create, and edit pages plus household settings. E2E in tests/e2e/navigation.spec.ts.

  • 87 — HTTP API v1 (commits ea5d1d0, d4304b0, TBD). Household bearer token + session auth; /api/v1/ routes for calendar, lists, notes, garden, bangs; OpenAPI in docs/api/openapi.yaml; owner token UI in Settings → Data. Unit tests: api-auth, api-v1-calendar, api-v1-auth. ADR 0006.

  • 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 dev serves placeholder, pnpm build produces .next/standalone/, pnpm lint clean. Added .npmrc with node-linker=hoisted for Windows symlink compatibility.

  • 03 — Drizzle + Postgres setup. drizzle-orm + postgres driver + drizzle-kit wired up. src/modules/_core/schema.ts declares users, households, household_members. docker-compose.dev.yaml starts Postgres 16. drizzle/0000_silent_magma.sql generated and applied. tsc --noEmit passes.

  • 04 — Module loader & registry. src/modules/_core/module.ts types (ModuleManifest, EntityTypeRegistration, DashboardWidget, etc.), registry.ts singleton with registerModule/getRegistry/getEntityType/getWidget, barrel _core/index.ts. Stub manifests for calendar, lists, notes. src/modules/index.ts loader. Root layout imports loader; AppNav reads registry for nav links. /debug/registry dumps full registry JSON (dev only). Uses zod v4 + built-in z.toJSONSchema(). tsc --noEmit, pnpm build, pnpm lint all clean.

  • 05 — Compose + Caddy. Dockerfile (3-stage: deps/builder/runner, pnpm fetch + offline install, non-root nextjs user, output: standalone), deploy/compose.yaml (famapp + famapp-db + full Authentik stack on famapp_net), deploy/Caddyfile.snippet, .env.production.example. docker build -t famapp . succeeds (~311 MB); docker compose -f deploy/compose.yaml config validates clean. Added .dockerignore and public/.gitkeep.

  • 06 — Authentik OIDC. next-auth@beta + @auth/drizzle-adapter wired up. src/lib/auth.ts configures OIDC provider (Authentik), database sessions, authorized callback guarding all routes except /login, /s/*, /api/auth/*. src/middleware.ts exports auth as middleware. src/app/api/auth/[...nextauth]/route.ts mounts the handlers. src/app/login/page.tsx has a single "Sign in with SSO" server action. getCurrentUser() available for server components/actions. Schema extended: users → added name/emailVerified/image; new accounts, sessions, verificationTokens tables; migration 0001_auth_tables.sql generated. deploy/authentik/README.md documents the manual Authentik bootstrap. tsc --noEmit and pnpm lint pass clean.

  • 07 — Household seed. pnpm db:seed inserts household "Home" idempotently (powered by tsx). signIn callback assigns owner to the first member of the household and member to all subsequent users. getCurrentSession() in src/lib/session.ts returns { user, household, role } and throws if unauthenticated or unmembered. /settings/household renders member list for all roles and a rename form for owner only. tsc --noEmit and pnpm lint both clean.

  • 08 — Theming infrastructure. CSS-variable multi-theme system (default + warm) × {light, dark, system}. users.theme + users.themeMode columns in migration 0002_naive_starbolt.sql. Theme registry in src/modules/_core/themes.ts (THEMES/THEME_MODES arrays — adding a third theme is one CSS block + one registry entry). Root layout reads session and sets data-theme/dark on <html> server-side; inline pre-paint <script> covers system mode and signed-out pages (no flash). useTheme() hook optimistically flips attributes, writes localStorage, and calls setUserTheme server action. <ThemePicker /> component mounts on /settings (select for theme, segmented buttons for mode). tsc --noEmit, pnpm lint, pnpm build all clean.

  • 10 — Calendar module. Added calendars and calendar_events schema + migration 0003_rainy_ravenous.sql, default Home/Personal calendar seeding, first-login default calendar creation, visibility-safe calendar/event queries, CRUD server actions, FullCalendar-backed /calendar UI 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 in tests/e2e/calendar.spec.ts. pnpm db:generate, pnpm typecheck, pnpm lint, and pnpm build pass.

  • 11 — Lists module. Added lists and list_items schema + migration 0004_opposite_wraith.sql, default Shopping/Tasks seeding on first access/sign-in/seed, household-gated list and item CRUD server actions, reorder support, and Postgres LISTEN/NOTIFY to SSE bridge documented in ADR 0002. Added /lists grouped 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 in tests/e2e/lists.spec.ts. pnpm typecheck, pnpm lint, and pnpm build pass.

  • 12 — Notes module. Added generic core reminders table plus household-scoped notes schema in migration 0006_new_hannibal_king.sql, notes CRUD server actions, reminder synchronization for notes.note, /notes index, /notes/new, /notes/[id] editor with safe markdown preview, manifest entity/search/reminder/share registration, notes.filtered widget registration, quick-add placeholder, and Playwright happy-path spec in tests/e2e/notes.spec.ts. pnpm typecheck, pnpm lint, pnpm build, and notes E2E pass.

  • 20 — Dashboard composition (single-dashboard MVP). Added default_dashboard_layout jsonb column to users + migration 0007_uneven_living_lightning.sql. Created src/modules/_core/manifest.tsx (core.activity placeholder widget) and registered it. Updated all three module manifests (calendar, lists, notes) with real async server component widget renders (data-fetching, empty states). Created src/lib/dashboard.ts (layout parsing + computeDefaultLayout greedy packer). Built src/app/page.tsx — 12-col CSS Grid, static smColSpan lookup 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: string to QuickAddAction type (action is now optional). Added getQuickAdds() / SerializedQuickAddItem to registry (strips non-serializable action fn before crossing server→client boundary). Updated all three module manifests with navigation URLs. Built QuickAddProvider (context + cmd+k global shortcut), QuickAddFab (opens sheet, replaces plain button in dashboard), QuickAddSheet (bottom drawer / desktop popover grouped by module), and CommandPalette (cmdk-powered modal with arrow + enter + esc keyboard nav). Provider in root layout receives actions from getQuickAdds() at render time — adding a module's quickAdds automatically 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_log table to _core/schema.ts with index on (household_id, created_at desc). Migration 0008_activity_log.sql applied. logActivity() server function in _core/activity.ts reads current session and inserts a row. Added ActivityLogEntry type and optional renderActivity?(entry): string to EntityTypeRegistration in _core/module.ts. All three module manifests implement renderActivity for each entity type (human-readable, no hardcoded branches in the widget). Replaced core.activity widget stub with a real async server component that queries the last 20 rows via getEntityType(entry.entityType)?.renderActivity(entry). Wired logActivity() into every create/update/delete in calendar, lists, and notes server actions. Also added text to getAuthorizedItem select 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_links table to _core/schema.ts + migration 0009_share_links.sql. Created _core/share.ts with createShareLink, resolveShareToken, revokeShareLink, and getActiveShareLinks. Token is 32 random bytes (URL-safe base64), stored as SHA-256 hash — raw token only returned at creation. createShareLink guards that the entity type is registered with canShare === true. resolveShareToken returns null for expired or revoked tokens. All three functions exported from _core/index.ts. /settings page gained a Share links card: lists active links (entity label, read/write capabilities, expiry) with a Revoke button per link (server action in settings/actions.ts). pnpm typecheck, pnpm lint, pnpm build, and all 4 E2E specs pass.

  • 25 — Multiple dashboards per user. Added dashboards table (migration 0012_dashboards.sql). Migrated each user's default_dashboard_layout into a "Home" dashboard row with is_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 build pass.

  • 26 — Customizable layout + widget configuration. Installed react-grid-layout v2. Dashboard pages check ?edit=1 to enter edit mode, rendering a client DashboardEditor instead of the static grid. Editor uses react-grid-layout with gridConfig/dragConfig v2 API; each widget shell shows a drag handle, configure button (⚙), and remove button (🗑). WidgetPicker is a two-step modal: step 1 lists all registry widgets grouped by category; step 2 is a WidgetConfigurator auto-generated from the widget's default config — handles "all"|string[] multi-selects, booleans, numbers, and enums. resolveWidgetConfigOptions server action fetches dynamic options (calendars, lists). Save validates each config against its registered Zod schema. Reset to defaults calls computeDefaultLayout(). pnpm typecheck, pnpm build pass.

  • 31 — Public share viewer. Made actorId nullable in activity_log (migration 0010_nullable_actor_id.sql, onDelete: "set null") for anonymous share-page mutations. Added logShareActivity to _core/activity.ts (no session, explicit householdId). Added householdId to resolveShareToken return. Added renderSharedView to EntityTypeRegistration type. Each module implements loadForShare (bare DB queries, no session) and renderSharedView: calendar shows upcoming 90-day events or single-event details, lists shows items with optional toggle, notes shows title + body. toggleShareListItem server action lives in lists/server/share-actions.ts — validates token write capability, verifies item→list→household chain, logs share.toggle with actorId = null. /app/s/[token]/page.tsx resolves token, dispatches to loadForShare + renderSharedView, returns friendly error for invalid/expired tokens, sets noindex. 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 by scripts/generate-icons.mjs (pnpm gen:icons); public/icon.svg committed as source. Hand-rolled service worker at public/sw.js: precaches offline.html on install, cache-first for /_next/static/, network-first for navigation with offline fallback, network-only for API routes. src/components/pwa-register.tsx registers the SW client-side. src/components/install-prompt.tsx shows a dismissible banner: beforeinstallprompt on Android/Chrome, a one-time "Add to Home Screen" hint on iOS (detected via UA + navigator.maxTouchPoints, suppressed in standalone mode). Root layout exports viewport (themeColor), updated metadata (manifest, appleWebApp, apple-touch-icon), and mounts both new components. pnpm typecheck, pnpm lint, pnpm build all clean.

  • 51 — Offline shell + service worker caching. next.config.ts generates public/sw.js as a side effect on every next build / next dev invocation, embedding a build timestamp as CACHE_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" } via postMessage and a synthetic 503 is returned. Activate handler evicts all famapp-* caches whose suffix doesn't match the current version, then claims clients. pwa-register.tsx extended with three inline toasts: amber "offline" banner (persistent, driven by navigator.onLine + online/offline events), 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 by controllerchange with hadController guard). pnpm typecheck, pnpm build pass.

  • 40 — Web Push (VAPID). Installed web-push + @types/web-push. Added pnpm vapid:generate script (scripts/vapid-generate.mjs) that prints all three env vars to stdout. Added push_subscriptions table to _core/schema.ts + migration 0013_push_notify_reminders.sql. Created _core/push.ts with sendPush(userId, payload) — iterates subscriptions, removes 404/410 stale entries. Added push and notificationclick event handlers to the generated public/sw.js template. Created <PushOptIn /> client component on /settings (opt-in button → subscribeToPush server action, disable button → unsubscribeFromPush, test button → sendTestNotification). Documented NEXT_PUBLIC_VAPID_PUBLIC_KEY in .env.example. pnpm typecheck, pnpm lint, pnpm build pass.

  • 42 — Notification bus + ntfy adapter. Added notifications table and notif_push/notif_inapp/notif_ntfy columns on users (migration 0013_push_notify_reminders.sql). Created _core/notify.ts with notify(userId, { title, body, url, channels? }) — fans out to push (if VAPID configured), in-app DB insert, and ntfy POST (if NTFY_URL+NTFY_TOPIC set). Added <NotificationBell /> async server component in AppNav: 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 build pass.

  • 41 — Reminders engine. Added fired_at and created_by columns to reminders table (migration 0013_push_notify_reminders.sql); default channel changed to 'auto'. Created _core/reminders.ts with scheduleReminder (upsert by entity), cancelReminder, listReminders, and tickReminders (30 s tick, pg_try_advisory_xact_lock guard). startReminderWorker() started via src/instrumentation.ts on the Node.js runtime. Notes actions updated to use scheduleReminder/cancelReminder instead of raw SQL. Calendar createEvent accepts optional remindMinutesBefore and schedules a reminder; deleteEvent calls cancelReminder. 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 build pass.

  • 60 — Postgres backups. famapp-backup Alpine service added to deploy/compose.yaml; scripts in deploy/backups/: backup.sh (pg_dump -Fc for 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 in backups named Docker volume. Restore procedure in deploy/backups/README.md. pnpm typecheck, pnpm lint, pnpm build pass.

  • 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) with consume(), isRateLimited(), and recordFailure() exports. src/middleware.ts calls consume(ip:prefix) for every /s/[token] request and returns 429 with Retry-After: 60 when the bucket is exceeded. src/app/s/[token]/page.tsx additionally tracks only failed resolveShareToken lookups via recordFailure() in the Node.js runtime (separate module instance from middleware; Redis would unify them for multi-replica deployments). pnpm typecheck, pnpm lint, pnpm build pass.

  • 62 — Structured logging. Installed pino + pino-pretty (dev). src/lib/logger.ts: pino instance — JSON in production (stdout), pretty-printed in dev; level from LOG_LEVEL env (default info); pid/hostname stripped, ISO timestamps. src/middleware.ts logs every request as structured JSON via console.log (Edge-compatible; pino not available in Edge runtime) with method, path, status, ms, authenticated. All console.error/console.log calls in _core/push.ts, _core/notify.ts, _core/reminders.ts replaced with logger.error/logger.info; sensitive fields (endpoint URLs, keys) are never logged as named fields. next.config.ts adds serverExternalPackages: ["pino","pino-pretty"] so webpack does not bundle them. pnpm typecheck, pnpm lint, pnpm build pass.

Next up

Phase 9 — Post-v0.1 (see docs/superpowers/specs/2026-07-03-backlog-triage-design.md, docs/issues-map.md).

Batch order:

  1. Bugs: tasks 8084 — done
  2. API foundation: task 87 (+ ADR 0006)
  3. Shared rich-text + notes overhaul: task 85 (+ ADR 0004); closes notes mobile overflow
  4. Journal: task 86 (+ ADR 0005), including journal API endpoints
  5. LLM agent chat: task 88

P2/P3 backlog is filed on Gitea only (no task briefs yet) — see docs/issues-map.md designs 79, 1112, 1519.

How to resume: Read AGENTS.md / CLAUDE.md / STATUS.md, open the next unchecked task in docs/tasks/8088, 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 for DEV_LOGIN_EMAIL when ENABLE_DEV_LOGIN=true and NODE_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 private or household visibility. Both calendar entities are independently shareable. Updated CLAUDE.md data model + task 10.
  • Uniform widget contract — no singletons. Every dashboard widget declares a configSchema and defaultConfig; 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

  1. Open the repo root in VS Code.
  2. 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."
  3. 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.
  • .env is not committed; copy .env.example.env when needed.
  • VS Code recommended extensions in .vscode/extensions.json; copy .vscode/settings.json.example.vscode/settings.json for the workspace defaults.
  • Memory files (cross-session, only seen by Claude): C:\Users\MattC\.claude\projects\C--Users-MattC-Documents-famapp\memory\.