Track Codex instructions and ignore local Claude settings
This commit is contained in:
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(pnpm add *)",
|
|
||||||
"Bash(pnpm dlx *)",
|
|
||||||
"Bash(pnpm install *)",
|
|
||||||
"Bash(pnpm build *)",
|
|
||||||
"Bash(pnpm lint *)",
|
|
||||||
"Bash(pnpm dev *)",
|
|
||||||
"Bash(pnpm typecheck)",
|
|
||||||
"Bash(pnpm format)",
|
|
||||||
"Bash(pnpm format:check)",
|
|
||||||
"Bash(git add *)",
|
|
||||||
"Bash(git commit *)",
|
|
||||||
"Bash(git push)"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,7 @@ Thumbs.db
|
|||||||
desktop.ini
|
desktop.ini
|
||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
|
.claude/
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/settings.json.example
|
!.vscode/settings.json.example
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# famapp
|
||||||
|
|
||||||
|
Self-hosted family coordination web app for Matt and his wife. Replaces a commercial family app with a shared calendar, shopping/task lists, and notes/reminders. Designed to be **deliberately extensible** — Matt expects to bolt on niche features over time, so the architecture treats every feature as a module.
|
||||||
|
|
||||||
|
This file is the canonical brief. Read it at the start of every session before making changes. Sub-task briefs in [`docs/tasks/`](docs/tasks/) reference this file; do not duplicate its contents there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Replace current family app: shared calendar, shopping list, task list, notes/reminders.
|
||||||
|
- Self-hosted on Matt's home server, accessible from outside the house with auth.
|
||||||
|
- Wife-friendly: installable PWA, passkey/SSO login, no extra apps to install for notifications.
|
||||||
|
- "Share anything" — most entities can produce a temporary public link for outsiders.
|
||||||
|
- Architecture optimized for adding many small features later without core changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack (decided)
|
||||||
|
|
||||||
|
| Layer | Choice | Notes |
|
||||||
|
| ------------------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Frontend + backend | Next.js 15 App Router + TypeScript | Single Node process, server actions for mutations |
|
||||||
|
| UI | Tailwind + shadcn/ui | |
|
||||||
|
| DB | Postgres 16 | Dedicated container per app (Matt's rule) |
|
||||||
|
| ORM | Drizzle | Lightweight, SQL-friendly, good for evolving schema |
|
||||||
|
| Auth/SSO | **Authentik** at `auth.ginnoir.com` | Picked over Pocket-ID because it ships forward-auth (Proxy Provider) for the rest of Matt's stack (Sonarr/Radarr/Prowlarr/NZBGet/qBittorrent/Tautulli/Overseerr/FreshRSS/ntfy) which has no native OIDC. famapp itself uses OIDC. |
|
||||||
|
| Push | Web Push (VAPID) | Works in installed PWAs incl. iOS Safari 16.4+. ntfy stays optional. |
|
||||||
|
| Realtime | Postgres `LISTEN/NOTIFY` → SSE | Simpler than WebSockets, fine for two users |
|
||||||
|
| Reverse proxy | Caddy (existing) | `fam.ginnoir.com` → famapp:3000, `auth.ginnoir.com` → authentik |
|
||||||
|
| Package manager | pnpm | Fast, content-addressed store, strict deps |
|
||||||
|
| Runtime | Node 22 LTS | In `node:22-alpine` container |
|
||||||
|
| Host | Ubuntu Server 24.04 x64, Docker + Compose | Existing self-host stack |
|
||||||
|
|
||||||
|
Domain: `fam.ginnoir.com`. SSO: `auth.ginnoir.com`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture: extensibility-first
|
||||||
|
|
||||||
|
Every feature is a self-contained **module** under `src/modules/<name>/`. A module declares its tables, routes, dashboard contributions, quick-add actions, and supported share/reminder/search behaviors via a manifest. Core services (sharing, push, activity log, search, reminders) operate generically against any registered entity — adding a new module should not require touching core code.
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
modules/
|
||||||
|
_core/ # auth, household, sharing, push, activity log, reminders
|
||||||
|
calendar/
|
||||||
|
schema.ts # drizzle tables
|
||||||
|
server/ # server actions + queries
|
||||||
|
components/
|
||||||
|
routes.ts # registers /calendar pages + API
|
||||||
|
dashboard.tsx # widget contributed to dashboard
|
||||||
|
manifest.ts # name, icon, nav, entity types, share/reminder handlers
|
||||||
|
lists/
|
||||||
|
notes/
|
||||||
|
app/ # Next.js routes — thin, mounts modules
|
||||||
|
lib/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core primitives every module gets
|
||||||
|
|
||||||
|
- **Entity registry.** Modules declare entity types; share-link, activity log, search, reminders all work against any registered entity.
|
||||||
|
- **Dashboard widget registry.** Every widget is uniformly configurable (no singleton/parameterized split) and reusable — each placement on a dashboard is an independent instance with its own config. Each user has multiple dashboards; the active dashboard composes whatever widgets they've placed.
|
||||||
|
- **Quick-add registry.** Modules register quick actions for the dashboard's `+` menu.
|
||||||
|
- **Share-link service.** `createShareLink(entityType, entityId, { expiresAt, capabilities })` → `fam.ginnoir.com/s/<token>`. Generic.
|
||||||
|
- **Notification bus.** `notify(userId, { title, body, url })` fans out to web push + in-app + (optional) ntfy.
|
||||||
|
- **Permissions.** Household-scoped by default. Share-tokens grant scoped read/write per entity.
|
||||||
|
- **Feature flags.** Env/db-driven for staging experiments.
|
||||||
|
|
||||||
|
**Rule for contributors (including future Sonnet sessions):** if a feature requires a change to `_core` to support a new entity type, that's a smell — extend the registry instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data model (v1)
|
||||||
|
|
||||||
|
- `users`, `households`, `household_members`
|
||||||
|
- `calendars` — name, color, owner_id, visibility (`private` | `household`). First-class entity; users create as many as they want, each independently shareable via the share-link service.
|
||||||
|
- `calendar_events` — `calendar_id` fk, title, start, end, all_day, location, notes, owner. `rrule` text column reserved (no recurrence in v1). `external_source`/`external_id` nullable for future Google/Apple sync.
|
||||||
|
- `lists` (type text — not enum, to allow extension), `list_items`
|
||||||
|
- `notes` — title, body, pinned, remind_at
|
||||||
|
- `dashboards` — per-user named dashboards (any user can have many), `layout` jsonb of placed widgets `[{ widgetId, config, x, y, w, h }]`.
|
||||||
|
- `share_links` — entity_type, entity_id, token, capabilities jsonb, expires_at
|
||||||
|
- `activity_log` — entity_type, entity_id, actor, action, payload jsonb
|
||||||
|
- `push_subscriptions`
|
||||||
|
- `reminders` — entity_type, entity_id, fire_at, channel. Generic; used by notes/events/anything.
|
||||||
|
|
||||||
|
All household-scoped entity tables include `household_id`. Sub-entities (`calendar_events`, `list_items`) inherit scope via their parent. All reads/mutations are gated by the caller's household membership and, where relevant, per-entity visibility (e.g. private calendars).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pages (v1)
|
||||||
|
|
||||||
|
- `/` Dashboard — today + next 3 days events, top of shopping list, open tasks, pinned notes, recent activity, quick-add `+`
|
||||||
|
- `/calendar` — month/week/day, drag-create
|
||||||
|
- `/lists/shopping`, `/lists/tasks` — defaults; create more
|
||||||
|
- `/notes`
|
||||||
|
- `/s/<token>` — public share viewer
|
||||||
|
- `/settings` — household, push toggles, share-link management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy shape
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# compose.yaml (sketch — final lives in /deploy)
|
||||||
|
services:
|
||||||
|
famapp: # node:22-alpine, Next.js
|
||||||
|
famapp-db: # postgres:16
|
||||||
|
authentik-server:
|
||||||
|
authentik-worker:
|
||||||
|
authentik-db: # postgres:16 (separate per Matt's rule)
|
||||||
|
authentik-redis:
|
||||||
|
```
|
||||||
|
|
||||||
|
Caddy: `fam.ginnoir.com` → `famapp:3000`. `auth.ginnoir.com` → `authentik-server:9000`.
|
||||||
|
|
||||||
|
Backups: nightly `pg_dump` to a host volume. Out of scope for phase 1 but reserve the cron slot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build phases
|
||||||
|
|
||||||
|
1. **Scaffold** — repo init, Next.js + Drizzle + Tailwind + shadcn, module loader, compose stack, Caddy snippet, Authentik wired up, seeded household with both users.
|
||||||
|
2. **Core modules** — calendar, lists, notes (CRUD only).
|
||||||
|
3. **Dashboard + quick-add + activity log.**
|
||||||
|
4. **Sharing** — share-link service + `/s/<token>` viewer.
|
||||||
|
5. **Push notifications + reminders.**
|
||||||
|
6. **PWA polish** — manifest, icons, offline shell, install prompt.
|
||||||
|
7. **Hardening** — backups (pg_dump cron), rate limiting on share links, logging.
|
||||||
|
|
||||||
|
Tasks for each phase live in [`docs/tasks/`](docs/tasks/). Sub-sessions should pick up a task file, follow its scope, and stop at its acceptance criteria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **TypeScript strict.** No `any` without a written reason.
|
||||||
|
- **Server actions** for mutations; route handlers only for webhooks/SSE/share-link viewer.
|
||||||
|
- **Drizzle migrations** committed under `drizzle/`. Never edit a shipped migration — add a new one.
|
||||||
|
- **No comments** unless the _why_ is non-obvious. Names should carry intent.
|
||||||
|
- **Module isolation.** A module imports from `_core` and `lib/` only — never from a sibling module.
|
||||||
|
- **One module = one PR/commit boundary** when possible.
|
||||||
|
- **Tests:** Vitest for units (where it pays off), Playwright for one happy-path E2E per module. Don't write tests for trivial CRUD.
|
||||||
|
- **Secrets** via `.env` (gitignored) and `.env.example` (committed, no values).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of scope for v1 (do not build)
|
||||||
|
|
||||||
|
- ICS export / Google / Apple / Outlook calendar sync (data model leaves room)
|
||||||
|
- Recurrence editor (column reserved, no UI)
|
||||||
|
- Native iOS/Android app (PWA only; architecture must not preclude it)
|
||||||
|
- Migration from any existing app (starting fresh)
|
||||||
|
- Forward-auth wiring for the rest of Matt's stack (Authentik is installed; wiring the \*arr apps is a separate later task)
|
||||||
|
- Multi-tenant / multi-household (schema has `household_id` but UI is single-household)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where things live
|
||||||
|
|
||||||
|
- **This brief:** `AGENTS.md`
|
||||||
|
- **Task briefs for sub-sessions:** `docs/tasks/NN-name.md`
|
||||||
|
- **Architecture decisions worth preserving:** `docs/decisions/NNNN-title.md` (lightweight ADR — only when a non-obvious choice is made)
|
||||||
|
- **App code:** `src/`
|
||||||
|
- **Drizzle migrations:** `drizzle/`
|
||||||
|
- **Deploy:** `deploy/` (compose.yaml, Caddyfile snippet, Authentik bootstrap)
|
||||||
|
- **Memory (cross-session):** `C:\Users\MattC\.Codex\projects\C--Users-MattC-Documents-famapp\memory\`
|
||||||
Reference in New Issue
Block a user