UMBRA/CLAUDE.md

114 lines
5.0 KiB
Markdown

# CLAUDE.md - UMBRA
## Hard Rules
- **Naive datetimes only.** The DB uses `TIMESTAMP WITHOUT TIME ZONE`. Never send timezone-aware strings (no `Z` suffix, no `.toISOString()`). Use local datetime formatting helpers instead.
- **Eager load relationships in async SQLAlchemy.** Any `response_model` that includes a relationship (e.g. `ProjectResponse.tasks`) must use `selectinload()` when querying. Lazy loading raises `MissingGreenlet` in async context.
- **Never shadow SQLAlchemy names.** Model columns must not be named `relationship`, `column`, `metadata`, or other SQLAlchemy reserved names. The `person.py` model aliases the import as `sa_relationship` for this reason.
- **Frontend date inputs require exact formats.** `<input type="date">` needs `YYYY-MM-DD`, `<input type="datetime-local">` needs `YYYY-MM-DDThh:mm`. Backend may return `2026-02-15T00:00:00` which must be sliced/converted before binding.
- **All API routes are prefixed with `/api`.** Frontend axios base URL is `/api`. Nginx proxies `/api/` to backend:8000. Never duplicate the prefix.
## Tech Stack
### Backend
- **Python 3.12** (slim Docker image - no curl, use `urllib` for healthchecks)
- **FastAPI** with async lifespan, Pydantic v2 (`model_dump()`, `ConfigDict(from_attributes=True)`)
- **SQLAlchemy 2.0** async with `Mapped[]` type hints, `mapped_column()`, `async_sessionmaker`
- **PostgreSQL 16** (Alpine) via `asyncpg`
- **Alembic** for migrations
- **Auth:** PIN-based with bcrypt + itsdangerous signed cookies (not JWT)
### Frontend
- **React 18** + TypeScript + Vite 6
- **TanStack Query v5** for server state (`useQuery`, `useMutation`, `invalidateQueries`)
- **FullCalendar 6** (dayGrid, timeGrid, interaction plugins)
- **Tailwind CSS 3** with custom dark theme + accent color CSS variables
- **Sonner** for toast notifications
- **Lucide React** for icons
- Custom shadcn/ui-style components in `frontend/src/components/ui/`
### Infrastructure
- **Docker Compose** - 3 services: `db`, `backend`, `frontend`
- **Nginx** (Alpine) serves frontend SPA, proxies `/api/` to backend
- Frontend served on port **80**, backend on port **8000**
## Authority Links
- [progress.md](progress.md) - Project tracker, milestone status, fix history, outstanding items
## Project Structure
```
backend/app/
main.py # FastAPI app, router registration, health endpoint
config.py # Pydantic BaseSettings (DATABASE_URL, SECRET_KEY)
database.py # Async engine, session factory, get_db dependency
models/ # SQLAlchemy 2.0 models (Mapped[] style)
schemas/ # Pydantic v2 request/response schemas
routers/ # One router per feature (auth, todos, events, etc.)
frontend/src/
App.tsx # Routes + ProtectedRoute wrapper
lib/api.ts # Axios instance + getErrorMessage helper
hooks/ # useAuth, useSettings, useTheme
types/index.ts # TypeScript interfaces matching backend schemas
components/
ui/ # 12 base components (Button, Dialog, Input, etc.)
layout/ # AppLayout + Sidebar
auth/ # LockScreen (PIN setup/login)
dashboard/ # DashboardPage + widgets
calendar/ # CalendarPage + EventForm
todos/ # TodosPage + TodoList + TodoItem + TodoForm
reminders/ # RemindersPage + ReminderList + ReminderForm
projects/ # ProjectsPage + ProjectCard + ProjectDetail + forms
people/ # PeoplePage + PersonCard + PersonForm
locations/ # LocationsPage + LocationCard + LocationForm
settings/ # SettingsPage (accent color, PIN change)
```
## Essential Commands
```bash
# Build and run all services
docker-compose up --build
# Rebuild single service after changes
docker-compose up --build backend
docker-compose up --build frontend
# View logs
docker-compose logs -f
docker-compose logs -f backend
# Reset database (destroys all data)
docker-compose down -v && docker-compose up --build
# Stop everything
docker-compose down
```
## API Routes
All routes require auth (signed cookie) except `/api/auth/*` and `/health`.
| Prefix | Resource |
|--------------------|-----------------|
| `/api/auth` | PIN setup/login/logout/status |
| `/api/todos` | Todos CRUD + toggle |
| `/api/events` | Calendar events CRUD |
| `/api/reminders` | Reminders CRUD + dismiss |
| `/api/projects` | Projects + nested tasks CRUD |
| `/api/people` | People CRUD |
| `/api/locations` | Locations CRUD |
| `/api/settings` | Settings + PIN change |
| `/api/dashboard` | Dashboard aggregation |
| `/api/upcoming` | Unified upcoming items |
## Stop Conditions
- **Do not** add timezone info to datetime strings sent to the backend
- **Do not** use `datetime.utcnow()` - use `datetime.now(timezone.utc)` instead (deprecated in 3.12)
- **Do not** return relationships from async endpoints without `selectinload()`
- **Do not** use `curl` in backend Docker healthchecks (not available in python:slim)
- **Do not** use `git push --force` or destructive git operations without explicit approval