# MeTube UI Redesign — Design Doc Date: 2026-08-09 Status: Proposal (not yet implemented) ## 1. Background MeTube (this fork) is a self-hosted web GUI for yt-dlp: Python 3.13 backend (`aiohttp` + `python-socketio`, ~2,000 LOC) and an Angular 19 frontend (Bootstrap 5.3 + ng-bootstrap, ~2,400 LOC, PWA). The frontend is a single-screen app built around one monolithic component: - `ui/src/app/app.component.ts` — 744 LOC - `ui/src/app/app.component.html` — 595 LOC, with the same table markup triplicated across three tabs - `ui/src/app/app.component.sass` — 341 LOC - Supporting: `downloads.service.ts` (socket/HTTP state), 2 checkbox components, 4 pipes, theme/format helpers The UI is stock Bootstrap: three tabs (Downloading / Pending / Completed), each a 7-column ``; an inline add-form; a collapsible "Advanced Options" card; `alert()` for errors; cookie-persisted preferences. ## 2. Problems with the current UI 1. **Table-based lists**: 7 text columns, horizontal `overflow-auto` scroll on mobile, no thumbnails — recognizing "which video is this" from text is slow. 2. **No design language**: default Bootstrap chrome, no empty states, no skeleton loaders, "Connecting to server..." as the loading experience. 3. **Errors via `alert()`**: blocking, non-actionable, disappears. 4. **Monolithic component**: imperative `@ViewChild(...).nativeElement.disabled` manipulation (7+ instances), triplicated template markup, dead tooling (tslint/codelyzer/protractor). Every change is slower and riskier than it should be. 5. **List tooling doesn't match real data volumes**: the primary user runs ~230 queued / ~300 completed items. Plain scrolling tables are unusable at that scale — no filter, no sort, weak batch selection, no virtualization. ## 3. Decision: redesign in place, do NOT rewrite the stack Evaluated and rejected: rewriting the frontend in Next.js (or Vite + React), and rewriting the backend in Node. Rationale: - ~90% of the user-visible benefit comes from the **design**, not the framework. Cards, thumbnails, filter/sort, batch actions, virtualization are all achievable in the existing Angular + Bootstrap stack (Angular CDK has virtual scrolling; Bootstrap renders cards fine). - The rewrite's only unique benefit is code health / developer velocity, which doesn't pay off for what is largely a one-off redesign. - Regression risk: cookie-persisted prefs, folder tag-creation, socket string-parsing quirks, PWA/service-worker setup would all need to be rediscovered and re-tested. - Next.js specifically is the wrong shape for this app: a LAN SPA with no SEO/SSR needs. If a rewrite ever happens, Vite + React is the right-sized tool — but it is not worth doing now. - The backend must stay Python regardless: yt-dlp is Python-native, and the subtle, battle-tested logic (precheck worker, filename-conflict resolution, multiprocess lifecycle, playlist expansion, cookie wiring) lives in `app/ytdl.py`. A Node port is 1.5–3 weeks of risk for zero user-visible gain. If the frontend keeps needing substantial work after this redesign and Angular becomes a friction point, revisit a rewrite then — with a proven design to port, it becomes a mechanical ~2-day job. ## 4. Goals / Non-goals Goals: - Modern card-based, thumbnail-first UI for all three lists. - Handle 200–500-item lists comfortably: filter, sort, folder grouping, multi-select batch actions, virtualized rendering. - Keep "now downloading" always visible. - Replace `alert()` with toasts; add empty states and skeleton loading. - Mobile-friendly (cards stack; no horizontal table scroll). - Refactor the monolith into components as part of the work (only as much as the redesign requires — no gratuitous re-architecture). Non-goals: - No framework/stack change (stays Angular 19 + Bootstrap 5.3). - No backend queue/precheck/persistence changes, except the additive thumbnail endpoint (§6.3). - No new features beyond what the design requires (no new download options, no playlist monitor — that's a separate planned effort). ## 5. Proposed design ### 5.1 Layout (desktop) ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ ▶ MeTube ⬇ 2 active · 12.4 MB/s 🌙 ⚙ │ ├──────────────────────────────────────────────────────────────────────────┤ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 🔗 Paste video / playlist URL… [Add] │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ [Best][1080p][720p] [MP4▾][MP3][M4A] 📁 movies ⋯ Advanced │ ├──────────────────────────────────────────────────────────────────────────┤ │ ── NOW DOWNLOADING (2) ───────────────────────────── 12.4 MB/s total ── │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ [thumb] Title ███████████████░░░░░ 62% 12.4MB/s ETA 0:43 ✕ │ │ │ │ [thumb] Title ████░░░░░░░░░░░░░░░ 18% 3.1MB/s ETA 4:12 ✕ │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────────────────────┤ │ [ Queued 230 ] [ Completed 303 ] 📁 All folders ▾ ▦ | ☰ │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ ☑ Select 🔍 Filter titles… Sort: Date added ▾ Group: 📁 │ │ │ ├───────────────────────────────────────────────────────────────────┤ │ │ │ ▸ 📁 shows (86) ☐ all │ │ │ │ ▸ 📁 music (54) ☐ all │ │ │ │ ▾ 📁 movies (90) ☐ all │ │ │ │ ☐ [thumb] Movie title one 720p · queued 2d ago ✕ │ │ │ │ ☑ [thumb] Movie title two 720p · queued 2d ago ✕ │ │ │ │ ☐ [thumb] Movie title three 720p · queued 3d ago ✕ │ │ │ │ …(virtualized scroll)… │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ 2 selected ── [ ▶ Start ] [ ✕ Cancel ] [ 📁 Move to… ] │ └──────────────────────────────────────────────────────────────────────────┘ ``` ### 5.2 Advanced options slide-over Replaces the inline collapsible card. Rarely-changed controls move here; cookie-persisted defaults keep the main bar to one row. ``` ┌────────────────────────────┐ │ Advanced options ✕ │ │ │ │ Auto-start [ toggle ]│ │ Playlist strict [ toggle ]│ │ Playlist limit [ 10 ] │ │ │ │ ── Batch ────────────── │ │ [Import URLs] [Export] │ │ [Copy all URLs] │ │ │ │ ── Cookies ──────────── │ │ domain.com ✓ saved │ │ [Paste cookie…] │ └────────────────────────────┘ ``` ### 5.3 Mobile Cards stack vertically; the active panel, tabs, filter bar, and list reflow to single-column. No horizontal scrolling anywhere. The ⋮ menu and slide-over become bottom sheets. ``` ┌─────────────────────────┐ │ ▶ MeTube 🌙 ⚙ │ │ ⬇2 · 12MB/s │ ├─────────────────────────┤ │ ┌─────────────────────┐ │ │ │🔗 Paste URL… [Add]│ │ │ └─────────────────────┘ │ │ [Best][1080p][720p] │ │ 📁 movies ⋯ More │ │ │ │ NOW DOWNLOADING (2) │ │ ┌─────────────────────┐ │ │ │[thumb] │ │ │ │ Title goes here │ │ │ │ ████████░░░ 62% │ │ │ │ 12MB/s · ETA 0:43 ✕ │ │ │ └─────────────────────┘ │ │ │ │ [Queued 230][Done 303] │ │ ┌────────┐ ┌────────┐ │ │ │ thumb │ │ thumb │ │ │ │ title… │ │ title… │ │ │ │ 384MB ⋮│ │ 1.2GB ⋮│ │ │ └────────┘ └────────┘ │ └─────────────────────────┘ ``` ### 5.4 Key design decisions and rationale 1. **Tabs for the archives, pinned panel for active downloads.** Stacked sections (all three lists on one scrolling page) were considered and rejected: at 230/303 items the lists never fit on a screen, so the "pipeline at a glance" argument fails. Tabs hide state, so each tab carries a count badge. The active-downloads panel stays pinned above the tabs because its size is bounded by the concurrency limit (~3) — it costs nothing and keeps live progress always visible. 2. **Tables → thumbnail-first cards/rows.** Recognition-by-image is far faster than recognition-by-text; it also eliminates the 7-column mobile squeeze. Completed items get a grid/list toggle (▦|☰) — the completed tab is a media library (browse, play, move, delete), not an audit log. 3. **Command bar instead of a form.** Usage is 95% two actions: paste URL, watch progress. Quality/format become one-tap chips with cookie-persisted defaults; folder picker stays; prefix and everything else moves to the slide-over. 4. **List tooling matched to volume** (the main volume-driven change): - Per-tab filter box (title substring), sort (date added, title, size, folder), and folder grouping. - Folder groups are collapsible with per-group select-all. - Multi-select with a batch action bar (start / cancel / delete / move to folder). With 230 queued items the real operations are plural. - Virtualized scrolling (Angular CDK `cdk-virtual-scroll-viewport`) — render ~30 rows instead of 533 DOM subtrees. Concrete perf win over the current tables. 5. **Toasts replace `alert()`** — non-blocking, can carry actions (e.g. "Duplicate skipped — view item"). The recent-events panel becomes a collapsible activity drawer (diagnostic info: available, not permanently occupying screen space). 6. **Theme**: keep the existing dark/light/auto Bootstrap `data-bs-theme` mechanism; refine palette to one accent color. Header gets slim stat chips (active count, total speed) replacing the current navbar counters. ### 5.5 Open question: per-folder navigation If queue work is frequently per-folder ("start all the music ones"), folder groups may deserve first-class status — e.g. a qBittorrent-style left sidebar of folders with counts instead of group headers inside the list. Decide based on actual usage before implementation; default is the group-header design above (simpler, keeps single-column layout). ## 6. Implementation plan ### 6.1 Component refactor (Angular, in place) Break the monolith into: - `CommandBarComponent` — URL input, quality/format chips, folder picker, slide-over trigger. Owns the add-form state and cookie-persisted defaults. - `AdvancedOptionsComponent` — slide-over (auto-start, playlist options, import/export/copy, cookie paste). - `ActiveDownloadsComponent` — pinned now-downloading panel (rows + total speed). - `DownloadListComponent` — reusable list for Queued and Completed tabs: filter, sort, folder grouping, multi-select, batch action bar, `cdk-virtual-scroll-viewport`. Grid/list toggle applies to Completed. - `DownloadRowComponent` — thumbnail, title, progress/status line, folder badge, actions (cancel / start / move / delete / reveal). - `ToastsComponent` / toast service — replaces `alert()`. - Keep: `downloads.service.ts` (socket + HTTP state) largely as-is; `downloads.pipe.ts` (eta/speed/fileSize); theme helpers. The triplicated tab markup collapses into two instances of `DownloadListComponent`. ### 6.2 Frontend behaviors to preserve exactly - Cookie-persisted prefs: quality, format, theme, active tab, sort, grouping. - Folder picker with tag-creation (currently ng-select) and per-row folder editing (calls `POST /set_folder`; moves completed files on disk). - Socket.IO event handling: events arrive as JSON *strings* and are parsed client-side; full state re-sync on connect via the `all` event. - PWA/service-worker setup (`custom-service-worker.js`, `ngsw-config.json`). - Import/Export/Copy URLs batch modal. - Existing HTTP API usage: `POST /add`, `POST /delete`, `POST /start`, `POST /set_folder`, `POST /cookie`, `GET /history`, `GET|POST /events`, `GET /version`. ### 6.3 Backend addition: thumbnail endpoint (additive only) Cards need thumbnails. yt-dlp metadata already contains `thumbnail` URLs, but hot-linking remote thumbnails from a LAN app leaks requests to third-party hosts and breaks for expired URLs. Add one endpoint: - `GET /thumbnail?id=` — backend fetches/caches the thumbnail (cache under `STATE_DIR/thumbnails/`, keyed by download id), returns the image with long-lived cache headers. Fall back to a placeholder icon when no thumbnail exists (e.g. audio-only downloads). No other backend changes. Queue, precheck, conflict resolution, persistence, socket events are untouched. ### 6.4 Phasing 1. **Phase 1 — component split** (no visual change): extract components from the monolith, keep the tables. Establishes the structure the redesign builds on; low regression risk, easily reviewable. 2. **Phase 2 — list redesign**: `DownloadListComponent` with cards, filter, sort, folder groups, multi-select, virtualization; pinned active panel; tabs with count badges. 3. **Phase 3 — command bar + slide-over**: new add flow, toasts, activity drawer, empty states, skeletons. 4. **Phase 4 — thumbnails**: backend endpoint + wire into rows/grid. 5. **Phase 5 — polish**: palette/accent, mobile bottom sheets, cleanup of dead tooling (tslint/codelyzer/protractor configs) if desired. Phases 1–3 deliver the bulk of the UX gain; Phase 4 is the biggest visual upgrade but is separable if backend changes need to wait. ### 6.5 Effort estimate | Phase | Estimate | |---|---| | 1. Component split | 0.5–1 day | | 2. List redesign | 1–1.5 days | | 3. Command bar + toasts | 0.5–1 day | | 4. Thumbnails (front + back) | 0.5 day | | 5. Polish | 0.5 day | | **Total** | **~3–4 days** | (For reference, the rejected full rewrite in React was estimated at 3–5 days for the frontend alone, plus regression risk on working behaviors.) ## 7. Risks - **Virtual scroll + folder groups**: `cdk-virtual-scroll-viewport` works best with flat lists; collapsible groups need flattening logic in the component. Mitigation: flatten grouped data into a single array with group-header rows (standard pattern). - **Thumbnail fetching load**: 300 completed items loading thumbnails at once. Mitigation: backend cache + lazy loading (`loading="lazy"` / viewport-based). - **Bootstrap theming limits**: the refined palette may fight Bootstrap defaults. Mitigation: CSS custom properties on top of `data-bs-theme`, not a Bootstrap rebuild. - **Regression in preserved behaviors** (§6.2): mitigate with manual test checklist per phase; the project has no real frontend test suite, and adding one is out of scope.