- 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>
19 KiB
19 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)
Phase 8: UI Refresh — Stages 1-2 (Dashboard & Global Shell)
- Dashboard overhaul: contextual greeting, week timeline, stat cards, upcoming widget, weather
- Typography: Sora headings + DM Sans body
- Sidebar refinement: accent hover states, border-left active indicator, collapse animation
- Custom scrollbar styling, staggered animations, skeleton loading states
- Stylesheet (
stylesheet.md) established as design system reference
Phase 9: Calendar Redesign & Improvements
- Multi-calendar backend with virtual birthday events from People
- Calendar sidebar with visibility toggles and color indicators
- Sheet component for slide-in form panels (replaced Dialog for all 4 forms)
- All-day event fixes: slim CSS bars, exclusive end-date offset handling
- Materialized recurring events: backend service, recurrence UI, scope dialog
- LocationPicker with OSM Nominatim + local DB search (EventForm + LocationForm)
- First day of week setting (Sunday/Monday) with FullCalendar sync
- Dashboard parent template exclusion, DayBriefing null guards + night logic
- 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:
- 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
Code Review Findings — Round 2 (Senior Review)
Critical:
- C1: Default SECRET_KEY only warns, doesn't block production — added env-aware fail-fast (
config.py) - C2:
secure=Truecookie breaks HTTP development — made configurable viaCOOKIE_SECUREsetting (auth.py,config.py) - C3: No enum validation on status/priority fields — added
Literaltypes (schemas/project_task.py,todo.py,project.py) - C4: Race condition in PIN setup (TOCTOU) — added
select().with_for_update()(auth.py)
High:
- H1: Rate limiter memory leak — added stale key cleanup,
delempty 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)
- H4: No
withCredentialson Axios for Vite dev — added toapi.ts - H5: Logout doesn't invalidate session server-side — added in-memory
_revoked_sessionsset (auth.py)
Medium:
- M1: TodosPage fetches all then filters client-side — deferred (acceptable for personal app scale)
- M2: Dashboard uses
.toISOString()violating CLAUDE.md rules — replaced with local date formatter (DashboardPage.tsx) - M3: No CSP header in nginx — added CSP + Referrer-Policy, removed deprecated X-XSS-Protection (
nginx.conf) - M4: Event date filtering misses range-spanning events — fixed range overlap logic (
events.py) - M5:
accent_coloraccepts arbitrary strings — addedLiteralvalidation for allowed colors (schemas/settings.py) - M6: Logout
delete_cookiedoesn't matchset_cookieattributes — matched all cookie params (auth.py) - M7: bcrypt silently truncates PIN at 72 bytes — added max 72 char validation (
schemas/settings.py)
Low:
- L1:
as anytype casts in frontend forms — replaced with properType['field']casts (TaskForm.tsx,ProjectForm.tsx) - 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) - L6:
X-XSS-Protectionheader is deprecated — removed, replaced with CSP (nginx.conf) - L7: Missing
Referrer-Policyheader — addedstrict-origin-when-cross-origin(nginx.conf)
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
│ ├── 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:
- 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