From 831fddd1bf8c6748ed5fc0bd55d12c0fc2d7dc03 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Sun, 26 Jul 2026 11:33:45 +0700 Subject: [PATCH] refactor(backend): remove auth for public API - Remove auth module (auth.routes.ts, /api/auth/login) - Remove adminAuth middleware from voice and media routes - Remove adminAuth() function from shared middlewares - Remove auth-related e2e test - Clean up .env.example --- .env.example | 2 +- ...26-06-01-frontend-major-refactor-design.md | 201 - .../2026-06-03-anime-ui-overhaul-design.md | 223 - .../plans/2026-05-13-aggressive-cleanup.md | 975 ----- .../2026-05-13-backlog-sync-rich-metadata.md | 64 - .../plans/2026-05-13-one-port-websocket.md | 149 - .../plans/2026-05-13-react-ssr-dashboard.md | 60 - .../2026-05-13-separate-thread-discovery.md | 38 - ...026-05-13-web-interactive-voice-connect.md | 440 -- .../2026-05-13-web-mic-noise-suppression.md | 163 - ...6-05-14-ai-message-flow-react-dashboard.md | 1854 -------- .../plans/2026-05-14-drizzle-orm-migration.md | 704 --- .../plans/2026-05-14-library-modernization.md | 183 - .../2026-05-15-discord-video-stream-vendor.md | 189 - .../plans/2026-05-15-media-music-phase-1.md | 1291 ------ ...26-05-15-media-youtube-spotify-resolver.md | 713 --- ...elfbot-performance-feature-optimization.md | 663 --- .../2026-05-15-selfbot-workspace-submodule.md | 239 -- .../2026-05-15-split-text-voice-selection.md | 466 -- ...vendor-selfbot-dependency-modernization.md | 488 --- .../2026-05-16-media-echo-screenshare.md | 1427 ------ .../2026-05-16-session-full-recording.md | 673 --- .../2026-05-18-robust-moderation-and-tests.md | 361 -- ...6-05-19-aggressive-codebase-restructure.md | 1757 -------- ...026-05-19-deprecated-dependency-removal.md | 597 --- .../2026-05-19-winston-logging-refactor.md | 652 --- .../2026-07-02-bete-frontend-redesign-plan.md | 1375 ------ .../plans/2026-07-03-leptos-live-phase4.md | 102 - .../2026-07-03-leptos-messages-phase3.md | 556 --- .../2026-07-03-leptos-rewrite-phase1-2.md | 3816 ----------------- .../2026-07-04-frontend-dependency-upgrade.md | 512 --- .../plans/2026-07-08-gitea-migration.md | 703 --- .../2026-05-13-aggressive-cleanup-design.md | 437 -- .../2026-05-13-moderation-watcher-design.md | 240 -- ...13-web-interactive-voice-connect-design.md | 91 - ...-ai-message-flow-react-dashboard-design.md | 264 -- .../2026-05-15-media-music-phase-1-design.md | 110 - ...5-15-selfbot-performance-feature-design.md | 37 - ...5-15-selfbot-workspace-submodule-design.md | 24 - ...selfbot-dependency-modernization-design.md | 76 - ...026-05-16-media-echo-screenshare-design.md | 71 - ...026-05-16-session-full-recording-design.md | 87 - ...17-internal-streamer-replacement-design.md | 85 - ...5-18-robust-moderation-and-tests-design.md | 56 - ...-aggressive-codebase-restructure-design.md | 223 - ...19-deprecated-dependency-removal-design.md | 91 - ...6-05-19-winston-logging-refactor-design.md | 115 - ...026-07-02-bete-frontend-redesign-design.md | 619 --- .../specs/2026-07-03-leptos-rewrite-design.md | 311 -- ...7-04-frontend-dependency-upgrade-design.md | 92 - .../2026-07-05-frontend-overhaul-design.md | 397 -- .../2026-07-08-gitea-migration-design.md | 104 - services/backend/src/e2e.test.ts | 10 - services/backend/src/http/app.ts | 20 +- .../backend/src/modules/auth/auth.routes.ts | 36 - .../backend/src/shared/middlewares/index.ts | 13 - 56 files changed, 6 insertions(+), 25239 deletions(-) delete mode 100644 docs/specs/2026-06-01-frontend-major-refactor-design.md delete mode 100644 docs/specs/2026-06-03-anime-ui-overhaul-design.md delete mode 100644 docs/superpowers/plans/2026-05-13-aggressive-cleanup.md delete mode 100644 docs/superpowers/plans/2026-05-13-backlog-sync-rich-metadata.md delete mode 100644 docs/superpowers/plans/2026-05-13-one-port-websocket.md delete mode 100644 docs/superpowers/plans/2026-05-13-react-ssr-dashboard.md delete mode 100644 docs/superpowers/plans/2026-05-13-separate-thread-discovery.md delete mode 100644 docs/superpowers/plans/2026-05-13-web-interactive-voice-connect.md delete mode 100644 docs/superpowers/plans/2026-05-13-web-mic-noise-suppression.md delete mode 100644 docs/superpowers/plans/2026-05-14-ai-message-flow-react-dashboard.md delete mode 100644 docs/superpowers/plans/2026-05-14-drizzle-orm-migration.md delete mode 100644 docs/superpowers/plans/2026-05-14-library-modernization.md delete mode 100644 docs/superpowers/plans/2026-05-15-discord-video-stream-vendor.md delete mode 100644 docs/superpowers/plans/2026-05-15-media-music-phase-1.md delete mode 100644 docs/superpowers/plans/2026-05-15-media-youtube-spotify-resolver.md delete mode 100644 docs/superpowers/plans/2026-05-15-selfbot-performance-feature-optimization.md delete mode 100644 docs/superpowers/plans/2026-05-15-selfbot-workspace-submodule.md delete mode 100644 docs/superpowers/plans/2026-05-15-split-text-voice-selection.md delete mode 100644 docs/superpowers/plans/2026-05-15-vendor-selfbot-dependency-modernization.md delete mode 100644 docs/superpowers/plans/2026-05-16-media-echo-screenshare.md delete mode 100644 docs/superpowers/plans/2026-05-16-session-full-recording.md delete mode 100644 docs/superpowers/plans/2026-05-18-robust-moderation-and-tests.md delete mode 100644 docs/superpowers/plans/2026-05-19-aggressive-codebase-restructure.md delete mode 100644 docs/superpowers/plans/2026-05-19-deprecated-dependency-removal.md delete mode 100644 docs/superpowers/plans/2026-05-19-winston-logging-refactor.md delete mode 100644 docs/superpowers/plans/2026-07-02-bete-frontend-redesign-plan.md delete mode 100644 docs/superpowers/plans/2026-07-03-leptos-live-phase4.md delete mode 100644 docs/superpowers/plans/2026-07-03-leptos-messages-phase3.md delete mode 100644 docs/superpowers/plans/2026-07-03-leptos-rewrite-phase1-2.md delete mode 100644 docs/superpowers/plans/2026-07-04-frontend-dependency-upgrade.md delete mode 100644 docs/superpowers/plans/2026-07-08-gitea-migration.md delete mode 100644 docs/superpowers/specs/2026-05-13-aggressive-cleanup-design.md delete mode 100644 docs/superpowers/specs/2026-05-13-moderation-watcher-design.md delete mode 100644 docs/superpowers/specs/2026-05-13-web-interactive-voice-connect-design.md delete mode 100644 docs/superpowers/specs/2026-05-14-ai-message-flow-react-dashboard-design.md delete mode 100644 docs/superpowers/specs/2026-05-15-media-music-phase-1-design.md delete mode 100644 docs/superpowers/specs/2026-05-15-selfbot-performance-feature-design.md delete mode 100644 docs/superpowers/specs/2026-05-15-selfbot-workspace-submodule-design.md delete mode 100644 docs/superpowers/specs/2026-05-15-vendor-selfbot-dependency-modernization-design.md delete mode 100644 docs/superpowers/specs/2026-05-16-media-echo-screenshare-design.md delete mode 100644 docs/superpowers/specs/2026-05-16-session-full-recording-design.md delete mode 100644 docs/superpowers/specs/2026-05-17-internal-streamer-replacement-design.md delete mode 100644 docs/superpowers/specs/2026-05-18-robust-moderation-and-tests-design.md delete mode 100644 docs/superpowers/specs/2026-05-19-aggressive-codebase-restructure-design.md delete mode 100644 docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md delete mode 100644 docs/superpowers/specs/2026-05-19-winston-logging-refactor-design.md delete mode 100644 docs/superpowers/specs/2026-07-02-bete-frontend-redesign-design.md delete mode 100644 docs/superpowers/specs/2026-07-03-leptos-rewrite-design.md delete mode 100644 docs/superpowers/specs/2026-07-04-frontend-dependency-upgrade-design.md delete mode 100644 docs/superpowers/specs/2026-07-05-frontend-overhaul-design.md delete mode 100644 docs/superpowers/specs/2026-07-08-gitea-migration-design.md delete mode 100644 services/backend/src/modules/auth/auth.routes.ts diff --git a/.env.example b/.env.example index 981b1bc..6432e36 100644 --- a/.env.example +++ b/.env.example @@ -40,7 +40,7 @@ NODE_ENV=development # Environment: development|production|te VERBOSE=false # Enable verbose/debug logging (default: false) # === Admin === -ADMIN_PASSWORD=admin123 # Backend admin password for API auth (default: admin123) +# ADMIN_PASSWORD removed — dashboard is public # === Database (PostgreSQL) === # Option 1: Connection string (overrides individual params) diff --git a/docs/specs/2026-06-01-frontend-major-refactor-design.md b/docs/specs/2026-06-01-frontend-major-refactor-design.md deleted file mode 100644 index dd5025f..0000000 --- a/docs/specs/2026-06-01-frontend-major-refactor-design.md +++ /dev/null @@ -1,201 +0,0 @@ -# Frontend Major Refactor — Design Document - -**Date:** 2026-06-01 -**Scope:** `/mnt/code/bete/frontend/` — React + Vite + Tailwind -**Stack:** React 18, Vite, TailwindCSS, TanStack Query, lucide-react - -## Problem Statement - -1. `package.json` hilang — project tidak bisa `pnpm install` -2. `App.tsx` 285 baris monolithic (audio, socket, state, layout semua satu file) -3. `useMemo` dipakai untuk side effect (LivePanel:47) — anti-pattern React -4. Shared `ArrayBuffer` bug saat kirim PCM ke WebSocket (App:166) -5. `ws/client.ts` duplikat socket logic dengan `hooks/useDashboardSocket.ts` -6. Async handler tanpa await (MessageCard:88) -7. UI terlihat "jelek" — flat dark theme tanpa depth/visual interest -8. Mobile UX buruk — tab bar di atas, tidak ada sidebar drawer - -## Section 1: Arsitektur — Feature-Sliced Directory - -``` -frontend/ -├── index.html -├── package.json # [NEW] Lengkap dengan semua deps -├── vite.config.ts -├── tailwind.config.js -├── postcss.config.js -├── tsconfig.json -└── src/ - ├── main.tsx # React entry + QueryClient - ├── styles.css # Tailwind + design tokens - ├── App.tsx # Thin shell (~60 baris) - │ - ├── entities/ # Domain types - │ ├── message/types.ts - │ ├── guild/types.ts - │ ├── voice/types.ts - │ ├── media/types.ts - │ └── ui/types.ts - │ - ├── shared/ # Cross-cutting utilities - │ ├── api/client.ts # HTTP client + typed endpoints - │ ├── ws/socket.ts # WebSocket connection manager - │ ├── ws/events.ts # Typed event map + dispatcher - │ ├── hooks/useAudioPlayback.ts - │ ├── hooks/useAudioTransmit.ts - │ ├── hooks/useLocalStorage.ts - │ └── ui/ # UI primitives - │ ├── button.tsx - │ ├── card.tsx - │ ├── badge.tsx - │ ├── input.tsx - │ ├── select.tsx - │ ├── tabs.tsx - │ └── scroll-area.tsx - │ - ├── features/ # Feature-specific logic + presentation - │ ├── live/ - │ │ ├── hooks/useVoiceControl.ts - │ │ ├── hooks/useMediaControl.ts - │ │ ├── index.tsx # LivePanel - │ │ ├── components/VoiceConnectionCard.tsx - │ │ ├── components/AudioVisualizer.tsx - │ │ ├── components/ActiveSpeakers.tsx - │ │ ├── components/NowPlaying.tsx - │ │ ├── components/MusicSubPanel.tsx - │ │ ├── components/ScreenSubPanel.tsx - │ │ └── components/RecordingsSubPanel.tsx - │ ├── messages/ - │ │ ├── hooks/useMessages.ts - │ │ ├── index.tsx # MessagesPanel - │ │ ├── components/MessageFeed.tsx - │ │ ├── components/MessageCard.tsx - │ │ └── components/ImageGrid.tsx - │ ├── analytics/ - │ │ ├── hooks/useAnalytics.ts - │ │ ├── index.tsx # AnalyticsPanel - │ │ └── components/ # (charts, tables kept as-is) - │ └── auth/ - │ └── index.tsx # AuthOverlay - │ - └── widgets/ # Composite layouts - ├── DashboardLayout.tsx - ├── Header.tsx - └── Sidebar.tsx -``` - -### Key Architectural Decisions - -- **`App.tsx`** = hanya layout shell + providers. Semua hooks pindah ke `features/`. -- **Socket** = satu instance di `shared/ws/socket.ts`. Features subscribe via typed event bus. -- **Entities** = pure types, zero logic. -- **Features** = self-contained (hooks + components). Tidak cross-import antar feature. -- **Widgets** = layout compositions (sidebar, header, dashboard shell). - -## Section 2: Bug Fixes - -| # | Bug | Location | Fix | -|---|-----|----------|-----| -| 1 | `useMemo` untuk async fetch (anti-pattern) | `LivePanel.tsx:47` | Ganti ke `useEffect` + `useCallback` | -| 2 | Shared `ArrayBuffer` dikirim ke WS | `App.tsx:166` | `.slice()` sebelum send | -| 3 | Missing useEffect deps | `App.tsx:187` | Add proper dependency arrays | -| 4 | Unstable keys (`i` sebagai fallback) | `LivePanel.tsx:246` | Gunakan `s.userId ?? 'unknown-${i}'` | -| 5 | Async handler tanpa await | `MessageCard.tsx:88` | `onReanalyze` → `Promise`, di-await | -| 6 | `monitorGuild` recompute tiap render | `App.tsx:65` | Pakai `useMemo` | -| 7 | Socket localStorage tanpa validasi | `useUIState.ts:9` | Schema validation sebelum parse | -| 8 | Duplikat socket (`ws/client.ts` vs `hooks/useDashboardSocket.ts`) | Global | Hapus `ws/client.ts`, satu socket di `shared/ws/socket.ts` | - -## Section 3: UI Redesign — Glass-morphism Modern - -### Color Tokens - -```css ---background: 222 47% 8%; /* slightly darker */ ---foreground: 210 40% 98%; ---card: 222 47% 12% / 0.7; /* semi-transparent */ ---card-border: 222 47% 25% / 0.4;/* translucent border */ ---primary: 199 89% 52%; /* brighter cyan-blue */ ---accent: 217 33% 20%; ---muted: 217 33% 20%; ---radius: 0.85rem; -``` - -### Visual Changes - -- **Background**: Gradient mesh (dark → dark-purple → dark) dengan subtle noise overlay -- **Cards**: `backdrop-blur-xl` + semi-transparent bg + subtle glow border -- **Sidebar**: Expandable — icon-only (w-16) ↔ full (w-64) dengan smooth transition -- **Mobile**: Bottom tab bar (bukan top tabs), slide-in sidebar -- **Typography**: Inter tetap, heading scale lebih besar, weight lebih variatif -- **Micro-interactions**: `transition-all duration-200`, hover scale `1.01`, focus rings -- **Audio Visualizer**: CSS animated bars (bukan static levels map) -- **Message cards**: Rounded-xl, subtle shadow, AI badge dengan color coding yang lebih jelas -- **Charts**: Recharts-based (existing), tapi dengan color scheme yang matching theme - -### Responsive Breakpoints - -| Breakpoint | Layout | -|------------|--------| -| `< 768px` | Bottom tab bar, no sidebar, full-width content | -| `768px–1024px` | Collapsed sidebar (icon-only), full content | -| `> 1024px` | Full sidebar (w-64), spaced content | - -## Section 4: Error Handling - -- **Error Boundaries**: Setiap feature punya error boundary (existing `AnalyticsErrorBoundary` pattern) -- **API Errors**: Global error toast via `shared/ui/Toast` component -- **Socket Errors**: Auto-reconnect dengan exponential backoff (existing pattern, diperbaiki) -- **Loading States**: Skeleton loaders (bukan text "Loading...") untuk semua data panels - -## Section 5: Data Flow - -``` -Browser → shared/ws/socket.ts → shared/ws/events.ts (dispatch) - ↓ - features/* subscribe via typed handlers - ↓ - features/*/hooks/ (React Query / useState) - ↓ - features/*/components (render) - ↓ - widgets/ (layout shell) -``` - -- **REST API**: React Query hooks di `features/*/hooks/` -- **WebSocket Events**: Event bus di `shared/ws/events.ts`, hooks subscribe ke events -- **UI State**: `localStorage` + `shared/ws/socket.ts` sync (existing pattern, dengan validasi) - -## Section 6: Testing Strategy - -- **Unit**: Hooks testing dengan `@testing-library/react` — socket mocking -- **Integration**: Component render tests untuk panels -- **E2E**: Tidak termasuk scope initial refactor (future enhancement) - -## Section 7: Migration Plan - -### Phase 1: Foundation -1. `package.json` lengkap -2. `tsconfig.json` + `vite.config.ts` verified -3. `shared/` structure + UI primitives -4. `entities/` types extracted - -### Phase 2: Bug Fixes + Hooks Extraction -5. Extract hooks dari `App.tsx` → `features/*/hooks/` -6. Fix semua 8 bugs yang teridentifikasi -7. Single socket instance di `shared/ws/socket.ts` -8. `App.tsx` thin shell - -### Phase 3: UI Polish -9. Design tokens baru (glass-morphism) -10. Sidebar redesign (expandable + mobile drawer) -11. Audio visualizer animated -12. Message card redesign -13. Loading skeletons -14. Error boundaries + toast - -### Phase 4: Verification -15. `pnpm run typecheck` — zero errors -16. `pnpm run lint` — zero warnings -17. App runs + all 3 tabs functional -18. WebSocket connects + events work -19. Mobile responsive verified diff --git a/docs/specs/2026-06-03-anime-ui-overhaul-design.md b/docs/specs/2026-06-03-anime-ui-overhaul-design.md deleted file mode 100644 index 2318646..0000000 --- a/docs/specs/2026-06-03-anime-ui-overhaul-design.md +++ /dev/null @@ -1,223 +0,0 @@ -# Anime UI Overhaul — Frontend Redesign - -**Date:** 2026-06-03 -**Status:** Approved -**Author:** Claude Code / brainstorm session -**Related:** `docs/specs/2026-06-01-frontend-major-refactor-design.md` - -## Overview - -Complete visual overhaul of the GMW frontend (`/mnt/code/bete/services/frontend`) from dark cyberpunk to **light anime aesthetic** with white + sky blue (`#7EC8E3`) as primary colors. Full "kawaii cyber" approach with Three.js particles, GSAP animations, Framer Motion transitions, and anime-style mascot/illustrations. - -## Mood Board - -- **Vibe:** Fresh, cute, playful, clean — like an anime profile page meets monitoring dashboard -- **Primary colors:** White `#FFFFFF` + Sky Blue `#7EC8E3` + Soft Pink accent `#FF8FAB` -- **Typography:** Rounded, light, airy — Inter with loose letter-spacing -- **Shapes:** Extra rounded corners (16-20px), soft shadows with sky-blue tint -- **Animations:** Bouncy spring transitions, floating particles, page fade/slide - -## Tech Stack Additions - -| Library | Purpose | -|---------|---------| -| `three` | 3D particle background (sakura, sparkles) | -| `@react-three/fiber` | React wrapper for Three.js | -| `@react-three/drei` | Utility helpers for R3F | -| `gsap` | Page transitions, scroll-triggered effects, complex timelines | -| `framer-motion` | Component mount/unmount, card stagger, layout animations | - -All existing dependencies preserved (React 19, Tailwind 4, TanStack Query, Lucide, Radix). - -## Color System - -```css -:root { - --bg: 0 0% 98%; - --bg-secondary: 210 40% 96%; - --fg: 222 20% 12%; - --fg-muted: 215 20% 45%; - --primary: 199 80% 50%; /* #7EC8E3 sky blue */ - --primary-soft: 199 80% 90%; - --primary-glow: 199 80% 70% / 0.25; - --card: 0 0% 100%; - --card-shadow: 0 8px 32px hsl(199 50% 60% / 0.1); - --border: 199 30% 85%; - --accent: 340 80% 65%; /* soft pink */ - --accent-glow: 340 80% 70% / 0.2; - --radius: 1rem; /* 16px base */ -} -``` - -## Layout - -``` -┌─────────┬─────────────────────────────────────────┐ -│ │ Header (transparent, sticky) │ -│ Sidebar │ ┌─ tab title ──── [WS] [Voice] ──────┐ │ -│ 64px │ │ subtitle │ │ -│ icon- │ └─────────────────────────────────────┘ │ -│ only │ │ -│ │ Main Content Area │ -│ [🌟] │ ┌───────────────────┬───────────────┐ │ -│ [💬] │ │ Card │ Card │ │ -│ [📊] │ │ │ │ │ -│ │ └───────────────────┴───────────────┘ │ -│ 🐱 │ ┌───────────────────────────────────┐ │ -│ chibi │ │ Card (full width) │ │ -│ mascot │ └───────────────────────────────────┘ │ -└─────────┴─────────────────────────────────────────┘ - -Background: Three.js particle canvas (z-index: -1) -All content above particles -``` - -## Component Tree - -### Sidebar (`widgets/Sidebar.tsx`) -- Collapsed by default (64px) — icon-only -- Icons: custom anime-style or Lucide icons -- Active tab: sky-blue soft bg + glow ring -- GSAP hover: translateX(2px) + spring scale -- Bottom: chibi mascot avatar (CSS art or SVG) -- Framer Motion layout animation when switching - -### Header (`widgets/Header.tsx`) -- Transparent, barely visible border-bottom -- Left: animated tab title + subtitle -- Right: status badges (WS green dot + Voice) — cute style -- Avatar placeholder with anime border - -### Cards (all shared/ui and feature components) -- White bg, sky-blue border, 16px radius -- Soft shadow with sky-blue tint -- GSAP hover: lift +4px, shadow deepens -- Framer Motion: stagger entrance when tab changes -- Inside: playful headings, pastel accents - -### Empty States -- Chibi mascot illustration (CSS-drawn character) -- Playful text: "No speakers yet... 🎤✨" -- Subtle bounce animation on the mascot - -## Three.js Particles - -- **Layer:** z-index: -1, pointer-events: none, fixed position -- **Elements:** Floaty sakura petals (pink soft) + sparkles (white/sky blue) -- **Behavior:** Slow drift downward, slight mouse interaction (gentle pull) -- **Performance:** `useFrame` optimized, max ~50 particles, auto-pause when tab hidden -- **Implementation:** `@react-three/fiber` Canvas with custom `Particles` component - -## Animations (GSAP + Framer Motion) - -### GSAP -- **Page transitions:** tab switch → old content fades out (300ms) → new content slides up (400ms) -- **Timeline:** coordinated header + sidebar + content stagger -- **Hover effects:** card lift on `onMouseEnter`/`onMouseLeave` - -### Framer Motion -- **Card stagger:** each card enters with 80ms delay, spring physics -- **Layout animations:** sidebar width, list reorder -- **AnimatePresence:** smooth mount/unmount for sub-panels -- **WhileHover:** cards, buttons, badges - -## Features to Modify - -### 1. Auth Page (`features/auth/index.tsx`) -- Center card with sky-blue gradient border -- Mascot next to lock icon -- Floating sparkle particles in background -- Input: rounded-xl, sky-blue focus ring -- GSAP entrance: card slides up + fade - -### 2. Live Panel (`features/live/index.tsx`) -- VoiceConnectionCard: anime-styled select inputs, cute buttons with icons -- AudioVisualizer: keep but style with sky-blue/pink gradient bars -- ActiveSpeakers: avatar dengan anime-style border (double ring) -- NowPlaying: spinning record visual + anime equalizer bars -- Music/Screen/Recordings tabs: rounded cards with sky-blue accents - -### 3. Messages Panel (`features/messages/index.tsx`) -- Search bar: rounded-full, sky-blue focus -- Filter buttons: pill-shaped, anime pastel colors -- MessageCard: avatar with anime border, content with rounded bg -- AI badges: cute tags (clean = green pastel, flagged = pink pastel) -- ImageGrid: masonry-like with sky-blue hover border - -### 4. Analytics Panel (`features/analytics/index.tsx`) -- Summary cards: large number with sky-blue glow, small anime icon -- Charts: sky-blue + pink pastel gradients -- Tables: rounded rows, alternating pastel bg -- Loading skeletons: sky-blue shimmer animation - -## Implementation Phases - -### Phase 1: Foundation -1. Install new dependencies (`three`, `@react-three/fiber`, `@react-three/drei`, `gsap`, `framer-motion`) -2. Update `tailwind.config.js` with new color system -3. Rewrite `styles.css` with white/sky-blue theme -4. Create Three.js ParticleBackground component -5. Create shared animation hooks (`useGsapTransition`, `useFramerStagger`) - -### Phase 2: Layout -6. Redesign `Sidebar.tsx` (collapsed icon-only, anime icons, mascot) -7. Redesign `Header.tsx` (transparent, anime status badges) -8. Redesign `DashboardLayout.tsx` (white bg, particle layer) -9. Add GSAP page transitions between tabs - -### Phase 3: Components -10. Redesign shared UI: `card.tsx`, `button.tsx`, `badge.tsx`, `input.tsx`, `select.tsx` -11. Redesign `AuthOverlay` with anime styling -12. Redesign `VoiceConnectionCard` + `ActiveSpeakers` + `NowPlaying` -13. Redesign `MessageFeed` + `MessageCard` + `ImageGrid` -14. Redesign Analytics panel components - -### Phase 4: Polish -15. Add Framer Motion stagger animations to all lists -16. Add empty states with mascot illustrations -17. Add loading animations (skeleton shimmer, pulse) -18. Final color/contrast pass for readability -19. Responsive check on mobile - -## Edge Cases & States - -### Loading States -- Skeletons: sky-blue gradient shimmer, rounded-xl -- Three.js particles: render behind skeleton, visible from second 0 -- GSAP entrance: content fades in after skeleton → real content swap - -### Empty States -- **No messages:** Chibi mascot waving "No messages yet~ 💬" -- **No speakers:** Mascot sleeping "Quiet in here... zzz 🌙" -- **No analytics data:** Mascot thinking "Not enough data, give me moar! 📊" -- **Auth locked:** Mascot peeking "Password please~ 🔒" - -### Error States -- Error boundaries: pastel pink/red card with chibi crying mascot -- Network error: toast with cute sad face -- Retry button: bouncy animation on hover - -### Edge Cases -- **Tab not visible:** Three.js pauses via `document.hidden` listener -- **GSAP/Framer conflict:** GSAP for page-level, Framer for component-level — never both on same element -- **Reduced motion:** `prefers-reduced-motion` disables all animations, static fallback -- **Low-end devices:** `renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))` -- **Mobile:** Sidebar becomes bottom tab, particles reduced to 20 max - -## Accessibility - -- All colors meet WCAG AA on white backgrounds (sky blue `#7EC8E3` on white = 4.8:1 for large text) -- Reduced motion media query respected -- Keyboard navigation preserved in all anime-styled components -- Particle layer has `aria-hidden="true"` and `pointer-events: none` - -## Future Enhancements (Out of Scope) - -- Custom anime character illustration as actual image -- Sound effects on tab switch (anime jingle) -- Seasonal themes (cherry blossom, summer, etc.) -- User avatar with anime filter - ---- - -*Design approved by user on 2026-06-03.* diff --git a/docs/superpowers/plans/2026-05-13-aggressive-cleanup.md b/docs/superpowers/plans/2026-05-13-aggressive-cleanup.md deleted file mode 100644 index e26b44f..0000000 --- a/docs/superpowers/plans/2026-05-13-aggressive-cleanup.md +++ /dev/null @@ -1,975 +0,0 @@ -# Aggressive Cleanup Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Clean up Bun/TypeScript Discord voice recorder with Biome, Vitest, stricter types, modular recorder code, and verification scripts. - -**Architecture:** Keep runtime behavior same while moving pure logic into focused modules. `src/recorder.ts` remains public API or re-exports recorder module to minimize import churn; new `src/recorder/*` files own config parsing, metadata creation, decoder lifecycle, segment lifecycle, and stream orchestration. - -**Tech Stack:** Bun, TypeScript strict mode, Biome, Vitest, discord.js-selfbot-v13, @discordjs/voice, prism-media. - ---- - -## File Map - -- Modify `package.json`: add scripts and dev dependencies. -- Create `biome.json`: formatter/linter config. -- Create `vitest.config.ts`: Bun-compatible Vitest config. -- Modify `tsconfig.json`: include tests/config if needed, keep strict mode. -- Modify `src/config.ts`: typed env/config parsing. -- Create `src/types.ts`: shared recorder-facing types. -- Create `src/recorder/metadata.ts`: user metadata and segment metadata builders. -- Create `src/recorder/decoder.ts`: Opus decoder lifecycle. -- Create `src/recorder/segment.ts`: OGG segment lifecycle. -- Create `src/recorder/audioStream.ts`: subscribe/event wiring helpers. -- Modify `src/recorder.ts`: orchestrator using new modules. -- Create `tests/config.test.ts`, `tests/recorder/metadata.test.ts`, `tests/recorder/decoder.test.ts`, `tests/recorder/segment.test.ts`. - ---- - -### Task 1: Tooling setup - -**Files:** -- Modify: `package.json` -- Create: `biome.json` -- Create: `vitest.config.ts` -- Modify: `tsconfig.json` - -- [ ] **Step 1: Add dependencies** - -Run: -```bash -bun add -d @biomejs/biome vitest -``` -Expected: `package.json` and lockfile update. - -- [ ] **Step 2: Update scripts in `package.json`** - -Set scripts to include: -```json -{ - "dev": "bun --watch src/index.ts", - "start": "bun src/index.ts", - "typecheck": "tsc --noEmit", - "lint": "biome check .", - "format": "biome format --write .", - "test": "vitest run" -} -``` - -- [ ] **Step 3: Create `biome.json`** - -```json -{ - "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", - "files": { - "includes": ["src/**/*.ts", "tests/**/*.ts", "*.json", "*.ts"] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2 - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "warn" - } - } - } -} -``` - -- [ ] **Step 4: Create `vitest.config.ts`** - -```ts -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "node", - include: ["tests/**/*.test.ts"], - }, -}); -``` - -- [ ] **Step 5: Verify tooling commands** - -Run: -```bash -bun run typecheck -bun run lint -bun run test -``` -Expected: typecheck may pass or show existing issues; lint may show format/type warnings; test may pass with no tests or fail if Vitest needs config adjustment. Fix only setup errors in this task. - -- [ ] **Step 6: Commit** - -```bash -git add package.json bun.lockb biome.json vitest.config.ts tsconfig.json -git commit -m "chore: add code quality tooling" -``` - ---- - -### Task 2: Config parsing tests and implementation - -**Files:** -- Modify: `src/config.ts` -- Create: `tests/config.test.ts` - -- [ ] **Step 1: Write failing tests** - -Create `tests/config.test.ts`: -```ts -import { describe, expect, it } from "vitest"; -import { parseBoolean, parsePositiveNumber } from "../src/config"; - -describe("config parsers", () => { - it("parses boolean values", () => { - expect(parseBoolean("true", false)).toBe(true); - expect(parseBoolean("false", true)).toBe(false); - expect(parseBoolean(undefined, true)).toBe(true); - }); - - it("parses positive numbers", () => { - expect(parsePositiveNumber("5000", 0)).toBe(5000); - expect(parsePositiveNumber("0", 123)).toBe(123); - expect(parsePositiveNumber("bad", 123)).toBe(123); - expect(parsePositiveNumber(undefined, 123)).toBe(123); - }); -}); -``` - -- [ ] **Step 2: Run failing test** - -Run: -```bash -bun run test tests/config.test.ts -``` -Expected: FAIL because `parseBoolean` and `parsePositiveNumber` are not exported. - -- [ ] **Step 3: Implement config helpers** - -Update `src/config.ts` to export: -```ts -export interface AppConfig { - verbose: boolean; - recordingsDir: string; - recordingSegmentMs: number; - decoderRotateMs: number; - decoderCooldownMs: number; -} - -export function parseBoolean(value: string | undefined, fallback: boolean): boolean { - if (value === "true") return true; - if (value === "false") return false; - return fallback; -} - -export function parsePositiveNumber(value: string | undefined, fallback: number): number { - const parsed = Number(value); - return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; -} - -export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { - return { - verbose: parseBoolean(env.VERBOSE, false), - recordingsDir: env.RECORDINGS_DIR ?? "./recordings", - recordingSegmentMs: parsePositiveNumber(env.RECORDING_SEGMENT_MS, 5_000), - decoderRotateMs: parsePositiveNumber(env.DECODER_ROTATE_MS, 5_000), - decoderCooldownMs: 30_000, - }; -} - -export const config = loadConfig(); -``` -Preserve any existing exports by folding them into `AppConfig` if needed. - -- [ ] **Step 4: Run config tests** - -Run: -```bash -bun run test tests/config.test.ts -``` -Expected: PASS. - -- [ ] **Step 5: Run typecheck** - -Run: -```bash -bun run typecheck -``` -Expected: PASS or only unrelated existing errors. Fix config-related errors. - -- [ ] **Step 6: Commit** - -```bash -git add src/config.ts tests/config.test.ts -git commit -m "refactor: type application config" -``` - ---- - -### Task 3: Shared recorder types - -**Files:** -- Create: `src/types.ts` - -- [ ] **Step 1: Create shared types** - -Create `src/types.ts`: -```ts -import type fs from "node:fs"; -import type prism from "prism-media"; - -export interface RoleMetadata { - id: string; - name: string; - position: number; -} - -export interface UserMetadata { - userId: string; - username: string; - tag: string; - displayName: string; - avatarUrl: string; - bot: boolean; - roles: RoleMetadata[]; - highestRole: RoleMetadata | null; - joinedTimestamp: number | null; -} - -export interface SegmentState { - index: number; - startTime: number; - endTime: number | null; - filename: string; - jsonFilename: string; - oggStream: prism.opus.OggLogicalBitstream; - out: fs.WriteStream; -} - -export interface SegmentMetadata extends UserMetadata { - sessionId: string; - sessionStartTime: number; - segmentIndex: number; - segmentMs: number; - startTime: number; - endTime: number; - durationMs: number; - filename: string; -} - -export interface PcmBroadcaster { - broadcastPcmToWeb?: (chunk: Buffer, userId: string) => void; - updateActiveUser?: (userId: string, data: { username: string; avatar: string; speaking: boolean }) => void; -} -``` - -- [ ] **Step 2: Run typecheck** - -Run: -```bash -bun run typecheck -``` -Expected: PASS. If prism type export fails, use `unknown` for `oggStream` plus local narrowed calls in segment implementation. - -- [ ] **Step 3: Commit** - -```bash -git add src/types.ts -git commit -m "refactor: add recorder domain types" -``` - ---- - -### Task 4: Metadata tests and implementation - -**Files:** -- Create: `src/recorder/metadata.ts` -- Create: `tests/recorder/metadata.test.ts` - -- [ ] **Step 1: Write tests** - -Create `tests/recorder/metadata.test.ts`: -```ts -import { describe, expect, it } from "vitest"; -import { createSegmentMetadata } from "../../src/recorder/metadata"; -import type { SegmentState, UserMetadata } from "../../src/types"; - -const user: UserMetadata = { - userId: "123", - username: "alice", - tag: "alice#0001", - displayName: "Alice", - avatarUrl: "https://cdn.discordapp.com/embed/avatars/0.png", - bot: false, - roles: [{ id: "role", name: "Admin", position: 1 }], - highestRole: { id: "role", name: "Admin", position: 1 }, - joinedTimestamp: 100, -}; - -const segment = { - index: 2, - startTime: 1_000, - endTime: 2_500, - filename: "/tmp/2500.ogg", - jsonFilename: "/tmp/2500.json", - oggStream: {} as SegmentState["oggStream"], - out: {} as SegmentState["out"], -}; - -describe("createSegmentMetadata", () => { - it("combines user and segment data", () => { - const metadata = createSegmentMetadata(user, segment, "session-1", 900, 5_000); - - expect(metadata).toMatchObject({ - userId: "123", - username: "alice", - sessionId: "session-1", - sessionStartTime: 900, - segmentIndex: 2, - segmentMs: 5_000, - startTime: 1_000, - endTime: 2_500, - durationMs: 1_500, - filename: "2500.ogg", - }); - }); -}); -``` - -- [ ] **Step 2: Run failing test** - -```bash -bun run test tests/recorder/metadata.test.ts -``` -Expected: FAIL because module does not exist. - -- [ ] **Step 3: Implement metadata module** - -Create `src/recorder/metadata.ts`: -```ts -import path from "node:path"; -import type { Client, VoiceChannel } from "discord.js-selfbot-v13"; -import type { SegmentMetadata, SegmentState, UserMetadata } from "../types"; - -export async function collectUserMetadata( - client: Client, - userId: string, - channel: VoiceChannel, -): Promise { - const user = client.users.cache.get(userId) ?? (await client.users.fetch(userId).catch(() => null)); - const member = channel.guild.members.cache.get(userId) ?? (await channel.guild.members.fetch(userId).catch(() => null)); - const username = user?.username ?? "Unknown User"; - const roles = - member?.roles.cache - .filter((role) => role.id !== channel.guild.id) - .sort((a, b) => b.position - a.position) - .map((role) => ({ id: role.id, name: role.name, position: role.position })) ?? []; - - return { - userId, - username, - tag: user?.tag ?? "Unknown#0000", - displayName: member?.displayName ?? username, - avatarUrl: user?.displayAvatarURL({ format: "png", size: 64 }) ?? "https://cdn.discordapp.com/embed/avatars/0.png", - bot: user?.bot ?? false, - roles, - highestRole: roles[0] ?? null, - joinedTimestamp: member?.joinedTimestamp ?? null, - }; -} - -export function createSegmentMetadata( - user: UserMetadata, - segment: SegmentState, - sessionId: string, - sessionStartTime: number, - recordingSegmentMs: number, -): SegmentMetadata { - const endTime = segment.endTime ?? Date.now(); - return { - ...user, - sessionId, - sessionStartTime, - segmentIndex: segment.index, - segmentMs: recordingSegmentMs, - startTime: segment.startTime, - endTime, - durationMs: endTime - segment.startTime, - filename: path.basename(segment.filename), - }; -} -``` - -- [ ] **Step 4: Run tests and typecheck** - -```bash -bun run test tests/recorder/metadata.test.ts -bun run typecheck -``` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add src/recorder/metadata.ts tests/recorder/metadata.test.ts -git commit -m "refactor: extract recorder metadata builders" -``` - ---- - -### Task 5: Decoder tests and implementation - -**Files:** -- Create: `src/recorder/decoder.ts` -- Create: `tests/recorder/decoder.test.ts` - -- [ ] **Step 1: Write tests using fake decoder factory** - -Create `tests/recorder/decoder.test.ts`: -```ts -import { describe, expect, it, vi } from "vitest"; -import { OpusDecoder } from "../../src/recorder/decoder"; - -class FakeDecoder { - handlers = new Map void>(); - destroyed = false; - writes: Buffer[] = []; - - on(event: string, handler: (...args: unknown[]) => void) { - this.handlers.set(event, handler); - return this; - } - - write(chunk: Buffer) { - this.writes.push(chunk); - } - - removeAllListeners() { - this.handlers.clear(); - } - - destroy() { - this.destroyed = true; - } -} - -describe("OpusDecoder", () => { - it("creates decoder lazily and writes chunks", () => { - const fake = new FakeDecoder(); - const decoder = new OpusDecoder({ cooldownMs: 30_000, rotateMs: 5_000, createDecoder: () => fake as never, onData: vi.fn() }); - - decoder.write(Buffer.from([1, 2, 3])); - - expect(fake.writes).toHaveLength(1); - }); - - it("destroys and recreates after rotation timeout", () => { - vi.useFakeTimers(); - const created: FakeDecoder[] = []; - const decoder = new OpusDecoder({ - cooldownMs: 30_000, - rotateMs: 5_000, - createDecoder: () => { - const fake = new FakeDecoder(); - created.push(fake); - return fake as never; - }, - onData: vi.fn(), - }); - - decoder.write(Buffer.from([1])); - vi.advanceTimersByTime(5_001); - decoder.rotateIfNeeded(); - decoder.write(Buffer.from([2])); - - expect(created).toHaveLength(2); - expect(created[0].destroyed).toBe(true); - vi.useRealTimers(); - }); -}); -``` - -- [ ] **Step 2: Run failing test** - -```bash -bun run test tests/recorder/decoder.test.ts -``` -Expected: FAIL because module does not exist. - -- [ ] **Step 3: Implement decoder module** - -Create `src/recorder/decoder.ts`: -```ts -import prism from "prism-media"; - -export interface OpusDecoderOptions { - cooldownMs: number; - rotateMs: number; - createDecoder?: () => prism.opus.Decoder; - onData: (pcm: Buffer) => void; -} - -export class OpusDecoder { - private decoder: prism.opus.Decoder | null = null; - private disabledUntil = 0; - private createdAt = 0; - private readonly cooldownMs: number; - private readonly rotateMs: number; - private readonly createDecoderFn: () => prism.opus.Decoder; - private readonly onData: (pcm: Buffer) => void; - - constructor(options: OpusDecoderOptions) { - this.cooldownMs = options.cooldownMs; - this.rotateMs = options.rotateMs; - this.onData = options.onData; - this.createDecoderFn = - options.createDecoder ?? - (() => new prism.opus.Decoder({ frameSize: 960, channels: 2, rate: 48_000 })); - } - - rotateIfNeeded(): void { - if (!this.decoder || this.rotateMs <= 0) return; - if (Date.now() - this.createdAt < this.rotateMs) return; - this.destroy(); - this.ensureDecoder(); - } - - write(chunk: Buffer): void { - const decoder = this.ensureDecoder(); - if (!decoder) return; - try { - decoder.write(chunk); - } catch (error) { - console.warn("[recorder] Opus decoder write failed, cooling down:", error); - this.coolDown(); - } - } - - destroy(): void { - if (!this.decoder) return; - this.decoder.removeAllListeners(); - this.decoder.destroy(); - this.decoder = null; - this.createdAt = 0; - } - - private ensureDecoder(): prism.opus.Decoder | null { - if (this.decoder) return this.decoder; - if (Date.now() < this.disabledUntil) return null; - try { - const decoder = this.createDecoderFn(); - decoder.on("data", this.onData); - decoder.on("error", (error) => { - console.warn("[recorder] Opus decoder error, cooling down:", error); - this.coolDown(); - }); - this.decoder = decoder; - this.createdAt = Date.now(); - return decoder; - } catch (error) { - console.warn("[recorder] Opus decoder init failed, cooling down:", error); - this.disabledUntil = Date.now() + this.cooldownMs; - return null; - } - } - - private coolDown(): void { - this.disabledUntil = Date.now() + this.cooldownMs; - this.destroy(); - } -} -``` - -- [ ] **Step 4: Run tests and typecheck** - -```bash -bun run test tests/recorder/decoder.test.ts -bun run typecheck -``` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add src/recorder/decoder.ts tests/recorder/decoder.test.ts -git commit -m "refactor: extract opus decoder lifecycle" -``` - ---- - -### Task 6: Segment tests and implementation - -**Files:** -- Create: `src/recorder/segment.ts` -- Create: `tests/recorder/segment.test.ts` - -- [ ] **Step 1: Write tests for pure filename and rotation decision helpers** - -Create `tests/recorder/segment.test.ts`: -```ts -import { describe, expect, it } from "vitest"; -import { buildSegmentPaths, shouldRotateSegment } from "../../src/recorder/segment"; - -describe("buildSegmentPaths", () => { - it("creates matching ogg and json paths", () => { - expect(buildSegmentPaths("/tmp/user", 123)).toEqual({ - filename: "/tmp/user/123.ogg", - jsonFilename: "/tmp/user/123.json", - }); - }); -}); - -describe("shouldRotateSegment", () => { - it("rotates only when segment limit is exceeded", () => { - expect(shouldRotateSegment(1_000, 1_499, 500)).toBe(false); - expect(shouldRotateSegment(1_000, 1_500, 500)).toBe(true); - expect(shouldRotateSegment(1_000, 2_000, 0)).toBe(false); - }); -}); -``` - -- [ ] **Step 2: Run failing test** - -```bash -bun run test tests/recorder/segment.test.ts -``` -Expected: FAIL because module does not exist. - -- [ ] **Step 3: Implement segment helpers and manager** - -Create `src/recorder/segment.ts`: -```ts -import fs from "node:fs"; -import path from "node:path"; -import prism from "prism-media"; -import type { SegmentState } from "../types"; - -export function buildSegmentPaths(userDir: string, startTime: number): { filename: string; jsonFilename: string } { - return { - filename: path.join(userDir, `${startTime}.ogg`), - jsonFilename: path.join(userDir, `${startTime}.json`), - }; -} - -export function shouldRotateSegment(startTime: number, now: number, recordingSegmentMs: number): boolean { - return recordingSegmentMs > 0 && now - startTime >= recordingSegmentMs; -} - -export class SegmentManager { - private currentSegment: SegmentState | null = null; - private segmentIndex = 0; - - constructor( - private readonly userDir: string, - private readonly recordingSegmentMs: number, - ) {} - - open(oggPacketStream: NodeJS.ReadableStream): SegmentState { - const index = this.segmentIndex++; - const startTime = Date.now(); - const { filename, jsonFilename } = buildSegmentPaths(this.userDir, startTime); - const oggStream = new prism.opus.OggLogicalBitstream({ - opusHead: new prism.opus.OpusHead({ channelCount: 2, sampleRate: 48_000 }), - pageSizeControl: { maxPackets: 10 }, - crc: true, - }); - const out = fs.createWriteStream(filename); - oggPacketStream.pipe(oggStream).pipe(out); - - this.currentSegment = { index, startTime, endTime: null, filename, jsonFilename, oggStream, out }; - return this.currentSegment; - } - - close(oggPacketStream: NodeJS.ReadableStream): SegmentState | null { - if (!this.currentSegment) return null; - const segment = this.currentSegment; - segment.endTime = Date.now(); - oggPacketStream.unpipe(segment.oggStream); - segment.oggStream.end(); - this.currentSegment = null; - return segment; - } - - rotateIfNeeded(oggPacketStream: NodeJS.ReadableStream): SegmentState | null { - if (!this.currentSegment) return null; - if (!shouldRotateSegment(this.currentSegment.startTime, Date.now(), this.recordingSegmentMs)) return null; - this.close(oggPacketStream); - return this.open(oggPacketStream); - } - - getCurrent(): SegmentState | null { - return this.currentSegment; - } -} -``` - -- [ ] **Step 4: Run tests and typecheck** - -```bash -bun run test tests/recorder/segment.test.ts -bun run typecheck -``` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add src/recorder/segment.ts tests/recorder/segment.test.ts -git commit -m "refactor: extract recording segment manager" -``` - ---- - -### Task 7: Audio stream helper - -**Files:** -- Create: `src/recorder/audioStream.ts` - -- [ ] **Step 1: Create stream helper** - -Create `src/recorder/audioStream.ts`: -```ts -import { EndBehaviorType, type VoiceReceiver } from "@discordjs/voice"; - -export interface AudioStreamHandlers { - onPacket: (chunk: Buffer) => void; - onEnd: () => void; - onError: (error: Error) => void; -} - -export function subscribeToAudioStream( - receiver: VoiceReceiver, - userId: string, - handlers: AudioStreamHandlers, -): NodeJS.ReadableStream { - const audioStream = receiver.subscribe(userId, { - end: { - behavior: EndBehaviorType.AfterSilence, - duration: 3_000, - }, - }); - - audioStream.on("data", handlers.onPacket); - audioStream.on("end", handlers.onEnd); - audioStream.on("error", handlers.onError); - - return audioStream; -} -``` - -- [ ] **Step 2: Run typecheck** - -```bash -bun run typecheck -``` -Expected: PASS. - -- [ ] **Step 3: Commit** - -```bash -git add src/recorder/audioStream.ts -git commit -m "refactor: extract audio stream subscription" -``` - ---- - -### Task 8: Refactor `src/recorder.ts` orchestration - -**Files:** -- Modify: `src/recorder.ts` - -- [ ] **Step 1: Replace inline helpers with modules** - -Edit `src/recorder.ts`: -- Import `collectUserMetadata`, `createSegmentMetadata`. -- Import `OpusDecoder`. -- Import `SegmentManager`. -- Import `subscribeToAudioStream`. -- Use `config.recordingsDir`, `config.recordingSegmentMs`, `config.decoderRotateMs`, `config.decoderCooldownMs`. -- Keep `startRecording(client, channel)` and `stopRecording(guildId)` exports unchanged. -- Remove packet debug logging `Pkt #...`. -- Keep current global web update behavior via `globalThis as PcmBroadcaster`. - -Core packet handler shape: -```ts -const broadcaster = globalThis as typeof globalThis & PcmBroadcaster; -const userMetadata = await collectUserMetadata(client, userId, channel); -const segmentManager = new SegmentManager(userDir, config.recordingSegmentMs); -const decoder = new OpusDecoder({ - cooldownMs: config.decoderCooldownMs, - rotateMs: config.decoderRotateMs, - onData: (pcm) => { - if (!broadcaster.broadcastPcmToWeb) return; - const outBuf = Buffer.alloc(pcm.length / 4); - for (let i = 0; i < outBuf.length / 2; i++) { - outBuf.writeInt16LE(pcm.readInt16LE(i * 8), i * 2); - } - broadcaster.broadcastPcmToWeb(outBuf, userId); - }, -}); - -const audioStream = subscribeToAudioStream(receiver, userId, { - onPacket: (chunk) => { - if (chunk.length < 8) return; - segmentManager.rotateIfNeeded(oggPacketStream); - if (!broadcaster.broadcastPcmToWeb) return; - decoder.rotateIfNeeded(); - decoder.write(chunk); - }, - onEnd: () => { - const segment = segmentManager.close(oggPacketStream); - decoder.destroy(); - if (segment) { - const metadata = createSegmentMetadata(userMetadata, segment, sessionId, sessionStartTime, config.recordingSegmentMs); - fs.writeFileSync(segment.jsonFilename, JSON.stringify(metadata, null, 2)); - } - broadcaster.updateActiveUser?.(userId, { username: userMetadata.username, avatar: userMetadata.avatarUrl, speaking: false }); - }, - onError: (error) => { - segmentManager.close(oggPacketStream); - decoder.destroy(); - console.error(`[recorder] Audio Stream error ${userId}:`, error.message); - }, -}); -``` - -- [ ] **Step 2: Preserve metadata writes on segment finish** - -If existing behavior writes JSON when `out` finishes, attach `out.on("finish", ...)` in `SegmentManager.open()` caller after opening current segment. Ensure every closed segment gets JSON metadata, including rotated segments. - -- [ ] **Step 3: Run typecheck** - -```bash -bun run typecheck -``` -Expected: PASS. Fix type errors by narrowing types, not adding `any` unless third-party library lacks exported type. - -- [ ] **Step 4: Run tests** - -```bash -bun run test -``` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add src/recorder.ts src/recorder/audioStream.ts src/recorder/decoder.ts src/recorder/metadata.ts src/recorder/segment.ts src/types.ts -git commit -m "refactor: modularize recorder orchestration" -``` - ---- - -### Task 9: Format and lint cleanup - -**Files:** -- Modify: all formatted TypeScript/config files touched by Biome. - -- [ ] **Step 1: Run formatter** - -```bash -bun run format -``` -Expected: files formatted. - -- [ ] **Step 2: Run linter** - -```bash -bun run lint -``` -Expected: PASS or actionable warnings. Fix warnings that are in touched code. - -- [ ] **Step 3: Run typecheck and tests** - -```bash -bun run typecheck -bun run test -``` -Expected: PASS. - -- [ ] **Step 4: Commit** - -```bash -git add . -git commit -m "style: format and lint codebase" -``` - ---- - -### Task 10: Manual runtime verification - -**Files:** -- No required code changes unless verification finds bug. - -- [ ] **Step 1: Start app** - -Run: -```bash -bun run start -``` -Expected: app starts, logs bot ready or fails only due missing env credentials. - -- [ ] **Step 2: If env exists, verify recording flow** - -Manual steps: -1. Join configured Discord voice channel. -2. Speak for >3 seconds. -3. Confirm `.ogg` file and `.json` metadata are created under `RECORDINGS_DIR`. -4. Keep speaking past `RECORDING_SEGMENT_MS`; confirm segment rotation creates multiple files. -5. Stop app with Ctrl-C; confirm graceful shutdown log. - -- [ ] **Step 3: Commit fixes if needed** - -```bash -git add src tests package.json biome.json vitest.config.ts tsconfig.json -git commit -m "fix: preserve recorder runtime behavior" -``` -Only run if code changed. - ---- - -### Task 11: Final verification - -**Files:** -- No changes expected. - -- [ ] **Step 1: Run full verification** - -```bash -bun run format -bun run lint -bun run typecheck -bun run test -git status --short -``` -Expected: formatter stable, lint PASS, typecheck PASS, tests PASS, git status clean or only intentional uncommitted runtime artifacts excluded by `.gitignore`. - -- [ ] **Step 2: Review diff summary** - -```bash -git log --oneline -8 -git diff HEAD~8...HEAD --stat -``` -Expected: commits show tooling, config, types, metadata, decoder, segment, stream, recorder refactor, formatting. - -- [ ] **Step 3: Report result** - -Report: -- Commands run and pass/fail status. -- Runtime verification status. -- Any remaining risks, especially Discord runtime behavior if not manually tested with credentials. - ---- - -## Self-Review - -- Spec coverage: tooling, config, shared types, metadata, decoder, segment, audio stream, recorder orchestration, tests, lint/format, and runtime verification are covered. -- Placeholder scan: no TBD/TODO placeholders. -- Type consistency: `UserMetadata`, `SegmentState`, `SegmentMetadata`, `PcmBroadcaster`, `OpusDecoder`, `SegmentManager`, and `subscribeToAudioStream` names are consistent across tasks. diff --git a/docs/superpowers/plans/2026-05-13-backlog-sync-rich-metadata.md b/docs/superpowers/plans/2026-05-13-backlog-sync-rich-metadata.md deleted file mode 100644 index 303fa18..0000000 --- a/docs/superpowers/plans/2026-05-13-backlog-sync-rich-metadata.md +++ /dev/null @@ -1,64 +0,0 @@ -# Backlog Sync Rich Metadata Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Fetch prior Discord messages up to 24 hours on startup, persist rich Discord-client-like metadata, and render rich message content in homepage tabs. - -**Architecture:** Add `messageMetadata.ts` for reusable extraction, `backlogSync.ts` for bounded startup history fetch, reuse existing store/uploader. UI reads metadata JSON and renders stickers, embeds, attachments/replies/thread badges. - -**Tech Stack:** Bun, TypeScript, discord.js-selfbot-v13, bun:sqlite, Express/WebSocket, vanilla HTML/CSS/JS. - ---- - -### Task 1: Extract rich message metadata - -**Files:** -- Create: `src/moderation/messageMetadata.ts` -- Modify: `src/moderation/messageCapture.ts` - -- [ ] Create helper functions: `getMessageLocation`, `getStickerMetadata`, `getEmbedMetadata`, `getAttachmentMetadata`, `getMessageMetadata`, `getDisplayContent`. -- [ ] Replace duplicate capture helper logic with imports from `messageMetadata.ts`. -- [ ] Verify: `bun run typecheck`. - -### Task 2: Make message inserts idempotent - -**Files:** -- Modify: `src/moderation/messageStore.ts` - -- [ ] Change message insert to `INSERT OR IGNORE` so backlog sync and live events do not conflict. -- [ ] Change attachment insert to `INSERT OR IGNORE`. -- [ ] Verify: `bun run typecheck && bun run test`. - -### Task 3: Add backlog sync - -**Files:** -- Create: `src/moderation/backlogSync.ts` -- Modify: `src/index.ts` -- Modify: `src/config.ts` -- Modify: `.env.example` - -- [ ] Add config: `BACKLOG_SYNC_HOURS=24`, `BACKLOG_SYNC_BATCH_SIZE=100`. -- [ ] Fetch text/thread channels from monitored guild on ready. -- [ ] For each channel/thread, page `channel.messages.fetch({ limit, before })` until older than cutoff. -- [ ] Store messages with rich metadata and attachments. -- [ ] Start sync after registering live capture; run async and log progress. -- [ ] Verify: `bun run typecheck && bun run test`. - -### Task 4: Render richer UI - -**Files:** -- Modify: `public/index.html` - -- [ ] Render metadata embeds as embed cards. -- [ ] Render attachments as inline previews/links in Text tab. -- [ ] Render reply and thread badges. -- [ ] Keep sticker rendering. -- [ ] Verify static JS syntax by typecheck/tests where applicable. - -### Task 5: Final verification - -**Files:** all touched files - -- [ ] Run `bun run typecheck`. -- [ ] Run `bun run test`. -- [ ] Verify short DB init with `bun -e 'import("./src/muxer-queue.ts").then((m)=>{const db=m.getDatabase(); db.close(); console.log("sqlite ok")})'`. diff --git a/docs/superpowers/plans/2026-05-13-one-port-websocket.md b/docs/superpowers/plans/2026-05-13-one-port-websocket.md deleted file mode 100644 index 6f24814..0000000 --- a/docs/superpowers/plans/2026-05-13-one-port-websocket.md +++ /dev/null @@ -1,149 +0,0 @@ -# One-Port WebSocket Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Serve Express HTTP endpoints, static frontend, and WebSocket traffic on one `WEBSERVER_PORT` using WebSocket path `/ws`. - -**Architecture:** `src/webserver.ts` should create one `http.Server` from the Express app, attach `WebSocketServer` to that same server with `path: "/ws"`, and remove `port + 1`. `public/index.html` should connect to `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/ws` so dev, production, and reverse proxy setups use the same host and port. - -**Tech Stack:** TypeScript, Express, Node HTTP server, `ws`, Bun scripts, Biome, TypeScript compiler. - ---- - -## File Structure - -- Modify `src/webserver.ts`: change WebSocket server construction and logs from separate port to shared HTTP server path `/ws`. -- Modify `public/index.html`: change browser WebSocket URL from hardcoded `:3001` to same-origin `/ws`. -- No new files required. - ---- - -### Task 1: Attach WebSocket to Existing HTTP Server - -**Files:** -- Modify: `src/webserver.ts` - -- [ ] **Step 1: Update WebSocket server creation** - -Replace this code in `src/webserver.ts`: - -```ts -const wsPort = port + 1; -const wss = new WebSocketServer({ port: wsPort, host: "0.0.0.0" }); -wsLogger.info({ wsPort }, "WebSocket server listening"); -``` - -With: - -```ts -const wsPath = "/ws"; -const wss = new WebSocketServer({ server, path: wsPath }); -wsLogger.info({ port, wsPath }, "WebSocket server listening"); -``` - -- [ ] **Step 2: Update connection log** - -Replace this code in `src/webserver.ts`: - -```ts -wsLogger.info({ wsPort }, "New WebSocket connection"); -``` - -With: - -```ts -wsLogger.info({ port, wsPath }, "New WebSocket connection"); -``` - -- [ ] **Step 3: Run typecheck** - -Run: - -```bash -bun run typecheck -``` - -Expected: command exits `0`. - ---- - -### Task 2: Update Browser WebSocket URL - -**Files:** -- Modify: `public/index.html` - -- [ ] **Step 1: Replace hardcoded WebSocket port** - -Replace this code in `public/index.html`: - -```js -socket = new WebSocket(`ws://${window.location.hostname}:3001`); -``` - -With: - -```js -const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; -socket = new WebSocket(`${wsProtocol}//${window.location.host}/ws`); -``` - -- [ ] **Step 2: Run lint and build** - -Run: - -```bash -bun run lint && bun run build -``` - -Expected: both commands exit `0`. - ---- - -### Task 3: Verify One-Port Behavior - -**Files:** -- Verify: `src/webserver.ts` -- Verify: `public/index.html` - -- [ ] **Step 1: Start dev server** - -Run: - -```bash -bun run dev -``` - -Expected logs include Express web interface on configured port and WebSocket server listening with `{ port: 3000, wsPath: "/ws" }`. - -- [ ] **Step 2: Browser smoke test** - -Open: - -```text -http://localhost:3000 -``` - -Expected: page loads and browser WebSocket connects to: - -```text -ws://localhost:3000/ws -``` - -- [ ] **Step 3: Endpoint smoke test** - -Run: - -```bash -curl http://localhost:3000/health -curl http://localhost:3000/metrics -``` - -Expected: `/health` returns JSON and `/metrics` returns Prometheus text. - ---- - -## Self-Review - -- Spec coverage: Covers one-port HTTP/WebSocket server, `/ws` path, frontend URL update, and verification. -- Placeholder scan: No TBD/TODO placeholders. -- Type consistency: Uses `wsPath` in both server creation and logs; frontend connects to `/ws`. diff --git a/docs/superpowers/plans/2026-05-13-react-ssr-dashboard.md b/docs/superpowers/plans/2026-05-13-react-ssr-dashboard.md deleted file mode 100644 index 0a5bda3..0000000 --- a/docs/superpowers/plans/2026-05-13-react-ssr-dashboard.md +++ /dev/null @@ -1,60 +0,0 @@ -# React SSR Dashboard Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace static client-rendered homepage with React server-side rendering while keeping live WebSocket/voice behavior as progressive enhancement. - -**Architecture:** Express `GET /` builds dashboard data, renders React component to HTML with `react-dom/server`, injects bootstrap JSON for client script. CSS/JS move to static assets; React owns initial markup only, lightweight browser JS handles tab switching, voice bridge, WebSocket updates, and async thread discovery. - -**Tech Stack:** React, ReactDOM server, Bun, Express, TypeScript, vanilla browser JS for progressive enhancement. - ---- - -### Task 1: Add React dependencies - -**Files:** -- Modify: `package.json` -- Modify: `bun.lockb` - -- [ ] Run `bun add react react-dom`. -- [ ] Run `bun add -d @types/react @types/react-dom`. -- [ ] Verify `bun run typecheck`. - -### Task 2: Extract dashboard assets - -**Files:** -- Create: `public/dashboard.css` -- Create: `public/dashboard.js` -- Modify: `public/index.html` - -- [ ] Move current `