fix(playlists): one rendering for a playlist total, honest below a minute (E9 R1)
Epic-end deep review, round 1. Six findings survived verification; five of them were one defect wearing different hats. formatRuntime floored 1-59s to "0m". Harmless while it only ever formatted a Plex movie's length, but 12D repurposed it for an aggregate, and a total is the only input that can land under a minute: a playlist of Shorts reported "0m" while the rail beside it read "0:45". Fixed in the function, not behind a `>= 60` guard at each of the seven call sites — the invariant belongs where it cannot be forgotten. Its contract comment claimed "Plex movie/episode lengths" and was two epics stale. The rail rendered the same playlist total as formatDuration (1:23:45) while the new header rendered formatRuntime (1h 23m) — two shapes of one number, side by side on one screen. The rail now uses formatRuntime too; formatTotalHours had already recorded that H:MM:SS is wrong for an aggregate. The rename input kept min-w-0 without flex-1 in the compacted row, so it was the only shrinkable child beside a shrink-0 action cluster. The Plex half of this same epic already had the fix; this restores the symmetry. plex.browse.items had no plural forms, so a one-item Plex playlist read "1 items". Pre-existing, but the new header is the call site where count=1 is ordinary. Both languages get both forms, no key drift. formatRuntime had no unit tests at all across seven call sites. Added a tier table plus a sweep asserting no non-zero second ever renders as "0m". Dropped after 3/3 adversarial refutations: the meta-line segment ordering (the filtered count is labelled, and count-then-runtime is the convention) and the null-duration lower bound (the server's own SUM has skipped NULLs since long before this branch; changing only the client would split them).
This commit is contained in:
@@ -96,10 +96,16 @@ export function formatDuration(sec: number | null): string {
|
||||
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
|
||||
}
|
||||
|
||||
/** Compact human runtime ("1h 30m", "45m") from seconds — used for Plex movie/episode lengths.
|
||||
* Empty string for a missing/zero duration (nothing to show). */
|
||||
/** Compact human runtime ("1h 30m", "45m", "45s") from seconds. Formats BOTH a single item's
|
||||
* length (Plex movies/episodes) and an aggregate total (playlist playtime, on the YouTube and the
|
||||
* Plex side alike) — it is the one renderer for a playlist total, so a total never appears in two
|
||||
* shapes on one screen. Empty string for a missing/zero duration (nothing to show).
|
||||
* The sub-minute tier is load-bearing, not decoration: an aggregate is the only input that can land
|
||||
* between 1 and 59 seconds (a playlist of Shorts), and flooring that to a flat "0m" made a
|
||||
* non-empty playlist report no runtime at all. Keep it, or that case silently regresses. */
|
||||
export function formatRuntime(sec?: number | null): string {
|
||||
if (!sec) return "";
|
||||
if (sec < 60) return `${Math.floor(sec)}s`;
|
||||
const h = Math.floor(sec / 3600);
|
||||
const m = Math.floor((sec % 3600) / 60);
|
||||
return h ? `${h}h ${m}m` : `${m}m`;
|
||||
|
||||
Reference in New Issue
Block a user