Lidarr queues whole albums rather than individual tracks, so a track row
is styled from the queue entry whose albumId matches the track's albumId,
mirroring the Sonarr episode row logic. Tracks that already have a file
keep the downloaded style regardless of the queue.
The season, album and book tables rendered 0/0 and 0.00 GB whenever the
API omitted the statistics object for a row, presenting fabricated counts
as real. The row colour already treated that case as indeterminate; the
cell text now does too, rendering N/A for both cells, matching what these
tables already do for a missing release date.
Sonarr's season row is restructured from a destructure-with-default to the
same Option-aware map_or_else shape lidarr and readarr use.
The readarr test fixture book has no statistics, so its existing author
details snapshots change from the fabricated zeros to N/A on exactly that
row.
The snapshot harness renders to a plain string, so colour and text modifiers never reach the assertion. The track row's downloaded and missing arms were therefore invisible to the suite: swapping either arm for the other, or inverting the has_file condition, all left the full run green.
Move the branch into decorate_track_row_with_style, mirroring the row decorators already extracted in the other library detail views, and unit test both arms directly against Row equality. All three mutations now fail the suite.
Row styling is invisible to the snapshot harness, because render_to_string_with_app returns a plain String and discards colour and modifiers. The album row style branch was therefore entirely uncovered, and any arm could be swapped for another without a single test noticing.
decorate_album_row_with_style now holds that branch, matching decorate_artist_row_with_style in the library view and decorate_book_row_with_style in the Readarr author details view. The extraction is behaviour preserving: the branch is token identical to the one it replaces, and no snapshot changed.
Verified by mutation. Against the previous tree all eight mutations left the suite green: replacing each of the unmonitored, downloaded, unreleased, both missing and indeterminate arms, flipping the track file count comparison, and inverting the monitored check. Against the new tests every one of those eight fails.
The collapsed one-line overview on the Lidarr artist, Sonarr series, and Readarr author details views replaced every line break and tab with an empty string. Words on either side of a paragraph break fused together, rendering as "20th century.She was born in Ohio".
Collapse each run of whitespace to a single space instead, and trim the result. Handling a whole run at a time keeps a Windows line ending, an indented paragraph, or a space that already precedes a line break from producing two spaces.
The overview popups are unaffected: they receive the raw text and keep their paragraph breaks.
draw_logs read the level from the log line's Display output, which drops the first offset characters. Once a log line is scrolled, the field at index 1 is no longer the level: past the timestamp it yields the logger name and colours the row wrong, and past the final delimiter the index is out of bounds and panics, taking the TUI down.
Parse log.text instead, matching what draw_logs_popup already does. The displayed text is unchanged.
The artist, album and season statistics fixtures set the file count equal to the total count, so the columns rendering the pair showed the same number twice and swapping the two fields changed nothing. The JSON fixtures move with them so the round trip assertions still hold.
IndexersUi dispatches to IndexerSettingsUi, but no test rendered that route through the parent, so deleting the dispatch arm in radarr, sonarr and lidarr left the whole suite green.
draw_updates_popup shows the loading block when update text is already present but a refresh is still in flight. Both existing tests fed empty update text, so they only exercised the first half of the guard and dropping the is_loading conjunct went undetected.
Sonarr and Lidarr each keep their own copy of the shared fixture, both with minimum_age and retention set to 1, so transposing those two fields stayed invisible in either tree. Radarr built its fixture from IndexerSettings::default(), leaving availability_delay, maximum_size, minimum_age and retention all 0: four mutually transposable fields, none of which could distinguish a threaded value from a hardcoded 0.
The no-op guard test seeded IndexerSettings::default() as its sentinel, which cannot show whether the guard held or the field was simply never written. It now seeds a distinct stale_indexer_settings(), mirroring the Readarr convention.
Verified by transposing minimum_age and retention in the Lidarr indexer settings UI, now caught by its snapshot, and by inverting the Radarr no-op guard, now caught by both GetAllIndexerSettings tests.
An artist's overview renders as one line of a description box that does
not scroll, so a long overview overflows with no way to read the rest.
Pressing v on the artist details Albums tab now opens a popup holding
the full overview.
The popup owns its own ScrollableText, built fresh from the artist's
overview when it opens and torn down on Esc so a reopened popup starts
at the top. Scrolling mutates only that owned state. The background
paragraph renders the Artist record's overview, which carries no scroll
offset, so it cannot scroll along with the popup.
Cite the existing view keybinding from the artist details context clues
and give the popup its own clue set so it does not inherit the hints of
the view underneath.
Enrich the shared artist fixture to a multi-paragraph overview, and the
blocklist response fixtures that embed the same artist, so a scrolled
popup is distinguishable from an unscrolled one.
Span::style assigns rather than patches, so chaining it after bold()
discarded the modifier that bold() had just set and every detail popup
label rendered plain. Apply the modifier to the style itself instead.
Snapshots compare rendered text and cannot observe styling, so this is
pinned by asserting the label span's modifier directly.
- Rename `ui_scroll_tick_count: u64` → `should_text_scroll: bool` across all 33
call sites; `on_ui_scroll_tick` now assigns true/false instead of 0/1
- Remove two now-redundant comments from `test_on_ui_scroll_tick`
- Move `WHITESPACE_RE` static declaration below module imports in network/mod.rs
- Remove `MANAGARR_TICK_RATE_MS` env var and its parsing helpers; `recv()`-based
blocking already brings idle CPU to near-0, and tuning tick rate silently
stretches the poll cadence — keep `DEFAULT_TICK_RATE_MS` const only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3FpbniZme9mJqgFkMVowz