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:
@@ -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.
|
||||
Reference in New Issue
Block a user