17 KiB
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 LOCui/src/app/app.component.html— 595 LOC, with the same table markup triplicated across three tabsui/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 <table>; an inline add-form; a collapsible "Advanced
Options" card; alert() for errors; cookie-persisted preferences.
2. Problems with the current UI
- Table-based lists: 7 text columns, horizontal
overflow-autoscroll on mobile, no thumbnails — recognizing "which video is this" from text is slow. - No design language: default Bootstrap chrome, no empty states, no skeleton loaders, "Connecting to server..." as the loading experience.
- Errors via
alert(): blocking, non-actionable, disappears. - Monolithic component: imperative
@ViewChild(...).nativeElement.disabledmanipulation (7+ instances), triplicated template markup, dead tooling (tslint/codelyzer/protractor). Every change is slower and riskier than it should be. - 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
-
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.
-
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.
-
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.
-
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.
-
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). -
Theme: keep the existing dark/light/auto Bootstrap
data-bs-thememechanism; 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 — replacesalert().- 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 ofDownloadListComponent.
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
allevent. - 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=<download_id>— backend fetches/caches the thumbnail (cache underSTATE_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
- 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.
- Phase 2 — list redesign:
DownloadListComponentwith cards, filter, sort, folder groups, multi-select, virtualization; pinned active panel; tabs with count badges. - Phase 3 — command bar + slide-over: new add flow, toasts, activity drawer, empty states, skeletons.
- Phase 4 — thumbnails: backend endpoint + wire into rows/grid.
- 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-viewportworks 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.