# UMBRA - Project Progress ## Overview Personal life administration web app with dark theme, accent color customization, and Docker deployment. **Stack:** React + TypeScript | FastAPI + SQLAlchemy | PostgreSQL | Docker Compose --- ## Milestone Tracker ### Phase 1: Scaffolding & Infrastructure - [x] `docker-compose.yaml` - 3-service architecture (db, backend, frontend) - [x] `.env` / `.env.example` - Environment configuration - [x] `.gitignore` - Root gitignore - [x] Backend: FastAPI app skeleton (`app/main.py`, `config.py`, `database.py`) - [x] Backend: `requirements.txt` with all Python dependencies - [x] Backend: Alembic migration setup (`alembic.ini`, `env.py`, initial migration) - [x] Backend: `start.sh` startup script - [x] Backend: `Dockerfile` - Python 3.12-slim - [x] Frontend: Vite + React scaffold (`package.json`, `vite.config.ts`, `tsconfig.json`) - [x] Frontend: Tailwind CSS + PostCSS configuration - [x] Frontend: `index.html` entry point - [x] Frontend: `nginx.conf` - SPA serving + API proxy - [x] Frontend: `Dockerfile` - Multi-stage node build + nginx serve - [x] Verify `docker-compose up --build` boots all services ### Phase 2: Auth & Layout - [x] Backend: Settings model + schema - [x] Backend: Auth router (PIN setup, login, logout, status with bcrypt + itsdangerous) - [x] Frontend: `LockScreen.tsx` - PIN setup/login UI - [x] Frontend: `useAuth.ts` hook (TanStack Query) - [x] Frontend: `AppLayout.tsx` + `Sidebar.tsx` - Main layout with navigation - [x] Frontend: Accent color theming system (`useTheme.ts`, CSS custom properties) - [x] Frontend: 12 shadcn/ui components (button, card, input, dialog, select, badge, etc.) ### Phase 3: Core Features - [x] Backend: Todo model, schema, router (CRUD + toggle + filters) - [x] Frontend: `TodosPage`, `TodoList`, `TodoItem`, `TodoForm` - [x] Backend: Calendar Event model, schema, router (CRUD + date range filter) - [x] Frontend: `CalendarPage` (FullCalendar integration), `EventForm` - [x] Frontend: FullCalendar dark theme CSS overrides in `index.css` - [x] Backend: Reminder model, schema, router (CRUD + dismiss) - [x] Frontend: `RemindersPage`, `ReminderList`, `ReminderForm` ### Phase 4: Project Management - [x] Backend: Project model with tasks relationship + cascade delete - [x] Backend: ProjectTask model, schema, router (nested CRUD under projects) - [x] Frontend: `ProjectsPage`, `ProjectCard`, `ProjectDetail`, `ProjectForm`, `TaskForm` ### Phase 5: People & Locations - [x] Backend: Person model, schema, router (CRUD + search) - [x] Frontend: `PeoplePage`, `PersonCard`, `PersonForm` - [x] Backend: Location model, schema, router (CRUD + category filter) - [x] Frontend: `LocationsPage`, `LocationCard`, `LocationForm` ### Phase 6: Dashboard & Upcoming - [x] Backend: Dashboard aggregation endpoint (stats, events, todos, reminders) - [x] Backend: Upcoming endpoint (unified items sorted by date) - [x] Frontend: `DashboardPage` with all widgets + upcoming integration - [x] Frontend: `StatsWidget` (projects, people, locations counts) - [x] Frontend: `UpcomingWidget` (unified timeline with type icons) - [x] Frontend: `TodoWidget` (upcoming todos with priority badges) - [x] Frontend: `CalendarWidget` (today's events with color indicators) - [x] Frontend: Active reminders section in dashboard ### Phase 6b: Project Subtasks - [x] Backend: Self-referencing `parent_task_id` FK on `project_tasks` with CASCADE delete - [x] Backend: Alembic migration `002_add_subtasks.py` - [x] Backend: Schema updates — `parent_task_id` in create, nested `subtasks` in response, `model_rebuild()` - [x] Backend: Chained `selectinload` for two-level subtask loading, parent validation on create - [x] Frontend: `ProjectTask` type updated with `parent_task_id` and `subtasks` - [x] Frontend: `TaskForm` accepts `parentTaskId` prop, context-aware dialog title - [x] Frontend: `ProjectDetail` — expand/collapse chevrons, subtask progress bars, indented subtask cards ### Phase 7: Settings & Polish - [x] Backend: Settings router (get/update settings, change PIN) - [x] Frontend: `SettingsPage` (accent color picker, upcoming range, PIN change) - [x] Integration fixes: All frontend API paths match backend routes - [x] Integration fixes: HTTP methods (PUT for updates, PATCH for toggle/dismiss) - [x] Integration fixes: Type definitions match backend response schemas - [ ] Integration testing (end-to-end CRUD verification) <-- POST-BUILD - [x] Final styling pass (responsive sidebar, empty states, loading skeletons) --- ## Integration Fixes Applied These were caught during review and fixed: | Issue | Fix | |-------|-----| | Settings field `upcoming_days_range` | Renamed to `upcoming_days` | | CalendarEvent fields `start`/`end` | Renamed to `start_datetime`/`end_datetime` | | Reminder field `dismissed` | Renamed to `is_dismissed`, added `is_active` | | Project status values | Changed from `active/on_hold` to `not_started/in_progress/completed` | | Calendar API path `/calendar/events` | Fixed to `/events` | | All update forms using `api.patch` | Fixed to `api.put` (backend uses PUT) | | Todo toggle using generic PATCH | Fixed to `api.patch('/todos/{id}/toggle')` | | Reminder dismiss using generic PATCH | Fixed to `api.patch('/reminders/{id}/dismiss')` | | Settings update using PATCH | Fixed to `api.put` | | PIN change path `/auth/change-pin` | Fixed to `/settings/pin` | | Dashboard data shape mismatch | Aligned TodoWidget, CalendarWidget with actual API response | | Missing UpcomingWidget in dashboard | Added with `/api/upcoming` fetch | ## Post-Build Fixes Applied These were found during first Docker build and integration testing: | Issue | Fix | |-------|-----| | `Person.relationship` column shadowed SQLAlchemy's `relationship()` function | Aliased import to `sa_relationship` in `person.py` | | Missing `backend/app/__init__.py` | Created empty `__init__.py` for Python package recognition | | Backend port 8000 not exposed in `docker-compose.yaml` | Added `ports`, `healthcheck`, and `condition: service_healthy` | | `get_db()` redundant `session.close()` inside `async with` | Removed `finally: await session.close()` | | `datetime.utcnow()` deprecated in Python 3.12 | Replaced with `datetime.now(timezone.utc)` in `todos.py` | | Calendar date selection wiped start/end fields | Added `formatInitialDate()` to convert date-only to `datetime-local` format | | Dashboard "today's events" used server UTC time | Added `client_date` query param so frontend sends its local date | | Calendar drag-and-drop didn't persist | Added `eventDrop` handler with backend PUT call | | Timed event drag-and-drop sent timezone-aware datetimes to naive DB column | Used `toLocalDatetime()` helper instead of `.toISOString()` | | All-day event dates empty when editing (datetime vs date format mismatch) | Added `formatForInput()` to normalize values for `date` vs `datetime-local` inputs | | Project create/update `MissingGreenlet` error (lazy load in async context) | Re-fetch with `selectinload(Project.tasks)` after commit in `projects.py` | | Generic error toasts gave no useful information | Added `getErrorMessage()` helper to `api.ts`, updated all 8 form components | | Sidebar not responsive on mobile | Split into desktop sidebar + mobile overlay with hamburger menu in `AppLayout` | | Plain "Loading..." text on all pages | Created `Skeleton`, `ListSkeleton`, `GridSkeleton`, `DashboardSkeleton` components | | Basic empty states with no visual cue | Created `EmptyState` component with icon, message, and action button across all pages | | No logout button | Added logout button to sidebar footer with `LogOut` icon and destructive hover style | | Todo category filter was case sensitive | Changed to case-insensitive comparison with `.toLowerCase()` | | Dialog/popup forms too narrow and cramped | Widened `DialogContent` from `max-w-lg` to `max-w-xl` with mobile margin | --- ## Code Review Findings (Senior Review) ### Critical: - [x] C1: CORS `allow_origins=["*"]` with `allow_credentials=True` — already restricted to `["http://localhost:5173"]` (`main.py`) - [x] C2: `datetime.now(timezone.utc)` in naive column — changed to `datetime.now()` (`todos.py`) - [x] C3: Session cookie missing `secure` flag — added `secure=True` + `_set_session_cookie` helper (`auth.py`) - [x] C4: No PIN length validation on backend — added `field_validator` for min 4 chars (`schemas/settings.py`) ### High: - [x] H1: No brute-force protection on login — added in-memory rate limiting (5 attempts / 5 min) (`auth.py`) - [x] H2: `echo=True` on SQLAlchemy engine — set to `False` (`database.py`) - [x] H3: Double commit pattern — removed auto-commit from `get_db`, routers handle commits (`database.py`) - [ ] H4: `Person.relationship` column shadows SQLAlchemy name — deferred (requires migration + schema changes across stack) - [x] H5: Upcoming events missing lower bound filter — added `>= today_start` (`dashboard.py`) - [x] H6: `ReminderForm.tsx` doesn't slice `remind_at` — added `.slice(0, 16)` for datetime-local input ### Medium: - [x] M1: Default `SECRET_KEY` is predictable — added stderr warning on startup (`config.py`) - [x] M3: `create_all` in lifespan conflicts with Alembic — removed (`main.py`) - [x] M6: No confirmation dialog before destructive actions — added `window.confirm()` on all delete buttons - [x] M7: Authenticated users can still navigate to `/login` — added `Navigate` redirect in `LockScreen.tsx` - [x] L1: Error handling in LockScreen used `error: any` — replaced with `getErrorMessage` helper --- ## Outstanding Items (Resume Here If Halted) ### Critical (blocks deployment): 1. ~~**Docker build verification**~~ - DONE: All 3 services boot successfully 2. ~~**npm install verification**~~ - DONE: Frontend packages install correctly ### Important (blocks functionality): 3. ~~**Alembic migration test**~~ - DONE: Tables create correctly on first boot 4. ~~**Auth flow test**~~ - DONE: PIN setup works, PIN change works, session persistence works, logout added 5. ~~**End-to-end CRUD test**~~ - DONE: All features verified — Calendar, Projects, Todos (create/edit/filter/search), People (CRUD + search), Locations (CRUD + filter), Reminders (CRUD + dismiss), Settings (accent color + PIN) ### Nice to have (polish): 6. ~~**Responsive sidebar**~~ - DONE: Mobile hamburger menu with overlay, desktop collapse/expand 7. ~~**Toast notifications**~~ - DONE: All forms now show meaningful error messages via `getErrorMessage()` 8. ~~**Empty states**~~ - DONE: All pages show icon + message + action button when empty 9. ~~**Loading skeletons**~~ - DONE: Animated skeleton placeholders replace plain "Loading..." text --- ## File Inventory (100+ files total) ### Backend (40 files) ``` backend/ ├── Dockerfile ├── requirements.txt ├── start.sh ├── .gitignore ├── alembic.ini ├── alembic/ │ ├── env.py │ ├── script.py.mako │ └── versions/ │ ├── 001_initial_migration.py │ └── 002_add_subtasks.py └── app/ ├── main.py ├── config.py ├── database.py ├── models/ │ ├── __init__.py │ ├── settings.py │ ├── todo.py │ ├── calendar_event.py │ ├── reminder.py │ ├── project.py │ ├── project_task.py │ ├── person.py │ └── location.py ├── schemas/ │ ├── __init__.py │ ├── settings.py │ ├── todo.py │ ├── calendar_event.py │ ├── reminder.py │ ├── project.py │ ├── project_task.py │ ├── person.py │ └── location.py └── routers/ ├── __init__.py ├── auth.py ├── dashboard.py ├── todos.py ├── events.py ├── reminders.py ├── projects.py ├── people.py ├── locations.py └── settings.py ``` ### Frontend (60+ files) ``` frontend/ ├── Dockerfile ├── nginx.conf ├── package.json ├── vite.config.ts ├── tsconfig.json ├── tsconfig.node.json ├── postcss.config.js ├── tailwind.config.ts ├── components.json ├── index.html ├── .gitignore └── src/ ├── main.tsx ├── App.tsx ├── index.css (includes FullCalendar dark overrides) ├── lib/ │ ├── utils.ts │ └── api.ts ├── hooks/ │ ├── useAuth.ts │ ├── useSettings.ts │ └── useTheme.ts ├── types/ │ └── index.ts └── components/ ├── ui/ (12 components) ├── layout/ (AppLayout, Sidebar) ├── auth/ (LockScreen) ├── dashboard/ (DashboardPage, StatsWidget, UpcomingWidget, TodoWidget, CalendarWidget, ProjectsWidget) ├── todos/ (TodosPage, TodoList, TodoItem, TodoForm) ├── calendar/ (CalendarPage, EventForm) ├── reminders/ (RemindersPage, ReminderList, ReminderForm) ├── projects/ (ProjectsPage, ProjectCard, ProjectDetail, ProjectForm, TaskForm) ├── people/ (PeoplePage, PersonCard, PersonForm) ├── locations/ (LocationsPage, LocationCard, LocationForm) └── settings/ (SettingsPage) ``` ### Root ``` docker-compose.yaml .env / .env.example .gitignore progress.md ``` --- ## How to Resume Development If development is halted, pick up from the **Outstanding Items** section above: 1. Check which items are still marked incomplete in the Milestone Tracker 2. Address **Critical** items first (Docker build verification) 3. Then **Important** items (auth flow, CRUD testing) 4. Finally **Polish** items (responsive, loading states) ### Quick Start Commands ```bash # Build and run everything docker-compose up --build # Rebuild just backend after changes docker-compose up --build backend # Rebuild just frontend after changes docker-compose up --build frontend # View logs docker-compose logs -f # View specific service logs docker-compose logs -f backend docker-compose logs -f frontend # Reset database (destructive) docker-compose down -v && docker-compose up --build # Stop everything docker-compose down ``` ### First-Time Setup 1. Run `docker-compose up --build` 2. Navigate to `http://localhost` 3. You'll see the PIN setup screen (first run) 4. Create a PIN and you'll be redirected to the dashboard 5. Use the sidebar to navigate between features