canvas is an optional dependency of jsdom used for testing. It fails to
compile on Alpine/musl due to missing <cstdint> includes in canvas 3.x.
Since canvas is not required for production or even for tests, configure
pnpm to skip its native build.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
canvas is an optional dependency of jsdom (used by vitest) and requires
native libraries (cairo, pango, pixman) to build on Alpine. Add these
dependencies to the base stage.
Also fix legacy ENV format warnings.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds an interstitial adjustment UI between camera capture and session
report, allowing users to fine-tune document cropping and rotation.
New features:
- DocumentAdjuster component with draggable corner handles
- Live preview of cropped/rotated result
- 90° rotation controls (clockwise/counter-clockwise)
- Auto-capture: automatically enters adjustment mode when detection
is locked (stable for 5+ frames with >50% stability)
New hook exports:
- cv: OpenCV reference for external use
- getBestQuadCorners(): get current detected quad corners
- captureSourceFrame(): capture video frame as canvas
Flow: Camera → Auto-capture on lock → Adjust corners/rotation → Confirm → Upload
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Analyzes cropped document orientation and auto-rotates:
- Detects text line direction via horizontal vs vertical edge analysis
- Rotates 90° if text appears sideways (vertical edges dominate)
- Checks content density top vs bottom to detect upside-down
- Rotates 180° if content is heavier at bottom (likely inverted)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds real-time document detection to the camera capture on session
summary pages. Uses OpenCV.js for edge detection and perspective
correction.
Key features:
- Multi-quad tracking: detects ALL quadrilaterals, not just largest
- Scores quads by stability over time (filters transient detections)
- Visual feedback: yellow (detecting) → green (stable) → bright green (locked)
- Auto-crops and deskews captured documents
- Falls back to raw photo if no document detected
Technical details:
- OpenCV.js (~8MB) lazy-loaded only when camera opens
- Tracks quads across frames by matching corner positions
- Filters by area (15-95% of frame) and document aspect ratios
- Locks on after 5 frames with 50%+ stability
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major refactoring of the session summary page to match the redesign plan:
Layout changes:
- Desktop (≥1200px): Two-column layout with Hero+Evidence left, Skills+Review right
- Extract individual components from monolithic SessionSummary
- Add ScrollspyNav fixed at bottom for mobile/tablet navigation
- Add scrollspy section attributes for navigation tracking
New components extracted:
- SessionHero: Celebration header, stats, practice type badges, trend indicator
- SkillsPanel: Skills breakdown by category with human-readable names
- ProblemsToReviewPanel: Auto-pause timing + problems needing attention
- TrendIndicator: Comparison to previous session accuracy
- ScrollspyNav: Fixed bottom navigation with dot indicator
- OfflineWorkSection: Photo upload zone with lightbox
- PhotoLightbox: Full-screen photo viewer with navigation
Utilities added:
- skillDisplay.ts: Human-readable skill name resolution
- trends.ts: Session trend calculations
Known issues documented for follow-up:
- Mobile section order needs adjustment (Evidence before Skills)
- Tablet layout not yet implemented (Skills+Review side-by-side)
- ScrollspyNav visibility breakpoint needs tuning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sticky practice banner in the sub-nav already provides the "Start Practice"
action, making the bottom buttons redundant:
- Remove Practice Again button from SessionSummary
- Remove Start Practice button for photo-only sessions
- Remove Start Practice button for no-session empty state
- Remove unused onPracticeAgain prop from SessionSummary interface
Also simplifies header logic since SessionSummary handles its own header
for sessions with problems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show celebration header only when just completed a session (?completed=1)
- Show session date as header when viewing historical sessions
- Add practice type badges (e.g., "Use Abacus", "Visualize") to main stats
- Pass justCompleted flag through page -> client -> SessionSummary
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create single source of truth for practice types in src/constants/practiceTypes.ts
with IDs, labels, descriptions, and icons. This makes it easy to add new
practice types app-wide.
Updated:
- SessionPartType now aliases PracticeTypeId from constants
- OfflineSessionModal uses PRACTICE_TYPES from constants
- offline-sessions API uses isValidPracticeTypeId for validation
- practiceTheme uses PracticeTypeId with default fallback for new types
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Photo-only sessions (offline practice with just photo uploads) now show
only the photos section with upload controls, not misleading 0% accuracy
and empty stats. Sessions with problems still show full stats.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove SessionPhotoGallery modal in favor of inline upload
- Add drag-and-drop support to Practice Photos section
- Upload photos immediately on file selection or camera capture
- Fullscreen camera modal using Radix Dialog
- Photos appear in grid directly on session summary page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add cancelled flag to prevent setting state after unmount and properly
cleanup streams that were acquired after component unmounted (e.g. in
React Strict Mode double-mounting).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use Radix Dialog for proper modal management
- Camera preview fills entire viewport
- Floating close button (top-right) and capture button (bottom-center)
- iOS-style capture button design
- Works fullscreen on both mobile and desktop
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed the concept of an "unstarted" camera state. When the camera
modal opens, the camera starts immediately. Just show "Starting..."
while initializing, then the capture button once ready.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Camera now starts immediately when clicking the Camera button,
eliminating the extra "Start Camera" click.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds ability to upload, view, and manage photos for practice sessions:
- New database schema for practice attachments with file metadata
- Photo upload zone component with drag & drop and camera capture
- Offline session modal for logging practice done away from the app
- Session photo gallery with lightbox viewer and upload capability
- Photo indicators on session list cards
- Inline photo thumbnails on session summary page
- APIs for creating offline sessions, uploading photos, and serving files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace count-based heuristic with actual width measurements to determine
which single-student sections flow together on the same row.
Key changes:
- Add useMeasuredCompactLayout hook that uses useLayoutEffect to measure
before paint (no flash of wrong layout)
- Hidden measurement container measures actual item widths
- ResizeObserver triggers re-measurement on container resize
- Group items by measured fit into rows
- Storybook stories demonstrate all layout scenarios including
interactive resize, various widths, and mixed compact/full sections
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Integrate "Needs Attention" section into unified compacting system
- Single attention student flows with other compact sections
- Multiple attention students get full sticky header
- Add comprehensive Storybook stories for Needs Attention scenarios:
- NeedsAttentionSingle, NeedsAttentionMultiple
- NeedsAttentionWithCompactBuckets, NeedsAttentionFullThenCompact
- NeedsAttentionRealistic, NeedsAttentionDarkMode
- Update GroupedStudentsDemo to use unified renderItems array
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add compact display for single-student categories so they flow
together on the same row instead of each taking a full row
- Hide "Present" (in-classroom) filter segment when no students present
- Update in-classroom empty state with bulk-prompt instructions
- Fix classroomId ReferenceError in ViewEmptyState component
- Add Storybook stories for GroupedCategories demonstrating
compact/full rendering patterns
- Add compact mode to StudentSelector component
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>