Backend: - Add rate limiting to login (5 attempts / 5 min window) - Add secure flag to session cookies with helper function - Add PIN min-length validation via Pydantic field_validator - Fix naive datetime usage in todos.py (datetime.now() not UTC) - Disable SQLAlchemy echo in production - Remove auto-commit from get_db to prevent double commits - Add lower bound filter to upcoming events query - Add SECRET_KEY default warning on startup - Remove create_all from lifespan (Alembic handles migrations) Frontend: - Fix ReminderForm remind_at slice for datetime-local input - Add window.confirm() dialogs on all destructive actions - Redirect authenticated users away from login screen - Replace error: any with getErrorMessage helper in LockScreen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
14 KiB
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
docker-compose.yaml- 3-service architecture (db, backend, frontend).env/.env.example- Environment configuration.gitignore- Root gitignore- Backend: FastAPI app skeleton (
app/main.py,config.py,database.py) - Backend:
requirements.txtwith all Python dependencies - Backend: Alembic migration setup (
alembic.ini,env.py, initial migration) - Backend:
start.shstartup script - Backend:
Dockerfile- Python 3.12-slim - Frontend: Vite + React scaffold (
package.json,vite.config.ts,tsconfig.json) - Frontend: Tailwind CSS + PostCSS configuration
- Frontend:
index.htmlentry point - Frontend:
nginx.conf- SPA serving + API proxy - Frontend:
Dockerfile- Multi-stage node build + nginx serve - Verify
docker-compose up --buildboots all services
Phase 2: Auth & Layout
- Backend: Settings model + schema
- Backend: Auth router (PIN setup, login, logout, status with bcrypt + itsdangerous)
- Frontend:
LockScreen.tsx- PIN setup/login UI - Frontend:
useAuth.tshook (TanStack Query) - Frontend:
AppLayout.tsx+Sidebar.tsx- Main layout with navigation - Frontend: Accent color theming system (
useTheme.ts, CSS custom properties) - Frontend: 12 shadcn/ui components (button, card, input, dialog, select, badge, etc.)
Phase 3: Core Features
- Backend: Todo model, schema, router (CRUD + toggle + filters)
- Frontend:
TodosPage,TodoList,TodoItem,TodoForm - Backend: Calendar Event model, schema, router (CRUD + date range filter)
- Frontend:
CalendarPage(FullCalendar integration),EventForm - Frontend: FullCalendar dark theme CSS overrides in
index.css - Backend: Reminder model, schema, router (CRUD + dismiss)
- Frontend:
RemindersPage,ReminderList,ReminderForm
Phase 4: Project Management
- Backend: Project model with tasks relationship + cascade delete
- Backend: ProjectTask model, schema, router (nested CRUD under projects)
- Frontend:
ProjectsPage,ProjectCard,ProjectDetail,ProjectForm,TaskForm
Phase 5: People & Locations
- Backend: Person model, schema, router (CRUD + search)
- Frontend:
PeoplePage,PersonCard,PersonForm - Backend: Location model, schema, router (CRUD + category filter)
- Frontend:
LocationsPage,LocationCard,LocationForm
Phase 6: Dashboard & Upcoming
- Backend: Dashboard aggregation endpoint (stats, events, todos, reminders)
- Backend: Upcoming endpoint (unified items sorted by date)
- Frontend:
DashboardPagewith all widgets + upcoming integration - Frontend:
StatsWidget(projects, people, locations counts) - Frontend:
UpcomingWidget(unified timeline with type icons) - Frontend:
TodoWidget(upcoming todos with priority badges) - Frontend:
CalendarWidget(today's events with color indicators) - Frontend: Active reminders section in dashboard
Phase 6b: Project Subtasks
- Backend: Self-referencing
parent_task_idFK onproject_taskswith CASCADE delete - Backend: Alembic migration
002_add_subtasks.py - Backend: Schema updates —
parent_task_idin create, nestedsubtasksin response,model_rebuild() - Backend: Chained
selectinloadfor two-level subtask loading, parent validation on create - Frontend:
ProjectTasktype updated withparent_task_idandsubtasks - Frontend:
TaskFormacceptsparentTaskIdprop, context-aware dialog title - Frontend:
ProjectDetail— expand/collapse chevrons, subtask progress bars, indented subtask cards
Phase 7: Settings & Polish
- Backend: Settings router (get/update settings, change PIN)
- Frontend:
SettingsPage(accent color picker, upcoming range, PIN change) - Integration fixes: All frontend API paths match backend routes
- Integration fixes: HTTP methods (PUT for updates, PATCH for toggle/dismiss)
- Integration fixes: Type definitions match backend response schemas
- Integration testing (end-to-end CRUD verification) <-- POST-BUILD
- 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:
- C1: CORS
allow_origins=["*"]withallow_credentials=True— already restricted to["http://localhost:5173"](main.py) - C2:
datetime.now(timezone.utc)in naive column — changed todatetime.now()(todos.py) - C3: Session cookie missing
secureflag — addedsecure=True+_set_session_cookiehelper (auth.py) - C4: No PIN length validation on backend — added
field_validatorfor min 4 chars (schemas/settings.py)
High:
- H1: No brute-force protection on login — added in-memory rate limiting (5 attempts / 5 min) (
auth.py) - H2:
echo=Trueon SQLAlchemy engine — set toFalse(database.py) - H3: Double commit pattern — removed auto-commit from
get_db, routers handle commits (database.py) - H4:
Person.relationshipcolumn shadows SQLAlchemy name — deferred (requires migration + schema changes across stack) - H5: Upcoming events missing lower bound filter — added
>= today_start(dashboard.py) - H6:
ReminderForm.tsxdoesn't sliceremind_at— added.slice(0, 16)for datetime-local input
Medium:
- M1: Default
SECRET_KEYis predictable — added stderr warning on startup (config.py) - M3:
create_allin lifespan conflicts with Alembic — removed (main.py) - M6: No confirmation dialog before destructive actions — added
window.confirm()on all delete buttons - M7: Authenticated users can still navigate to
/login— addedNavigateredirect inLockScreen.tsx - L1: Error handling in LockScreen used
error: any— replaced withgetErrorMessagehelper
Outstanding Items (Resume Here If Halted)
Critical (blocks deployment):
Docker build verification- DONE: All 3 services boot successfullynpm install verification- DONE: Frontend packages install correctly
Important (blocks functionality):
Alembic migration test- DONE: Tables create correctly on first bootAuth flow test- DONE: PIN setup works, PIN change works, session persistence works, logout addedEnd-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):
Responsive sidebar- DONE: Mobile hamburger menu with overlay, desktop collapse/expandToast notifications- DONE: All forms now show meaningful error messages viagetErrorMessage()Empty states- DONE: All pages show icon + message + action button when emptyLoading 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:
- Check which items are still marked incomplete in the Milestone Tracker
- Address Critical items first (Docker build verification)
- Then Important items (auth flow, CRUD testing)
- Finally Polish items (responsive, loading states)
Quick Start Commands
# 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
- Run
docker-compose up --build - Navigate to
http://localhost - You'll see the PIN setup screen (first run)
- Create a PIN and you'll be redirected to the dashboard
- Use the sidebar to navigate between features