Commit Graph

3169 Commits

Author SHA1 Message Date
Thomas Hallock 78a63e35e3 feat(classroom): consolidate filter pill to single-row design
- 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>
2025-12-30 10:04:51 -06:00
Thomas Hallock da92289ed1 feat(classroom): integrate create student form into unified add-student modal
- 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>
2025-12-30 09:42:45 -06:00
Thomas Hallock dca696a29f feat(classroom): add unified add-student modal with two-column layout
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>
2025-12-30 09:03:26 -06:00
Thomas Hallock a5e5788fa9 feat(storybook): add TeacherClassroomCard stories
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>
2025-12-30 08:05:45 -06:00
Thomas Hallock a8f55c9f4f docs: document migration file modification failure pattern
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>
2025-12-30 07:56:09 -06:00
Thomas Hallock 474c4da05a feat(practice): implement retry wrong problems system
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>
2025-12-29 21:56:48 -06:00
Thomas Hallock a6584143eb fix(practice): always show add student FAB button
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>
2025-12-29 18:31:09 -06:00
Thomas Hallock 4d6adf359e feat(classroom): add unified TeacherClassroomCard with auto-enrollment
- 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>
2025-12-29 18:26:47 -06:00
Thomas Hallock 5fee1297e1 fix(practice): allow teachers to create student profiles
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>
2025-12-29 16:44:53 -06:00
Thomas Hallock de39ab52cc feat(classroom): implement entry prompts system
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>
2025-12-29 16:41:47 -06:00
Thomas Hallock ece319738b fix(practice): show active sessions for teacher's own children
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>
2025-12-29 11:36:06 -06:00
Thomas Hallock 91d6d6a1b6 feat(observer): add live active session item to history list
- 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>
2025-12-29 10:05:57 -06:00
Thomas Hallock 98a69f1f80 fix(share): use getShareUrl for correct production URLs
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>
2025-12-28 18:05:51 -06:00
Thomas Hallock aab7469d9e fix(observer): seed results panel with full session history
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>
2025-12-28 17:51:36 -06:00
Thomas Hallock 3ac7b460ec feat(observer): implement shareable session observation links
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>
2025-12-28 17:46:34 -06:00
Thomas Hallock 8527f892e2 feat(observer): add live results panel and session progress indicator
- 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>
2025-12-28 16:49:00 -06:00
Thomas Hallock d06048bc2c
Merge pull request #14 from antialias/codex/determine-auto-pause-timer-reset-criteria 2025-12-28 15:12:10 -06:00
Thomas Hallock 01a606af4e Reset auto-pause timer on digit input 2025-12-28 15:11:48 -06:00
Thomas Hallock ca1ed1980a
Merge pull request #13 from antialias/codex/add-full-screen-toggle-for-observation-modal 2025-12-28 14:59:06 -06:00
Thomas Hallock cd259c9c58 Add full-screen practice observation view 2025-12-28 14:58:56 -06:00
Thomas Hallock 1708899183
Merge pull request #12 from antialias/codex/define-input-conditions-for-submission 2025-12-28 14:32:11 -06:00
Thomas Hallock 9f86077bef Allow manual submit input after correction threshold 2025-12-28 14:31:54 -06:00
Thomas Hallock c0e63ff68b fix(practice): real-time progress in observer modal + numeric answer comparison
- 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>
2025-12-28 12:18:09 -06:00
Thomas Hallock b31aba7aa3 refactor(family): migrate FamilyCodeDisplay to Radix Dialog
- Replace custom modal with @radix-ui/react-dialog
- Adds proper focus trapping, escape key handling, ARIA attributes
- Improves accessibility for screen readers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 11:26:21 -06:00
Thomas Hallock c8f2984d7b refactor(practice): improve relationship display and dashboard components
- DashboardClient: refactor session observation and error boundary handling
- PracticeSubNav: streamline relationship indicator integration
- RelationshipCard: improve layout and styling
- RelationshipIndicator: simplify component structure
- useStudentRelationship: cleanup hook implementation
- Export relationship components from practice index

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 11:20:01 -06:00
Thomas Hallock 6def610877 fix(practice): use Next.js Link for student tiles + fix session observer z-index
- 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>
2025-12-28 11:18:25 -06:00
Thomas Hallock 07484fdfac feat(practice): parent session observation + relationship UI + error boundaries
- 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>
2025-12-28 10:29:17 -06:00
github-actions[bot] c631e10728 🎨 Update template examples and crop mark gallery
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>
2025-12-28 02:47:00 +00:00
Thomas Hallock 7b82995664 feat: enable parents to observe children's practice sessions
- 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>
2025-12-27 19:52:37 -06:00
Thomas Hallock d6e369f9dc feat: API authorization audit + teacher enrollment UI + share codes
Security:
- Add authorization checks to curriculum API endpoints (session plans, skills, record-game)
- Add e2e tests for API authorization (positive and negative cases)
- Fix missing player_stats table migration

