- recording-card: kartu aktif di-highlight (ring primary + glow pulse),
badge 'Now Playing'/'Loading'/'Paused', tombol play berubah jadi Pause
saat playing dan spinner saat loading, waveform equalizer beranimasi
(animate-eq, delay per bar) saat playing / pulse saat loading.
- recording-player: jadi now-playing panel — tombol play/pause + spinner
loading, progress bar + waktu (current/duration), status 'loading…',
audio element pindah ke sini + event onPlay/onPause/onWaiting/onCanPlay/
onPlaying/onError naik ke page.
- recordings/page: state isPlaying/isLoadingAudio + audioRef, togglePlay
(klik card lain = ganti track, klik card sama = pause/resume).
- globals.css: keyframes eq-bounce + card-glow.
Verified: FE tsc0, next build 10/10 static pages.
Voice page (connection tab):
- ListenControl baru: toggle Listen (Headphones) — mulai PcmPlayer dari
user gesture, subscribe onPcm WS, volume slider, bar level per-user
REAL dari PCM (bukan random).
- lib/audio/pcm-player.ts (baru): ScriptProcessorNode mixer — ring buffer
2s per user (hash FNV-1a sama dengan gateway), upsampling 24k→48k
linear, mix semua user ke mono, gain volume, cleanup ring diam 5s.
- useVoiceListen + hashUserId di hooks; auto-stop saat disconnect.
Recordings:
- recording-player: reset src+load+play() eksplisit (bukan autoPlay doang),
tampilkan filename + error state 'playback failed' kalau file rusak.
- recording-card: tombol Download fetch blob (CORS tele open) → objectURL
→ force download dengan nama asli; fallback buka tab baru kalau fetch
gagal; spinner saat mendownload.
Verified: FE tsc 0, next build 10/10 static pages.
Audit lanjutan: 6x 'LLM API request failed: Request was aborted' per jam.
Root cause: 9router/omniroute SELALU balas SSE (data: chunks) walau request
tanpa stream:true — SDK OpenAI non-stream menunggu FULL body sebelum parse,
jadi batch moderasi besar yang upstream-nya lambat kena timeout 30-60s dan
di-abort. llmClient sudah punya agregasi streaming (chunks → ChatCompletion).
Fix: stream:true di llmCaller (moderasi batch/individual), llmVision,
cultureLearner, userProfileLearner. Verified: SDK stream test 806ms vs
sebelumnya abort. Caller lain (recovery worker dll) lewat llmCaller sama.
Audit log produksi (sejak deploy13:38) menemukan 3 isu:
1. mediaDownloader.ts spawn /usr/bin/ffprobe + /usr/bin/ffmpeg (path keras) —
ENOENT di Nix karena binary cuma di ffmpeg-headless closure. Pakai
PATH-resolved ('ffprobe'/'ffmpeg') seperti voice-recording module
(ffmpegProcess.ts/transmitter.ts) — 5 media warning hilang.
2. individualFallbackProcessor log error 'Success' di level50 tiap fallback
BERHASIL (logModerationError dengan new Error('Success')) — ganti
logger.info dengan verdict yang sama; error log cuma untuk error asli.
3. moderationResponseParser: strip frasa penutup generik ('Tidak ada
indikasi pelanggaran.') yang masih sering dikeluarkan LLM walau prompt
melarang (277/1486 analisis mengandung frasa, termasuk hari ini).
sanitizeGenericCleanCloser hanya mencocok frasa di AKHIR, teks substantif
tetap utuh. Unit test: 6/6 pass.
Migration 0011 (add voice_recordings.transcription) when=1781388000000
lebih kecil dari 0010 (1781390000000) yang sudah ter-apply — drizzle
skip diam-diam (folderMillis <= max(created_at)), kolom transcription
tidak pernah dibuat. Recording OGG sukses tapi INSERT voice_recordings
gagal 42703 di produksi.
Fix: when=1785600000000 (> max applied 1785551832190) + apply manual
ALTER TABLE + insert row __drizzle_migrations dengan hash file yang
sama (c368acb0...) supaya gateway restart berikutnya skip (idempotent).
pnpm 11.17 mengabaikan field pnpm.onlyBuiltDependencies di package.json.
Native deps voice (@discordjs/opus, @lng2004/node-datachannel, zeromq, dll)
tidak pernah kebangun di Nix store karena flake pnpmInstall pakai
--ignore-scripts dan pnpm rebuild tanpa approval. Hasil: receiver/rekaman/
GoLive diam-diam tanpa decoder/encoder native.
pnpm approve-builds --all menulis allowBuilds:true per package di
pnpm-workspace.yaml (harus tracked — flake source cuma ikut file git).
node-crc tetap gagal build (MSRV cargo:: check) tapi tidak pernah
di-import di source — harmless.
Root cause voice tidak berfungsi di produksi: ffmpeg & yt-dlp cuma ada
di devShell, bukan di package discord-gateway. Bukti dari log gateway:
'FFmpeg/avconv not found!' saat voice:transmit:start (mic -> Discord),
yang juga mematikan music playback (StreamType.Arbitrary butuh ffmpeg)
dan segment muxing rekaman.
- buildInputs: pkgs.ffmpeg-headless + pkgs.yt-dlp
- wrapper export PATH ke keduanya sebelum exec node
Verifikasi: nix build PASS; closure berisi ffmpeg-8.1.2 + yt-dlp-2026.07.04;
wrapper PATH mengarah ke keduanya; ffmpeg/yt-dlp jalan.
Hapus tab Live + komponennya (LiveStream, ModQueue) dari halaman
dashboard — tab Stats/Users/Channels tetap. useReview tetap dipakai
messages page (review tab), jadi hook tidak dihapus.
Verifikasi: tsc PASS, biome 0 error, next build PASS.
msg.username bisa undefined saat pesan live masuk lewat WS
(message_updated Partial payload / capture tidak lengkap) ->
msg.username.charAt(0) TypeError, halaman /messages mati.
- message-card + search-panel: username?.charAt(0) ?? '?'
- created_at di-guard juga biar tidak render 'Invalid Date'
Verifikasi: tsc PASS, build PASS, biome 0 error. DB saat ini 0 row
null username (1176 total) — crash murni dari jalur WS live.
- useMessageDetail: guard attachments fetcher — revalidate bisa race
detail load, detail.data undefined saat fetcher jalan ->
TypeError 'Cannot read properties of undefined (reading channel_id)'
yang bikin halaman /messages mati (Next error boundary). Sekarang
fetcher balikin [] kalau channel_id belum ada; hapus non-null
assertion. Diverifikasi: /messages sebelumnya crash, sekarang render
dengan data asli (list, verdict, confidence, sticker, emoji).
- ResponsiveContainer (recharts 3.8): initialDimension -1 di render
pertama -> warning 'width(-1) and height(-1)'. Pakai height numerik
tetap (192/160/48) + minWidth/minHeight 0 -> calculatedHeight >0,
warning hilang; width tetap responsif via ResizeObserver. Chart
baru dirender setelah mount (useMounted) biar container punya ukuran.
Diverifikasi console: 0 warning, 0 error di dashboard & voice.
/api/chat/history 500 (relation "chatbot_messages" does not exist):
codebase di-rename mascot->chatbot (977a6f9) tapi tabel DB tidak
pernah di-migrate — backend SELECT dari chatbot_messages, DB masih
mascot_chat_messages dengan kolom mascot_response.
- Migration 0013 (idempotent): ALTER TABLE mascot_chat_messages RENAME
TO chatbot_messages, RENAME COLUMN mascot_response -> bot_response,
RENAME INDEX -> idx_chatbot_messages_user_created; journal when >
max(created_at) di __drizzle_migrations (0012)
- Diterapkan live via psql; riwayat chat lama tetap ada
- Verifikasi: GET /api/chat/history 200 + data, POST /api/chat 200
+ tersimpan (total history 1 -> 2)
Rombak data layer frontend:
- Hapus @tanstack/react-query (package.json, lockfile, provider di
dashboard layout) — ganti SWR 2.4.2 + SWRConfig (revalidateOnFocus
false, deduping 10s, no retry on 404)
- Semua hooks data ditulis ulang ke useSWR; useAction() helper baru
pengganti useMutation dengan surface kompatibel (mutate/mutateAsync/
isPending/error)
- useMessages + useMessagesHasMore share satu SWR key — probe cursor
yang tadinya dobel fetch API sekarang deduped
- WS sync (messages/media/recordings) pindah dari queryClient ke
SWR mutate dengan filter key + revalidate:false
- useMessageSearch() dipakai search-panel & search-overlay; search
overlay backdrop div -> button (fix a11y lint)
Rapikan UI + isi data:
- Tab stats recordings: placeholder 'coming soon' diganti stat asli
(total, ukuran, speaker unik, top speakers)
- Empty states konsisten via EmptyState (images/review/recordings),
EmptyState terima className
- biome check --write: 0 error, 8 warning pre-existing
- Verifikasi: tsc --noEmit PASS, next build PASS (11 halaman static),
API live dicek — semua endpoint dashboard/messages/guilds/config/
voice/media/recordings/review balikin data
QoL lanjutan dari fix60084b3: content pesan mentah masih nampilin
snowflake (<@&roleid>, <@userid>, <:emoji:id>) di log moderasi dan
prompt LLM. Sekarang dirender ke nama yang bisa dibaca:
- Gateway capture: metadata menyimpan mentionedRoles + mentionedUsers
(id+name) dari message.mentions, disimpan ke metadata JSON
- renderDiscordMentions(): <@&id> -> @RoleName, <@id> -> @Username,
<:name:id> -> :name:, fallback @role/@user — dipakai di
conversationContext (konteks LLM) dan moderationBuilders
(getAnalysisContent) sehingga LLM lihat nama role/user beneran,
bukan placeholder generik
- Frontend renderMessageContent() (mirror gateway) dipasang di semua
tempat nampilin content: message-card, message-detail(-view),
search-overlay, search-panel, users/channels section, live-stream,
mod-queue, review list; sticker-only message tetap [Sticker: name],
pesan teks+sticker kini ikut nampilin nama sticker
- tsc --noEmit PASS di gateway & frontend; renderDiscordMentions
diverifikasi manual (6 kasus: role/user/emoji/unknown/plain)
- Updated VoicePage component to utilize useVoiceConnect, useVoiceDisconnect, and useMicTransmit mutations.
- Replaced local state management with React Query's useQuery for voice status, guilds, and channels.
- Removed custom useAsync hook and replaced it with useQuery in useConfig, useStats, useUsers, useChannels, and useMessages hooks.
- Simplified useRecordings and useSpeakers hooks to leverage React Query for data fetching and mutations.
- Removed deprecated use-async hook and related code.
- Enhanced error handling and loading states across various hooks.
- Implemented AppHeader component with theme toggle and connection status.
- Created AppSidebar component for navigation with connection status indicator.
- Added MobileNav component for mobile navigation with responsive design.
- Introduced shared components: DetailStat, EmptyState, ErrorState, LoadingSkeleton, and StatCard for consistent UI.
- Developed hooks for async data fetching: useAsync, useConfig, useDashboard, useGuilds, useMedia, useMessages, useRecordings, and useVoice.
- Added chatbot API functions for sending messages and managing chat history.
- Implemented RecordingsPage to display and manage voice recordings with live updates via WebSocket.
- Created SettingsPage for user preferences, including theme toggling and server configuration display.
- Developed VoicePage for managing voice connections, including guild and channel selection, and active speaker display.
- Introduced GuildSelector component for selecting Discord guilds with error handling and loading states.
- Added utility functions for formatting numbers and bytes, and safely parsing JSON.
- Established navigation structure for the dashboard with relevant links for new features.
- Refactored MessagesPanel to utilize new UI components such as Avatar, Badge, Button, Card, Dialog, Input, Progress, ScrollArea, Select, Skeleton, and Tabs.
- Improved error handling and loading states with enhanced user feedback.
- Updated message rendering logic to support new design patterns and animations.
- Added support for image previews and improved layout for message details.
- Enhanced mobile responsiveness with useIsMobile hook adjustments.
- Cleaned up utility functions for better readability and consistency.
- Updated import statements to use consistent semicolon usage.
- Refactored component code to ensure consistent formatting and style.
- Improved readability by adding missing semicolons and adjusting spacing.
- Ensured all components follow the same structure for props and return statements.
- Added new dependencies for Next.js and lucide-react in pnpm-workspace.yaml.
- Refactored DashboardPage component to improve readability and error handling.
- Enhanced Header component to display error status with an alert icon.
- Updated MobileTabBar and Sidebar components to use a centralized tabs definition.
- Improved ChannelsView in dashboard-panel to handle channel fetching more cleanly.
- Fixed ActiveSpeaker type to use camelCase for userId.
- Updated MessagesPanel to handle guildId checks more gracefully.
- Adjusted API calls in dashboard and messages to align with backend expectations.
- Refined type definitions across various interfaces for consistency and clarity.
- Change pnpm-workspace.yaml from specific paths to services/* and packages/*
- Remove bun.lock (frontend now uses pnpm lockfile)
- Use --no-frozen-lockfile so lockfile auto-updates in CI
- Fix noImplicitAnyLet: add type to let match variable
- Fix noAssignInExpressions: use matchAll() + for-of instead of while
- Suppress useExhaustiveDependencies in mascot scroll effect
- Suppress useSemanticElements for message card click handler
Root and services/frontend each had a biome.json causing Biome to
error on nested configuration. Merged frontend-specific rules and
domains (next, react) into root biome.json, removed nested config.
- Remove auth module (auth.routes.ts, /api/auth/login)
- Remove adminAuth middleware from voice and media routes
- Remove adminAuth() function from shared middlewares
- Remove auth-related e2e test
- Clean up .env.example
- Implemented WebSocket connection management in `connection.rs` with automatic reconnection and event handling.
- Created `handlers.rs` to define WebSocket event and status enums.
- Added global CSS reset styles in `reset.css`.
- Introduced design tokens in `tokens.css` for consistent theming.
- Developed UI component styles in `ui.css`, including buttons, cards, badges, and modals.
- Added utility classes in `utilities.css` for layout, spacing, and typography.
- Introduced a new `logger` module for structured logging with levels, timestamps, and styled console output.
- Replaced ad-hoc console logging with structured logger in various modules including API client, WebSocket, auth, and feature components.
- Enhanced logging in `app.rs`, `auth.rs`, `dashboard`, `messages`, `live`, and `polish` features.
- Updated UI components to include logging for user interactions and state changes.
- Rewrote `app.css` for a premium design overhaul, introducing glassmorphism, gradients, and improved responsiveness.
- Added a new `plan.md` file outlining the scope and changes made in this commit.
deploy.sh now writes to host directories bind-mounted into containers
instead of docker exec tar-pipes (volatile). Added bind mounts to
docker-compose.yml for all 3 services (frontend, backend, gateway).
Memory: hotpatch-volatile-container.md
CI now only builds + pushes images to GitLab Container Registry.
Deploy is done manually via deploy.sh with specific SHA tag support.
Usage: ./deploy.sh [sha] # default: latest
- Changed the empty state message in ImageGrid to use a dedicated class for styling.
- Adjusted padding and border-radius for the "Guild Watcher" label in the header for better aesthetics.
- Introduced a new class for the status indicator dot and linked its visibility to the connection state.
- Updated ChannelRef struct to include optional fields for thread_id and thread_name with appropriate serialization settings.
Deploy now pulls images pinned to $CI_COMMIT_SHA instead of :latest,
eliminating race-condition deployments where a concurrent pipeline
overwrites the mutable latest tag before deploy runs.
Changes:
- docker-compose.yml: image tags use ${IMAGE_TAG:-latest} env var
- deploy step: IMAGE_TAG=$CI_COMMIT_SHA docker compose pull + up
- scp docker-compose.yml to VPS before running deploy commands
body is outside the [data-theme] scope, so var(--text-primary)
on body always resolved to the dark :root value (#e8edf5, near-white).
Elements inside [data-theme] inherited this white color in light mode,
causing white-on-white text. Fix by moving background/color to
.app-shell which is inside the theme scope.
The Discord embed metadata sometimes returns thumbnail/image fields as
plain URL strings (e.g., "thumbnail":"https://...") instead of the
expected object format ({"url":"...","width":...,"height":...}).
Since serde deserializes the entire PageResult<MessageRecord> in one call,
even one message with a string-format embed field caused the entire response
to fail, making the message section appear empty despite the API returning
valid data.
Added a custom deserializer deser_embed_media that accepts null, a string
(interpreted as URL), or an object (standard struct deserialization).
Applied to both image and thumbnail fields in EmbedInfo with #[serde(default)]
so missing fields don't error. Includes 5 unit tests covering all formats.
Backend returns metadata, ai_moderation_flags, and ai_categories as raw
JSON strings (PostgreSQL JSONB cast to string). Frontend expected parsed
structs/arrays, causing serde to fail silently — entire PageResult parse
failed and user saw empty message list with no error indicator.
Add custom deserialize_with handler (from_json_string_or_value) that
transparently handles null / direct value / JSON-string cases for the
three affected fields.
- Wiring WS events di LivePanel:
- on_voice_active_user -> update ActiveSpeakers signal
- on_media_state -> update NowPlaying signal
- on_voice_recording_uploaded -> trigger RecordingsSubPanel refresh
- on_binary -> process PCM data dan auto-start audio playback
- AudioPlaybackState: tambah Clone derive + abort AtomicBool
untuk mencegah loop leak saat teardown
- MusicSubPanel: hapus loading state yang premature reset
(race condition: loading false sebelum async selesai)
- NowPlaying: ganti prop state jadi RwSignal agar reaktif ke WS
- RecordingsSubPanel: tambah refresh_trigger signal untuk reload
dari WS event
- MascotChatbot: fetch chat history dari backend saat panel dibuka
- get_review_messages: hapus guildId param (backend ignore)
- Dashboard: ganti Effect::new dengan spawn_local untuk initial fetch agar
tidak terjadi infinite loop karena reactive dependency tracking
- Config: tambah api/config.rs dan jadikan AppConfig.monitor_guild_id
RwSignal supaya di-fetch dari backend (saat startup & setelah login)
- Messages: perbaiki messageFetch agar guild_id terbaca dari config reaktif
- Infinite scroll: tambah observer_ready signal + spawn_local trigger
agar IntersectionObserver setup setelah DOM mount; tambah fallback button
- AudioVisualizer & MicLevelMeter: tambah periodic tick signal (100ms)
agar efek re-run dan update bars/level dari shared PCM buffer
- pcm_decoder: ganti js_sys::eval dengan wasm-bindgen binding langsung ke btoa
- Add PartialEq to MessageRecord and all nested types in shared-types
- Fix String references in view! macro by cloning owned values
- Fix match arm types with .into_any() in app.rs
- Clone on_load_more before FnMut closure in message_feed.rs
- Pre-compute class strings before passing to view! in message_card.rs
- Fix lifetime issues with imgs/vids/cats by using owned Vec<AttachmentRef>
- Use separate closures for different event types in mod.rs
- Fix disabled property to use signal pattern
- Fix filter chip closure signatures
Fixed:
- Icon imports: Replaced lucide-leptos icons with SVG/emoji alternatives
- AnyView lifetime: Fixed render_emojis to use owned strings
- Missing html import in message_feed.rs
- use_messages: Removed #[component] to make it a regular function
- MessageRecord fields: Removed is_reply, is_forward, is_crosspost checks
- String wrapping in view! macros: Wrapped strings in <span> elements
- Closure handling: Changed .into_forget() to .forget() for web_sys Closure
- URL borrowing in image_grid: Clone URLs before using in view!
Remaining issues (in progress):
- String references in class attributes need owned values
- Type inference for None.into_any()
- Closure capture issues with on_load_more
- Match arm type incompatibilities
19 errors remaining out of 31 initial errors - ~61% fixed