Root cause of 'navbar mobile tak bisa pindah halaman': Next <Link>
client-side navigation is dead app-wide. A React hydration mismatch
(#418: 'server rendered text didn't match the client') is thrown by the
SSR-seeded live feeds — relative times (formatRelativeTime(e.edited_at) /
m.created_at) computed with Date.now() render slightly differently on
server vs client, which breaks the Next client router (router.push is a
no-op). The desktop NavRail worked only because it uses plain <a href>
(hard navigation bypasses the broken router).
Fixes:
- mobile-nav.tsx: use plain <a href> (NOT Next <Link>), identical to the
working sidebar NavRail, so mobile nav always navigates regardless of
router state ('ikuti cara kerja sidebar').
- Add suppressHydrationWarning to the SSR-seeded relative-time spans so
server/client drift no longer throws #418 (EditHistory, LiveModerationFeed,
messages/results + detail rows, recordings, TermGlossary,
ChannelCultureGlossary, CategoryDrilldown).
Verified on non-prod :4024 @375px: Voice/Media/Search all navigate, no #418
in console. Plan: .hermes/plans/mobile-nav-hydration-fix.md
- Root cause of 'navbar mobile tak bisa pindah halaman': the chatbot FAB
(fixed right-4 bottom-5 z-50) overlapped the rightmost mobile bottom-nav
items (z-40) and intercepted taps. Raise the FAB above the nav on mobile
(bottom above nav, md:bottom-5 on desktop) so it never blocks nav taps.
- Mobile bottom nav now mirrors the FULL desktop sidebar (all 7 items:
dashboard, messages, voice, media, recordings, moderation, analysis);
horizontally scrollable + snap-to-active on narrow screens.
- Select dropdown: clamp portal position within viewport + min-width so it
never overflows off-screen on mobile triggers near the right edge; larger
tap targets on touch.
- Input/Textarea: text-base (16px) on touch to prevent iOS auto-zoom on
focus, text-sm on >=sm; comfortable mobile min-height for chat input.
- Add .hermes/plans/mobile-nav-form-responsive.md spec.
Extract server_nick from metadata.member.displayName in backend messageMapper,
add server_nick to frontend MessageRecord type, and update messages view +
analysis view to display the member's server-specific nickname (with @username
as secondary context) instead of the global username.
- Updated RootLayout to include a noise overlay class.
- Refactored LiveModerationFeed to use CSS animations for item entry.
- Simplified AmbientCanvas by removing WebGL and using pure CSS for ambient effects.
- Converted Chatbot to use CSS for floating window animations.
- Updated Card component to include a gradient border.
- Refactored PageTransition to use CSS for fade-scale animations.
- Enhanced SectionHeader with reveal-up animation class.
- Replaced GSAP animations in NavRail with CSS animations for item entry.
- Refactored VoiceStage to use CSS for speaker node animations and pulse rings.
- Removed GSAP dependencies from use-gsap-animation hook, implementing CSS-based stagger reveal.
- Refactored ChannelCultureGlossary, LiveModerationFeed, TermGlossary, and Chatbot components to use new design tokens and styles.
- Updated button, badge, and section components to align with the new design system.
- Enhanced the visual hierarchy and accessibility of various UI elements.
- Improved responsiveness and hover states across components.
- Replaced hardcoded colors with design tokens for better maintainability.
- Cleaned up imports and removed unused hooks in ModerationView and RecordingsView.
- Updated UI elements for better visual consistency and accessibility.
- Improved performance by optimizing rendering logic and reducing unnecessary state updates.
- Added filtering functionality in ChannelCultureGlossary and TermGlossary components.
- Enhanced LiveModerationFeed with GSAP animations for smoother transitions.
- Updated chatbot component for better user experience and responsiveness.
- Refined NavRail component for cleaner code and improved navigation item rendering.
- Recordings: custom RecordingAudioPlayer (play/pause, buffering spinner,
click-to-seek, time label, eq bars, single-playback antar kartu) +
highlight kartu now-playing
- Media: thumbnail di disc hero + queue row, equalizer saat playing,
badge 'up next', label Paused vs Now playing
- MiniPlayer global di AppFrame (fixed bottom-right, hidden on /media)
menggantikan use-media-player.tsx dead provider (dihapus)
- Voice: mic level meter live (AnalyserNode RMS) + slider mic/listen volume
- Persist structured verdict (flags/severity/confidence/evidence) on
moderation_actions so the public web can show WHY a message was moderated.
- Add a persistent Qdrant archive collection (gmw_message_archive); embed
every captured message at capture time (fire-and-forget, best-effort).
- Public semantic search over the archive (backend oRPC + FE toggle on the
messages view). Both features are read-only/public and fully automatic.
Migration: 0015_add_moderation_explainability.sql
- Add shared skeleton building blocks (SkeletonHero, SkeletonMetricRow,
SkeletonPanel, SkeletonRows) and wire them into every view's initial
loading branch, replacing bare spinners for a cohesive shimmering shell.
- Polish EmptyState with a glow-ring icon chip instead of a flat icon.
- Harden .light theme: color-scheme, tuned scrollbar + selection for pale
canvas. Dark/light theme toggle (next-themes) already persisted in TopBar.