feat: Phase 3 - Habits + Projects CRUD API, frontend, completions, sections

Habits REST API:
- GET/POST /api/domains/[domainId]/habits (list with filters, create)
- GET/PATCH/DELETE /api/domains/[domainId]/habits/[id] (detail, update, soft delete)
- POST /api/domains/[domainId]/habits/[id]/complete (completion + streak calc)
- GET /api/domains/[domainId]/habits/[id]/completions (list with date range)
- POST/DELETE /api/domains/[domainId]/habits/[id]/tags

Projects REST API:
- GET/POST /api/domains/[domainId]/projects (list with task counts, create)
- GET/PATCH/DELETE /api/domains/[domainId]/projects/[id] (detail with sections/tasks, update, soft delete)

Sections REST API:
- GET/POST /api/domains/[domainId]/projects/[projectId]/sections (list, create)
- GET/PATCH/DELETE /api/domains/[domainId]/projects/[projectId]/sections/[id]

Frontend:
- Habits page: checklist view, difficulty badges, streak display, filter
- Habit create dialog: name, description, frequency, difficulty, goal, unit, reminder, mood toggle
- Habit completion dialog: value, mood (1-5 emoji), notes
- Calendar heatmap: 365-day grid, color by value, hover tooltip
- Projects page: grid of cards with progress bars, status badges, tags
- Project detail page: sections board, drag tasks between sections
- Project create dialog: name, description, status, color picker, target date
- Section dialog: name, kind (section/milestone), status, target date

Keyboard shortcuts: c h (new habit), c p (new project), c s (new section)

All write routes follow AGENTS.md contract (Drizzle + recordActivity + pg_notify).
Build, typecheck, and 15 new tests pass.
This commit is contained in:
2026-07-29 06:37:37 -04:00
parent 1fcd12fc14
commit 064a46f97d
23 changed files with 3633 additions and 523 deletions
+15
View File
@@ -73,6 +73,21 @@ export function useKeyboardShortcuts() {
document.dispatchEvent(new CustomEvent('open-create-task', { detail: { status: 'todo' } }));
e.preventDefault();
}
// c h — new habit
if (window.location.pathname.startsWith('/habits')) {
document.dispatchEvent(new CustomEvent('open-create-habit'));
e.preventDefault();
}
// c p — new project
if (window.location.pathname.startsWith('/projects')) {
document.dispatchEvent(new CustomEvent('open-create-project'));
e.preventDefault();
}
// c s — new section (on project detail page)
if (window.location.pathname.match(/^\/projects\/[^/]+$/)) {
document.dispatchEvent(new CustomEvent('open-create-section'));
e.preventDefault();
}
break;
}
case 'e': {