Adds a workflow_dispatch-only GitHub Actions workflow that builds and pushes the backend and frontend images to GHCR for linux/amd64 (dev PC) and linux/arm64 (64-bit Raspberry Pi OS), using infra/docker-compose.build.yml as the build definition via docker buildx bake rather than duplicating the build config. No new secrets needed — pushes to ghcr.io under the repo owner using the built-in GITHUB_TOKEN, filling in the ghcr.io/<your-org>/... image placeholders already referenced by infra/docker-compose.yml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adaptive Household Support App
A cooperative household management platform for couples. Focuses on emotional fairness, adaptive workload balancing based on self-declared capacity states, light gamification, and real-time collaboration.
Features
Authentication
- Register, log in, and log out with email and password
- Sessions stored as
httpOnlycookies — no tokens exposed to client JavaScript - Password reset by email
- Protected routes redirect unauthenticated users to login automatically
Households
- Create a named household on first login
- Choose the household's planning day (the day of the week you review and plan) and timezone at creation
- Invite a partner via a shareable invite code
- Join an existing household by entering the code
- View all household members with avatars and roles
- Regenerate the invite code at any time
- Owner can update planning day and timezone from household settings
- Leave a household
Tasks
- Weekly task board grouped by day, anchored to the household's planning day
- Navigate to any past or future week with Prev/Next controls — catch up on and complete tasks from weeks you forgot to check off
- Create one-off tasks with a title, optional description (up to 500 chars), due date, and optional assignee
- Overdue pending tasks are highlighted with a red border and "Overdue" badge
- Mark tasks complete or skip them (skip requires a confirmation tap)
- Late completions (finished after the due date) are flagged in the completion record
- View and edit a task's full detail page including description
- Real-time updates — task changes made by either partner appear instantly without a page refresh
- Dashboard task widget — today's tasks at a glance, last on the dashboard (after the positive/social widgets, so the page doesn't open with a wall of chores). Overdue tasks are collapsed behind a "Show N overdue" toggle rather than shown by default
Routines
- Define recurring routines (daily, weekly, bi-weekly, or monthly)
- Set the day of the week and an estimated duration per routine
- Assign a routine to a specific household member or leave it unassigned
- Edit or deactivate routines at any time; inactive routines stay visible and can be reactivated
- Tasks are auto-generated from active routines each night by a background job — the household's planning day controls which night tasks are generated for the upcoming week
- Opening the planner auto-generates tasks for the week if none exist yet
Weekly Planner
- Plan the upcoming week together on the household's chosen planning day
- The week always runs from the day after the planning day through the planning day itself (e.g. Friday planning → Sat – Fri week)
- Session starts as a draft — assign tasks to yourself or your partner using dropdowns
- Summary bar shows how many tasks are mine, partner's, or unassigned at a glance
- Add shared notes for the week
- Confirm the session to lock in assignments and mark the week as planned
- Real-time sync — partner's assignment changes and confirmations appear instantly
- Browse past planning sessions and their confirmed status
Capacity States
- Declare a reduced-capacity label for the week (lower capacity, busy week, exam period, in recovery, health day, or high stress)
- Set a private reason only you can see — your partner sees the label only, never the reason
- Choose a duration from 1–7 days; the state expires automatically overnight
- Dashboard shows your active state with an "End early" button
- Dashboard shows your partner's label and days remaining (no reason shown)
- Both partners can have a state simultaneously — reassignment suggestions are paused when both are in low capacity
- Real-time updates — partner's state appears on your dashboard instantly without a page refresh
Adaptive Balancing
- When your partner declares a capacity state, a suggestions banner appears on your dashboard listing pending tasks due during their low-capacity window
- Choose Take all to accept every suggestion, Choose to pick individual tasks via checkboxes, or Maybe later to dismiss for the session
- Accepting reassigns those tasks to you immediately; your task list updates in real time via PocketBase realtime
- The banner appears automatically when your partner activates a state — no page reload needed
- Suggestions are scoped to tasks due today through the partner's state end date — only tasks you can actually act on appear
- Tasks already assigned to you are excluded from suggestions
Support Mode
- Either partner can activate support mode to signal they are actively supporting the other — for yourself or your partner
- Dashboard shows a shared yellow banner on both screens the moment support mode is activated: "You're supporting Alex" / "Alex is supporting you" / self-support message
- Choose a duration: 1 day, 3 days, 5 days, or 1 week — the event expires automatically at midnight after the last covered day
- Either partner can end support mode early with the End early button; the banner disappears for both in real time
- Only one support event can be active per household at a time; activating a new one replaces the current
- Each event stores a support multiplier (1.5×) for the future Rewards engine
Rewards
- Tasks carry a variable point value based on effort: Quick = 5 pts, Normal = 10 pts (default), Big = 20 pts — set per task or routine at creation
- When support mode is active, the supporting partner earns 1.5× the task's point value (rounded); the supported partner earns base points
- Create your own custom milestone rewards with a name and point cost — anything meaningful to your household
- Either partner can create, edit (inline), or delete (2-click confirm) milestones directly from the rewards widget
- When the household has enough points, a Claim button appears — claiming deducts the cost; the remainder carries over toward the next goal
- Milestones are not one-time unlocks — they cycle and can be claimed repeatedly
- Task completion shows a +N pts toast and inline badge so you always know what you earned
- Redemption history shows the last 20 claims, recording who claimed what and when even if the milestone is later deleted
- A streak counter tracks how many consecutive planning weeks you've confirmed together — visible as a 🔥 badge on the rewards widget
- Real-time updates — partner's claims and point changes appear on your screen instantly
Mascot
- A cat mascot lives on the dashboard and reacts to the household's current state with contextual messages
- Role-aware support messages: the supporting partner sees "You're covering for {name} this week. Earning 1.5× pts! 💪"; the supported partner sees "Your partner has your back this week. Keep going! 💛"
- Support and streak coexist: when both support mode and a streak ≥ 3 are active, both messages appear on separate lines simultaneously
- Other message priorities: long streak ≥ 7 weeks (exclusive) → milestone just claimed (5 seconds, exclusive) → support/streak combination → default
- "Milestone just claimed" message shows for 5 seconds then reverts to the next applicable messages
App Navigation
- Every authenticated page shows a persistent nav bar with the household name, links, user badge, and log out
- Household name displays to the left of nav links, truncated with a tooltip if too long
- Nav links: Dashboard · Tasks · Planner · Routines · Settings — active link highlighted; Settings stays active for all
/settings/*sub-routes - User badge: circular initials badge (up to 2 chars) next to the user's display name — truncated with a tooltip if long
- Log out — ends the session, clears client state, and redirects to login; shows a toast if the request fails
- Settings sub-navigation — all settings pages share a secondary nav listing Household and Capacity sections
Triggering routine tasks manually
To generate tasks immediately for your household without waiting for the nightly cron:
curl -X POST http://localhost:4000/recurring-rules/generate \
-H "Cookie: auth_token=<your-token>"
# → {"generated": 3}
The response includes a count of newly created tasks. Already-existing tasks for the same date are never duplicated.
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 14 (App Router) + shadcn/ui + Tailwind CSS v4 |
| Client State | Zustand |
| Form Validation | react-hook-form + zod |
| Backend API | NestJS + @nestjs/schedule (cron jobs) |
| Auth / DB / Realtime | PocketBase |
| Realtime Client | pocketbase npm package (direct browser → PocketBase subscriptions) |
| Toast Notifications | sonner (via shadcn) |
| File Storage | Backblaze B2 |
| Push Notifications | Firebase Cloud Messaging |
| Deployment | Docker Compose |
Getting Started
Prerequisites
- Node.js 24 LTS
- pnpm 11+
- Docker + Docker Compose
curlandjq(for the PocketBase schema import script)
Development (Docker Compose)
# Install dependencies
pnpm install
# Copy and fill in the dev env file
cp infra/.env.dev.example infra/.env.dev
# Edit infra/.env.dev — set PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, and PUBLIC_* URLs
# Start all services (run from infra/)
cd infra && docker compose -f docker-compose.dev.yml up
# Frontend → http://localhost:3000 (or http://<host-ip>:3000)
# Backend → http://localhost:4000 (or http://<host-ip>:4000)
# PocketBase → http://localhost:8090/_/ (admin UI)
Production (Docker Compose)
# Copy and fill in the prod env file
cp infra/.env.example infra/.env
# Edit infra/.env — set credentials, FRONTEND_URL, PUBLIC_* URLs
# Build from source and start (run from infra/)
cd infra && docker compose -f docker-compose.build.yml up -d
# Or pull pre-built images (if published to a registry)
cd infra && docker compose -f docker-compose.yml up -d
Fixed IP overlay (PocketBase superuser whitelist)
PocketBase supports a superuser IP whitelist. The NestJS backend authenticates to PocketBase as a superuser directly over the Docker network, so its container IP must be whitelisted alongside your own IP. Use the fixed-IP overlay to give the backend a stable, predictable IP (172.20.0.10):
# Run from infra/
docker compose -f docker-compose.build.yml -f docker-compose.fixed-ip.yml up -d
Then add 172.20.0.10 to PocketBase Dashboard → Settings → Application → Superuser IPs.
Running Without Docker
Step 1 — PocketBase
PocketBase is an external dependency. Choose one option:
Option A — Docker (recommended):
cd infra && docker compose -f docker-compose.dev.yml up pocketbase
# Admin UI → http://localhost:8090/_/
Option B — Existing PocketBase instance:
Set POCKETBASE_URL in backend/.env to point to your running instance:
POCKETBASE_URL=http://<your-pocketbase-host>:8090
Step 2 — Environment files
backend/.env — copy from backend/.env.example and fill in:
PORT=4000
POCKETBASE_URL=http://localhost:8090
FRONTEND_URL=http://localhost:3000
PB_ADMIN_EMAIL=<your-pocketbase-admin-email>
PB_ADMIN_PASSWORD=<your-pocketbase-admin-password>
ENABLE_DEV_TOOLS=true
frontend/.env.local — copy from frontend/.env.local.example:
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_PB_REALTIME_URL=http://localhost:8090
API_URL=http://localhost:4000
Note on realtime URL: The frontend subscribes to PocketBase directly for real-time updates. This URL must be reachable from the browser — use your host's IP address (e.g.
http://192.168.1.x:8090) when accessing the app from other devices on the same network.
Step 3 — Backend and Frontend
Run each in a separate terminal:
# Terminal 1 — Backend (loads backend/.env automatically)
pnpm dev:backend
# → http://localhost:4000
# Terminal 2 — Frontend
pnpm dev:frontend
# → http://localhost:3000
First-time PocketBase setup
Do this once after PocketBase starts for the first time (or after wiping its data).
Step 1 — Import the collection schema
Run the helper script from the repo root (requires curl and jq):
# PocketBase running locally (default: http://localhost:8090)
./scripts/import-pb-schema.sh
# PocketBase on a different host
./scripts/import-pb-schema.sh http://<host-ip>:8090
The script reads PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD from your environment or auto-loads them from infra/.env.dev. It merges the schema without deleting any existing collections — superusers and the built-in auth collection are preserved.
Step 2 — Set the users collection view rule
PocketBase excludes its built-in auth collections from schema import, so this one step must be done manually in the admin UI:
- Open the PocketBase admin UI (e.g.
http://localhost:8090/_/) - Go to Collections → users → Edit → API Rules
- Set View rule to:
@request.auth.id != "" - Save
This allows authenticated users to read each other's name and avatar, which is required for household member listing.
Running Tests
# Backend unit tests
pnpm test:backend
# Backend e2e tests
pnpm test:backend:e2e
# Frontend component tests
pnpm test:frontend
Project Structure
household-management/
├── frontend/ # Next.js App Router application
│ ├── app/(auth)/ # Login, register, forgot-password pages
│ ├── app/(app)/ # Protected routes (dashboard, tasks, routines, settings)
│ ├── components/ui/ # shadcn/ui components
│ ├── lib/ # api.ts, pocketbase.ts, validations/
│ ├── stores/ # Zustand stores (household, tasks, capacity, support, rewards)
│ └── middleware.ts # Auth redirect logic
├── backend/ # NestJS application
│ └── src/
│ ├── auth/ # Auth module + JwtAuthGuard + /auth/me + /auth/pb-token
│ ├── households/ # Household CRUD, invite/join
│ ├── tasks/ # Tasks, recurring rules, recurring engine (cron)
│ ├── planner/ # Weekly planning sessions
│ ├── capacity/ # Capacity states, cron expiry, engine stub
│ ├── support/ # Support mode activation, banner, cron expiry
│ ├── rewards/ # Points, milestones, claims, redemption history, streak tracking
│ └── pocketbase/ # PocketBase SDK wrapper
├── pocketbase/ # PocketBase binary and data
├── infra/ # Docker Compose config + .env.example
├── docs/ # Architecture and product spec
└── specs/ # Feature specs (spec-driven development)
Documentation
- Architecture — system design, tech stack, layer responsibilities
- Product Spec — features, user flows, development iterations
Specs (Iteration 1)
| Spec | Feature | Status |
|---|---|---|
| 00 - Project Setup | Monorepo, tooling, Docker Compose | ✓ Done |
| 01 - Auth | Registration, login, session management | ✓ Done |
| 02 - Households | Household creation, invite/join | ✓ Done |
| 03 - Recurring Tasks | Routines, task generation, completions | ✓ Done |
| 04 - Weekly Planner | Weekly planning workflow | ✓ Done |
| 04b - Iteration 1 Addendum | Overdue tasks, descriptions, planning day, auto-generate | ✓ Done |
Specs (Iteration 2)
| Spec | Feature | Status |
|---|---|---|
| 05 - Capacity States | Declare capacity, privacy-filtered partner view | ✓ Done |
| 06 - Adaptive Balancing | Reassignment suggestions when partner has low capacity | ✓ Done |
| 07 - Support Mode | Explicit support activation, shared banner, reward multiplier | ✓ Done |
Specs (Iteration 3)
| Spec | Feature | Status |
|---|---|---|
| 09 - Rewards Engine | Points per task completion, support multiplier, milestone rewards | ✓ Done |
| 10 - Streaks | Consecutive confirmed planning weeks, streak counter | ✓ Done |
| 11 - Mascot | Cat mascot with contextual messages based on household state | ✓ Done |
Specs (Enhancements)
| Spec | Feature | Status |
|---|---|---|
| 08 - App Navigation Shell | Household name, user display, logout in nav bar | ✓ Done |
Specs (Iteration 4 — Usability Fixes)
Prioritized queue from user testing feedback. Implemented one at a time via feat/spec-NN-slug branches, in table order.
| Spec | Feature | Status |
|---|---|---|
| 13 - Tasks Page Week Navigation | Prev/Next week navigation on the tasks board; catch up on tasks from past weeks | ✓ Done |
| 03b - Recurring Frequency and Time | Multi-day weekly routines, "every N days" interval, time-of-day on tasks/routines | — |
| 04c - Planner Effort Breakdown | Per-person estimated-minutes rollup in the planner summary | — |
| 12 - Dashboard Task List | Today + overdue task list widget on the dashboard | ✓ Done |
| 14 - Mobile-Responsive Layout | Hamburger nav drawer below 640px, spot-check dense rows at phone width | — |
| 03c - Multi-User Task Assignment | Assign a task to both partners — scoped now, deferred (touches capacity/rewards assumptions, has open questions) | Scoped, not queued |