commits
🪻 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
The classes tests were creating a separate test database file but the
functions being tested still used the production database, causing tests
to fail when production data existed.
Changed to match the pattern used in other tests (auth.test.ts, etc):
- Use production database
- Track created resources in beforeEach/afterEach
- Clean up all test data after each test
- Helper functions to create and track test users/classes
💖 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Migrate biome config to version 2.3.2
- Replace explicit any types with proper interfaces
- Remove non-null assertions with proper null checks
- Apply all auto-formatting fixes
💖 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
The audio endpoint now accepts both "pending" and "completed" status, allowing admins to preview pending recordings before transcription.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced sidebar navigation with horizontal tab layout matching the admin panel design for better consistency and cleaner appearance.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced intrusive browser confirm dialogs with a progressive three-click delete system:
- First click: "Delete" → "Are you sure?"
- Second click: "Are you sure?" → "Final warning!"
- Third click: "Final warning!" → Actually deletes
Features:
- 1 second timeout between clicks before resetting
- State tracked per item (can't accidentally delete wrong item)
- Visual feedback through button text changes
- Works for both class deletion and waitlist deletion
- No blocking dialogs, cleaner UX
Implementation:
- State management with deleteState tracking id, type, clicks, and timeout
- Automatic cleanup with setTimeout
- Separate handlers for performing actual deletion
- Button text updates reactively based on state
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced text-based meeting time inputs with an intuitive visual day picker:
- New meeting-time-picker component with clickable day buttons (Mon-Sun)
- Click to select/deselect days when class meets
- Selected days highlight with primary color
- No time inputs needed - just tracks which days
- Auto-generates labels from selected days (e.g., "Monday", "Tuesday")
Student workflow:
- Waitlist form uses meeting-time-picker
- Click days when their class meets
- Data stored as JSON array of day objects
Admin workflow:
- Approval modal shows all editable fields (not just meeting times)
- Course code, name, professor, semester, year all editable
- Meeting time picker pre-filled with student's selections
- Admin can edit any field before creating class
- Error messages now display inside modal (not behind it)
- Better error logging for debugging
Technical improvements:
- Fixed SQL query to remove section column reference
- Proper Lit reactivity with updated() lifecycle
- Meeting times load correctly from waitlist data
- Clean separation between MeetingTime interface and picker component
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students can now request classes that don't exist yet:
- Search for classes, if not found, request it via waitlist form
- Waitlist form collects: course code, name, professor, semester, year, meeting times, and optional additional info
- Meeting times field allows multiple entries (e.g., "Monday Lecture", "Wednesday Lab")
- Pressing Enter in meeting time field adds another field instead of submitting
Admin workflow for waitlist management:
- New "Classes" tab in admin panel with two sub-tabs: Classes and Waitlist
- Waitlist shows all pending requests with badge count
- "Approve & Create Class" opens modal pre-filled with student's request
- Admin can edit meeting times before creating the class
- Creates class with meeting times, removes from waitlist, and switches to Classes tab
- Can also delete waitlist requests
Additional changes:
- Removed section field completely (not used, meeting times are more meaningful)
- Database migrations: v3 (waitlist table), v4 (meeting_times column), v5 (drop section)
- Meeting times stored as JSON array in waitlist, created as separate records when approved
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students now search for classes and select from results:
- Added section column to classes table (migration v2)
- Search by course code returns all matching classes
- Results show professor, section, semester, year
- Students pick specific section/professor when joining
- Updated Class interface to include section field
- Replaced joinClassByCode with searchClassesByCourseCode + joinClass
- New GET /api/classes/search?q=<query> endpoint
- Updated POST /api/classes/join to accept class_id
- Redesigned modal with search form and clickable result cards
Better UX for multi-section courses with different professors.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students can now join classes by entering a class code:
- Created class-registration-modal component with form
- Added joinClassByCode() function in classes.ts
- Added POST /api/classes/join API endpoint
- Integrated modal into classes-overview component
- Classes are joined by their ID (used as the class code)
- Validates class exists, not archived, and user not already enrolled
UI shows "+ Register for Class" card that opens the modal.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Updated the auth component user menu to link to /classes instead of
/transcribe, aligning with the new class-based workflow.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced table-based layouts with modern card components for better UX:
- Transcriptions tab now uses admin-transcriptions component
- Users tab now uses admin-users component
- Both tabs feature search, filtering, and click-to-view modals
- Removed ~500 lines of duplicate table-rendering code
- Cleaned up CSS (removed table/search/sort styles)
- Simplified JavaScript to just handle stats and modal events
All three admin tabs now have consistent card-based design.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Replace table layout with card-based grid layout
- Add embedded audio player to each card for preview
- Organize metadata in labeled sections (Class, Meeting Time, Uploader, Upload Date)
- Improve visual hierarchy with larger filename and better spacing
- Make approve/delete buttons full-width for easier interaction
- Audio player uses /api/transcriptions/:id/audio endpoint
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Create admin-pending-recordings component
- Display all pending recordings across all classes
- Show class info (course code, name), meeting time, uploader, upload date
- Approve & Transcribe button to select recordings for transcription
- Delete button to remove unwanted recordings
- Add new "Pending Recordings" tab as first tab in admin panel
- Fetch recordings from all classes and aggregate them
- Call PUT /api/transcripts/:id/select to approve recordings
- Reload data after approve/delete actions
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Create upload-recording-modal component with file picker
- Meeting time selector dropdown
- File validation (audio formats, 100MB max)
- Upload to /api/transcriptions with class_id and meeting_time_id
- Modal with overlay, close on click outside or X button
- Disable upload when class is archived
- Reload class data after successful upload
- Upload status indicator (uploading state)
- Error display for failed uploads
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Rewrite classes-overview component to fetch from /api/classes
- Group classes by semester/year with section headers
- Display class cards with course code, name, professor, archived badge
- Add register for class card (placeholder)
- Rewrite class-view component to fetch from /api/classes/:id
- Display class info, meeting times, and recordings
- Show recording status badges (pending/selected/processing/completed/failed)
- Real-time transcription progress via SSE
- VTT viewer for completed transcriptions
- Archive banner and upload restrictions for archived classes
- Search functionality for recordings
- Update routing from /class/:className to /classes/* (wildcard for HTML routes)
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Consolidate migrations into single schema with class system
- Add tables: classes, class_members, meeting_times
- Update transcriptions table with class_id, meeting_time_id, status
- Add class management lib with CRUD operations
- Add complete REST API for classes, enrollment, meetings
- Implement admin-driven transcription selection workflow
- Recordings default to 'pending' status (admin must select to transcribe)
- Add enrollment verification for class access
- Add archive support for classes
- Add getUserByEmail() to auth lib
- Include test suite and test script
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Include 'newemail%' pattern in cleanup to catch users whose emails
were updated during email change tests, preventing duplicate email
errors on subsequent test runs.
- Make rate limit tests more resilient by checking if rate limiting triggers
rather than expecting exact iteration counts, preventing failures from
rate limit accumulation across tests
- Fix file upload size limit test to use 101MB instead of 26MB to properly
exceed the actual 100MB limit
- Update error message to show "100MB" instead of "25MB" to match the actual
MAX_FILE_SIZE constant
The previous cleanup only deleted rate limit attempts with keys containing
'test' or 'admin', but IP-based rate limits have keys like 'register:ip:unknown'
which don't match this pattern. This caused rate limit tests to fail because
attempts accumulated across tests.
Now clearing all rate limit attempts between tests to ensure proper isolation.
The classes tests were creating a separate test database file but the
functions being tested still used the production database, causing tests
to fail when production data existed.
Changed to match the pattern used in other tests (auth.test.ts, etc):
- Use production database
- Track created resources in beforeEach/afterEach
- Clean up all test data after each test
- Helper functions to create and track test users/classes
💖 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced intrusive browser confirm dialogs with a progressive three-click delete system:
- First click: "Delete" → "Are you sure?"
- Second click: "Are you sure?" → "Final warning!"
- Third click: "Final warning!" → Actually deletes
Features:
- 1 second timeout between clicks before resetting
- State tracked per item (can't accidentally delete wrong item)
- Visual feedback through button text changes
- Works for both class deletion and waitlist deletion
- No blocking dialogs, cleaner UX
Implementation:
- State management with deleteState tracking id, type, clicks, and timeout
- Automatic cleanup with setTimeout
- Separate handlers for performing actual deletion
- Button text updates reactively based on state
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced text-based meeting time inputs with an intuitive visual day picker:
- New meeting-time-picker component with clickable day buttons (Mon-Sun)
- Click to select/deselect days when class meets
- Selected days highlight with primary color
- No time inputs needed - just tracks which days
- Auto-generates labels from selected days (e.g., "Monday", "Tuesday")
Student workflow:
- Waitlist form uses meeting-time-picker
- Click days when their class meets
- Data stored as JSON array of day objects
Admin workflow:
- Approval modal shows all editable fields (not just meeting times)
- Course code, name, professor, semester, year all editable
- Meeting time picker pre-filled with student's selections
- Admin can edit any field before creating class
- Error messages now display inside modal (not behind it)
- Better error logging for debugging
Technical improvements:
- Fixed SQL query to remove section column reference
- Proper Lit reactivity with updated() lifecycle
- Meeting times load correctly from waitlist data
- Clean separation between MeetingTime interface and picker component
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students can now request classes that don't exist yet:
- Search for classes, if not found, request it via waitlist form
- Waitlist form collects: course code, name, professor, semester, year, meeting times, and optional additional info
- Meeting times field allows multiple entries (e.g., "Monday Lecture", "Wednesday Lab")
- Pressing Enter in meeting time field adds another field instead of submitting
Admin workflow for waitlist management:
- New "Classes" tab in admin panel with two sub-tabs: Classes and Waitlist
- Waitlist shows all pending requests with badge count
- "Approve & Create Class" opens modal pre-filled with student's request
- Admin can edit meeting times before creating the class
- Creates class with meeting times, removes from waitlist, and switches to Classes tab
- Can also delete waitlist requests
Additional changes:
- Removed section field completely (not used, meeting times are more meaningful)
- Database migrations: v3 (waitlist table), v4 (meeting_times column), v5 (drop section)
- Meeting times stored as JSON array in waitlist, created as separate records when approved
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students now search for classes and select from results:
- Added section column to classes table (migration v2)
- Search by course code returns all matching classes
- Results show professor, section, semester, year
- Students pick specific section/professor when joining
- Updated Class interface to include section field
- Replaced joinClassByCode with searchClassesByCourseCode + joinClass
- New GET /api/classes/search?q=<query> endpoint
- Updated POST /api/classes/join to accept class_id
- Redesigned modal with search form and clickable result cards
Better UX for multi-section courses with different professors.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Students can now join classes by entering a class code:
- Created class-registration-modal component with form
- Added joinClassByCode() function in classes.ts
- Added POST /api/classes/join API endpoint
- Integrated modal into classes-overview component
- Classes are joined by their ID (used as the class code)
- Validates class exists, not archived, and user not already enrolled
UI shows "+ Register for Class" card that opens the modal.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
Replaced table-based layouts with modern card components for better UX:
- Transcriptions tab now uses admin-transcriptions component
- Users tab now uses admin-users component
- Both tabs feature search, filtering, and click-to-view modals
- Removed ~500 lines of duplicate table-rendering code
- Cleaned up CSS (removed table/search/sort styles)
- Simplified JavaScript to just handle stats and modal events
All three admin tabs now have consistent card-based design.
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Replace table layout with card-based grid layout
- Add embedded audio player to each card for preview
- Organize metadata in labeled sections (Class, Meeting Time, Uploader, Upload Date)
- Improve visual hierarchy with larger filename and better spacing
- Make approve/delete buttons full-width for easier interaction
- Audio player uses /api/transcriptions/:id/audio endpoint
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Create admin-pending-recordings component
- Display all pending recordings across all classes
- Show class info (course code, name), meeting time, uploader, upload date
- Approve & Transcribe button to select recordings for transcription
- Delete button to remove unwanted recordings
- Add new "Pending Recordings" tab as first tab in admin panel
- Fetch recordings from all classes and aggregate them
- Call PUT /api/transcripts/:id/select to approve recordings
- Reload data after approve/delete actions
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Create upload-recording-modal component with file picker
- Meeting time selector dropdown
- File validation (audio formats, 100MB max)
- Upload to /api/transcriptions with class_id and meeting_time_id
- Modal with overlay, close on click outside or X button
- Disable upload when class is archived
- Reload class data after successful upload
- Upload status indicator (uploading state)
- Error display for failed uploads
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Rewrite classes-overview component to fetch from /api/classes
- Group classes by semester/year with section headers
- Display class cards with course code, name, professor, archived badge
- Add register for class card (placeholder)
- Rewrite class-view component to fetch from /api/classes/:id
- Display class info, meeting times, and recordings
- Show recording status badges (pending/selected/processing/completed/failed)
- Real-time transcription progress via SSE
- VTT viewer for completed transcriptions
- Archive banner and upload restrictions for archived classes
- Search functionality for recordings
- Update routing from /class/:className to /classes/* (wildcard for HTML routes)
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Consolidate migrations into single schema with class system
- Add tables: classes, class_members, meeting_times
- Update transcriptions table with class_id, meeting_time_id, status
- Add class management lib with CRUD operations
- Add complete REST API for classes, enrollment, meetings
- Implement admin-driven transcription selection workflow
- Recordings default to 'pending' status (admin must select to transcribe)
- Add enrollment verification for class access
- Add archive support for classes
- Add getUserByEmail() to auth lib
- Include test suite and test script
💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
- Make rate limit tests more resilient by checking if rate limiting triggers
rather than expecting exact iteration counts, preventing failures from
rate limit accumulation across tests
- Fix file upload size limit test to use 101MB instead of 26MB to properly
exceed the actual 100MB limit
- Update error message to show "100MB" instead of "25MB" to match the actual
MAX_FILE_SIZE constant
The previous cleanup only deleted rate limit attempts with keys containing
'test' or 'admin', but IP-based rate limits have keys like 'register:ip:unknown'
which don't match this pattern. This caused rate limit tests to fail because
attempts accumulated across tests.
Now clearing all rate limit attempts between tests to ensure proper isolation.