commits
Admin endpoints now return clean arrays instead of paginated objects:
- /api/admin/transcriptions â returns array (pagination happens behind scenes)
- /api/admin/users â returns array (pagination happens behind scenes)
- /api/classes (admin) â returns flat array instead of grouped object
Pagination still works via ?limit=50&cursor=... params but frontend just
sees clean arrays. This keeps the API simple while maintaining scalability.
User-facing /api/classes still returns grouped format for better UX.
Backend supports pagination, frontend can add "Load More" UI later.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Admin transcriptions and users components now extract data from paginated
response format { data: [...], pagination: {...} } with fallback to direct
array for backward compatibility.
Convert all message-only responses to include success field for consistency:
- { message: "..." } â { success: true, message: "..." }
Standardized patterns:
- Success operations: { success: true }
- Success with message: { success: true, message: "..." }
- Success with data: { success: true, data: {...}, message?: "..." }
- Error responses: { error: "..." } (unchanged)
- Data responses: Direct objects like { user, jobs, classes } (unchanged)
Changes:
- Email verification success response
- Verification email sent response
- Verification code sent response
- Password reset link sent response
- Password reset success response
Added comprehensive test suite documenting API response standards.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Implement cursor-based pagination with base64url-encoded cursors for:
- GET /api/transcriptions (user's transcriptions)
- GET /api/admin/transcriptions (all transcriptions)
- GET /api/admin/users (all users with stats)
- GET /api/classes (user's classes)
Response format: { data: [...], pagination: { limit, hasMore, nextCursor } }
Query params: ?limit=50&cursor=<base64url-string>
Cursors are opaque, URL-safe, short strings (20-40 chars) that prevent
page drift and support efficient pagination at any scale.
Implementation:
- Created src/lib/cursor.ts for encoding/decoding cursors
- Updated getAllTranscriptions, getAllUsersWithStats, getClassesForUser
- Default limit: 50, max: 100
- Comprehensive test coverage (23 tests)
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
- Session cleanup: Run every 15 minutes instead of hourly (#26)
- Health check: Add detailed status for database, whisper, and storage (#25)
- Admin email change: Require verification unless skipVerification flag set (#36)
- Transcription selection: Validate existence and status before selecting (#28)
These changes improve system monitoring, security, and error handling.
Added detailed documentation for tangled-cli including:
- Authentication and installation
- Creating, listing, showing, editing issues
- Commenting on issues
- Repository commands
- Priority label filtering
- JSON output format
- Known issues with the CLI
Changed from semi-realistic looking values to clearly fake
placeholders using 'paste_your_*_here' format to prevent
confusion with real credentials.
Fixes issue #24
Check if class exists when toggling archive status and throw error
if not found. Prevents silent failures when archiving non-existent
classes.
Fixes issue #28 (partial)
- File uploads: 20/hour
- User settings (name, avatar, notifications): 10 per 5 min
- Session deletion: 20/hour
- Passkey operations: 10 per 5 min (auth), 10/hour (update/delete)
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Implements graceful shutdown on SIGTERM/SIGINT that:
- Stops accepting new requests via server.stop()
- Closes all active SSE streams (sync will reconnect on restart)
- Stops transcription service streams to Murmur
- Clears cleanup intervals (sessions, sync, files)
- Closes database connections cleanly
Prevents database corruption and ensures clean shutdown.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
đĒģ 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>
Admin endpoints now return clean arrays instead of paginated objects:
- /api/admin/transcriptions â returns array (pagination happens behind scenes)
- /api/admin/users â returns array (pagination happens behind scenes)
- /api/classes (admin) â returns flat array instead of grouped object
Pagination still works via ?limit=50&cursor=... params but frontend just
sees clean arrays. This keeps the API simple while maintaining scalability.
User-facing /api/classes still returns grouped format for better UX.
Backend supports pagination, frontend can add "Load More" UI later.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Convert all message-only responses to include success field for consistency:
- { message: "..." } â { success: true, message: "..." }
Standardized patterns:
- Success operations: { success: true }
- Success with message: { success: true, message: "..." }
- Success with data: { success: true, data: {...}, message?: "..." }
- Error responses: { error: "..." } (unchanged)
- Data responses: Direct objects like { user, jobs, classes } (unchanged)
Changes:
- Email verification success response
- Verification email sent response
- Verification code sent response
- Password reset link sent response
- Password reset success response
Added comprehensive test suite documenting API response standards.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Implement cursor-based pagination with base64url-encoded cursors for:
- GET /api/transcriptions (user's transcriptions)
- GET /api/admin/transcriptions (all transcriptions)
- GET /api/admin/users (all users with stats)
- GET /api/classes (user's classes)
Response format: { data: [...], pagination: { limit, hasMore, nextCursor } }
Query params: ?limit=50&cursor=<base64url-string>
Cursors are opaque, URL-safe, short strings (20-40 chars) that prevent
page drift and support efficient pagination at any scale.
Implementation:
- Created src/lib/cursor.ts for encoding/decoding cursors
- Updated getAllTranscriptions, getAllUsersWithStats, getClassesForUser
- Default limit: 50, max: 100
- Comprehensive test coverage (23 tests)
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
- Session cleanup: Run every 15 minutes instead of hourly (#26)
- Health check: Add detailed status for database, whisper, and storage (#25)
- Admin email change: Require verification unless skipVerification flag set (#36)
- Transcription selection: Validate existence and status before selecting (#28)
These changes improve system monitoring, security, and error handling.
Implements graceful shutdown on SIGTERM/SIGINT that:
- Stops accepting new requests via server.stop()
- Closes all active SSE streams (sync will reconnect on restart)
- Stops transcription service streams to Murmur
- Clears cleanup intervals (sessions, sync, files)
- Closes database connections cleanly
Prevents database corruption and ensures clean shutdown.
đ Generated with Crush
Assisted-by: Claude Sonnet 4.5 via 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>
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>