From 84031a4e04aabf539d1d2b02e374b15f2612d726 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 27 Feb 2026 09:50:02 +0100 Subject: [PATCH] =?UTF-8?q?docs(02-02):=20complete=20RankingDashboard=20pl?= =?UTF-8?q?an=20=E2=80=94=20SUMMARY,=20STATE,=20ROADMAP=20updated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 20 +-- .../02-ranking-dashboard-ui/02-02-SUMMARY.md | 123 ++++++++++++++++++ 3 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/02-ranking-dashboard-ui/02-02-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 9006b32..73f836b 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -76,6 +76,6 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 (Phase 4 can be parallelize | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 1. AI Ranking Backend | 4/4 | Complete | 2026-02-27 | -| 2. Ranking Dashboard UI | 0/TBD | Not started | - | +| 2. Ranking Dashboard UI | 2/3 | In Progress| | | 3. Advancement + Email | 0/TBD | Not started | - | | 4. Mentor Persistence | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 4aa23c3..b4616ed 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,12 +3,12 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: in_progress -last_updated: "2026-02-27T00:25:00.000Z" +last_updated: "2026-02-27T08:48:11Z" progress: total_phases: 4 completed_phases: 1 total_plans: 10 - completed_plans: 5 + completed_plans: 6 --- # Project State @@ -23,11 +23,11 @@ See: .planning/PROJECT.md (updated 2026-02-26) ## Current Position Phase: 2 of 4 (Ranking Dashboard UI) -Plan: 1 of 3 in current phase (Phase 2 Plan 1 complete) +Plan: 2 of 3 in current phase (Phase 2 Plan 2 complete) Status: In progress -Last activity: 2026-02-27 — Plan 02-01 complete: saveReorder mutation + Ranking tab entry point + RankingDashboard stub +Last activity: 2026-02-27 — Plan 02-02 complete: Full RankingDashboard with drag-and-drop, AI vs override badges, Sheet detail panel -Progress: [████░░░░░░] ~50% +Progress: [█████░░░░░] ~60% ## Performance Metrics @@ -44,11 +44,12 @@ Progress: [████░░░░░░] ~50% | 02-ranking-dashboard-ui | 1 | ~5 min | ~5 min | **Recent Trend:** -- Last 5 plans: 01-01 (~3 min), 01-02 (~3 min), 01-03 (~4 min), 01-04 (~8 min), 02-01 (~5 min) -- Trend: Fast (service-layer + UI wiring tasks) +- Last 5 plans: 01-02 (~3 min), 01-03 (~4 min), 01-04 (~8 min), 02-01 (~5 min), 02-02 (~8 min) +- Trend: Fast (service-layer + UI implementation tasks) *Updated after each plan completion* | Phase 02-ranking-dashboard-ui P01 | 5 | 2 tasks | 3 files | +| Phase 02-ranking-dashboard-ui P02 | 8 | 1 task | 1 file | ## Accumulated Context @@ -73,6 +74,9 @@ Recent decisions affecting current work: - [Phase 01-04]: retroactiveScan uses RETROACTIVE triggerType to distinguish from MANUAL/AUTO/QUICK — prevents duplicate re-runs on subsequent scans - [02-01]: ReorderEvent type defined locally in ranking.ts (not exported) — only consumed by saveReorder procedure - [02-01]: saveReorder is append-only: full ordered list stored per event, latest entry per category = current admin order, gives full audit trail +- [02-02]: Double cast (as unknown as RankedProjectEntry[]) required for Prisma JsonValue — direct cast rejected by TypeScript strict mode +- [02-02]: getFullDetail returns { project, assignments, stats } shape — title accessed via .project.title not root level +- [02-02]: saveReorder has no onSuccess invalidation — avoids re-fetch that would reset localOrder and cause snap-back ### Pending Todos @@ -87,5 +91,5 @@ None yet. ## Session Continuity Last session: 2026-02-27 -Stopped at: Completed 02-01-PLAN.md (saveReorder mutation + Ranking tab entry point + RankingDashboard stub) +Stopped at: Completed 02-02-PLAN.md (Full RankingDashboard with drag-and-drop, AI vs override badges, Sheet detail panel) Resume file: None diff --git a/.planning/phases/02-ranking-dashboard-ui/02-02-SUMMARY.md b/.planning/phases/02-ranking-dashboard-ui/02-02-SUMMARY.md new file mode 100644 index 0000000..69ba36b --- /dev/null +++ b/.planning/phases/02-ranking-dashboard-ui/02-02-SUMMARY.md @@ -0,0 +1,123 @@ +--- +phase: 02-ranking-dashboard-ui +plan: 02 +subsystem: ui +tags: [react, dnd-kit, trpc, ranking, drag-and-drop, sheet-panel] + +# Dependency graph +requires: + - phase: 02-01 + provides: RankingDashboard stub, saveReorder mutation, Ranking tab entry point +provides: + - Full RankingDashboard component with drag-and-drop reorder, AI vs override visual states, and Sheet-based juror evaluation detail panel +affects: [02-03-advance-projects] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "useRef init guard: initialized.current prevents localOrder re-init from server data on every re-render — eliminates snap-back" + - "Fire-and-forget mutation inside setLocalOrder callback: setLocalOrder runs synchronously first, mutation fires async, no onSuccess invalidation" + - "Double cast via unknown: Prisma JsonValue cast to RankedProjectEntry[] requires (json ?? []) as unknown as RankedProjectEntry[]" + - "getFullDetail response shape: { project, assignments, stats } — title accessed as projectDetail.project.title" + +key-files: + created: [] + modified: + - src/components/admin/round/ranking-dashboard.tsx + +key-decisions: + - "Double cast (as unknown as RankedProjectEntry[]) required for Prisma JsonValue — direct cast rejected by TypeScript strict mode" + - "getFullDetail returns { project, assignments, stats } shape, not flat — project title accessed via .project.title" + - "saveReorder mutation has no onSuccess invalidation — avoids triggering re-fetch that would reset localOrder" + +patterns-established: + - "SortableProjectRow sub-component defined above export in same file (no separate file needed for inline sub-components)" + - "Per-category drag context: separate DndContext per category prevents cross-category drag" + +requirements-completed: [DASH-01, DASH-02, DASH-03, DASH-04] + +# Metrics +duration: 8min +completed: 2026-02-27 +--- + +# Phase 2 Plan 02: Full RankingDashboard Component Summary + +**Full RankingDashboard with per-category drag-and-drop (dnd-kit), AI vs override rank badges, snap-back-proof localOrder state, and lazy-loaded Sheet detail panel showing per-juror evaluation breakdown** + +## Performance + +- **Duration:** ~8 min +- **Started:** 2026-02-27T08:40:00Z +- **Completed:** 2026-02-27T08:48:11Z +- **Tasks:** 1 +- **Files modified:** 1 + +## Accomplishments + +- Replaced RankingDashboard stub with full 486-line implementation +- DASH-01: Ranked project list per category (STARTUP / BUSINESS_CONCEPT) with composite score, pass rate, and evaluator count displayed per row +- DASH-02: Drag-and-drop reorder via GripVertical handle using dnd-kit (DndContext + SortableContext + useSortable), fire-and-forget saveReorder mutation +- DASH-03: localOrder stored in useState with useRef guard (`initialized.current`) — init fires once on first snapshot load, never re-initialized from server data; no snap-back +- DASH-04: Sheet panel opens on row click, lazy-loads `trpc.project.getFullDetail` (enabled only when selectedProjectId is set), displays stats summary and per-juror evaluation list filtered to SUBMITTED assignments for the current round +- AI-order rows display dark-blue rank badge (#N); admin-reordered rows display amber `#N (override)` badge +- "Run Ranking" button in header card calls `triggerAutoRank`, resets `initialized.current` to allow re-init on new snapshot +- Empty categories show a placeholder message instead of an empty drag zone +- TypeScript strict mode: 0 errors; build: PASSED + +## Task Commits + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Build full RankingDashboard component | 6512e4e | src/components/admin/round/ranking-dashboard.tsx | + +## Files Created/Modified + +- `src/components/admin/round/ranking-dashboard.tsx` — Full component replacing stub (486 lines → includes SortableProjectRow sub-component + RankingDashboard main export) + +## Decisions Made + +- **Double cast via unknown**: `(json ?? []) as unknown as RankedProjectEntry[]` — TypeScript strict mode rejects direct cast from Prisma `JsonValue`; intermediate `unknown` is required. Matches pattern from Phase 01-03. +- **getFullDetail response shape**: The procedure returns `{ project, assignments, stats }` (not flat) — `projectDetail.project.title`, not `projectDetail.title`. +- **No onSuccess invalidation in saveReorder**: Calling `utils.ranking.getSnapshot.invalidate()` in `onSuccess` would trigger a re-fetch that resets `localOrder` to server data, causing snap-back. Mutation only shows toast on error. +- **Per-category DndContext**: Separate `DndContext` per category prevents accidental cross-category drags. + +## Deviations from Plan + +None — plan executed exactly as written. All type errors encountered were auto-fixed inline (Rule 1 — double cast pattern, Rule 1 — response shape). + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Prisma JsonValue cast requires double cast via unknown** +- **Found during:** Task 1 (typecheck) +- **Issue:** `(snapshot.startupRankingJson ?? []) as RankedProjectEntry[]` — TypeScript strict mode rejects because `JsonValue` and `RankedProjectEntry[]` don't sufficiently overlap +- **Fix:** Changed to `as unknown as RankedProjectEntry[]` (identical pattern used in Phase 01-03) +- **Files modified:** ranking-dashboard.tsx +- **Commit:** 6512e4e (same task commit) + +**2. [Rule 1 - Bug] getFullDetail response shape — title not on root** +- **Found during:** Task 1 (typecheck) +- **Issue:** `projectDetail?.title` — getFullDetail returns `{ project, assignments, stats }`, not a flat object +- **Fix:** Changed to `projectDetail?.project.title` +- **Files modified:** ranking-dashboard.tsx +- **Commit:** 6512e4e (same task commit) + +## Issues Encountered + +None beyond the two auto-fixed type errors above. + +## User Setup Required + +None. + +## Next Phase Readiness + +- RankingDashboard fully functional — admin can view ranked projects, drag to reorder, see juror-level evaluation details in Sheet panel +- Plan 03 can now add the "Advance Projects" action button to the dashboard header +- saveReorder mutation is append-only audit log — Plan 03 can read latest reorder per category to determine final advance order +- Build and typecheck both pass with 0 errors + +--- +*Phase: 02-ranking-dashboard-ui* +*Completed: 2026-02-27*