feat: Implement video capture improvements and mobile navigation fixes

- Added eager selfbot voice connection establishment to ensure video capture works seamlessly during voice channel joins.
- Introduced a manual video watch command for selfbots to allow operators to initiate screen recording of other members' streams.
- Enhanced video recording functionality to split recordings into segments based on user activity, similar to voice recordings.
- Fixed mobile navigation issues by extending the navbar to include all items and ensuring responsive form controls.
- Standardized error handling across frontend components to improve user experience during failures.
This commit is contained in:
asepharyana
2026-09-11 18:56:28 +07:00
parent 0eb089d9dc
commit b3418ad799
26 changed files with 0 additions and 0 deletions
@@ -0,0 +1,81 @@
# GMW FE Rombak — Extend Tactical-HUD/GSAP Treatment Wide (2026-08-26)
## Context / lessons that constrain this plan
- 2026-08-24: full novel navigation metaphor (constellation/three.js) was
SHIPPED green on every gate then REJECTED by user for hurting usability.
Rolled back entirely. Lesson: do NOT invent a new nav metaphor.
- 2026-08-24: accepted direction = monochrome theme + game-menu style sidebar
animation ON TOP of the familiar shell (rail/topbar/panel kept).
- 2026-08-25: `dashboard` and `media` routes were already revamped into a
"Tactical HUD" style with GSAP (`@gsap/react` useGSAP) reactive stages —
commits 183a510, cab451f. This is the established, ACCEPTED visual language.
- Remaining routes (`channels`, `voice`, `messages`, `moderation`,
`recordings`, `analysis`, `glossary`) have NOT received the tactical-HUD/GSAP
treatment — last touch was an error-handling/state pass (31e303c) or the
constellation revert (eda5c75).
## Scope of THIS request
User: "rombak fe dengan sekeren mungkin tapi tidak menyusahkan user, pewarnaan
sesuai tidak berlebihan, pakai gsap, sekreatif mungkin."
Read against the history above, this means: EXTEND the already-accepted
tactical-HUD + GSAP + monochrome visual language to the remaining routes,
adding creative micro-interactions and view-specific "reactive stage" touches
— NOT a new structural/navigation metaphor, NOT a color explosion.
## Hard constraints (do not violate)
1. Keep the existing shell: top bar / nav rail / mobile bottom dock. No new
navigation metaphor.
2. Monochrome/greyscale token system stays as-is (`--color-signal/amber/
vermilion` luminance greys). Do not introduce saturated colors; accent use
stays restrained (used sparingly for state: active/alert/success).
3. GSAP via `@gsap/react` `useGSAP({ scope, dependencies })` + `clearProps` on
cleanup, per `dashboard-rombak/references/tactical-hud-gsap-pattern.md`.
Respect `prefers-reduced-motion` (extend the kill-list in globals.css for
any new `animate-*` class).
4. Mobile lightness gate: any WebGL/heavy canvas work must gate off on
<768px / pointer:coarse / saveData / deviceMemory<=4 (see AmbientCanvas
pattern already in the codebase).
5. Each route ships independently: edit → `pnpm format` → `npx @biomejs/biome
check --write` → `pnpm lint` (0 errors, 0 warnings) → `tsc --noEmit` →
`pnpm build` → commit (no Co-Authored-By trailer) → push → `gh run watch`
for Build & Deploy (Nix) → live verify via browser_vision on
imphnen.asepharyana.my.id/<route>/.
6. NEVER claim "done" from reading code — every route gets a live screenshot
check confirming: (a) not a stock template, (b) monochrome/restrained,
(c) GSAP entrance/interaction actually fires, (d) nav/theme toggle/command
palette still work.
## Per-route creative direction (reactive-stage motifs, one distinct idea per
route so it doesn't feel copy-pasted from dashboard/media)
- **channels**: node/roster telemetry — GSAP stagger-in channel cards with a
"signal strength" bar-fill tween keyed to activity; hover reveals HUD-style
meta readout.
- **voice**: live speaker HUD — waveform/ring pulse tied to WS voice_state
(GSAP timeline looping while speaking, `clearProps` on stop); ambient tile
glow keyed to active speaker count.
- **messages**: scan-line reveal on list mount + WS-driven "new message" slide
insert (respect existing sortMessages fix — do not reintroduce the race).
- **moderation**: alert-priority HUD — flagged severity as inverted mono
badge with a pulse GSAP loop (no red, per accepted mono direction);
queue-clear micro celebration (subtle, no confetti-color).
- **recordings**: waveform scrub deck echoing media's frequency-deck visual
language but for static recordings (progress-tied GSAP scrub, not looping).
- **analysis**: gauge/metric reveal using the existing `radial-gauge.tsx`
driven by a GSAP number tween (count-up) instead of an instant value snap.
- **glossary**: lightweight — accordion/definition reveal with GSAP height
auto + fade, kept minimal since it's low-traffic reference content.
## Verification checklist (must pass before calling any route "shipped")
- [ ] tsc --noEmit clean
- [ ] biome check: 0 errors AND 0 warnings
- [ ] next build succeeds
- [ ] CI green (Build & Deploy (Nix))
- [ ] Live screenshot: monochrome, GSAP motion visible, nav/theme/command
palette intact, not a stock template
- [ ] Reduced-motion: new animate-* classes added to the kill-list
- [ ] Mobile: WebGL/heavy work gated per existing pattern
## Rollback plan
Every route ships as its own atomic commit. If user rejects a specific route's
direction, `git revert <that commit>` — do not touch unrelated routes.
@@ -0,0 +1,91 @@
# FE Optimasi: Error Handling, State Refactor, Polish
**Scope:** `services/frontend/src` — Next.js 16, React 19, SWR, oRPC-over-WS.
**Branch:** `feat/fe-error-handling-state` (buat baru dari `main`).
**User:** MythEclipse — bahasa Indonesia/Inggris campur, "gas" = eksekusi langsung.
## 1. Masalah yang teridentifikasi
### 1.1 Error handling tidak seragam
- `ErrorState` (states.tsx:126) sudah ada dengan `onRetry?`, tapi **80% pemanggilan tidak pass `onRetry`** → user stuck, harus refresh manual.
- Pola `if (error && !data) return <ErrorState />` berulang di 5+ view, setiap satu hardcode logic-nya.
- `useAction` (use-action.ts) set `error` di state tapi komponen pakai `try/catch` lokal juga — **dua error states** (hook `action.error` + lokal `catch (e)`).
### 1.2 State duplication / double source of truth
- `useReview` (use-messages.ts:129) pakai `refreshInterval: 15_000` (polling) padahal WS sudah broadcast `moderation_action` real-time. **Polling + WS = double-fetch + race condition.**
- `page.tsx` dan `view.tsx` duplikat state `guildId`/`channelId`/`query` — logic selection ada di 2 tempat.
- Chatbot pakai local `failed` state + `setFailed(text)` untuk retry, tapi `useAction` sudah ada `error` field yang tidak dipakai.
- `useSpeakers` (use-voice.ts:50) pakai local `useState` untuk speakers, bukan SWR → tidak konsisten dengan pola SWR yang dipakai semua hook lain.
### 1.3 WebSocket error/user feedback lemah
- `WsConnection.onerror` (connection.ts:78) cuma `setStatus("error")` — tidak ada info ke user "reconnecting… (attempt N)".
- Tidak ada broadcast error ke komponen (tidak ada `ws_error` handler).
## 2. Rencana perubahan
### 2.1 Standardisasi ErrorBoundary + Retry (FE-only)
**File:** `src/components/shared/states.tsx`
- Tambahkan komponen `ErrorBoundary` (React error boundary untuk crash React, bukan fetch error).
- Refactor semua `<ErrorState error={error} />` → `<ErrorState error={error} onRetry={retryFn} />`.
- Tambahkan helper `withSWR` atau pattern: tiap view pakai `error` + `mutate`/`refetch` dari SWR dan pass ke ErrorState.
**File:** `src/app/(dashboard)/*/view.tsx` (6 files)
- Setiap `ErrorState` dapat `onRetry` yang memanggil `mutate`/`refetch`.
- `DashboardView`: `onRetry={() => void mutate(["dashboard-stats"])}` — tapi SWR keys tersebar. Solusi: export `mutate` via custom hook atau pakai `useSWR` config `onErrorRetry` global.
- **Keputusan:** gunakan pola **global SWR config** (`src/lib/swr-config.ts`) dengan `onErrorRetry` backoff, dan tiap view pass `onRetry` explicit ke ErrorState.
### 2.2 Hapus polling `useReview`, ganti WS-driven
**File:** `src/hooks/use-messages.ts`
- Hapus `refreshInterval: 15_000` dari `useReview`.
- Tambahkan `useReviewWsSync(ws)` — subscribe ke `moderation_action` WS event, mutate key `["messages-review", channelId]`.
- Backend sudah broadcast `moderation_action` via WS (redis-channels.ts:130). Review messages yang di-flag akan dapat `moderation_action` event. **Bisa pakai ini.**
**File:** `src/app/(dashboard)/messages/view.tsx`
- Tambahkan `useReviewWsSync(ws)` call.
### 2.3 Konsistensi state: `useSpeakers` → SWR
**File:** `src/hooks/use-voice.ts`
- Refactor `useSpeakers` agar pakai SWR key `["voice-speakers"]` + initialData dari `useVoiceStatus`. Ini memungkinkan revalidate + cache sharing.
- Tapi `voice_state` dan `voice_active_user` adalah WS events — perlu persist ke SWR cache via `mutate`. Refactor: `useSpeakers` subscribe WS + mutate SWR key.
### 2.4 Chatbot: gunakan `useAction` error state, hapus duplikat `failed`
**File:** `src/components/chatbot/chatbot.tsx`
- Hapus `const [failed, setFailed]` — sebalihoikan ke `useAction` return `error`.
- Tapi `useAction` `mutateAsync` throw — perlu catch. Refactor: pakai `mutate` (fire-and-forget) + `isPending` + `error`.
- **Note:** chatbot pakai `send` yang butuh pemuatan history — tetap pakai local state untuk msgs tapi gunakan `action.error` untuk display.
### 2.5 WS error feedback
**File:** `src/lib/ws/connection.ts`
- `onerror` emit kode/status ke status listeners.
- Tambahkan method `getReconnectAttempt()` atau expose via status change.
**File:** `src/lib/ws/context.tsx`
- Subscribe `onStatusChange` di `WsProvider`, toast "Reconnecting… (attempt N)" ketika status `error`/`connecting`.
## 3. Verification
- `npx tsc --noEmit` — compile OK
- `npx biome check src/` — lint OK
- `npm run build` — build OK (Next 16 SSG/SSR)
- Manual: refresh halaman, pastikan ErrorState muncul dengan tombol Retry yang bisa diklik.
## 4. Files yang disentuh
```
src/components/shared/states.tsx # ErrorBoundary + helper
src/lib/swr-config.ts # global SWR config (baru)
src/hooks/use-messages.ts # useReviewWsSync, hapus polling
src/hooks/use-voice.ts # useSpeakers → SWR
src/hooks/use-action.ts # expose resetError
src/lib/ws/connection.ts # reconnect info
src/lib/ws/context.tsx # WS error toast
src/app/(dashboard)/messages/view.tsx # useReviewWsSync
src/app/(dashboard)/messages/page.tsx # SSR error passthrough
src/app/(dashboard)/moderation/view.tsx # onRetry
src/app/(dashboard)/dashboard/view.tsx # onRetry
src/app/(dashboard)/voice/view.tsx # onRetry
src/app/(dashboard)/media/view.tsx # onRetry
src/app/(dashboard)/recordings/view.tsx # onRetry
src/components/chatbot/chatbot.tsx # pakai useAction error
```
## 5. Out of scope (BE)
- Regex heuristic di moderation.repository.ts (scam domain extraction) — ini BE task, catat tapi jangan sentuh kecuali diminta.
@@ -0,0 +1,53 @@
# GMW Frontend — Mobile Nav + Form Controls Responsive Pass
## Problem
Two mobile UX defects reported by user:
1. **Mobile navbar can't navigate between pages** (`tidak bisa pindah halaman`).
- **Root cause A (click-block):** the floating chatbot FAB
(`chatbot.tsx` line ~125: `fixed right-4 bottom-5 z-50 size-11`) overlaps the
mobile bottom nav (`MobileNav` is `fixed bottom-0 z-40`). Because the FAB is
`z-50` and sits at `bottom-5` on the right, it intercepts taps on the
rightmost bottom-nav items (Voice/Media). Verified in Playwright: click on
`Media` nav item is blocked by `button[aria-label="Open neural HUD assistant"]`.
- **Root cause B (missing destinations):** `mobileNavItems` in
`src/lib/navigation.ts` filters `navItems` to only
`['/dashboard','/messages','/voice','/media']`, so `/recordings`,
`/moderation`, `/analysis` are unreachable from the mobile bottom bar.
- **Chosen design (user directive):** extend the mobile bottom bar to **all 7
nav items**, horizontally **scrollable/flex-wrap** so they fit on narrow
screens, following the desktop sidebar's items (same set as `navItems`).
2. **Form controls not responsive on mobile** (`perbagus textfield, dropdown,
lainnya agar responsif di mobile`).
- `Select` dropdown (`primitives/select.tsx`) is portalled w/ `fixed`
positioning computed from trigger rect only (top/left/width). On a narrow
viewport a dropdown anchored near the right edge overflows past the screen's
right edge and can be clipped/clickable-outside the visible area. Clamp
horizontally within viewport.
- `Input`/`Textarea` (`primitives/input.tsx`) use `text-sm` (14px) which on
iOS triggers auto-zoom on focus (a known mobile annoyance) and can be small
touch targets. Give inputs a comfortable mobile baseline.
## Files touched
- `src/components/shell/mobile-nav.tsx` — use `navItems` (all 7), make the bar
scrollable on narrow widths (overflow-x-auto + snap), keep active indicator.
- `src/components/chatbot/chatbot.tsx` — raise FAB above the mobile bottom nav on
`md:hidden` (e.g. `bottom-[calc(4.75rem+env(safe-area-inset-bottom))]`),
keep `bottom-5` on `md+` so it never overlaps the nav.
- `src/lib/navigation.ts` — drop `mobileNavItems` special-case (or repoint to
`navItems`); keep `navItems` as the single source.
- `src/components/primitives/select.tsx` — clamp dropdown `left` so it stays
within viewport; cap width on narrow screens; keep max-height.
- `src/components/primitives/input.tsx` — mobile-friendly font size / touch
height (16px inputs to avoid iOS zoom; keep desktop as-is via responsive
classes).
## Verification
- `pnpm format`, `pnpm lint` (biome clean), `pnpm build` compiles.
- Smoke on NON-PROD port (4024) — confirm free first via `ss -ltnp`.
- Playwright mobile viewport (390px): ALL 7 nav items present in bottom bar;
tapping each navigates (esp. Media rightmost item NOT blocked by FAB); FAB is
raised above the nav and no longer intercepts taps.
- Select dropdown on a 390px viewport stays fully on-screen (no right overflow).
- Commit (no trailer), push, `gh run watch`, verify all 7 live routes 200.