Files
GMW/services/frontend/AGENTS.md
T
asepharyana 14bd20f072 fix(frontend): mobile navbar navigation + SSR hydration mismatch
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
2026-08-28 09:57:49 +07:00

3.7 KiB

Bete Frontend — Project Overview

Next.js 16 (App Router), React 19, TypeScript strict, Tailwind v4, shadcn/ui, base-ui.

Key points:

  • Server-side rendered (SSR)output: "standalone" in next.config.ts; pages are React Server Components that fetch initial data from the backend at render-time, then hydrate interactive client components (no blank-spinner-first-load).
  • Server data layer at src/lib/api/server.ts — server-only fetchers that call the backend directly via GMW_BACKEND_URL (default http://127.0.0.1:4001). Never import from a client component.
  • API client at src/lib/api/client.ts — browser-side fetch for live ops, same-origin through the reverse proxy.
  • WebSocket at src/lib/ws/ — auto-reconnecting client with typed event subscriptions. Realtime state (voice, media, messages) stays client-side.
  • Shared realtime state is server-authoritative: the backend aggregates the gateway's voice_active_user deltas into a live speaker snapshot (GET /api/voice/statusactiveSpeakers, plus WS voice_state sent on connect). Every browser converges on the same voice state; useSpeakers seeds from the server snapshot instead of accumulating per-tab.
  • No authentication: all endpoints are public

Data flow (match these — do not invent endpoints)

Discord → discord-gateway → Redis pub/sub → backend (Express :4001) ←→ Next.js SSR
                                                ↑ REST /api/*        (server: GMW_BACKEND_URL
                                                └ WS /ws (events + PCM binary)  127.0.0.1:4001)
                                                      ↑ browser WS (same-origin /ws)
  • Rendering: gmw-proxy nginx (:4009) proxies / → Next standalone server (:4017, node .next/standalone/server.js), and /api + /ws → backend :4001. Public host: imphnen.asepharyana.my.id (Caddy reverse proxy → :4009).
  • SSR seed pattern: each page.tsx is a server component that fetches via src/lib/api/server.ts and passes typed data to a view.tsx client component; the hooks take initialData as SWR fallbackData.
  • Local dev overrides: NEXT_PUBLIC_API_URL and NEXT_PUBLIC_WS_URL for the browser; GMW_BACKEND_URL for the server.
  • Never hardcode a host in api/ws clients — same-origin/env or GMW_BACKEND_URL only.

Backend response shapes that bite

  • GET /api/chat/history{ history: ChatbotHistoryRow[], total } where each row is { id, user_id, user_message, bot_response, context, created_at }. Map rows to display messages in chatbot-context.tsx.
  • message_deleted WS payload → { id, deleted_at } (an object, not a string).
  • voice_recording_uploaded WS payload has no duration_bytes (REST rows do).
  • Dashboard endpoints: /api/dashboard/stats|users|channels (+ /:id details).
  • Channel/guild names live inside message.metadata JSON (channel.channelName), not top-level.
  • GET /api/voice/status now includes activeSpeakers (authoritative shared snapshot from src/modules/voice/live-speaker.ts on the backend).

This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.