Ghostty shows an image smaller than its placement at its own size, so
library posters (Radarr's poster-500 is 333x500, not 500x750) came out
smaller than the 426x640 remote ones. Images are now scaled (bilinear)
to exactly cols*cellWidth x rows*cellHeight pixels before upload, and
library titles use the original poster for real images. The disk cache
stores posters as decoded (JPEG) instead of the possibly large originals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The poster box followed the image's shape, so images Radarr/Sonarr hand
out in another shape came out smaller than the others in a list. The box
is now always 2:3: an image within 12% of that fills it, trimmed at the
long sides (block characters too, which were stretched before); one
further off is shown whole, centred in the box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Posters (the downloaded bytes, per size, 30 days, at most 200 MB) and
casts (people.json, 7 days, at most 2000) are stored in the user's cache
directory (os.UserCacheDir()/arr-tui) and used on the next start before
asking the servers. Expired posters are pruned at startup, casts are
written shortly after new ones arrive and on exit. Tests turn the cache
off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While the Library is visible it reads the Radarr and Sonarr queues (on
opening, every 10 s, and shortly after a grab) and matches them to the
titles by movieId / seriesId. Those titles get a blue dot instead of the
green or grey one, and their details say how far the download is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Back to where the search started (library, drill-down or search), unless
something was opened on top of the list meanwhile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drill-down reads the series with includeSeasonImages=true (Sonarr
only sends season posters when asked, as TheTVDB links). A selected
season or episode shows its season's poster; without one, or when it
cannot be loaded, the series poster.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a Trakt client ID, the details of a movie or series show the
director(s) or creators and up to 8 cast members with their roles below
the plot, in every view. The title is found on Trakt by its TMDb/TVDB
(else IMDb) id, then its people are read. Casts are cached and, like
posters, only loaded once the selection rests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status bar only ever named Enter, while the hint for searching
releases of a title not in the library said (a). Enter is now the one
key for adding, and the hint says Enter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grey number of every entry ("1.2k watching", "on 18k lists") stood
right after its title, so they did not line up. The rows are now padded
to the list's width on every draw where it changed, putting the numbers
against the right edge; a title too long for that keeps the number right
after it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake services gain a Trakt API (port 19894) and a sequel that is not
in the library; a new scene shows the Anticipated list. The other
pictures are retaken (Config has the Trakt section, the search status
bar mentions [ ] lists, the calendar is two days on).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an empty search box, Movies and Series show one of Trakt's public
lists: trending now, most watched this week, anticipated, popular, and
for movies the US box office. [ ] switch lists, r reloads. Each entry is
looked up in Radarr (tmdb:) or Sonarr (tvdb:), so it behaves like a
search result: details, poster, tick when in the library, add, releases.
Needs a Trakt client ID: new optional trakt.client_id setting (Config
view section "Trakt", ARR_TUI_TRAKT_CLIENT_ID), masked like the API keys
and checked by Test. ARR_TUI_TRAKT_URL points at another server for
development.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
take.sh builds arr-tui, starts a fake Radarr, Sonarr and SABnzbd with a
made-up library (catalog.py, server.py, generated posters), and runs the
scene scripts in Ghostty under Xvfb, writing docs/screenshots/. Run on the
same day it reproduces the committed pictures byte for byte. make test
also shellchecks the scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
A library screenshot at the top and a gallery of the other views (search,
releases, episodes, downloads, calendar, config, light theme). They show a
made-up library with generated posters, served by a fake Radarr, Sonarr
and SABnzbd, taken in Ghostty with real-image posters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Library titles fetched Radarr's/Sonarr's 250×375 poster copy, which
real images show at up to 640 pixels, so movie posters looked soft next
to series posters that came from the provider's full-size original
(Sonarr behind a login). With real images the 500×750 copy is used;
block-character posters keep the small one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Without a config file arr-tui refused to start. It now creates an empty
one (private, with its directory) and opens the Config view. A service
with neither address nor key is "not configured", which validation
allows: its client returns ErrNotConfigured without a request, its
indicator stays grey, it is logged once as information, and its lists
say so instead of showing errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Long titles made the movies and series tables wider than their pane, so
they scrolled sideways and status and size left the view. The title
column is now cut on every draw to what the other columns leave, ending
in an ellipsis (done by hand: tview only draws its ellipsis for untagged
text, and every title starts with the colored monitored mark). Titles are
now escaped, so square brackets in them are not taken as color tags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
The list is reloaded after a removal and the selection follows the
selected title by id; the removed title is gone, so it fell back to the
top. Now a title that has left the library hands its row to the one that
moved up into it (or the new last one). A title that is only filtered out
still starts the list from the top.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
The form navigation recognised dropdowns only as *tview.DropDown, so on
the new profile and folder dropdowns ←→ did nothing and ↑↓ left an open
list instead of moving through it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
The two defaults of Radarr and Sonarr are dropdowns filled from the
server with the address and key in the fields: when the view opens, on
Revert, and shortly after either is changed. A configured value the
server does not list stays selectable, marked "(not on the server)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Form.SetFocus moves between items without telling the application, which
kept the item that was left. Opening the help from the Config view then
blurred only that one: the field in use kept drawing its cursor through
the help window, the form still had the focus and took the help's keys
(no scrolling), and closing the help returned to the wrong field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Widgets copy their colors when they are created, so they now get role
tokens (one unique RGB value per role) instead of colors, and a wrapper
around the tcell screen replaces the tokens with the current theme's
colors in SetContent. Changing the theme in the Config view swaps that
table; the next draw recolors everything. Poster colors that equal a
token are nudged off it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Colors no longer come from the terminal's palette, whose color 0 many
color themes set to a grey that is not their background. A theme sets
tview's default styles and the color names used in texts, all chosen from
entries 16-255 of the 256-color table so terminals without 24-bit color
show them exactly. "auto" picks light or dark from the background color
the terminal reports. Settable in the Config view, applied at the next
start (widgets copy their colors when created). Replaces using the
terminal's own background and text colors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
On a light background (Catppuccin Latte in Ghostty on macOS) the panes
kept palette color 0, a dark grey slab with white text. The probe now also
asks for the text color (OSC 10). Light backgrounds are used as well, with
text, borders and titles in the terminal's text color, input fields and
buttons in a light shade of it, and the active tab drawn in it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
The check only looked at COLORTERM, but tcell also draws 24-bit color for
terminals whose description has it (xterm-ghostty, xterm-kitty), so in
Ghostty on macOS without COLORTERM the background was never used. Use
tcell's rules, and always log which background is used and why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
The panes used palette color 0, which color themes such as Catppuccin set
to a grey lighter than their background: everything looked washed out and
grey text almost disappeared (seen in Ghostty on macOS). The startup probe
now also asks for the background color (OSC 11), and with 24-bit color and
a dark background that color is used. The probe always runs at startup, so
a later switch to real images in the Config view never has to ask the
terminal while the screen owns it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Go cannot set a read deadline on a terminal device on macOS (kqueue does
not support them), so the probe failed at once and Ghostty and kitty got
block posters there. Wait for the answer with select(2) instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
On a terminal zoomed far out a poster is tens of thousands of cells, and
redrawing it on every step of a held arrow key was more output than the
terminal could draw (about 100 KB per step at 600x180), so the list
lagged. A selection that changes again within 120 ms keeps the poster's
place empty and shows the poster once it rests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
Terminal.app draws block elements from the font, with the halves off the
cell's middle, so quadrant posters came out as rows of shifted dots. The
new cover style "cells" uses one background color per cell and no glyph;
auto picks it when TERM_PROGRAM is Apple_Terminal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW