Wire the existing useLoadMore + useMessagesHasMore pagination hooks into the
Messages view: add a "↑ Load older messages" button at the top of the list and
auto-load the next (older) page when the user scrolls to the top. Backend
messages.list already returns a created_at-based nextCursor (DESC order), so
older pages are just subsequent cursors. Newest-first live feed is preserved;
the load-older control is hidden during search.
Adds per-message AI moderation analysis time (ai_analysis_duration_ms)
so operators can see how long the LLM took to moderate each message.
Gateway:
- messagesTable: new ai_analysis_duration_ms (bigint) column.
- AIAnalysisUpdate + buildAIAnalysisSet: carry analysisDurationMs through
both single and bulk update paths.
- ai-analysis-worker: measure wall-clock time around runModerationAnalysis
and attach it to every result in the batch.
Backend:
- Mirror schema column; messageMapper maps ai_analysis_duration_ms;
moderation-types + MappedMessage expose it.
Frontend:
- message.ts type gains ai_analysis_duration_ms.
- AiBadge (messages view) shows 'status · 1.2s' when duration is present;
analysis view badge mirrors the same formatting.
DB:
- scripts/add-ai-analysis-duration.sql (idempotent ADD COLUMN IF NOT EXISTS).
No behavior change for moderation logic; null until new gateway build
records values.
Rombak total alur data frontend: dari static-export CSR (tiap browser
fetch sendiri + akumulasi state voice per-tab) jadi server-side rendering.
Frontend (Next.js):
- next.config: output export -> standalone; halaman jadi server components
- server data layer baru src/lib/api/server.ts (GMW_BACKEND_URL, no window)
- dashboard/media/messages/moderation/recordings/voice page -> RSC yang
fetch backend di render-time, seed ke client view (SWR fallbackData)
- hook-hook utama terima initialData -> first paint data server, revalidate
SWR setelahnya, tanpa spinner-blank-load
- messages: guild/channel/tab/selected dibaca dari URL di server, page awal
di-fetch server-side
Shared realtime state (voice) server-authoritative:
- backend src/modules/voice/live-speaker.ts: agregat voice_active_user dari
gateway jadi snapshot authoritatif (single source of truth semua browser)
- GET /api/voice/status kini include activeSpeakers
- WS initial states kirim voice_state snapshot saat connect (late join
langsung dapat state yang sama, bukan daftar kosong)
- useSpeakers seed dari server snapshot + voice_state full-replace +
voice_active_user delta upsert
Deploy:
- flake.nix: frontend package build SSR standalone (server.js wrapper,
GMW_FRONTEND_PORT=4017); proxy nginx template proxy / -> Next server,
/api + /ws tetap ke backend :4001