Files
famapp/ARCHITECTURE.md
ginnoir 76f68548b2 docs: complete Phase 9 backlog triage (issues map, briefs, ADRs)
Map design IDs to Gitea #1-#37, add tasks 80-88, stub ADRs 0004-0006,
and point STATUS at the P1 batch order.
2026-07-04 01:23:30 -05:00

33 KiB
Raw Permalink Blame History

famapp Architecture Diagram

System Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                              EXTERNAL LAYER                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────┐         ┌──────────────┐         ┌──────────────┐        │
│  │   Browser    │         │  Mobile PWA  │         │  Share Link  │        │
│  │  (Next.js)   │         │  (Installed) │         │  (Public)    │        │
│  └──────┬───────┘         └──────┬───────┘         └──────┬───────┘        │
│         │                        │                        │                 │
│         └────────────────────────┴────────────────────────┘                 │
│                                  │                                           │
│                         HTTPS (Caddy Reverse Proxy)                          │
│                                  │                                           │
│                     fam.ginnoir.com → :3000                                  │
│                     auth.ginnoir.com → :9000                                 │
└──────────────────────────────────┼───────────────────────────────────────────┘
                                   │
┌──────────────────────────────────┼───────────────────────────────────────────┐
│                         APPLICATION LAYER                                     │
├──────────────────────────────────┼───────────────────────────────────────────┤
│                                  │                                           │
│         ┌────────────────────────┴────────────────────────┐                   │
│         │              Next.js 15 App Router               │                   │
│         │              (Node 24 LTS Container)            │                   │
│         └────────────────────────┬────────────────────────┘                   │
│                                  │                                           │
│  ┌───────────────────────────────┼───────────────────────────────┐          │
│  │                               │                               │          │
│  │  ┌───────────────────────────┐│  ┌───────────────────────────┐│          │
│  │  │     Middleware            ││  │     Auth (NextAuth)       ││          │
│  │  │  - Rate limiting          ││  │  - OIDC (Authentik)       ││          │
│  │  │  - Session check          ││  │  - Session management     ││          │
│  │  └───────────┬───────────────┘│  └───────────┬───────────────┘│          │
│  │              │                │              │                │          │
│  │  ┌───────────┴───────────────┐│  ┌───────────┴───────────────┐│          │
│  │  │   Routes (app/)          ││  │   Module Registry        ││          │
│  │  │   / → /d/{slug}          ││  │   - Entity types         ││          │
│  │  │   /d/[slug] (dashboard)  ││  │   - Dashboard widgets    ││          │
│  │  │   /calendar              ││  │   - Quick-add actions    ││          │
│  │  │   /lists                 ││  │   - Share handlers       ││          │
│  │  │   /notes                 ││  │   - Search adapters      ││          │
│  │  │   /garden                ││  │   - Reminder handlers    ││          │
│  │  │   /s/[token] (share)     ││  │   - Activity renderers   ││          │
│  │  │   /settings              ││  │   - Toggle hooks         ││          │
│  │  │   /api/*                 ││  └───────────┬───────────────┘│          │
│  │  └───────────┬───────────────┘              │                │          │
│  │              │                                │                │          │
│  │  ┌───────────┴───────────────┬────────────────┴───────────────┐│          │
│  │  │                           │                                ││          │
│  │  │  ┌───────────────────────┴──────────────────────────────┐││          │
│  │  │  │              MODULE SYSTEM (src/modules/)             │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  _core/                                           │ │││          │
│  │  │  │  │  - schema.ts (users, households, activity_log,  │ │││          │
│  │  │  │  │    share_links, dashboards, reminders, push)    │ │││          │
│  │  │  │  │  - registry.ts (module registration)             │ │││          │
│  │  │  │  │  - share.ts (share-link service)                 │ │││          │
│  │  │  │  │  - push.ts (web push notifications)               │ │││          │
│  │  │  │  │  - reminders.ts (reminder scheduling)            │ │││          │
│  │  │  │  │  - activity.ts (activity logging)                │ │││          │
│  │  │  │  │  - themes.ts (theme configuration)                │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  calendar/                                        │ │││          │
│  │  │  │  │  - schema.ts (calendars, calendar_events)        │ │││          │
│  │  │  │  │  - server/ (queries, mutations, share-queries)   │ │││          │
│  │  │  │  │  - components/ (shared-view, calendar views)      │ │││          │
│  │  │  │  │  - manifest.tsx (widgets: upcoming, month)       │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  lists/                                           │ │││          │
│  │  │  │  │  - schema.ts (lists, list_items)                │ │││          │
│  │  │  │  │  - server/ (queries, mutations, share-queries)   │ │││          │
│  │  │  │  │  - components/ (shared-view, list-widget)         │ │││          │
│  │  │  │  │  - manifest.tsx (widget: list items)             │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  notes/                                           │ │││          │
│  │  │  │  │  - schema.ts (notes)                             │ │││          │
│  │  │  │  │  - server/ (queries, mutations, share-queries)   │ │││          │
│  │  │  │  │  - components/ (shared-view)                      │ │││          │
│  │  │  │  │  - manifest.tsx (widget: notes)                  │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  garden/                                          │ │││          │
│  │  │  │  │  - schema.ts (plants, containers, care_logs)    │ │││          │
│  │  │  │  │  - server/ (queries, mutations, share-queries)   │ │││          │
│  │  │  │  │  - components/ (plant-widget, shared-view)        │ │││          │
│  │  │  │  │  - manifest.tsx (widgets: care-due, overview)     │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  │  ┌────────────────────────────────────────────────┐ │││          │
│  │  │  │  │  bangs/                                           │ │││          │
│  │  │  │  │  - schema.ts (bang_events)                       │ │││          │
│  │  │  │  │  - server/ (queries)                             │ │││          │
│  │  │  │  │  - components/ (bang-widget)                     │ │││          │
│  │  │  │  │  - manifest.tsx (widget: bang counter)           │ │││          │
│  │  │  │  └────────────────────────────────────────────────┘ │││          │
│  │  │  │                                                        │││          │
│  │  │  └────────────────────────────────────────────────────┘││          │
│  │  │                                                           ││          │
│  │  └─────────────────────────────────────────────────────────┘│          │
│  │                                                                │          │
│  └────────────────────────────────────────────────────────────┘          │
│                                                                     │         │
│  ┌───────────────────────────────────────────────────────────────┐          │
│  │  Shared Libraries (src/lib/)                                 │          │
│  │  - db.ts (Drizzle client)                                     │          │
│  │  - auth.ts (NextAuth configuration)                           │          │
│  │  - session.ts (session helpers)                               │          │
│  │  - dashboard.ts (dashboard helpers)                           │          │
│  │  - logger.ts (pino logging)                                   │          │
│  │  - minio.ts (S3 client for uploads)                          │          │
│  │  - rate-limit.ts (rate limiting)                             │          │
│  └───────────────────────────────────────────────────────────────┘          │
│                                                                             │
└───────────────────────────────────────┬─────────────────────────────────────┘
                                        │
┌───────────────────────────────────────┼─────────────────────────────────────┐
│                         DATA LAYER                                          │
├───────────────────────────────────────┼─────────────────────────────────────┤
│                                       │                                      │
│         ┌─────────────────────────────┴──────────────────────────┐           │
│         │              Postgres 16 (famapp-db)                   │           │
│         │              (Dedicated Container)                     │           │
│         └─────────────────────────────┬──────────────────────────┘           │
│                                       │                                      │
│  ┌────────────────────────────────────┼────────────────────────────┐         │
│  │                                     │                            │         │
│  │  Core Tables                        │  Module Tables             │         │
│  │  ┌──────────────────────────────┐  │  ┌──────────────────────┐  │         │
│  │  │ users                        │  │  │ calendars            │  │         │
│  │  │ accounts                     │  │  │ calendar_events      │  │         │
│  │  │ sessions                     │  │  │ lists                │  │         │
│  │  │ verification_tokens          │  │  │ list_items           │  │         │
│  │  │ households                   │  │  │ notes                │  │         │
│  │  │ household_members            │  │  │ garden_plants        │  │         │
│  │  │ activity_log                 │  │  │ garden_containers    │  │         │
│  │  │ share_links                  │  │  │ garden_care_logs     │  │         │
│  │  │ dashboards                   │  │  │ bang_events          │  │         │
│  │  │ reminders                    │  │  └──────────────────────┘  │         │
│  │  │ push_subscriptions           │  │                            │         │
│  │  │ notifications                │  │                            │         │
│  │  └──────────────────────────────┘  │                            │         │
│  │                                     │                            │         │
│  └────────────────────────────────────┴────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                         AUTHENTICATION LAYER                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐  │
│  │                    Authentik (auth.ginnoir.com)                     │  │
│  │                    (Separate Docker Stack)                           │  │
│  │                                                                      │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐             │  │
│  │  │ authentik-   │  │ authentik-   │  │ authentik-   │             │  │
│  │  │ server       │  │ worker       │  │ redis        │             │  │
│  │  │ (:9000)      │  │              │  │              │             │  │
│  │  └──────┬───────┘  └──────────────┘  └──────┬───────┘             │  │
│  │         │                                  │                        │  │
│  │         └────────────────┬─────────────────┘                        │  │
│  │                          │                                          │  │
│  │  ┌───────────────────────┴──────────────────┐                     │  │
│  │  │         authentik-db (Postgres 16)       │                     │  │
│  │  │         (Dedicated Container)             │                     │  │
│  │  └──────────────────────────────────────────┘                     │  │
│  │                                                                      │  │
│  │  - OIDC Provider for famapp                                         │  │
│  │  - Forward-auth for *arr apps (future)                              │  │
│  │  - User management                                                   │  │
│  └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Module System Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                        MODULE REGISTRATION FLOW                               │
└─────────────────────────────────────────────────────────────────────────────┘

1. Module Registration (src/modules/index.ts)
   ┌─────────────────────────────────────────────────────────────────────┐
   │  import { registerModule } from "./_core/registry";                  │
   │  import coreManifest from "./_core/manifest";                        │
   │  import calendarManifest from "./calendar/manifest";                 │
   │  import listsManifest from "./lists/manifest";                        │
   │  import notesManifest from "./notes/manifest";                        │
   │  import gardenManifest from "./garden/manifest";                     │
   │  import bangsManifest from "./bangs/manifest";                       │
   │                                                                        │
   │  registerModule(coreManifest);                                        │
   │  registerModule(calendarManifest);                                    │
   │  registerModule(listsManifest);                                       │
   │  registerModule(notesManifest);                                       │
   │  registerModule(gardenManifest);                                      │
   │  registerModule(bangsManifest);                                       │
   └─────────────────────────────────────────────────────────────────────┘

2. Registry Storage (src/modules/_core/registry.ts)
   ┌─────────────────────────────────────────────────────────────────────┐
   │  const modules = new Map<string, ModuleManifest>();                  │
   │  const entityTypes = new Map<string, EntityTypeRegistration>();       │
   │  const widgets = new Map<string, DashboardWidget>();                  │
   │                                                                        │
   │  registerModule() stores:                                              │
   │    - Module metadata (id, name, nav)                                  │
   │    - Entity types (with share/reminder/search handlers)                │
   │    - Dashboard widgets (with render functions)                         │
   │    - Quick-add actions                                                │
   └─────────────────────────────────────────────────────────────────────┘

3. Module Manifest Structure (src/modules/_core/module.ts)
   ┌─────────────────────────────────────────────────────────────────────┐
   │  ModuleManifest {                                                     │
   │    id: string;              // "calendar", "lists", etc.              │
   │    name: string;            // "Calendar", "Lists", etc.              │
   │    nav?: {                  // Navigation entry                       │
   │      href: string;          // "/calendar"                            │
   │      label: string;         // "Calendar"                            │
   │      icon?: string;         // "calendar"                            │
   │    };                                                                  │
   │    entities: EntityTypeRegistration[];  // Entity type declarations   │
   │    dashboardWidgets?: DashboardWidget[];     // Widget declarations   │
   │    quickAdds?: QuickAddAction[];            // Quick-add actions     │
   │  }                                                                     │
   └─────────────────────────────────────────────────────────────────────┘

4. Entity Type Registration
   ┌─────────────────────────────────────────────────────────────────────┐
   │  EntityTypeRegistration {                                              │
   │    type: string;              // "calendar.event", "lists.item", etc. │
   │    label: { singular, plural };                                       │
   │    share?: {                                                          │
   │      canShare: boolean;                                               │
   │      defaultCapabilities?: string[];                                 │
   │    };                                                                  │
   │    reminder?: { canRemind: boolean };                                 │
   │    search?: { search: (query, householdId) => SearchResult[] };     │
   │    resolveUrl: (id) => string;                                        │
   │    canShareEntity?: (id, ctx) => Promise<boolean>;                   │
   │    loadForShare?: (id, ctx) => Promise<unknown>;                     │
   │    renderSharedView?: (data, capabilities, token) => ReactNode;      │
   │    renderActivity?: (entry) => string;                                │
   │  }                                                                     │
   └─────────────────────────────────────────────────────────────────────┘

5. Dashboard Widget Registration
   ┌─────────────────────────────────────────────────────────────────────┐
   │  DashboardWidget {                                                   │
   │    id: string;              // "calendar.upcoming", "lists.list", etc│
   │    title: string;                                                      │
   │    description: string;                                               │
   │    category?: string;         // "Calendar", "Lists", etc.             │
   │    defaultSize: { w, h };                                            │
   │    minSize?: { w, h };                                               │
   │    maxSize?: { w, h };                                               │
   │    defaultPriority: number;  // For default dashboard ordering       │
   │    configSchema: ZodType;    // Zod schema for widget config         │
   │    defaultConfig: unknown;                                            │
   │    resolveConfigOptions?: (ctx) => Promise<unknown>;                │
   │    render: (props) => ReactNode;  // Server component render        │
   │  }                                                                     │
   └─────────────────────────────────────────────────────────────────────┘

Data Flow Patterns

User creates share link
  → Module's canShareEntity() checks permissions
  → Core share.ts creates share_links record
  → Returns token: /s/<token>

Public viewer accesses /s/<token>
  → Middleware rate-limits by IP:token prefix
  → Share viewer loads share_links record
  → Module's loadForShare() loads entity data
  → Module's renderSharedView() renders public view
  → Share-link grants scoped read/write per capabilities

Activity Logging Flow

Any entity mutation (server action)
  → Core activity.ts logs activity_log entry
  → Includes: entityType, entityId, actorId, action, payload
  → Dashboard activity widget queries activity_log
  → Module's renderActivity() formats entry for display

Reminder Flow

Entity with remind_at field (note, event)
  → Module schedules reminder via core reminders.ts
  → reminders table stores: entityType, entityId, fireAt, channel
  → Background worker (planned) fires reminders
  → notify() fans out to: web push, in-app, optional ntfy

Dashboard Widget Flow

User configures dashboard layout
  → dashboards.layout stores: [{ widgetId, config, x, y, w, h }]
  → Dashboard page loads layout
  → For each widget: calls module's render() with config
  → Widget component queries data via module's server/ queries
  → Renders widget in grid layout

Key Design Principles

  1. Module Isolation: Modules only import from _core and lib/ - never from sibling modules
  2. Generic Core Services: Sharing, reminders, activity log, search work against any registered entity type
  3. Household Scoping: All entity data scoped to household_id; permissions gated by household membership
  4. Server Actions: All mutations use Next.js server actions; route handlers only for webhooks/SSE/share viewer
  5. Type Safety: TypeScript strict mode; Drizzle schema inference; Zod validation for all configs
  6. Extensibility: Adding new features requires only creating a new module - no core changes needed
  7. PWA First: Installable PWA with web(push) notifications; no native apps required
  8. Privacy-First Sharing: Share links are temporary, scoped, and revocable

Future (Phase 9)

Placeholders only — details land with tasks 8588:

  • journal/ module — per-user journal (task 86, Gitea #19)
  • Versioned HTTP API (/api/v1/…) with token auth alongside OIDC session (task 87, Gitea #15)
  • LLM agent chat — OpenAI-compatible tools over the API (task 88)