serializeCanvasShapes recomputed each region's owner purely from geometry, ignoring the persisted question_id: a region with no enclosing part (or any region when a template had no parts) was silently dropped, and one overflowing its part by a pixel re-parented to parts[0]. Resolve the owner in order: geometric containment (a drag into a part re-attaches) -> persisted question_id if it still points at a saved question (survives drift / no-container) -> nearest part on the page -> first part -> first question of any kind. exam_response_areas.question_id is NOT NULL, so a region is never dropped while any question exists. Tests: never drops a region with no enclosing part; persisted question wins over geometric nearest. Full model.test.ts suite (6) passes; tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Description
React 18 + TypeScript + TLDraw 3.6 frontend for Classroom Copilot — whiteboard, transcription panel, timetable, and graph views
Languages
TypeScript
96.1%
CSS
3.1%
JavaScript
0.5%
Dockerfile
0.2%
HTML
0.1%