- Move classroom name to first segment of compound chip (replaces "Enrolled" label)
- Move add student "+" button to prefix position on the chip
- Integrate settings gear into classroom name segment
- Remove two-row card layout, now matches other filter pill styling
- Add settingsTrigger prop to TeacherCompoundChip for popover integration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Integrate create student form directly into AddStudentToClassroomModal
instead of opening a separate modal
- Left column shows choose mode (create button + family code) or create form
- Clicking "Cancel" in create mode returns to choose view
- User stays in one modal throughout the entire flow
- Hide "Link existing child" option in AddStudentModal when in classroom mode
(this functionality is now in the unified modal)
- Remove unused onCreateStudent prop from PracticeClient
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidates three add-student flows into a single discoverable modal:
- Left column "Add Now": Create student button + family code input
- Right column "Invite Parents": QR code + copy code/link buttons
Also refactors TeacherClassroomCard:
- Moves classroom name from header to first filter segment
- Removes ShareCodePanel from header (now in unified modal)
- Adds classroomName prop to TeacherCompoundChip for label override
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Storybook stories for the TeacherClassroomCard component with
14 variants covering:
- Basic usage (enrolled, in-classroom, active views)
- View count variations (empty, large, none present)
- Custom settings (expiry time, long names)
- Dark theme variants
- Responsive width testing
Also exports TeacherClassroomCard and TeacherClassroomCardProps
from StudentFilterBar.tsx for use in stories.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add CRITICAL section documenting the failure pattern where migration
files are modified after deployment, causing Drizzle to skip them
(it tracks by name, not content). This pattern has caused three
production outages in December 2025.
Includes:
- Explanation of why this happens
- The correct pattern (complete SQL before committing)
- How to fix with new migrations
- Emergency fix procedure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When students get problems wrong, they are now re-queued to retry:
- Problems retry in epochs (max 3 attempts per problem)
- Mastery weight decays: 100% → 50% → 25% → 0%
- Transition screen shows encouraging message between epochs
- Progress indicator shows retry attempt badges on slots
- BKT calculations respect mastery weight from retries
Also fixes entry prompts UNIQUE constraint error by marking
expired prompts before inserting new ones.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The FAB button should always be available for creating students
without auto-enrollment. Teachers now have both:
- FAB button: creates student only
- Classroom card button: creates student and auto-enrolls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create TeacherClassroomCard component that combines:
- Editable classroom name
- Share code chip
- Add student button (auto-enrolls)
- Settings popover
- Embedded filter tabs (Enrolled/Present/Active)
- Add auto-enrollment when teachers create students from classroom:
- AddStudentModal accepts classroomId/classroomName props
- Shows notice: "This student will be auto-enrolled in..."
- Button text changes to "Add & Enroll"
- Calls directEnrollStudent API after creation
- Add directEnrollStudent functionality:
- New function in enrollment-manager.ts
- POST /api/classrooms/[id]/enrollments endpoint
- useDirectEnrollStudent hook in useClassroom.ts
- Refactor filter bar layout:
- Move TeacherClassroomCard inside ViewSelector
- Align filter chips to bottom
- Match border radius and padding consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teachers were only able to add students via family code (EnrollChildModal).
This change shows the full AddStudentModal which allows both creating new
student profiles and adding existing students via code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teachers can now send entry prompts to parents requesting their child
enter the classroom. Features include:
- Entry prompts API with create, list, and respond endpoints
- Real-time notifications via WebSocket to parents
- Parent can accept (enters child) or decline prompts
- Configurable expiry time per classroom (default 30 min)
- Classroom name editing in settings popover
- Active sessions API returns sessions for all enrolled students
- E2E and unit tests for the complete feature
Also fixes bug where expired prompts could block creating new prompts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The useUnifiedStudents hook was only subscribing to child session
updates for non-teachers. When a user is both a teacher AND a parent,
their child's active session wouldn't show on /practice unless the
child was also present in the classroom.
Changed from `!isTeacher` to `hasChildren` so the child session socket
is enabled for ALL users who have children.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add active session item at top of history tab that opens observation modal
- Create useLiveSessionTimeEstimate hook for real-time WebSocket updates
- Extract shared time estimation logic to useSessionTimeEstimate hook
- Add subscribe-session-stats socket event for lightweight session updates
- Display live progress, accuracy, idle time, and estimated time remaining
- Add corner ribbon "In Progress" indicator with two-line layout
- Use inset box-shadow for border to avoid overlapping ribbon
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The share link API was using request.nextUrl.origin which returns
localhost when behind a reverse proxy. Now uses getShareUrl helper
which properly reads NEXT_PUBLIC_APP_URL on server-side.
Also adds comprehensive unit tests for:
- session-share.ts utilities (12 tests)
- autoPauseCalculator.ts functions (20 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously the LiveResultsPanel only showed problems completed after the
observer connected. Now it seeds the results array from slotResults on
the first practice-state event, converting historical SlotResult data to
ObservedResult format.
This ensures observers see all completed problems, not just those
completed while actively observing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add time-limited shareable links that allow anyone to observe a student's
practice session without logging in. Links expire after 1 hour or 24 hours.
Key features:
- Parents can generate share links from the observation modal/page
- Teachers cannot create share links (API enforces parent-only)
- Shared observers are view-only (no pause/resume, no abacus control)
- Links are automatically invalidated when sessions complete
- QR code and copy buttons for easy sharing
- View count and expiration tracking for active shares
New files:
- Session observation shares DB schema and migration
- Token generation utilities (10-char base62, ~59 bits entropy)
- Share CRUD API routes
- Public observation page for token-based access
- SessionShareButton component with popover UI
Modified:
- Socket server to accept token-based observer auth
- useSessionObserver hook to support shareToken param
- Session planner to revoke shares on session end
- Observer modal/page to show share button (parents only)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add LiveResultsPanel component showing real-time problem results
- Add LiveSessionReportModal for detailed session analytics view
- Broadcast session structure (parts, slots, results) to observers
- Display SessionProgressIndicator in observer modal (same as student view)
- Show inline full report view instead of separate modal
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add currentProblemNumber and totalProblems to broadcast state
- Observer modal now shows live "Problem X of Y" updates as student progresses
- Fix answer validation to use numeric comparison (parseInt) instead of string
comparison, so "09" correctly equals 9 (fixes red background when correct)
- Simplify MiniStartPracticeBanner to always show "Resume" for active sessions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace <button> with <Link> in StudentCard for proper routing
- Enables Cmd/Ctrl+Click to open in new tab
- Shows URL on hover in browser status bar
- Enables Next.js prefetching
- Close NotesModal before opening SessionObserverModal
- Fixes z-index stacking issue where observer appeared behind quicklook
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix parent authorization for session observation (use userId not viewerId)
- Move SessionObserverModal from NotesModal to parent components to fix z-index
- Add session observation support to student dashboard
- Add PracticeErrorBoundary to dashboard for tighter error handling
- Add RelationshipBadge, RelationshipCard, RelationshipIndicator components
- Add stakeholders API endpoint and useStudentStakeholders hook
- Integrate relationship info into PracticeSubNav and StudentSelector
- Add hover cards for relationship details on student tiles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Auto-generated fresh SVG examples and unified gallery from latest templates.
Includes comprehensive crop mark demonstrations with before/after comparisons.
Files updated:
- packages/templates/gallery-unified.html
🤖 Generated with GitHub Actions
Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Add GET /api/players/[playerId]/active-session endpoint
- Add useChildActiveSession and useChildrenActiveSessions hooks
- Update useUnifiedStudents to fetch child session status
- Parents now see "Watch Session" button when child is practicing
Polls every 10 seconds to detect session start/end.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove `!isTeacher` check from enterClassroom, leaveClassroom, and
enrollInClassroom actions. The `isMyChild` check is sufficient to
ensure these parent-specific actions only appear for the user's own
children, not for other students in their classroom roster.
This fixes the case where a parent who is also a teacher couldn't
enroll their own child in another teacher's classroom.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add classroom data (enrolled, current presence) to useStudentActions hook
- Add enterSpecificClassroom handler for entering a specific classroom
- Replace simple enter/leave actions with smart classroom section:
- In classroom: shows presence indicator with leave option
- 1 enrollment: direct enter action
- Multiple enrollments: Radix submenu with classroom list
- Always shows enroll option
- Remove EnterClassroomButton from dashboard (now handled by StudentActionMenu)
- Fix EnrollChildModal z-index to use same value as overlay
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add StudentActionMenu to student dashboard with inline variant
- Build proper StudentActionData with relationship/activity info
- Use useEnrolledClassrooms and useMyClassroom hooks for context
- Fix z-index layering for sub-modals (FamilyCodeDisplay, EnrollChildModal, SessionObserverModal)
- Use Z_INDEX.TOOLTIP (15000) for nested modals to appear above parent modals
- Remove unnecessary portal from FamilyCodeDisplay
- Add variant prop to StudentActionMenu: 'card' (absolute) vs 'inline' (normal flow)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Dynamic import echarts-for-react in SkillProgressChart, ValidationCharts,
and SkillDifficultyCharts to avoid bundling 58MB echarts library
- Lazy load game-registry in CreateRoomModal to prevent all arcade games
from being bundled into every page using PageWithNav
The practice page was bundling echarts (58MB) and all arcade game code
because of static imports in shared components. These changes ensure:
- echarts only loads when charts are actually rendered
- game-registry only loads when CreateRoomModal is opened
Bundle size: 47MB → 115KB (99.8% reduction)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create MiniStartPracticeBanner component showing session mode or active session status
- Shows "Start" for idle students (navigates to dashboard with startPractice=true)
- Shows "Resume" for practicing students (parent view)
- Shows "Watch" for practicing students (teacher view, opens SessionObserverModal)
- Add session observer integration for teachers to watch active sessions
- Mode-specific styling: amber for remediation, green for progression, blue for maintenance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PageTransitionContext to manage transition state across navigation
- Add PageTransitionOverlay component with react-spring animations
- Add fullscreen ⛶ button to NotesModal header
- Implement cross-fade transitions:
- Modal fades out while overlay fades in at modal position
- Overlay expands to fullscreen during navigation
- Overlay fades out while dashboard content fades in
- Use sessionStorage to persist transition state across page navigation
- Add pulse keyframe animation for loading indicator
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Completes Steps 14-18 of the unified student list plan:
Step 14: Move edit mode to secondary menu
- Replace prominent Edit button with ⋮ dropdown menu
- Add "Select Multiple" option, "Done" button in edit mode
Step 15-16: Clean up deprecated components
- Remove ClassroomDashboard, ClassroomTab, StudentManagerTab exports
- Add StudentQuickLook alias for NotesModal
Step 17: Real-time updates
- Add useClassroomSocket to PracticeClient for live updates
- Teachers now see presence/session changes in real-time
Step 18: Mobile responsive polish
- ViewSelector: horizontal scroll on mobile, wrap on desktop
- NotesModal: responsive height for virtual keyboard
Bonus: Arrow key navigation in QuickLook modal
- Left/Right/Up/Down arrows navigate between students
- Uses bounding rects to find nearest card in direction
- Disabled when editing notes (textarea focused)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract all student action logic into a single useStudentActions hook
used by both StudentActionMenu (on tiles) and NotesModal (in quicklook).
This ensures consistent action availability across the UI.
Key changes:
- Create useStudentActions hook with actions, handlers, and modal state
- Create StudentActionMenu as thin component using the hook
- Refactor NotesModal to use same hook (removes duplicated logic)
- Add studentActions.ts for shared types and getAvailableActions
- Clean up actionContext prop drilling from StudentSelector/PracticeClient
- Add useUnifiedStudents hook for consistent student data across views
- Add ViewSelector component for grid/list toggle
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This rule documents the failure pattern from December 2025 where
columns were added directly to the local dev database, causing
migration 0043 to be committed as a no-op. Production never got
the columns and crashed.
The rule requires all schema changes to go through the Drizzle
migration system.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The is_paused column was missing from migration 0043, causing a production
error: "SqliteError: no such column 'is_paused'"
The other pause columns (paused_at, paused_by, paused_reason) were added
manually during development but is_paused was overlooked.
This commit:
- Reverts 0043 to a no-op (those columns already exist)
- Adds 0044 to add only the missing is_paused column
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teacher observing a student's practice session can now:
- Dock their MyAbacus alongside the observed problem (AbacusDock pattern)
- Manipulate their abacus to sync the student's docked abacus
- See their abacus sync when the student manipulates theirs
Key changes:
- SessionObserverModal: Uses same AbacusDock pattern as ActiveSession
with ResizeObserver for height matching
- MyAbacus: Fixed docked mode to use setDockedValue instead of
setAbacusValue, ensuring read/write use same state source
- MyAbacusContext: Added contextAbacusValue for external abacus control
- useSessionObserver: Sends abacus-control events to student
- useSessionBroadcast: Receives abacus-control events from teacher
The two-way sync flow:
1. Teacher→Student: Teacher moves beads → sendControl(set-abacus-value)
→ student's abacus syncs via onAbacusControl callback
2. Student→Teacher: Student moves beads → broadcasts studentAnswer
→ observer receives → setDockedValue syncs teacher's abacus
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Auto-transition: When teacher observes a tutorial that completes,
automatically switch to observing the student's practice session
- Added polling mechanism while waiting for session to start
- Shows "Waiting for practice to start" overlay with cancel button
- 30-second timeout to prevent indefinite waiting
- Fixed NaN display in complexity tooltip "Per term (avg)":
- Added !Number.isNaN() checks in PurposeBadge and ActiveSession
- Fixed problemGenerator reduce to explicitly filter NaN values
(the ?? operator only catches null/undefined, not NaN)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teachers can now observe students' skill tutorials in real-time:
- Added TutorialObserverModal that reuses SkillTutorialLauncher component
- Student broadcasts tutorial state via WebSocket (useSkillTutorialBroadcast)
- Teacher receives live updates via useClassroomTutorialStates
- Teacher controls work: Start, Skip, Prev, Next, Complete buttons
- Socket server forwards skill-tutorial-state and skill-tutorial-control events
- Added onControl prop to SkillTutorialLauncher and TutorialPlayer
- In observation mode, buttons send control events instead of changing local state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add onBroadcastStateChange callback to ActiveSession for real-time state
- Change studentAnswer from number to string for digit-by-digit display
- Map internal phases correctly: inputting→problem, showingFeedback→feedback, helpMode→tutorial
- Update SessionObserverModal to display string answers directly
- Teachers can now see each digit as students type their answers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teachers can now observe students' practice sessions in real-time:
- Added SessionObserverModal with live problem display
- Created useSessionObserver hook for receiving broadcasts
- Refactored useSessionBroadcast to re-broadcast on observer join
- Added join-session socket event for session channel subscription
- Created shared PurposeBadge component with tooltip
- Created shared PracticeFeedback component
- Fixed tooltip z-index to work inside modals (15000)
- Used Z_INDEX constants throughout modal components
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Step 1: Student broadcasts practice state via WebSocket
- Create useSessionBroadcast hook that emits practice-state events
- Only broadcasts when student is present in a classroom
- Wired into PracticeClient to broadcast during active sessions
Step 2: Teacher sees active sessions indicator
- Add useActiveSessionsInClassroom to ClassroomTab
- Show "Practicing" badge with problem progress for active students
- Blue styling for practicing students vs green for idle
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Provides API for useActiveSessionsInClassroom hook to fetch
which students in a classroom are currently practicing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix parent enrollment creation cache invalidation (Step 7)
- Add usePlayerEnrollmentSocket for student-side enrollment updates (Step 8)
- Update parent approval route to use socket-emitter helper (Step 8)
- Fix enter/leave classroom to use centralized playerKeys (Step 9)
- Add session started/ended socket events for active sessions (Step 11)
- Teacher sees real-time session updates when students in classroom
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add studentUnenrolled event to socket system for real-time updates
- Emit socket events when teacher or parent unenrolls a student
- Handle enrollment-approved events in usePlayerPresenceSocket
- Add EnrollChildModal with reactive success state and auto-close
- Detects when teacher approves while modal is open
- Shows countdown progress bar in Done button
- Smooth fade-out transition on close
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>