Classroom:
- Add TeacherEnrollmentSection for teachers to approve parent enrollment requests
- Add share code system with ShareCodePanel component and useShareCode hook
- Add /join/classroom/[code] and /join/family/[code] pages
- Remove dead code: ClassroomDashboard, ClassroomTab, StudentManagerTab

UI:
- Update StudentFilterBar and StudentSelector styling
- Fix PageTransitionOverlay z-index
- Minor chart and banner improvements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 19:27:09 -06:00
Thomas Hallock 52df7f4697 fix: allow teacher-parents to enroll their children in other classrooms
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>
2025-12-27 12:29:46 -06:00
Thomas Hallock 2f1b9df9d9 feat(classroom): integrate Enter Classroom into StudentActionMenu
- 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>
2025-12-27 12:14:30 -06:00
Thomas Hallock bf262e7d53 feat(practice): add StudentActionMenu to dashboard + fix z-index layering
- 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>
2025-12-27 10:02:29 -06:00
Thomas Hallock fd1df93a8f perf: reduce practice page dev bundle from 47MB to 115KB
- 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>
2025-12-27 08:02:08 -06:00
Thomas Hallock d1176da9aa feat(practice): add mini start practice banner to QuickLook modal
- 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>
2025-12-26 22:58:07 -06:00
Thomas Hallock cb8b0dff67 feat(practice): add smooth fullscreen transition from QuickLook to dashboard
- 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>
2025-12-26 22:29:36 -06:00
Thomas Hallock 0ba1551fea feat(practice): polish unified student list with keyboard nav and mobile UX
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>
2025-12-26 21:31:52 -06:00
Thomas Hallock 576abcb89e refactor(practice): unify student actions with shared useStudentActions hook
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>
2025-12-26 19:00:36 -06:00
Thomas Hallock 1b3dcbe14f docs: add rule about never directly modifying database schema
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>
2025-12-26 12:41:39 -06:00
Thomas Hallock 9d8b5e1148 fix(db): add missing is_paused column to session_plans
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>
2025-12-26 12:28:08 -06:00
Thomas Hallock ccea0f86ac feat(classroom): implement teacher-initiated pause and fix manual pause
- Add teacher pause/resume functionality to SessionObserverModal
- Extend PauseInfo interface with 'teacher' reason and teacherMessage
- Handle session-paused/session-resumed WebSocket events in student client
- Update SessionPausedModal UI for teacher-initiated pauses:
  - Show teacher emoji (👩‍🏫) and custom message
  - Disable resume button (only teacher can resume)
- Fix manual pause not showing modal when clicking HUD dropdown
- Add pause columns to session_plans schema (isPaused, pausedAt, pausedBy, pauseReason)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 18:43:15 -06:00
Thomas Hallock 2f7002e575 feat(classroom): implement two-way abacus sync for session observation (Step 5)
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>
2025-12-25 18:03:04 -06:00
Thomas Hallock 962a52d756 fix(classroom): auto-transition tutorial→session observation + fix NaN display
- 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>
2025-12-25 16:42:25 -06:00
Thomas Hallock 4b7387905d feat(classroom): implement real-time skill tutorial observation
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>
2025-12-25 15:55:35 -06:00
Thomas Hallock fb73e85f2d fix(classroom): broadcast digit-by-digit answer and correct phase indicator
- 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>
2025-12-25 13:41:10 -06:00
Thomas Hallock 2feb6844a4 feat(classroom): implement real-time session observation (Step 3)
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>
2025-12-25 13:29:59 -06:00
Thomas Hallock 9636f7f44a feat(classroom): add session broadcast and active session indicators
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>
2025-12-25 08:24:15 -06:00
Thomas Hallock 07f6bb7f9c feat(classroom): add active sessions API endpoint
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>
2025-12-25 07:57:43 -06:00
Thomas Hallock 2015494c0e feat(classroom): complete reactivity fixes (Steps 7-11)
- 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>
2025-12-25 07:54:57 -06:00
Thomas Hallock a0693e9084 feat(classroom): add real-time enrollment/unenrollment reactivity
- 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>
2025-12-25 07:30:11 -06:00