# Family Planner — Project Reference A self-hosted family dashboard designed for always-on display (wall tablet, TV, Unraid server). Manages calendars, chores, shopping, meals, a message board, countdowns, and a photo screensaver — all in one Docker container with zero external services required. --- ## Tech Stack | Layer | Technology | |---|---| | **Runtime** | Node.js 22, Docker (Alpine) | | **Backend** | Express 4, TypeScript 5 | | **Database** | Node.js built-in SQLite (`node:sqlite`), WAL mode | | **File uploads** | Multer | | **Frontend** | React 18, TypeScript 5, Vite 5 | | **Styling** | Tailwind CSS 3, CSS custom properties (theme tokens) | | **Animation** | Framer Motion 11 | | **State / data** | TanStack Query 5, Zustand 4 | | **Routing** | React Router 6 | | **Icons** | Lucide React | | **Date utils** | date-fns 3 | | **Package manager** | pnpm (workspaces monorepo) | | **CI/CD** | Gitea Actions → Docker build & push to private registry | | **Deployment target** | Unraid (Community Applications / CLI install) | --- ## Repository Structure ``` family-planner/ ├── apps/ │ ├── client/ # React frontend │ │ └── src/ │ │ ├── components/ │ │ │ ├── layout/ │ │ │ │ └── AppShell.tsx # Sidebar, mobile drawer, page wrapper │ │ │ ├── screensaver/ │ │ │ │ └── Screensaver.tsx # Idle screensaver w/ Ken Burns slideshow │ │ │ └── ui/ # Design-system primitives │ │ │ ├── Avatar.tsx │ │ │ ├── Badge.tsx │ │ │ ├── Button.tsx │ │ │ ├── Input.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Select.tsx │ │ │ ├── Textarea.tsx │ │ │ └── ThemeToggle.tsx │ │ ├── features/ # Feature-scoped sub-components │ │ │ ├── calendar/ │ │ │ ├── chores/ │ │ │ └── shopping/ │ │ ├── hooks/ │ │ │ └── useMembers.ts │ │ ├── lib/ │ │ │ └── api.ts # Axios instance + all API types │ │ ├── pages/ # One file per route │ │ │ ├── Calendar.tsx ✅ complete │ │ │ ├── Chores.tsx ✅ complete │ │ │ ├── Photos.tsx ✅ complete │ │ │ ├── Settings.tsx ✅ complete │ │ │ ├── Members.tsx ✅ complete │ │ │ ├── Shopping.tsx ✅ complete │ │ │ ├── Dashboard.tsx 🔲 stub │ │ │ ├── Meals.tsx 🔲 stub │ │ │ ├── Board.tsx 🔲 stub │ │ │ └── Countdowns.tsx 🔲 stub │ │ └── store/ │ │ ├── settingsStore.ts # Zustand settings (synced from DB) │ │ └── themeStore.ts # Zustand theme + CSS token application │ └── server/ │ └── src/ │ ├── db/ │ │ ├── db.ts # node:sqlite wrapper + transaction helper │ │ ├── runner.ts # Sequential migration runner │ │ └── migrations/ │ │ └── 001_initial.ts # Full schema + seed data │ ├── routes/ # One router file per domain │ │ ├── members.ts ✅ full CRUD │ │ ├── settings.ts ✅ key/value GET + PATCH │ │ ├── events.ts ✅ full CRUD + date-range filter │ │ ├── shopping.ts ✅ full CRUD (lists + items) │ │ ├── chores.ts ✅ full CRUD + completions │ │ ├── meals.ts ✅ full CRUD (upsert by date) │ │ ├── messages.ts ✅ full CRUD + pin/expiry │ │ ├── countdowns.ts ✅ full CRUD + event link │ │ └── photos.ts ✅ list, upload, serve, delete, slideshow │ └── index.ts # Express app + static client serve ├── .gitea/workflows/ │ └── docker-build.yml # Push to main → build + push Docker image ├── Dockerfile # Multi-stage: client build → server build → runtime ├── docker-compose.yml ├── docker-entrypoint.sh # PUID/PGID ownership fix + app start ├── UNRAID.md # Full Unraid install guide (GUI + CLI) ├── INSTALL.md └── PROJECT.md # ← this file ``` --- ## Database Schema All tables live in a single SQLite file at `$DATA_DIR/family.db`. | Table | Purpose | |---|---| | `members` | Family member profiles (name, color, avatar) | | `settings` | Key/value app configuration store | | `events` | Calendar events (all-day, timed, recurrence field) | | `shopping_lists` | Named shopping lists | | `shopping_items` | Line items (quantity, checked state, member assignment) | | `chores` | Chore definitions (recurrence, due date, status) | | `chore_completions` | Completion history per chore per member | | `meals` | One dinner entry per calendar date (upsert pattern) | | `messages` | Board messages (color, emoji, pin, optional expiry) | | `countdowns` | Countdown timers (linked to calendar event or standalone) | Photos are stored as files on disk — no database table. The configured folder path lives in `settings.photo_folder` (or `$PHOTOS_DIR` env var in Docker). --- ## Completed Features ### Infrastructure - [x] pnpm monorepo (`apps/client` + `apps/server`) - [x] Multi-stage Dockerfile — client build, server build, minimal Alpine runtime - [x] Sequential migration runner — aborts startup on failure - [x] Gitea Actions CI — builds and pushes Docker image on push to `main` - [x] Unraid install guide (GUI template + CLI script) - [x] PUID/PGID support via `docker-entrypoint.sh` - [x] `tini` for correct PID 1 signal handling ### Design System - [x] CSS custom property token system (surface, border, text, accent) - [x] Light / dark mode with smooth transitions - [x] 5 accent colours (Indigo, Teal, Rose, Amber, Slate) - [x] Collapsible sidebar with animated labels - [x] Mobile overlay drawer - [x] Primitive component library: `Button`, `Input`, `Textarea`, `Select`, `Modal`, `Avatar`, `Badge`, `ThemeToggle` - [x] Page entry/exit animations via Framer Motion ### Members - [x] Create, edit, delete family members - [x] Custom display colour per member - [x] Avatar field - [x] Member assignment used throughout app (events, chores, shopping items) ### Settings - [x] Theme mode + accent colour (persisted to DB and applied via CSS tokens) - [x] Photo folder path (absolute path or Docker bind-mount override) - [x] Slideshow speed (3 s – 15 s) - [x] Slideshow order (random / sequential / newest first) - [x] Idle timeout (1 min – 10 min, or disabled) - [x] Time format (12h / 24h) - [x] Date format (MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD) - [x] Weather API key, location, units (stored — widget pending) ### Calendar - [x] Month grid view with event dots - [x] Per-day event list modal - [x] Create / edit / delete events - [x] All-day events - [x] Member colour coding - [x] Custom event colour override - [x] Recurrence field (stored, display only) - [x] Month navigation with slide animation ### Shopping - [x] Multiple named lists - [x] Add / edit / delete items with optional quantity - [x] Check / uncheck items (checked items grouped separately) - [x] Clear all checked items - [x] Member assignment per item - [x] Keyboard shortcut: Enter to add - [x] Create / delete lists ### Chores - [x] Create / edit / delete chores - [x] Title, description, due date, recurrence label, member assignment - [x] Status: pending / done - [x] Filter by status or by member - [x] Completion count badge - [x] Completion history recorded in `chore_completions` ### Photos - [x] Batch upload via browser (drag & drop anywhere on page, or file picker) - [x] Multi-file selection — up to 200 files per upload, 50 MB each - [x] Server saves to configured photo folder root (multer disk storage) - [x] Filenames sanitised + timestamp-suffixed to prevent collisions - [x] Responsive photo grid (2 – 6 columns) - [x] Hover overlay: filename label + delete button - [x] Click to open full-screen lightbox - [x] Lightbox: prev / next navigation, keyboard arrows, Esc to close, photo counter - [x] Permanent delete with confirmation modal - [x] Graceful "not configured" state with link to Settings - [x] Empty state with prominent upload drop zone - [x] Recursive folder scan (pre-existing subdirectory photos appear in slideshow) - [x] Path traversal protection on all file-serving and delete endpoints ### Screensaver - [x] Activates automatically after configurable idle timeout - [x] Idle detection: `mousemove`, `mousedown`, `keydown`, `touchstart`, `scroll` - [x] Ken Burns effect — 8 presets alternating zoom-in / zoom-out with diagonal, vertical, and horizontal pan - [x] Crossfade between photos (1.2 s, `AnimatePresence mode="sync"`) - [x] Respects `slideshow_order` setting (random shuffled each cycle, sequential, newest first) - [x] Respects `slideshow_speed` setting (3 s – 15 s) - [x] Next photo preloaded while current is displayed - [x] Live clock overlay — large thin numerals, responsive font size (`clamp`) - [x] Clock respects `time_format` setting (12h with AM/PM, 24h) - [x] Date line below clock - [x] "Tap to dismiss" hint fades out after 3.5 s - [x] Dismiss on click, touch, or any keypress - [x] No-photo fallback: dark gradient + clock only - [x] Gradient scrim over photos for clock legibility - [x] `z-[200]` — renders above all modals and UI --- ## In Progress / Stubs These pages have **complete backend APIs** but their frontend is a placeholder `