- Video frame extraction via ffmpeg (4 key frames per video → vision LLM)
- Video display in FE MessageCard with HTML5 <video> player
- Reply/forward/crosspost indicator in FE + pipeline in DG/BE
- Fix: missing sanitizeAiContent + escapeXml in media path (prompt injection)
- Optimize: text-only batch results saved to DB immediately, no longer wait for media analysis
- BE mapper/schema/repo: add reference fields (is_reply, is_forward, etc.)
- Add pgCorrectedModerationsTable to shared schema
- Create backend corrections module (stats, list, create endpoints)
- Add TunerPanel with Stats, History, and Submit sub-tabs
- Add AuthOverlay gate for Tuner (admin-only, same as Live)
- Set messages as default tab
- Wire Tuner into sidebar, header, and mobile tab bar
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces several significant improvements across the backend and gateway services:
- **Data Retention**: Added an automated cleanup scheduler in `discord-gateway` to prune expired messages, attachments, and voice recordings based on configurable retention policies.
- **Observability**: Integrated `prom-client` in the `backend` service to expose Prometheus metrics via `/api/metrics` and added default Node.js runtime metrics.
- **Media Handling**: Enhanced `MediaHandler` in `discord-gateway` to support media URL resolution and improved playback status tracking.
- **API & Config**: Expanded the configuration endpoint to expose more system settings and reorganized `.env.example` for better readability.
- **Refactoring & Cleanup**:
- Removed unused `better-sqlite3` dependency.
- Refactored voice channel routing.
- Improved error handling and testing coverage with comprehensive unit tests for shared utilities and error classes.
- **Documentation**: Added `MEMORY.md` for project context.
Improve system reliability and real-time capabilities by implementing a robust lifecycle management system and adding new broadcast events for attachments and voice recordings.
- Implement asynchronous graceful shutdown in backend to close HTTP, WebSocket, Redis, and database connections.
- Add new WebSocket broadcast events: `attachment_created`, `voice_recording_started`, `voice_recording_stopped`, `voice_recording_uploaded`, and `analysis_queue_status`.
- Refactor media status handling to use boolean `playing` state instead of string-based status.
- Centralize `PageResult` and `VoiceRecording` types to improve consistency between frontend and backend.
- Update frontend API client to include `listRecordings` and handle new WebSocket event types.
- Fix type mismatches in voice command handling and media status reporting.
Implements full audit from double_queue_audit.md.
## Critical
fix(backend): reanalyze-batch — per-scope in-flight guard (messages.routes.ts)
Two concurrent admin sessions clicking 'Retry All Errors' simultaneously now
get a 409 REANALYZE_BATCH_IN_PROGRESS for the same guildId:channelId scope.
Prevents the recovery worker from being triggered twice for the same set of
error messages.
fix(discord-gateway): messageUpdate embed resolution skip (messageCapture.ts)
Discord fires messageUpdate when link previews resolve 1-2s after send
even though the message body is unchanged. Compare newContent vs
existingContent before resetting ai_status to pending and re-queueing LLM.
Eliminates a spurious duplicate analysis that could overwrite a valid result.
## High
fix(discord-gateway): scheduleAutoDelete idempotency (aiAnalyzer.ts)
Add autoDeleteInFlight Set. Both processBatch and processIndividualFallback
call scheduleAutoDelete; without the guard, a message that races through
both paths launches two concurrent attemptAutoDeleteFlaggedMessage calls,
producing a duplicate moderation-action log entry and a Discord 10008 error.
The Set is cleaned up in a .finally() block after each attempt completes.
## Medium
fix(frontend): revert optimistic pending state on HTTP failure (useMessages.ts)
Capture the prior MessageRecord inside the setMessages functional updater
(no extra useCallback deps needed). If reanalyzeMessage() throws, restore
the snapshot so the UI reflects the real DB state instead of lying.
fix(discord-gateway): exclude individual_analysis_exhausted from recovery queries
Both getConversationKeysWithIncompleteAnalysis and
getIncompleteMessagesByConversation now add a NOT LIKE guard for
individual_analysis_exhausted. Prevents an infinite recovery loop if a bug
ever writes both flags to the same row.
## Low
fix(discord-gateway): unified timer path in scheduleConversationAnalysis
Remove the separate cooldown-path that created a secondary timer calling
scheduleConversationAnalysis recursively. Replace with a single
clear-and-reset pattern where delayMs = max(cooldownRemainder+500, debounce).
Eliminates the edge case where both timers were live simultaneously.
## Misc
fix(backend): cast req.params.id to String() to satisfy Express typings
Pre-existing tsc error (string | string[]) exposed by our edit.
String() is correct; route params are always scalar strings at runtime.
- Remove collapsible toggle for AI analysis section
- Always show ai_analysis content inline
- Clean up unused ChevronDown/ChevronUp imports and showAnalysis state
- Simplify layout with flex layout instead of button + conditional div
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>