Split out of #355 so PR #447's auto-close doesn't strand it (the #355 comment thread flagged it as "same defect class, worth fixing together").
graph_service.py::get_courses (≈:280–311) — backing GET /api/graph/{user_id}/courses — returns one row per enrollment. A user enrolled in two offerings of the same abstract course (e.g. rich-user-active in both offerings of rich-course-cs101 in the rich seed) gets two rows with identical course_id, so every screen that keys on c.course_id from this payload (Tree.tsx course pills, Dashboard, Library, Study, ManageCoursesModal, SyllabusUploadFlow) renders duplicate React keys — the same symptom class #355 fixed for the graph payload.
Why this wasn't folded into #447: deduping get_courses by course_id is NOT mechanically safe — rows also carry enrollment_id, and the gradebook keys on enrollment_id (repo convention). The fix needs a decision: dedupe at the API (and give enrollment-scoped consumers their own path), or key the course-list consumers on enrollment_id/composite keys client-side. Per-enrollment rows may be load-bearing for some consumers.
Discovered-by: PR #447 review pass (git-history reviewer), confirmed against origin/main.
🤖 Generated with Claude Code
Split out of #355 so PR #447's auto-close doesn't strand it (the #355 comment thread flagged it as "same defect class, worth fixing together").
graph_service.py::get_courses(≈:280–311) — backingGET /api/graph/{user_id}/courses— returns one row per enrollment. A user enrolled in two offerings of the same abstract course (e.g.rich-user-activein both offerings ofrich-course-cs101in the rich seed) gets two rows with identicalcourse_id, so every screen that keys onc.course_idfrom this payload (Tree.tsx course pills, Dashboard, Library, Study, ManageCoursesModal, SyllabusUploadFlow) renders duplicate React keys — the same symptom class #355 fixed for the graph payload.Why this wasn't folded into #447: deduping
get_coursesby course_id is NOT mechanically safe — rows also carryenrollment_id, and the gradebook keys onenrollment_id(repo convention). The fix needs a decision: dedupe at the API (and give enrollment-scoped consumers their own path), or key the course-list consumers onenrollment_id/composite keys client-side. Per-enrollment rows may be load-bearing for some consumers.Discovered-by: PR #447 review pass (git-history reviewer), confirmed against origin/main.
🤖 Generated with Claude Code