Commit Graph
54 Commits
Author SHA1 Message Date
ginnoir 29795e0d51 chore: format pre-existing files missed by lint-staged 2026-06-01 18:46:53 -05:00
ginnoir 8921cb0444 chore: release v0.3.0
Release / build-and-push (push) Has been cancelled
v0.3.0
2026-06-01 18:43:13 -05:00
ginnoir e3d2b5a364 chore: sync version to v0.2.0 and load .env for release scripts 2026-06-01 18:41:32 -05:00
ginnoir ed310d042f docs: add github_token to .env.example 2026-06-01 18:34:57 -05:00
ginnoir 30af9f63bf chore: add release workflow (commitlint, husky, lint-staged, release-it) 2026-06-01 18:33:37 -05:00
ginnoir e9bb2a5444 feat: expand theme system to 10 full-skin palettes with light and dark modes 2026-06-01 18:25:58 -05:00
ginnoir a95f10fcde fix: scope rail nav-label hiding to sidebar and fall back fab-only to sidebar on desktop 2026-06-01 18:00:49 -05:00
ginnoir b255aaeac1 fix: refresh server components after nav style change to fix broken layout 2026-06-01 17:53:21 -05:00
ginnoir 19308be768 feat: add dev startup script, fix push notifications, and scope test sends to device
- pnpm dev:local/dev:reset: orchestrate DB container, migrations, seed, and Next.js dev server in one command; Caddy snippet + docs for HTTPS via dev.ginnoir.com
- Fix dev login on HTTPS: set both authjs.session-token and __Secure-authjs.session-token so Auth.js finds the session regardless of cookie name resolution
- Suppress hydration mismatch on <html> caused by pre-paint script changing data-nav before React hydrates
- VAPID startup warning if keys not configured; remove dead NEXT_PUBLIC_VAPID_PUBLIC_KEY var
- PushOptIn: hydrate subscription state on mount; reuse existing subscription on iOS to avoid redundant prompts
- sendPushToEndpoint: new function to send to a single device endpoint
- sendTestNotification: scoped to the calling device's endpoint (ownership-verified) instead of all user subscriptions
2026-06-01 17:45:20 -05:00
ginnoirandClaude Opus 4.7 9612a54e52 Apply paper-and-ink design system across all surfaces
Release / build-and-push (push) Has been cancelled
Replaces the generic shadcn/ui gray theme + horizontal top-bar shell with
the paper-and-ink language from the Claude Design handoff bundle: warm
off-white paper, near-black ink, Source Serif 4 + Inter, hairline borders,
muted ink accents (clay/indigo/sage/plum/ochre) used functionally for
calendars and share scopes.

Theme switcher expanded from 2 dimensions (theme × mode) to 7: palette ×
mode × fontPair × density × dashLayout × calView × navStyle. All exposed
in Settings → Appearance and persisted on the users row. Pre-paint script
applies all four data-* attributes from localStorage so reload doesn't
flash.

App shell restructured to a CSS-grid driven by data-nav on <html>: sidebar
on desktop, bottom-nav + FAB under 760px. Four desktop nav modes wired
(sidebar/rail/top/fab-only). Topbar gets a search-→-CommandPalette button,
notification bell, "+ New" quick-add, avatar.

Dashboard, calendar, lists, notes, settings, login, public share viewer,
and quick-add sheet all reskinned. Dashboard editor gains a Preset menu
(classic/split/glance) that fills the layout from the registered widgets.
FullCalendar wrapped in .fc-skin and inherits all paper-and-ink tokens via
CSS variable overrides. Public share viewer (/s/<token>) rebuilt around
ShareFrame: expiration banner, brand strip, eyebrow chip, 38px serif
title, mini-day + mini-map cards, share-rows.

Schema: drops users.theme; adds theme_palette, theme_font_pair,
theme_density, theme_dash_layout, theme_cal_view, theme_nav_style with
defaults that match the design (clay / serif-sans / regular / classic /
month / rail-desktop). Migration 0014_paper_ink_theme.

