UMBRA/.claude/context/progress.md
Kyle Pope bad1332d1b Update project docs: calendar phase complete, Stage 3 done
- ui_refresh.md: Stage 3 marked complete with full item list
- progress.md: Added Phase 8-10 milestones, updated file inventory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 02:41:45 +08:00

385 lines
19 KiB
Markdown

# 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
- [x] Integration testing (end-to-end CRUD verification) <-- POST-BUILD
- [x] Final styling pass (responsive sidebar, empty states, loading skeletons)
### Phase 8: UI Refresh — Stages 1-2 (Dashboard & Global Shell)
- [x] Dashboard overhaul: contextual greeting, week timeline, stat cards, upcoming widget, weather
- [x] Typography: Sora headings + DM Sans body
- [x] Sidebar refinement: accent hover states, border-left active indicator, collapse animation
- [x] Custom scrollbar styling, staggered animations, skeleton loading states
- [x] Stylesheet (`stylesheet.md`) established as design system reference
### Phase 9: Calendar Redesign & Improvements
- [x] Multi-calendar backend with virtual birthday events from People
- [x] Calendar sidebar with visibility toggles and color indicators
- [x] Sheet component for slide-in form panels (replaced Dialog for all 4 forms)
- [x] All-day event fixes: slim CSS bars, exclusive end-date offset handling
- [x] Materialized recurring events: backend service, recurrence UI, scope dialog
- [x] LocationPicker with OSM Nominatim + local DB search (EventForm + LocationForm)
- [x] First day of week setting (Sunday/Monday) with FullCalendar sync
- [x] Dashboard parent template exclusion, DayBriefing null guards + night logic
- [x] Recurrence crash fixes: null stripping, _rule_int helper, first-occurrence generation
### Phase 10: UI Refresh — Stage 3+ (Remaining Pages)
- [ ] Stage 4: CRUD pages (Todos, Reminders, Projects) refined filters, cards, empty states
- [ ] Stage 5: Entity pages (People, Locations) avatar placeholders, consistent badges
- [ ] Stage 6: Settings & Login full-width layout, UMBRA branding
- [ ] Stage 7: Final polish consistency audit, animation review, accessibility pass
---
## 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 — Round 1 (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
## Code Review Findings — Round 2 (Senior Review)
### Critical:
- [x] C1: Default SECRET_KEY only warns, doesn't block production added env-aware fail-fast (`config.py`)
- [x] C2: `secure=True` cookie breaks HTTP development made configurable via `COOKIE_SECURE` setting (`auth.py`, `config.py`)
- [x] C3: No enum validation on status/priority fields added `Literal` types (`schemas/project_task.py`, `todo.py`, `project.py`)
- [x] C4: Race condition in PIN setup (TOCTOU) added `select().with_for_update()` (`auth.py`)
### High:
- [x] H1: Rate limiter memory leak added stale key cleanup, `del` empty entries (`auth.py`)
- [ ] H2: Dashboard runs 7 sequential DB queries deferred (asyncpg single-session limitation)
- [ ] H3: Subtask eager loading fragile at 2 levels accepted (business logic enforces single nesting)
- [x] H4: No `withCredentials` on Axios for Vite dev added to `api.ts`
- [x] H5: Logout doesn't invalidate session server-side added in-memory `_revoked_sessions` set (`auth.py`)
### Medium:
- [ ] M1: TodosPage fetches all then filters client-side deferred (acceptable for personal app scale)
- [x] M2: Dashboard uses `.toISOString()` violating CLAUDE.md rules replaced with local date formatter (`DashboardPage.tsx`)
- [x] M3: No CSP header in nginx added CSP + Referrer-Policy, removed deprecated X-XSS-Protection (`nginx.conf`)
- [x] M4: Event date filtering misses range-spanning events fixed range overlap logic (`events.py`)
- [x] M5: `accent_color` accepts arbitrary strings added `Literal` validation for allowed colors (`schemas/settings.py`)
- [x] M6: Logout `delete_cookie` doesn't match `set_cookie` attributes matched all cookie params (`auth.py`)
- [x] M7: bcrypt silently truncates PIN at 72 bytes added max 72 char validation (`schemas/settings.py`)
### Low:
- [x] L1: `as any` type casts in frontend forms replaced with proper `Type['field']` casts (`TaskForm.tsx`, `ProjectForm.tsx`)
- [x] L2: Unused imports in `events.py` false positive, all imports are used
- [ ] L3: Upcoming endpoint mixes date/datetime string sorting deferred (works correctly for ISO format)
- [ ] L4: Backend port 8000 exposed directly, bypassing nginx deferred (useful for dev)
- [ ] L5: `parseInt(id!)` without NaN validation deferred (low risk, route-level protection)
- [x] L6: `X-XSS-Protection` header is deprecated removed, replaced with CSP (`nginx.conf`)
- [x] L7: Missing `Referrer-Policy` header added `strict-origin-when-cross-origin` (`nginx.conf`)
---
## 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
│ ├── 003_add_location_to_events.py
│ ├── 004_add_starred_field.py
│ ├── 005_add_weather_fields.py
│ ├── 006_add_calendars.py
│ ├── 007_add_recurrence_fields.py
│ └── 008_add_first_day_of_week.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
│ └── calendar.py
├── services/
│ └── recurrence.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/ (14 components: + sheet, location-picker)
├── layout/ (AppLayout, Sidebar)
├── auth/ (LockScreen)
├── dashboard/ (DashboardPage, StatsWidget, UpcomingWidget, TodoWidget, CalendarWidget, ProjectsWidget)
├── todos/ (TodosPage, TodoList, TodoItem, TodoForm)
├── calendar/ (CalendarPage, CalendarSidebar, 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