From 4fb59cda95b3d60ca14214eda769c04f8e229e19 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 3 Jul 2026 06:07:12 +0700 Subject: [PATCH] docs: leptos frontend rewrite design spec --- .../specs/2026-07-03-leptos-rewrite-design.md | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-03-leptos-rewrite-design.md diff --git a/docs/superpowers/specs/2026-07-03-leptos-rewrite-design.md b/docs/superpowers/specs/2026-07-03-leptos-rewrite-design.md new file mode 100644 index 0000000..68b509c --- /dev/null +++ b/docs/superpowers/specs/2026-07-03-leptos-rewrite-design.md @@ -0,0 +1,311 @@ +# Leptos Rewrite — Frontend Architecture Design + +**Date:** 2026-07-03 +**Status:** Approved Design + +## Overview + +Rewrite the React 19 + Vite + Tailwind dashboard (46 source files, ~6k LOC) to a Rust Leptos CSR WASM app. All layers — components, styling, animations, WebSocket, audio, icons — are rewritten in Rust. + +## Motivation + +- **Performance** — WASM eliminates JS bundle parsing/execution overhead +- **Type Safety** — Rust's type system catches more errors at compile time +- **Bundle Size** — WASM binary smaller than equivalent JS bundle + +## Status + +The current React frontend (`services/frontend/`) remains in active use. The new Leptos app lives in `services/frontend-leptos/`. No React code is removed until the Leptos version is feature-complete. + +## Tech Stack + +| Layer | Choice | Rationale | +|-------|--------|-----------| +| Framework | Leptos 0.7 (CSR) | WASM-native reactive framework, signals-based | +| Styling | Plain CSS | No framework dependency, port from existing CSS | +| Animations | CSS keyframes + transitions | Replace all Framer Motion usage | +| Icons | `lucide-leptos` | Direct port of current lucide-react icons | +| HTTP | `gloo-net` | Rust fetch wrapper, works with WASM | +| WS | `web-sys::WebSocket` | Direct binding, no abstraction overhead | +| Audio Viz | Canvas 2D (`web-sys`) | AudioVisualizer, WaveformPlayer | +| Audio Playback | Web Audio API (`web-sys`) | PCM stream playback | +| State | Leptos signals + contexts | No external state library needed | +| Build | `trunk` | Standard WASM builder for Leptos | + +## Architecture + +``` +Leptos App (WASM) + │ + ├── App Shell (layout, sidebar, header, tabs) + ├── Messages Panel (feed, cards, search, filters) + ├── Live Panel (voice, music, screen, recordings, audio viz) + └── Dashboard Panel (stats, users, channels) + +Shared infrastructure: + ├── WebSocket Context (singleton + per-event channels) + ├── API Client (fetch wrapper + typed endpoint functions) + ├── UI Primitives (Button, Card, Badge, Input, Tabs, Toast, etc.) + └── CSS Design System (custom properties, component classes, animations) +``` + +### Workspace Structure + +``` +services/frontend-leptos/ +├── Cargo.toml # workspace root +├── shared-types/ # Rust types (port of @bete/shared) +│ ├── Cargo.toml +│ └── src/ +│ ├── lib.rs +│ ├── message.rs # MessageRecord, AttachmentRecord +│ ├── guild.rs # Guild, Channel +│ ├── voice.rs # VoiceStatus, ActiveSpeaker +│ ├── media.rs # MediaItem, MediaState, MediaMode +│ ├── dashboard.rs # DashboardStats, DashboardUser, DashboardChannel +│ ├── recording.rs # VoiceRecording +│ └── ui_state.rs # UIState, AppConfig +├── frontend/ +│ ├── Cargo.toml # Leptos + deps +│ ├── Trunk.toml # Build config +│ ├── index.html # Entry point (lang="id", Poppins font) +│ └── src/ +│ ├── main.rs # mount_to_body with panic_hook + logger +│ ├── app.rs # Root component (providers + auth gate + routing) +│ ├── app.css # Full CSS design system +│ ├── lib.rs # Module declarations +│ ├── auth.rs # AuthOverlay + login logic +│ ├── ws/ # WebSocket context & event system +│ ├── api/ # HTTP client + typed API functions +│ ├── ui/ # Shared UI primitives +│ ├── layout/ # App shell components +│ └── features/ +│ ├── messages/ # Message feed, cards, search, filters +│ ├── live/ # Voice, audio, media, recordings +│ └── dashboard/ # Stats, users, channels +├── rust-toolchain.toml # nightly (required by Leptos CSR) +└── .env # API_URL, WS_URL +``` + +### Component Tree + +``` + + + {!authenticated && !is_public → } + + + + Messages | Live | Dashboard + + +
+ + + +
+ + {active_tab == "messages" → } + {active_tab == "live" → } + {active_tab == "dashboard" → } +
+ +
+
+``` + +### State Architecture + +**Contexts** (provided at `` level via `provide_context`): + +| Context | Key Signals | Persisted | +|---------|-------------|-----------| +| AuthContext | `authenticated: ReadSignal` | sessionStorage | +| WsContext | `status: ReadSignal`, per-event channels | — | +| UiContext | `active_tab: RwSignal`, `selected_guild: RwSignal>`, etc. | localStorage | + +**Data fetching** via Leptos `Resource` (parallel to React useEffect + fetch, with built-in loading/error/ok states). + +**WebSocket events** dispatched through per-event-type `mpsc::UnboundedReceiver` channels. Components subscribe to relevant channels via `watch!` or `create_effect`. + +## State Machine: Tab Navigation + +``` +[messages] ←── [live] ←──→ [dashboard] + | ↑ + └─── (unauth redirect) ──┘ +``` + +- Default: "messages" +- Unauthenticated on "live" → redirect to "messages" (same as current behavior) +- Tab state persisted via localStorage + +## Data Flow + +### Message Capture → Display +``` +Discord → gateway → Redis → backend → WS → ws::socket receive + → parse JSON → match event type → WsContext.on_message_created.send(data) + → watch! { set_messages.update(|msgs| merge(msgs, data)) } + → UI re-renders via Leptos reactivity +``` + +### Voice PCM → Audio Playback +``` +Discord → gateway → Redis → backend → WS binary frame + → ws::socket onbinary → parse [u32be userId][i16 samples] + → WsContext.on_voice_pcm.send(packet) + → use_audio_playback → AudioContext.decodeAudioData → play via AudioBufferSourceNode + → AudioVisualizer: CanvasRenderingContext2D draw 32 bars on requestAnimationFrame +``` + +### User Action → Command +``` +Button click → call api::voice_connect(guild_id, channel_id) + → POST /api/voice/connect + → backend → Redis (backend:command) → discord-gateway + → gateway connects → Redis (discord:voice:started) + → WS event → komponen update status +``` + +## Phase Breakdown + +### Phase 1: Foundation (project scaffold + types + build) +- [ ] Cargo workspace with `shared-types` and `frontend` crates +- [ ] All Rust types (port 1:1 from TypeScript types + `@bete/shared`) +- [ ] `Trunk.toml` + `index.html` + `main.rs` with `mount_to_body` +- [ ] `rust-toolchain.toml` (nightly) +- [ ] `.env` for API/WS URLs +- [ ] Build verification: `trunk serve` produces a blank WASM page + +### Phase 2: Skeleton (shell, auth, WS, API, UI primitives) +- [ ] CSS Design System (`app.css`) — custom properties, component classes, keyframes, dark theme +- [ ] `` component with providers +- [ ] AuthOverlay + login flow +- [ ] WebSocket singleton (connect, reconnect with exponential backoff, per-event channels) +- [ ] API client (fetch wrapper with auth header, typed endpoint functions) +- [ ] UI primitives: Button (7 variants), Badge (9), Card, Input, Select, Tabs, ScrollArea, Toast, Skeleton, StatusBadge, EmptyState, Modal +- [ ] Layout shell: DashboardLayout, Sidebar, Header, TabStrip, MobileTabBar + +### Phase 3: Messages Feature +- [ ] MessagesPanel with AI status filter tabs +- [ ] MessageFeed (infinite scroll via IntersectionObserver) +- [ ] MessageCard with user grouping +- [ ] MessageRow (content, edited/deleted indicators, Discord emoji, stickers, attachments, AI analysis box, severity badge, reanalyze) +- [ ] ImageGrid (masonry grid from attachments/embeds/stickers) +- [ ] ModerationAlertListener → Toast dispatch +- [ ] Search (via `/api/analysis/search`) +- [ ] Reanalyze (single + batch) + +### Phase 4: Live Feature +- [ ] VoiceConnectionCard (guild/channel selectors, join/disconnect/listen/transmit) +- [ ] ActiveSpeakers (user list with speaking indicators) +- [ ] AudioVisualizer (Canvas 2D, 32 bars, gradient, ResizeObserver) +- [ ] MicLevelMeter (horizontal bar, 0-100%, green→red scale) +- [ ] NowPlaying (current media + queue) +- [ ] MusicSubPanel (URL input, volume slider, queue/skip/stop) +- [ ] ScreenSubPanel (URL input, start/skip/stop) +- [ ] RecordingsSubPanel (list + pagination + delete + status badges) +- [ ] WaveformPlayer (Canvas 64 bars, AudioContext, play/pause/seek) +- [ ] PCM audio playback (Web Audio API via web-sys) +- [ ] Mic transmit (AudioContext → WebSocket binary frames) + +### Phase 5: Dashboard Feature +- [ ] StatsOverview (8 stat cards, top channels, moderation queue) +- [ ] UserSummaryList (search + pagination + grid) +- [ ] UserProfileDetail (stats + recent messages) +- [ ] ChannelSummaryList (search + pagination + grid) +- [ ] ChannelProfileDetail (stats + culture + recent messages) + +### Phase 6: Polish +- [ ] ParticleBackground (3 CSS glow orbs) +- [ ] MascotChatbot (floating chat panel, API integration) +- [ ] MascotImage (clickable mascot) +- [ ] Edge cases: WS reconnect, stale data handling, concurrent requests +- [ ] Mobile responsive (< md: sidebar → bottom nav, cards → single column) +- [ ] Loading states + error boundaries + empty states for all panels +- [ ] Theme toggle (dark/light via CSS custom properties) +- [ ] Error animation states (Framer Motion fade/slide ported to CSS keyframes) + +## API Client + +All existing endpoints mapped to typed Rust functions: + +```rust +// pattern +pub async fn get_messages(guild_id: &str, params: &MessageParams) -> Result, ApiError>; + +// All endpoints (20+): +// auth, messages, review, reanalyze, guilds, voice, media, recordings, +// dashboard (stats/users/channels), ui-state, chat, search +``` + +Full list in `services/frontend/src/shared/api/client.ts` — 1:1 port. + +## CSS Design System + +### Source: `services/frontend/src/styles.css` (676 lines) + +**Port strategy:** +1. CSS custom properties (`:root` / `[data-theme="dark"]`) — port verbatim +2. Component classes (`.im-btn`, `.im-card`, etc.) — rename to `.btn`, `.card` etc., port styling +3. Keyframes — rename from `.im-*` prefix, port verbatim +4. Layout utility classes (`.flex`, `.grid`, `.gap-*`) — keep as utility classes or inline +5. Remove all Tailwind-specific directives + +**Component classes to define:** +- `.btn` / `.btn-primary` / `.btn-secondary` / `.btn-ghost` / `.btn-destructive` / `.btn-outline` / `.btn-link` + `.btn-sm` / `.btn-lg` / `.btn-icon` +- `.card` / `.card-elevated` / `.card-bordered` + `.card-header` / `.card-title` / `.card-content` / `.card-footer` +- `.badge` / `.badge-primary` / `.badge-success` / `.badge-warning` / `.badge-destructive` / `.badge-outline` +- `.input` / `.input-soft` + `.input-error` +- `.tabs` / `.tab-list` / `.tab-trigger` / `.tab-content` +- `.skeleton` / `.skeleton-circular` / `.skeleton-rectangular` +- `.toast` / `.toast-success` / `.toast-error` / `.toast-warning` + +## WebSocket Protocol (unchanged from current) + +- **Binary**: PCM audio `[4-byte userIdHash UInt32LE][Int16 PCM samples @ 24kHz mono]` +- **JSON**: Typed envelope `{ type, data }` — 20+ event types (see `events.md`) + +Reconnection: exponential backoff with full jitter (1s base, 30s max, 20 attempts). + +## Out of Scope (Phase 1) + +- React Native / mobile apps +- PWA / service worker +- E2E testing +- Performance benchmarking +- Bundle size optimization +- Server-side rendering (Leptos SSR mode) +- CI/CD integration + +These can be added after the CSR WASM version is stable. + +## Dependencies + +### Rust crates + +| Crate | Version | Purpose | +|-------|---------|---------| +| leptos | 0.7 (csr) | Reactive UI framework | +| leptos-use | latest | `use_local_storage`, `use_interval`, `use_event_listener` etc. | +| lucide-leptos | latest | SVG icons | +| wasm-bindgen | 0.2 | JS/WASM bindings | +| web-sys | 0.3 | Browser API bindings | +| js-sys | 0.3 | JS type bindings | +| gloo-net | 0.6 | HTTP fetch | +| serde | 1 + derive | JSON serialization | +| serde-wasm-bindgen | 0.6 | Serde ↔ JS interop | +| wasm-logger | 0.2 | Logging to console | +| console_error_panic_hook | 0.1 | Debug panic traces | + +### web-sys features required + +WebSocket, CanvasRenderingContext2d, AudioContext, Window, Document, Element, HtmlElement, KeyboardEvent, Storage, IntersectionObserver, ResizeObserver, Url, Headers, Request, RequestInit, Response, HtmlInputElement, HtmlAudioElement, HtmlCanvasElement, MediaDevices, MediaStream, AudioBuffer, AudioBufferSourceNode + +## Migration Notes + +- The React app (`services/frontend/`) stays intact until Leptos version is complete +- No shared code between React and Leptos versions — complete rewrite +- Build output: `trunk build` → `dist/` folder, deployable as static files alongside or replacing the current Vite build +- Environment variables: port from `.env` to Rust compile-time config or runtime JS interop