Middleware sets x-pathname so the AppShell server component can render
bare for /s/* and /login without a route-group refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.2.0
2026-05-07 01:03:21 -05:00
ginnoirandClaude Sonnet 4.6 e130cda6c5 Remove signIn callback — enrollment now happens in getCurrentSession
Release / build-and-push (push) Has been cancelled
Auth.js v5 calls signIn before the new user's row is fully committed, so
inserting into household_members immediately caused a FK violation for any
new user (e.g. cam on first sign-in). Enrollment is now handled lazily in
getCurrentSession's auto-enroll fallback, which runs after the session is
established and the user row is guaranteed to exist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.1.0
2026-05-06 22:23:34 -05:00
ginnoirandClaude Sonnet 4.6 2129f9718f Fix revalidatePath during render in getDefaultDashboardSlug
On a new user's first load, getDefaultDashboardSlug fell through to
createDashboard("Home"), which calls revalidatePath("/"). That is illegal
in a render context (page component → server action → revalidatePath).

Inline the DB insert directly in the fallback and skip revalidatePath —
the redirect that immediately follows is a fresh request, so no cache
invalidation is needed here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:58:32 -05:00
ginnoirandClaude Sonnet 4.6 484f747f66 Auto-enroll user into household if membership missing on session load
If a user has a valid session but no household_members row (e.g. they
first signed in before the household was seeded), getCurrentSession now
auto-enrolls them instead of throwing. Assigns owner if first member,
member otherwise, and creates their default calendars and lists.

Removes the permanently-stuck state that occurred on first deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:12:26 -05:00
ginnoirandClaude Sonnet 4.6 c66bf68373 Seed household on container startup via seed.mjs
Without a household row the signIn callback has nothing to attach the
first user to, causing "No household membership" on first load. Add
scripts/seed.mjs (plain ESM, only needs postgres which is already in the
image) and run it from the entrypoint after migrations. Idempotent — skips
if a household already exists. Default calendars/lists are created by the
signIn callback when the first user authenticates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:49:20 -05:00
ginnoirandClaude Sonnet 4.6 1e076bd2f3 Fix migrations: commit drizzle/meta/ so journal is present in Docker build
drizzle/meta/ was gitignored, so GitHub Actions had no _journal.json to
copy into the image. The migrator requires this file to know which SQL
files to apply. Unblock by committing the meta directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:50:07 -05:00
ginnoirandClaude Sonnet 4.6 64b8180d9a Fix migrate.mjs: explicitly copy full drizzle-orm and postgres into image
The standalone tracer only includes subpaths the Next.js app imports.
drizzle-orm/postgres-js/migrator is only used by migrate.mjs (not app
code) so it is excluded from the trace. Copy the full packages from the
builder stage to ensure all subpaths are available at startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:22:18 -05:00
ginnoirandClaude Sonnet 4.6 39fcd54f0c Fix migrate.mjs: externalize drizzle-orm and postgres from Next.js bundle
Next.js bundles all non-externalized server packages into server chunks.
drizzle-orm and postgres were bundled, so they were absent from the
standalone node_modules/ and unavailable to migrate.mjs at startup.
Adding them to serverExternalPackages keeps them as separate modules and
tells the standalone tracer to include them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:15:00 -05:00
ginnoirandClaude Sonnet 4.6 524efac05f Fix Docker build: include drizzle/ migrations in build context
drizzle/ was excluded in .dockerignore but the Dockerfile copies it into
the runner stage (needed by migrate.mjs at container startup). Remove the
exclusion so migration SQL files are present in the builder stage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:50:48 -05:00
ginnoirandClaude Sonnet 4.6 bb3a770cf3 Fix Docker build: move entrypoint out of excluded deploy/ dir
.dockerignore excludes deploy/ entirely. The entrypoint script lived
there, so the COPY in Stage 3 failed with "not found". Move it to the
project root (alongside Dockerfile) where the build context includes it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:46:36 -05:00
ginnoirandClaude Sonnet 4.6 01945fbd0a Gitignore generated public/sw.js
next.config.ts regenerates public/sw.js on every build with a new
timestamp; tracking it produces noise on every build. Add to .gitignore
and untrack — it is always regenerated by `pnpm dev` / `pnpm build`
and by the Docker build stage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:34:40 -05:00
ginnoirandClaude Sonnet 4.6 1185f5ab50 Production-ready wiring: VAPID prop fix, port mappings, AUTH_URL
- PushOptIn now accepts vapidKey as a prop from its server-component
  parent (settings page reads VAPID_PUBLIC_KEY at runtime) — eliminates
  the NEXT_PUBLIC_* build-time dependency so pre-built GHCR images work
  without a build arg.
- deploy/compose.yaml: famapp exposes 3010:3000, authentik-server exposes
  9200:9000 so the existing Caddy stack can proxy by IP, matching every
  other service in the homelab.  NEXT_PUBLIC_APP_URL replaced by AUTH_URL
  (correct next-auth v5 var).
- deploy/Caddyfile.snippet: updated to 192.168.1.69:3010 / :9200.
- .env.production.example: AUTH_URL, ntfy pre-wired to ntfy.ginnoir.com,
  VAPID_SUBJECT prefilled with real email.
- typecheck and pnpm build both pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:32:08 -05:00
ginnoir c73338e256 Code-side
src/lib/dev-login-config.ts — startup assertion: throws if NODE_ENV=production + ENABLE_DEV_LOGIN=true, scoped to runtime (skipped during next build).
Container

scripts/migrate.mjs — runs Drizzle migrations against DATABASE_URL.
deploy/docker-entrypoint.sh — runs migrations then exec node server.js. Skip with RUN_MIGRATIONS=false.
Dockerfile — copies drizzle/, scripts/migrate.mjs, entrypoint into runner stage; ENTRYPOINT now points at the script.
Compose

deploy/compose.yaml — famapp now image: ${FAMAPP_IMAGE:-ghcr.io/ginnoir/famapp:latest} (build still works locally as fallback). Authentik pinned via AUTHENTIK_IMAGE_TAG (default 2024.12.3). New RUN_MIGRATIONS env passed through.
.env.production.example — documents FAMAPP_IMAGE, AUTHENTIK_IMAGE_TAG, RUN_MIGRATIONS.
CI/CD

.github/workflows/ci.yml — push/PR: typecheck + lint + format:check + build.
.github/workflows/release.yml — v* tag: build + push ghcr.io/ginnoir/famapp:vX.Y.Z, :X.Y, :latest to GHCR.
Docs

deploy/README.md — full deploy/rollback/release runbook.
CHANGELOG.md — release log seeded with an Unreleased entry.
docs/tasks/09-pre-deploy-checklist.md — task 09 reframed from one-shot removal to a recurring pre-deploy checklist.
STATUS.md — updated.
Verified: pnpm typecheck, pnpm format, pnpm build, and docker compose config all clean.
2026-05-06 17:37:37 -05:00
ginnoirandClaude Sonnet 4.6 285a460eb8 Implement tasks 60, 61, 62: backups, rate limiting, structured logging
Task 60 — Postgres backups:
- deploy/backups/: backup.sh (pg_dump -Fc nightly), retain.sh (14/8/6 tiers),
  restore.sh, entrypoint.sh, crontab
- famapp-backup Alpine service + backups volume added to deploy/compose.yaml
- Restore procedure in deploy/backups/README.md

Task 61 — Rate limiting on share links:
- src/lib/rate-limit.ts: Edge-compatible sliding-window counter (50/min, LRU eviction)
  with consume(), isRateLimited(), recordFailure() exports
- middleware.ts: enforces 429 with Retry-After: 60 for /s/[token] (IP + token prefix)
- /s/[token]/page.tsx: tracks only failed resolveShareToken calls via recordFailure()

Task 62 — Structured logging:
- pino + pino-pretty installed; serverExternalPackages added to next.config.ts
- src/lib/logger.ts: JSON in production, pretty in dev, level from LOG_LEVEL env
- middleware.ts: structured JSON request log (method, path, status, ms, authenticated)
- _core/push.ts, notify.ts, reminders.ts: console.error/log → logger.error/info

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:23:29 -05:00
ginnoirandClaude Sonnet 4.6 b6abe052c9 Implement tasks 40, 41, 42: web push, notification bus, reminders engine
Task 40 — Web Push (VAPID):
- Add web-push package + @types/web-push
- pnpm vapid:generate script prints VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, NEXT_PUBLIC_VAPID_PUBLIC_KEY
- push_subscriptions schema + migration 0013
- _core/push.ts: sendPush() iterates subscriptions, prunes 404/410 stale entries
- SW push/notificationclick event handlers added to generated sw.js template
- PushOptIn client component on /settings (opt-in, disable, send test)

Task 42 — Notification bus + ntfy adapter:
- notifications table + notif_push/notif_inapp/notif_ntfy user columns (migration 0013)
- _core/notify.ts: notify() fans out to push, in-app DB, and optional ntfy POST
- NotificationBell server component in AppNav: unread badge, dropdown inbox, mark-read
- NotifyChannelToggles client component in /settings

Task 41 — Reminders engine:
- fired_at + created_by added to reminders; default channel changed to 'auto'
- _core/reminders.ts: scheduleReminder (upsert), cancelReminder, listReminders, tickReminders
- tickReminders uses pg_try_advisory_xact_lock for horizontal-scale safety
- src/instrumentation.ts starts reminder worker (30s tick) on Node.js boot
- Notes actions use scheduleReminder/cancelReminder instead of raw SQL
- Calendar createEvent: optional remindMinutesBefore, deleteEvent: cancelReminder
- Calendar-shell: "Remind me 30 min before" checkbox on new event form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:55:02 -05:00
ginnoirandClaude Sonnet 4.6 ec20f1bc87 Fix offline banner stuck after DevTools network toggle
The online event could fire between page load and useEffect registering
its listener, leaving isOffline stuck at true despite navigator.onLine
being true. A zero-delay setTimeout after listener registration re-syncs
from navigator.onLine, covering that race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:20:49 -05:00
ginnoirandClaude Sonnet 4.6 afff22e994 Implement offline shell + service worker caching (task 51)
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>
2026-05-06 16:14:36 -05:00
ginnoirandClaude Sonnet 4.6 8aab25e0ac Implement PWA shell (task 50)
Add web manifest, placeholder icons, app-shell service worker, and
install prompt so famapp is installable on iOS and Android home screens.

- public/manifest.webmanifest — name, short_name, standalone display,
  start_url /, all four icon sizes (192/384/512 + maskable)
- public/icon-{180,192,384,512,512-maskable}.png — indigo placeholder
  squares generated by scripts/generate-icons.mjs (pnpm gen:icons)
- public/icon.svg — house-icon SVG source for future branded export
- public/sw.js — hand-rolled app-shell SW: precaches offline.html,
  cache-first for /_next/static/, network-first navigation with offline
  fallback, network-only for /api/*
- public/offline.html — branded offline fallback page
- src/components/pwa-register.tsx — client SW registration
- src/components/install-prompt.tsx — dismissible install banner:
  beforeinstallprompt on Android, "Add to Home Screen" hint on iOS
- Root layout: viewport themeColor, manifest/appleWebApp metadata,
  apple-touch-icon, mounts PwaRegister + InstallPrompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:00:02 -05:00
ginnoirandClaude Sonnet 4.6 d5a8bf9d95 Implement tasks 25, 26 + completion visibility setting
Task 25: Multiple dashboards — dashboards table + migration, /d/[slug]
route, root redirect, dashboard tabs in nav with create/rename/delete/
set-default. Task 26: Editable dashboard — react-grid-layout drag/resize
editor, widget picker modal with per-widget configurator auto-generated
from default config, save/reset server actions with Zod validation.

Completion visibility: server-side per-user setting (hours) replaces
localStorage timer; queries filter completed items by updatedAt cutoff;
Settings page dropdown persists preference via server action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:52:59 -05:00
ginnoirandClaude Sonnet 4.6 2ad9521ef9 Delay item disappearance after check-off; add setting in Settings
Items on the lists page and dashboard widget now show as crossed-out for
a configurable duration (default 3s) before vanishing. Unchecking within
the window cancels the removal. The delay is stored in localStorage and
configurable via Settings → Lists (options: 1s / 3s / 5s / 10s / 30s).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:02:04 -05:00
ginnoirandClaude Sonnet 4.6 8fda3e47dd Inline items on lists page and interactive dashboard widget
- Lists page: show up to 10 open items per list in collapsible cards;
  items can be checked off directly without opening the list
- Dashboard widget: replace static span-checkboxes with a real client
  component using useOptimistic so items can be toggled from the dashboard
- listWidgetItems now returns listId for navigation links
- New listListsWithItems query fetches counts + top 10 open items per list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:57:01 -05:00
ginnoirandClaude Sonnet 4.6 734268cb75 Wire ShareButton into list/note/calendar pages; fix nav and calendar select labels
- Add ShareButton to list detail, note editor, and calendar event panel
- Add Settings gear icon to AppNav (core page not in module registry)
- Fix calendar event dialog showing raw UUID: pass explicit children to
  SelectValue so labels display before SelectContent mounts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:41:10 -05:00
ginnoirandClaude Sonnet 4.6 39aa5704f9 Implement share viewer (task 31)
- Makes actorId nullable in activity_log (migration 0010) with onDelete:
  set null so audit history survives user deletion; adds logShareActivity
  for anonymous mutations
- Adds resolveShareToken return of householdId for downstream validation
- Adds renderSharedView to EntityTypeRegistration; each module implements
  loadForShare (no-session bare queries) and renderSharedView
- Calendar: CalendarSharedView (upcoming 90-day events) and
  EventSharedView (title/time/location/notes)
- Lists: ListSharedView (client component with optimistic toggles via
  toggleShareListItem server action in lists/server/share-actions.ts)
- Notes: NoteSharedView (title + body + updated date)
- /app/s/[token]/page.tsx: resolves token, dispatches to loadForShare +
  renderSharedView, friendly error for invalid/expired tokens, noindex
- Middleware /s/* exemption confirmed present (no change needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:26:38 -05:00
ginnoirandClaude Sonnet 4.6 d5deee9a46 Implement share-link service (task 30)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:09:10 -05:00
ginnoirandClaude Sonnet 4.6 28475e483d Implement activity log (task 22)
- activity_log table in _core/schema.ts with household+created_at index;
  migration 0008_activity_log.sql applied
- logActivity() in _core/activity.ts reads current session and inserts a row
- ActivityLogEntry type + renderActivity?(entry): string added to
  EntityTypeRegistration so modules declare human-readable labels without
  any if/else branches in the widget
- core.activity widget replaced with a real async server component that
  queries the last 20 rows and renders via the registry
- logActivity wired into every create/update/delete in calendar, lists,
  and notes server actions; getAuthorizedItem also returns text so
  toggle/delete can include item text in the payload
- pnpm typecheck, lint, build, and all 4 E2E specs pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:57:03 -05:00
ginnoirandClaude Sonnet 4.6 1133fa8aac Implement quick-add registry (task 21)
- Add url field to QuickAddAction; make action optional
- Add getQuickAdds() + SerializedQuickAddItem to registry so quick-add
  data can cross the server→client RSC boundary without serializing fns
- Update calendar/lists/notes manifests with navigation URLs
- QuickAddProvider wraps root layout: holds sheet/palette open state and
  global cmd/ctrl+k shortcut
- QuickAddSheet: bottom drawer on mobile, right-side popover on desktop,
  actions grouped by module
- CommandPalette: cmdk-powered modal with fuzzy filter and full keyboard
  nav (arrows + enter + esc)
- QuickAddFab: client button replacing the plain + stub in the dashboard
- Add .claude/** to ESLint ignores to prevent stale worktree artifacts
  from failing lint

All 4 E2E specs pass; typecheck, lint, and build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:44:33 -05:00
ginnoirandGitHub b9d3cce73a Merge pull request #2 from ginnoir/claude/great-einstein-b073e2
Implement dashboard composition (task 20)
2026-05-06 13:26:32 -05:00
ginnoirandClaude Sonnet 4.6 b4dde92757 Implement dashboard composition (task 20)
- Add default_dashboard_layout jsonb to users + migration 0007
- Create _core/manifest.tsx with core.activity placeholder widget
- Register core manifest alongside calendar/lists/notes
- Update all three module manifests with real async server-component
  widget renders (upcoming events, month view, list items, notes)
- Add src/lib/dashboard.ts: computeDefaultLayout greedy packer +
  parseDashboardLayout Zod validator
- Build src/app/page.tsx: 12-col CSS Grid, static smColSpan lookup,
  per-widget Suspense for parallel loading, generic widget.render()
  dispatch — no widgetId branches
- Add tests/e2e/dashboard.spec.ts; all 4 E2E specs pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:24:56 -05:00
ginnoirandGitHub d7069d14a2 Merge pull request #1 from ginnoir/codex/notes-module
Add notes module
2026-05-06 04:12:14 -05:00
ginnoir 5b434702ba Add notes module 2026-05-06 04:10:50 -05:00
ginnoir 016d01cc25 Track Codex instructions and ignore local Claude settings 2026-05-06 03:51:00 -05:00
ginnoir 7e3ae6eb04 Implement lists module and dev login setup 2026-05-06 03:49:03 -05:00
ginnoir 744c1119a9 Implement calendar module 2026-05-06 03:10:28 -05:00
ginnoirandClaude Sonnet 4.6 8cc2ef0732 Add theming infrastructure (task 08)
CSS-variable multi-theme system (default + warm) × {light, dark, system}; per-user theme/theme_mode columns; no-flash pre-paint script; useTheme hook + ThemePicker component on /settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:50:05 -05:00
ginnoirandClaude Sonnet 4.6 6710c8b231 Add household seeding & session (task 07)
- scripts/seed.ts: idempotent pnpm db:seed inserts "Home" household if none exists (powered by tsx)
- signIn callback: first member of household gets owner role, subsequent users get member
- src/lib/session.ts: getCurrentSession() returns { user, household, role }, throws if unauthenticated or unmembered
- /settings/household: member list for all roles; rename form gated to owner only (server action also enforces)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:42:48 -05:00
ginnoirandClaude Sonnet 4.6 5da472d6ff Add Authentik OIDC integration (task 06)
- next-auth@beta + @auth/drizzle-adapter wired up with database sessions
- src/lib/auth.ts: OIDC provider, authorized callback, signIn household-attach, getCurrentUser()
- src/middleware.ts: protects all routes except /login, /s/*, /api/auth/*
- src/app/api/auth/[...nextauth]/route.ts: mounts Auth.js handlers
- src/app/login/page.tsx: single SSO sign-in button (server action)
- Schema: users extended (name/emailVerified/image), accounts/sessions/verificationTokens added
- drizzle/0001_auth_tables.sql: migration for schema changes
- deploy/authentik/README.md: manual bootstrap steps for Authentik
- src/lib/db.ts: pass schema to drizzle for relational query builder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:37:41 -05:00
ginnoirandClaude Sonnet 4.6 f59753404e Add Compose stack & Caddy deploy artifacts (task 05)
Multi-stage Dockerfile (pnpm fetch/offline, standalone output, non-root
nextjs user), deploy/compose.yaml with famapp + famapp-db + full Authentik
stack on famapp_net, Caddyfile.snippet for fam/auth.ginnoir.com, and
.env.production.example. Added .dockerignore and public/.gitkeep.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:23:28 -05:00
ginnoirandClaude Sonnet 4.6 e00c95d4d6 Expand permission allowlist to reduce approval prompts
Adds git add/commit/push and pnpm typecheck/format/format:check so
routine dev-loop actions no longer require manual approval.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 02:10:38 -05:00
ginnoirandClaude Sonnet 4.6 e237fe8449 Add module loader & registry (task 04)
Implements the extensibility foundation: ModuleManifest/DashboardWidget/EntityTypeRegistration types in _core/module.ts, a plain-Map registry singleton with registerModule/getRegistry/getEntityType/getWidget, and stub manifests for calendar/lists/notes. Root layout imports src/modules/index.ts for side-effect registration; AppNav reads the registry to render nav links. /debug/registry dumps the full registry JSON in dev using zod v4 + z.toJSONSchema(). Adding a fourth module requires one folder + one line in index.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 01:52:47 -05:00
ginnoirandClaude Sonnet 4.6 37977568ed Add Drizzle + Postgres setup (task 03)
drizzle-orm + postgres driver + drizzle-kit wired up. Core schema declares
users, households, and household_members. docker-compose.dev.yaml runs
Postgres 16 locally. Initial migration generated and verified applied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 01:21:40 -05:00
ginnoir 09d6adbf18 Calendars as entities, uniform widget contract, multi-dashboard tasks
Architecture refinements before module work begins:

- Calendars become first-class entities (mirror of lists). Each
  calendar has its own visibility (private | household) and is
  independently shareable. Updated CLAUDE.md data model and task 10.

- Drop the singleton/parameterized split for dashboard widgets. Every
  widget declares a configSchema + defaultConfig + resolveConfigOptions
  and every placement is an independent instance — same widgetId can
  appear multiple times on one dashboard pointed at different things.
  Updated task 04 contract; tasks 11/12 widget sections aligned.

- Add tasks 25 (multiple dashboards per user) and 26 (customizable
  layout + widget configuration). Replaces the previously-considered
  three-task plan with a cleaner two-task split that the uniform
  contract enables. Phase 3 index updated; STATUS records the rationale.

- Task 20 reframed as a single-dashboard MVP that exercises the new
  contract end-to-end before the customization layer lands.
2026-05-06 01:14:49 -05:00