diff --git a/.gitignore b/.gitignore index 4302cfa..0cbe363 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,9 @@ -# Dependencies -node_modules - -# Build outputs -dist -dist-ssr -*.local +# Rust build outputs +/target/ +Cargo.lock # Logs -logs *.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* # Editor directories and files .vscode @@ -28,10 +18,6 @@ lerna-debug.log* .DS_Store Thumbs.db -# Rust/Tauri build -src-tauri/target -src-tauri/gen/schemas - # Environment files .env .env.* diff --git a/.opencode/plans/GPUI_REFACTOR_PLAN.md b/.opencode/plans/GPUI_REFACTOR_PLAN.md new file mode 100644 index 0000000..6d1a04d --- /dev/null +++ b/.opencode/plans/GPUI_REFACTOR_PLAN.md @@ -0,0 +1,1199 @@ +# Secousse GPUI Refactor Plan + +## Executive Summary + +This document outlines the complete refactoring of **Secousse** from a Tauri/React/Tailwind stack to a pure Rust application using **GPUI** - the GPU-accelerated UI framework from Zed. This is a **complete rewrite** that will port all features while eliminating the JavaScript/web layer entirely. + +--- + +## Table of Contents + +1. [Current Architecture Analysis](#1-current-architecture-analysis) +2. [GPUI Framework Overview](#2-gpui-framework-overview) +3. [Feature Mapping](#3-feature-mapping) +4. [New Architecture Design](#4-new-architecture-design) +5. [Implementation Phases](#5-implementation-phases) +6. [Technical Deep Dives](#6-technical-deep-dives) +7. [Dependencies](#7-dependencies) +8. [Platform Considerations](#8-platform-considerations) +9. [Risk Assessment](#9-risk-assessment) +10. [Timeline Estimate](#10-timeline-estimate) + +--- + +## 1. Current Architecture Analysis + +### 1.1 Technology Stack (Current) + +| Layer | Technology | Purpose | +|-------|------------|---------| +| Desktop Runtime | Tauri 2.0 | Native wrapper, IPC bridge | +| Frontend | React 19 | UI rendering | +| Build Tool | Vite 7 | Frontend bundling | +| Styling | Tailwind CSS 4 | Utility-first CSS | +| Backend | Rust | API client, WebSocket, business logic | + +### 1.2 Current Features (Complete List) + +#### Video Playback +- [x] HLS live stream playback via hls.js +- [x] Custom TauriHlsLoader for CORS bypass (proxies through Rust) +- [x] Quality selection (auto + manual levels: 1080p60, 720p60, etc.) +- [x] Play/Pause, Volume control with slider +- [x] Fullscreen toggle (CSS-based) +- [x] Live viewer count display +- [x] Offline channel detection with profile picture + +#### Chat System +- [x] Real-time IRC WebSocket connection to `irc-ws.chat.twitch.tv` +- [x] Anonymous (justinfan) and authenticated modes +- [x] Send messages when logged in +- [x] Emote rendering inline in messages +- [x] Badge display (subscriber, moderator, VIP, broadcaster, bits) +- [x] Username colors +- [x] Auto-scroll with manual scroll detection +- [x] "Scroll to bottom" button when scrolled up +- [x] Auto-reconnect on disconnect + +#### Authentication +- [x] OAuth 2.0 implicit flow via system browser +- [x] Local HTTP callback server (port 17563) +- [x] Token persistence via Tauri Store plugin +- [x] Token validation on startup +- [x] Login/Logout functionality + +#### Following System +- [x] View followed live channels in sidebar +- [x] Follow/Unfollow channels +- [x] Auto-refresh every 60 seconds + +#### Browse/Discovery +- [x] Top 30 live streams grid +- [x] Stream preview thumbnails (with size templating) +- [x] Viewer count, game category display +- [x] Auto-refresh every 60 seconds + +#### Search +- [x] Debounced channel search (300ms) +- [x] Live/offline status indicators +- [x] Quick channel navigation +- [x] Click-outside to dismiss + +#### Emote Support +- [x] Twitch global emotes +- [x] Twitch channel/subscriber emotes +- [x] 7TV global and channel emotes +- [x] BTTV global and channel emotes +- [x] FFZ channel emotes + +#### Badge Support +- [x] Global Twitch badges +- [x] Channel-specific badges (subscriber tiers, bits) + +#### Analytics +- [x] Spade events (minute-watched) every 60 seconds +- [x] Contributes to viewership statistics + +#### UI Components +- [x] Navbar (logo, tabs, search, login, profile) +- [x] Sidebar (collapsible, channel list with viewer counts) +- [x] Video player (custom controls overlay) +- [x] Chat panel (resizable concept, message list, input) +- [x] Stream info bar (avatar, title, game, follow button) +- [x] Browse grid (responsive card layout) + +### 1.3 Current Data Flow + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ FRONTEND (React/TypeScript) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ useAuth │ │ useChat │ │useEmotes│ │useSearch│ ... │ +│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ │ +│ └────────────┴────────────┴────────────┘ │ +│ │ │ +│ invoke() / listen() │ +└─────────────────────────┼───────────────────────────────────────┘ + │ IPC +┌─────────────────────────┼───────────────────────────────────────┐ +│ BACKEND (Rust/Tauri) │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ AppState (Mutex) │ │ +│ │ - twitch_client: TwitchClient │ │ +│ │ - chat_handle: JoinHandle │ │ +│ │ - chat_sender: mpsc::Sender │ │ +│ │ - watch_state: WatchState │ │ +│ │ - cached_username: Option │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │twitch.rs │ │ chat.rs │ │emotes.rs │ │ +│ │GQL/Helix │ │WebSocket │ │7TV/BTTV │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. GPUI Framework Overview + +### 2.1 What is GPUI? + +GPUI is a **hybrid immediate/retained mode, GPU-accelerated UI framework** for Rust, created by Zed Industries for the Zed editor. Key characteristics: + +- **GPU Rendering**: Uses Metal (macOS), Vulkan (Linux), Direct3D (Windows) +- **Reactive Model**: Entity-based state management with observers +- **Tailwind-style API**: Familiar styling with `.flex()`, `.bg()`, `.p_4()`, etc. +- **High Performance**: Designed for 120fps editor rendering +- **Pure Rust**: No JavaScript, no web views, no CSS + +### 2.2 Core Concepts + +#### Entities (`Entity`) +- Application state containers owned by the `App` context +- Similar to React's `useState` but with explicit ownership +- Can be observed and emit events + +```rust +// Creating an entity +let counter: Entity = cx.new(|_| Counter { count: 0 }); + +// Updating an entity +counter.update(cx, |counter, cx| { + counter.count += 1; + cx.notify(); // Trigger re-render for observers +}); + +// Reading an entity +let count = counter.read(cx).count; +``` + +#### Views (Entities that implement `Render`) +- Entities that can produce UI +- Re-render when `notify()` is called + +```rust +struct MyView { + label: SharedString, +} + +impl Render for MyView { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div() + .flex() + .p_4() + .bg(rgb(0x1a1a1a)) + .child(self.label.clone()) + } +} +``` + +#### Elements (Building Blocks) +- `div()` - The swiss-army knife element +- `img()` - Image rendering +- `svg()` - SVG rendering +- `uniform_list()` - Virtualized lists +- Custom elements via `Element` trait + +#### Contexts +- `App` - Global application state +- `Context` - Entity-specific context with notify/emit +- `Window` - Window state and operations +- `AsyncApp` - For async operations across await points + +#### Actions (Keyboard Shortcuts) +```rust +#[gpui::action] +struct MoveUp; + +actions!(menu, [MoveUp, MoveDown, Select]); + +// Binding in render +div() + .key_context("menu") + .on_action(|_: &MoveUp, window, cx| { /* handle */ }) +``` + +### 2.3 Styling API (Tailwind-like) + +```rust +div() + // Layout + .flex() + .flex_col() + .flex_row() + .gap_2() + .p_4() + .m_2() + .w_full() + .h_64() + .size_8() + + // Colors + .bg(rgb(0x1a1a1a)) + .text_color(rgb(0xffffff)) + .border_color(rgb(0x3f3f46)) + + // Borders + .border_1() + .rounded_md() + .rounded_full() + + // Effects + .shadow_lg() + .opacity(0.5) + + // Interactions + .cursor_pointer() + .hover(|style| style.bg(rgb(0x2f2f35))) + .on_click(|event, window, cx| { /* handle */ }) + .on_mouse_down(MouseButton::Left, |event, window, cx| { /* handle */ }) +``` + +### 2.4 Async Operations + +```rust +// Spawn async task +cx.spawn(|this, mut cx| async move { + let result = fetch_data().await; + this.update(&mut cx, |this, cx| { + this.data = result; + cx.notify(); + }).ok(); +}).detach(); + +// With background executor +cx.background_executor().spawn(async { + heavy_computation() +}).detach(); +``` + +### 2.5 Event System + +```rust +// Define event +struct DataLoaded { + items: Vec, +} + +// Implement emitter +impl EventEmitter for MyModel {} + +// Emit event +cx.emit(DataLoaded { items }); + +// Subscribe to events +cx.subscribe(&model, |this, _model, event: &DataLoaded, cx| { + this.items = event.items.clone(); + cx.notify(); +}).detach(); +``` + +--- + +## 3. Feature Mapping + +### 3.1 React Hooks → GPUI Entities + +| React Hook | GPUI Equivalent | Notes | +|------------|-----------------|-------| +| `useAuth` | `Entity` | Global app state | +| `useChat` | `Entity` | Per-channel, with WebSocket task | +| `useEmotes` | `Entity` | Shared cache, lazy loading | +| `useSearch` | `Entity` | Debounced with timer | +| `useTopStreams` | `Entity` | Cached with refresh | + +### 3.2 React Components → GPUI Views + +| React Component | GPUI Equivalent | Complexity | +|-----------------|-----------------|------------| +| `App.tsx` | `SecousseApp` (root view) | High | +| `Navbar.tsx` | `NavbarView` | Medium | +| `Sidebar.tsx` | `SidebarView` | Medium | +| `VideoPlayer.tsx` | `VideoPlayerView` + gpui-video-player | High | +| `Chat.tsx` | `ChatView` with `uniform_list` | High | +| `StreamInfo.tsx` | `StreamInfoView` | Low | +| `BrowseGrid.tsx` | `BrowseGridView` | Medium | + +### 3.3 State Management Comparison + +**React (Current)**: +```typescript +const [channel, setChannel] = useState(null); +const [isLoggedIn, setIsLoggedIn] = useState(false); + +useEffect(() => { + // Side effects +}, [channel]); +``` + +**GPUI (New)**: +```rust +struct AppState { + channel: Option, + auth: Entity, +} + +impl AppState { + fn set_channel(&mut self, channel: Option, cx: &mut Context) { + self.channel = channel; + cx.notify(); + // Trigger side effects via observers + } +} + +// Observers react to changes +cx.observe(&app_state, |this, app_state, cx| { + if let Some(channel) = &app_state.read(cx).channel { + this.load_channel_data(channel, cx); + } +}).detach(); +``` + +--- + +## 4. New Architecture Design + +### 4.1 Project Structure + +``` +secousse/ +├── Cargo.toml +├── build.rs # Build-time asset embedding +├── assets/ +│ ├── icons/ # SVG icons +│ ├── fonts/ # Custom fonts (optional) +│ └── keymap.json # Default keybindings +├── src/ +│ ├── main.rs # Entry point +│ ├── app.rs # SecousseApp root view +│ ├── state/ +│ │ ├── mod.rs +│ │ ├── app_state.rs # Global application state +│ │ ├── auth_state.rs # Authentication state +│ │ ├── chat_state.rs # Chat connection state +│ │ ├── emote_cache.rs # Emote/badge caching +│ │ └── settings.rs # Persistent settings +│ ├── views/ +│ │ ├── mod.rs +│ │ ├── navbar.rs # Top navigation bar +│ │ ├── sidebar.rs # Left sidebar +│ │ ├── video_player.rs # Video player view +│ │ ├── chat.rs # Chat panel +│ │ ├── chat_message.rs # Individual chat message +│ │ ├── stream_info.rs # Stream info bar +│ │ ├── browse_grid.rs # Browse/discovery grid +│ │ └── search.rs # Search dropdown +│ ├── components/ +│ │ ├── mod.rs +│ │ ├── button.rs # Reusable button +│ │ ├── icon.rs # Icon wrapper +│ │ ├── input.rs # Text input field +│ │ ├── avatar.rs # User avatar +│ │ ├── badge.rs # Chat badge +│ │ ├── emote.rs # Emote image +│ │ └── tooltip.rs # Hover tooltip +│ ├── api/ +│ │ ├── mod.rs +│ │ ├── twitch.rs # Twitch API client (from current) +│ │ ├── chat.rs # IRC WebSocket (from current) +│ │ ├── emotes.rs # 7TV/BTTV/FFZ (from current) +│ │ └── hls.rs # HLS stream URL fetching +│ ├── actions.rs # Keyboard actions +│ ├── theme.rs # Color/styling constants +│ └── util.rs # Utility functions +└── tests/ + └── ... +``` + +### 4.2 Data Flow (New) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ GPUI Application │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ SecousseApp (Root View) │ │ +│ │ ┌─────────────────────────────────────────────────────┐ │ │ +│ │ │ Entity │ │ │ +│ │ │ - current_channel: Option │ │ │ +│ │ │ - active_tab: ActiveTab │ │ │ +│ │ │ - auth: Entity │ │ │ +│ │ │ - chat: Entity │ │ │ +│ │ │ - emotes: Entity │ │ │ +│ │ │ - settings: Entity │ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ ┌────────────────┼────────────────┐ │ │ +│ │ ▼ ▼ ▼ │ │ +│ │ ┌──────────┐ ┌──────────────┐ ┌───────────┐ │ │ +│ │ │ Navbar │ │ VideoPlayer │ │ Chat │ │ │ +│ │ │ (View) │ │ (View) │ │ (View) │ │ │ +│ │ └──────────┘ │ +gpui-video │ │+uniform │ │ │ +│ │ │ -player │ │ _list │ │ │ +│ │ ┌──────────┐ └──────────────┘ └───────────┘ │ │ +│ │ │ Sidebar │ │ │ +│ │ │ (View) │ ┌──────────────┐ ┌───────────┐ │ │ +│ │ └──────────┘ │ StreamInfo │ │ BrowseGrid│ │ │ +│ │ │ (View) │ │ (View) │ │ │ +│ │ └──────────────┘ └───────────┘ │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ Background Tasks (Async): │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ IRC Chat │ │ Spade │ │ Auto- │ │ Token │ │ +│ │WebSocket │ │ Events │ │ Refresh │ │Validation│ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ Twitch │ │ 7TV/BTTV │ │ Local │ + │ API │ │ /FFZ │ │ Storage │ + └──────────┘ └──────────┘ └──────────┘ +``` + +### 4.3 State Architecture + +```rust +// Main application state +pub struct AppState { + // Current view state + pub current_channel: Option, + pub active_tab: ActiveTab, + pub is_sidebar_open: bool, + pub is_chat_open: bool, + pub is_fullscreen: bool, + + // Sub-entities (owned by App context) + pub auth: Entity, + pub chat: Entity, + pub emotes: Entity, + pub browse: Entity, + pub settings: Entity, + + // Cached data + pub current_user_info: Option, + pub followed_channels: Vec, +} + +pub struct AuthState { + pub is_logged_in: bool, + pub access_token: Option, + pub self_info: Option, + pub device_id: String, +} + +pub struct ChatState { + pub messages: Vec, + pub is_connected: bool, + pub channel: Option, + // WebSocket handle managed internally +} + +pub struct EmoteCache { + pub global_emotes: HashMap, + pub channel_emotes: HashMap>, + pub global_badges: HashMap, + pub channel_badges: HashMap>, + pub twitch_global_emotes: HashMap, + pub twitch_channel_emotes: HashMap>, +} +``` + +--- + +## 5. Implementation Phases + +### Phase 1: Project Setup & Core Infrastructure (Week 1) + +**Goals**: Set up project structure, basic window, theme system + +**Tasks**: +1. Create new Cargo project with GPUI dependency +2. Set up build.rs for asset embedding +3. Implement basic window with dark theme +4. Create theme.rs with Twitch color palette +5. Implement Settings entity with persistence (serde + local file) +6. Port TwitchClient from current `twitch.rs` +7. Port emotes API from current `emotes.rs` + +**Deliverable**: Empty window with correct colors, settings persistence working + +### Phase 2: Authentication System (Week 1-2) + +**Goals**: Full OAuth flow working + +**Tasks**: +1. Implement AuthState entity +2. Port OAuth local server logic +3. Implement token validation on startup +4. Create login/logout actions +5. Persist token to local storage +6. Create basic Navbar view with login button + +**Deliverable**: Can login via browser, token persists across restarts + +### Phase 3: Navigation & Layout (Week 2) + +**Goals**: Main app layout with navigation + +**Tasks**: +1. Implement SecousseApp root view +2. Create Navbar view (logo, tabs, profile) +3. Create Sidebar view (collapsible) +4. Implement tab switching (Following/Browse) +5. Create placeholder views for main content areas +6. Implement keyboard shortcuts for navigation + +**Deliverable**: Full layout visible, tabs switch, sidebar collapses + +### Phase 4: Browse & Discovery (Week 2-3) + +**Goals**: Browse top streams functionality + +**Tasks**: +1. Implement BrowseState entity with refresh logic +2. Create BrowseGrid view with stream cards +3. Implement async image loading for thumbnails +4. Add viewer count formatting +5. Implement click-to-select channel +6. Add auto-refresh timer (60s) + +**Deliverable**: Can browse top 30 streams, click to select + +### Phase 5: Search System (Week 3) + +**Goals**: Channel search functionality + +**Tasks**: +1. Create SearchState entity with debouncing +2. Implement text input component +3. Create search results dropdown +4. Handle live/offline status display +5. Implement click-outside to dismiss +6. Connect to channel selection + +**Deliverable**: Can search channels, results show live status + +### Phase 6: Video Playback (Week 3-4) + +**Goals**: Live stream video playback + +**Tasks**: +1. Integrate gpui-video-player dependency +2. Implement HLS URL fetching (GQL PlaybackAccessToken) +3. Create VideoPlayerView with video element +4. Implement play/pause controls +5. Implement volume control with slider +6. Implement quality selection menu +7. Implement fullscreen toggle +8. Add loading state overlay +9. Handle offline channel display +10. Add live indicator with viewer count + +**Deliverable**: Can watch live streams with full controls + +### Phase 7: Chat System (Week 4-5) + +**Goals**: Real-time chat with emotes/badges + +**Tasks**: +1. Port chat.rs WebSocket logic +2. Implement ChatState entity with message buffer +3. Create ChatView with uniform_list for messages +4. Implement ChatMessage view with badge/emote rendering +5. Create text input for sending messages +6. Implement auto-scroll with scroll detection +7. Add "scroll to bottom" button +8. Handle anonymous vs authenticated modes +9. Implement auto-reconnect logic + +**Deliverable**: Full chat functionality with emotes and badges + +### Phase 8: Emote & Badge Rendering (Week 5) + +**Goals**: Proper emote/badge display + +**Tasks**: +1. Implement EmoteCache entity with lazy loading +2. Create Emote component (async image loading) +3. Create Badge component +4. Implement emote parsing in messages +5. Handle Twitch, 7TV, BTTV, FFZ emotes +6. Implement badge positioning and sizing +7. Cache loaded images + +**Deliverable**: All emotes and badges render correctly + +### Phase 9: Following System (Week 5-6) + +**Goals**: Followed channels sidebar + +**Tasks**: +1. Implement followed channels fetching (Helix API) +2. Create followed channel list in sidebar +3. Add follow/unfollow actions +4. Implement StreamInfo view with follow button +5. Add auto-refresh for followed channels +6. Show live status in sidebar + +**Deliverable**: Can view/manage followed channels + +### Phase 10: Analytics & Polish (Week 6) + +**Goals**: Spade events, final polish + +**Tasks**: +1. Implement Spade event sending (minute-watched) +2. Add watch state tracking +3. Implement remaining keyboard shortcuts +4. Add tooltips throughout +5. Performance optimization pass +6. Error handling improvements +7. Logging system setup + +**Deliverable**: Feature-complete application + +### Phase 11: Testing & Platform Support (Week 7) + +**Goals**: Cross-platform testing, bug fixes + +**Tasks**: +1. Test on macOS thoroughly +2. Test on Linux (track GPUI issues) +3. Test on Windows (experimental) +4. Fix platform-specific issues +5. Document platform limitations +6. Create release builds + +**Deliverable**: Working builds for all target platforms + +--- + +## 6. Technical Deep Dives + +### 6.1 Video Playback with gpui-video-player + +The gpui-video-player library provides GStreamer-based video playback integrated with GPUI. + +**Key Integration Points**: + +```rust +use gpui_video_player::{Video, VideoOptions, video}; + +pub struct VideoPlayerView { + video: Option