feat: US-010 — Projects sidebar tree view and project detail routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-02-19 22:54:02 +01:00
parent 6bf465c983
commit 4180c3d215
11 changed files with 846 additions and 283 deletions

View File

@@ -14,6 +14,8 @@
- electron-store@8 (CJS) used for app settings; use lazy init pattern `getStore()` like `getDb()` to avoid calling before app ready
- ESLint uses `eslint-import-resolver-typescript` to resolve `@/*` aliases; configured in `.eslintrc.json` under `settings.import/resolver`
- App settings (sidebar state, etc.) exposed via `settings` tRPC sub-router for type-safe renderer access
- `z.string().nullable().optional()` in tRPC inputs enables three-state semantics: undefined = don't change, null = clear, string = set value
- TanStack Router `validateSearch` with Zod schema for passing selected-item IDs via URL search params (e.g., `?projectId=...`)
---
## 2026-02-19 - US-002
@@ -158,3 +160,24 @@
- `useCallback` ref pattern (`ref={callbackRef}`) is used for auto-focus + select on mount without useEffect
- The two-stage delete flow (try simple delete first → if error, show cascade option) maps well to the backend's `clients.delete` (guards) + `clients.deleteWithCascade` (force) pattern
---
## 2026-02-19 - US-010
- What was implemented:
- Rewrote `ProjectSidebar` from a client-hierarchy tree to a project-centric sidebar grouped by client
- Projects grouped by `clientId` using Collapsible headers; projects without a client appear under "Internal / No Client"
- Search input filters projects by name in real-time (auto-expands all groups when searching)
- Show/hide archived projects via Switch toggle (queries `projects.list` with `includeArchived`)
- Context menu per project (DropdownMenu): Edit Client (Dialog + Select to assign/change/remove client), Archive/Unarchive, Delete (AlertDialog)
- Clicking a project sets `projectId` in search params → renders ProjectDetail placeholder in right pane
- Active project highlighted with `bg-sidebar-accent`
- Updated `projects.update` tRPC procedure to accept `clientId: z.string().nullable().optional()` (allows unlinking from client)
- Created placeholder `ProjectDetail` component (full implementation deferred to US-013)
- Installed shadcn/ui: dialog, select, switch
- Files changed: `src/renderer/components/projects/ProjectSidebar.tsx`, `src/renderer/routes/projects.tsx`, `src/renderer/components/projects/ProjectDetail.tsx` (new), `src/main/router/index.ts`, `src/renderer/components/ui/dialog.tsx` (new), `src/renderer/components/ui/select.tsx` (new), `src/renderer/components/ui/switch.tsx` (new), `scripts/ralph/prd.json`, `scripts/ralph/progress.txt`
- **Learnings for future iterations:**
- TanStack Router `validateSearch` with Zod schema is the cleanest way to pass selected-item IDs via URL search params without creating nested routes
- `Route.useNavigate()` returns a typed navigate fn; use `void navigate({ search: { ... } })` to avoid unhandled promise warnings
- For project grouping, query both `projects.list` and `clients.list` separately then join in-memory via a Map — avoids complex SQL joins for display-only data
- `projects.update` with `clientId: z.string().nullable().optional()` allows three states: undefined (don't change), null (unlink), string (assign)
- Auto-expanding all groups during search (`effectiveExpanded` computed from grouped keys) gives a better UX than forcing users to manually expand
---