# CLAUDE.md - UMBRA ## CRUCIAL CONTEXT EFFICIENCY ### Subagent Discipline - Prefer inline work for tasks under ~5 tool calls. Subagents have overhead — don't delegate trivially. - When using subagents, include output rules: "Final response under 2000 characters. List outcomes, not process." - Never call TaskOutput twice for the same subagent. If it times out, increase the timeout — don't re-read. ### File Reading - Read files with purpose. Before reading a file, know what you're looking for. - Use Grep to locate relevant sections before reading entire large files. - Never re-read a file you've already read in this session. - For files over 500 lines, use offset/limit to read only the relevant section. ### Responses - Don't echo back file contents you just read — the user can see them. - Don't narrate tool calls ("Let me read the file..." / "Now I'll edit..."). Just do it. - Keep explanations proportional to complexity. Simple changes need one sentence, not three paragraphs. ## Deployment & Access - **UMBRA is hosted on a remote machine** at `http://10.0.69.35` (not localhost). - **Login PIN:** `1993` - **Rebuilds are manual.** Do not run `docker-compose` commands — the user will rebuild on the remote machine when needed. - **Browser automation** can access the live app at `http://10.0.69.35`. ## Operational Procedures - Before making any changes or edits to code, create a branch. Once work is completed, commit and push to the branch automatically, then merge into main and push. - When required: For backend work invoke the 'backend-engineer' subagent, for work on the front end, invoke the 'frontend-engineer' subagent. To review work use the 'senior-code-reviewer' subagent and for any research use the 'research-analyst' subagent. - For any frontend UI related work you MUST use the frontend design skill AND reference the [stylesheet](.claude/context/stylesheet.md) to ensure visual consistency. The stylesheet defines all colors, typography, spacing, component patterns, and design principles for UMBRA. Do not invent new patterns. ## Future Roadmap After the UI refresh is complete, the next major phases are: - **Multi-user authentication** — replace single PIN auth with per-user accounts - **Backend restructure** — add user_id foreign keys to all models, scope all queries per-user - **Always build for scale.** Even though UMBRA is currently single-user, design features, indexes, validations, and state machines with multi-user in mind. Cutting corners now means rework later. ## Key Features & Patterns - **Reminder alerts**: Real-time polling (30s) via `AlertsProvider` context. Dashboard shows `AlertBanner`, other pages get Sonner toasts (max 3 + summary). Snooze/dismiss with `client_now` for Docker UTC offset. - **Two-click delete**: `useConfirmAction` hook in `hooks/useConfirmAction.ts` — first click shows "Sure?", auto-resets after 4s, second click executes. Used by TodoItem and ReminderItem. - **Naive datetime contract**: All datetimes are naive (no timezone). Frontend sends `toLocalDatetime()` from `lib/date-utils.ts` when the backend needs "now". Docker container runs UTC; `client_now` bridges the gap. ## Known Issues - **Git push auth flake.** The first `git push` to the Gitea remote will fail with an authentication error. Simply retry the same push command — the second attempt succeeds. ## 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.** `` needs `YYYY-MM-DD`, `` 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 - [ui_refresh.md](.claude/projects/ui_refresh.md) - UI overhaul project plan, audit findings, implementation stages - [stylesheet.md](.claude/context/stylesheet.md) - Design system & visual reference for all frontend work ## 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, useCalendars types/index.ts # TypeScript interfaces matching backend schemas components/ ui/ # 16 base components (Button, Dialog, Sheet, Card, Input, Select, etc.) layout/ # AppLayout + Sidebar auth/ # LockScreen (PIN setup/login) dashboard/ # DashboardPage + widgets (Stats, Calendar, Todo, Upcoming, Countdown, etc.) calendar/ # CalendarPage, CalendarSidebar, CalendarForm, EventForm, TemplateForm todos/ # TodosPage + TodoList + TodoItem + TodoForm reminders/ # RemindersPage + ReminderList + ReminderForm projects/ # ProjectsPage, ProjectCard, ProjectDetail, ProjectForm, KanbanBoard, TaskRow, TaskForm, TaskDetailPanel people/ # PeoplePage + PersonCard + PersonForm locations/ # LocationsPage + LocationCard + LocationForm settings/ # SettingsPage (accent color, PIN change, first day of week) ``` ## 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 (incl. recurring) | | `/api/event-templates` | Event templates CRUD | | `/api/calendars` | User calendars CRUD + visibility | | `/api/reminders` | Reminders CRUD + dismiss | | `/api/projects` | Projects + nested tasks CRUD | | `/api/people` | People CRUD | | `/api/locations` | Locations CRUD + search | | `/api/settings` | Settings + PIN change | | `/api/dashboard` | Dashboard aggregation | | `/api/upcoming` | Unified upcoming items | | `/api/weather` | Weather data proxy | ## 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