From 76750cadf6a537e00a82b671544a8ebdef627674 Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Tue, 20 Oct 2026 20:06:50 +0200 Subject: [PATCH] Library: stack movies above series with details on the right; refresh SPECS keybindings Co-Authored-By: Claude Sonnet 5.5 --- README.md | 2 +- SPECS.md | 70 ++++++++++++++++++++++-------------------- internal/ui/library.go | 10 +++--- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c05456f..3e480a0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A terminal UI to search for movies and series with [Radarr](https://radarr.video optionally start the automatic search. - **Interactive search**: list the releases Radarr/Sonarr find (approved or rejected, with reasons), sort and filter them, and grab one. Series: pick a season or a single episode first. -- **Library**: browse your movies and series side by side, filter by missing / downloaded / monitored, toggle +- **Library**: browse movies (top) and series (below) with a details pane on the right, filter by missing / downloaded / monitored, toggle monitoring, trigger searches. Series open into seasons and episodes with per-season and per-episode monitoring, automatic search and release search. - **Downloads**: SABnzbd queue and history plus the Radarr/Sonarr queues. Pause/resume, delete, diff --git a/SPECS.md b/SPECS.md index a898f39..4c62027 100644 --- a/SPECS.md +++ b/SPECS.md @@ -55,7 +55,7 @@ Notes: ### 4.1 MVP (v0.1) - **Config:** load URLs + API keys from a config file; fail with a clear message if missing or unreachable. -- **Search:** one search box, with a toggle (movies / series). Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio. +- **Search:** a Movies view (Radarr) and a Series view (Sonarr), each with a search box. Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio. - **Add:** from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls `POST /movie` or `POST /series` with `addOptions.searchForMovie` / `searchForMissingEpisodes`. - **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. - **Downloads view:** all downloads and queues in one place, auto-refreshing every few seconds: @@ -65,60 +65,62 @@ Notes: - **Status bar / help:** the bottom bar lists the keys that work in the current view or panel; the top right shows the connection state of each service; `?` opens the full key reference. - **Notifications and log (F5):** feedback about actions (added, deleted, grabbed, failures) appears as popups in the bottom right that fade away (errors stay longer). Everything is also kept in the Log view; only connection changes of the background polling are logged, not every failed poll. -### 4.2 v0.2 +### 4.2 v0.2 (done, M8) -- **Library view:** browse existing movies/series with filters (missing, downloaded, monitored). -- **Series drill-down:** seasons and episodes, toggle monitoring per season/episode, trigger season/episode search. +- **Library view:** movies (top) and series (below) with the details of the selected item on the right; Tab switches between the two lists. Filters (all, missing, downloaded, monitored, unmonitored) and a text filter apply to both lists. `t` toggles monitoring, `s` starts an automatic search, `i`/Enter opens the release search (movies) or the drill-down (series). +- **Series drill-down:** seasons and episodes, toggle monitoring per season/episode, trigger season/episode search and release search. ### 4.3 Later / ideas - Calendar / upcoming view. -- Notifications on completed downloads. +- Desktop/terminal notification when a download completes. - Multiple instances per service (e.g. 4K Radarr). - Poster preview via sixel / kitty graphics where supported. ## 5. UI Design (tview) -Layout sketch: +Layout sketch (Library view; the other views follow the same frame): ``` ┌ arr-tui ───────────────────────────────────────────────────────────┐ -│ [Movies] Series Downloads Library │ ← tab bar -├────────────────────────────────┬───────────────────────────────────┤ -│ Search: ______________________ │ Title (Year) │ -│ │ ★ 7.8 · 142 min · Sci-Fi │ -│ > Movie One (2021) ✓ │ │ -│ Movie Two (2019) │ Overview text wraps here... │ -│ Movie Three (2024) │ │ +│ 1 Movies 2 Series 3 Downloads 4 Library 5 Log ● Radarr ● Sonarr ● SAB │ ← tab bar + connection state +│ Library · Movies 12/40 · Series 5/9 · filter: missing │ +│ Filter: ______________________ │ +├─ Movies ───────────────────────┬─ Details ─────────────────────────┤ +│ ● Movie One (2021) missing │ Title (Year) │ +│ ● Movie Two (2019) ✓ WEB-DL │ ★ 7.8 · 142 min · Sci-Fi │ +├─ Series ───────────────────────┤ Overview text wraps here... │ +│ ● Show One (2008) 12/62 │ │ +│ ○ Show Two (2015) 10/10 │ │ ├────────────────────────────────┴───────────────────────────────────┤ -│ ↓ 12.4 MB/s · 2 jobs · Radarr ● Sonarr ● SABnzbd ● ? help │ ← status bar +│ Tab movies/series · f filter · / text · t monitor · ? help · q quit │ ← key hints of the current view └────────────────────────────────────────────────────────────────────┘ ``` -tview primitives: `Pages` (views), `Flex` (layout), `InputField` (search), `List`/`Table` (results, queue), `TextView` (details), `Form` (add dialog), `Modal` (confirmations). +Popups for action feedback appear in the bottom right and fade away; the Log view (F5) keeps the history. -Keybindings (initial proposal): +tview primitives: `Pages` (views and stacked overlays), `Flex` (layout), `InputField` (search), `List`/`Table` (results, queues, library), `TextView` (details, log), `Form` (add dialog), `Modal` (confirmations). Popups are drawn by a wrapper around the root primitive. + +Keybindings (the status bar shows the ones that apply to the current view): | Key | Action | |---|---| -| `Tab` / `Shift+Tab` | Switch panel within the current view (never the view) | -| `F1-F4` | Switch view, also while typing in a search box | -| `/` | Focus search | -| `Enter` | Search / open details / confirm | -| `a` | Add selected result | -| `m` / `s` | Switch to movies / series search | -| `p` | Pause/resume SABnzbd (Downloads view) | -| `d` | Delete selected download, history entry or Radarr/Sonarr queue item (with confirm) | -| `t` | Retry the selected failed download (history) | -| `Tab` / `Shift+Tab` | Downloads view: cycle focus between SABnzbd queue, Radarr/Sonarr queue and history | -| `b` | Downloads view (Radarr/Sonarr queue): blocklist the release and search for another | -| `i` | Interactive release search for the selected library item (movies; series ask for a season, then an episode or the whole season) | -| `g` / `Enter` | Release list: grab the selected release (rejected ones ask for confirmation) | -| `s` / `f` | Release list: cycle sort (best / size / age) / hide rejected | -| `Esc` | Close a dialog or the release list | -| `r` | Refresh | -| `?` | Help | +| `F1-F5` | Switch view (Movies, Series, Downloads, Library, Log); also while typing, and from full-screen views. Dialogs block them. | +| `Tab` / `Shift+Tab` | Switch panel within the current view (never the view): search box / results, Downloads panels, Library lists | +| `?` / `F9` | Help (`F9` also works while typing) | | `q` / `Ctrl+C` | Quit | +| `Esc` | Close a dialog or a full-screen view | +| `/` | Movies, Series, Library: focus the search / text filter | +| `Enter` / `a` | Movies, Series: add the selected result (dialog: ↑↓ move, ←→ change dropdowns) | +| `i` | Movies, Series, Library: interactive release search (series: seasons and episodes) | +| `f` | Library: cycle the filter; release list: hide rejected releases | +| `t` | Library, episodes: toggle monitored; Downloads history: retry a failed download | +| `s` | Library, episodes: automatic search; release list: cycle sort (best / size / age) | +| `Enter` / `g` | Release list: grab the selected release (rejected ones ask for confirmation) | +| `Enter` | Library: open; episodes: expand a season or search releases for an episode | +| `p` / `d` / `b` / `r` | Downloads: pause/resume SABnzbd, delete job / history entry / arr queue item, remove from arr queue + blocklist, refresh | +| `r` | Library, episodes: reload | +| `G` / `c` | Log: follow new entries / clear | ## 6. Architecture @@ -197,7 +199,7 @@ The file should be created with mode `0600`; warn if it is more permissive. API - [x] **M7** — Polish: help screen (`?`/F9), per-service connection indicators, README, Makefile release builds, `--version`. - [x] **M8** — Library view (movies/series, filters, text filter, monitor toggle, automatic search) and series drill-down (seasons/episodes, monitoring, search, release search). - [x] **M9** — Popup notifications, Log view (F5), context-sensitive key hints in the status bar. -- [ ] **M10+** — ideas: calendar, notifications, delete from library, multiple instances. +- [ ] **M10+** — ideas: calendar, completion notifications, delete from library, multiple instances. ## 10. Open Questions diff --git a/internal/ui/library.go b/internal/ui/library.go index c76c0db..7e0420e 100644 --- a/internal/ui/library.go +++ b/internal/ui/library.go @@ -171,7 +171,8 @@ type libPane struct { gen int // invalidates stale loads } -// libraryView shows the movies of Radarr and the series of Sonarr side by side. +// libraryView shows the movies of Radarr above the series of Sonarr, with the +// details of the selected item on the right. type libraryView struct { app *App radarr *radarr.Client @@ -197,7 +198,7 @@ func newLibraryView(app *App, r *radarr.Client, s *sonarr.Client) *libraryView { } v.details.SetBorder(true).SetTitle(" Details ") - lists := tview.NewFlex() + lists := tview.NewFlex().SetDirection(tview.FlexRow) // movies above series for _, kind := range []libKind{kindMovies, kindSeries} { p := &libPane{kind: kind, table: tview.NewTable().SetSelectable(true, false).SetFixed(1, 0)} p.table.SetBorder(true) @@ -216,8 +217,9 @@ func newLibraryView(app *App, r *radarr.Client, s *sonarr.Client) *libraryView { v.root = tview.NewFlex().SetDirection(tview.FlexRow). AddItem(v.header, 1, 0, false). AddItem(v.input, 1, 0, false). - AddItem(lists, 0, 3, true). - AddItem(v.details, 0, 2, false) + AddItem(tview.NewFlex(). + AddItem(lists, 0, 3, true). + AddItem(v.details, 0, 2, false), 0, 1, true) v.input.SetChangedFunc(func(string) { v.render() }) v.input.SetDoneFunc(func(tcell.Key) { v.app.app.SetFocus(v.pane().table) })