fix(web): keep season controls on one line
ci / web (push) Successful in 55s
e2e / e2e (push) Successful in 1m30s
ci / rust (push) Failing after 1m38s

This commit is contained in:
Miguel Palhas
2026-08-25 21:14:29 +01:00
parent 06f8a6f0ea
commit eeada0ef65
2 changed files with 68 additions and 19 deletions
+24 -9
View File
@@ -3628,10 +3628,20 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
disclose.click();
});
// season 0 never counts toward tracking or totals (§4.1, §4.2); say so
// season 0 never counts toward tracking or totals (§4.1, §4.2); say so.
// Both forms are rendered and CSS picks one: narrow viewports show the
// SxxEyy-style tag the rest of the app already uses, so the row's controls
// keep their place instead of wrapping under it.
const name = document.createElement("span");
name.className = "season-name";
name.textContent = season.number === 0 ? "specials · s00" : `season ${PAD_TWO(season.number)}`;
const nameLong = document.createElement("span");
nameLong.className = "season-name-long";
nameLong.textContent =
season.number === 0 ? "specials · s00" : `season ${PAD_TWO(season.number)}`;
const nameShort = document.createElement("span");
nameShort.className = "season-name-short";
nameShort.textContent = seasonTag(season.number);
name.append(nameLong, nameShort);
const track = document.createElement("button");
track.type = "button";
@@ -3658,8 +3668,13 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
});
});
const space = document.createElement("span");
space.className = "season-space";
// everything that describes the season shares one shrinkable box; the
// controls sit outside it, so a long flag chip wraps within the head
// rather than pushing search and delete onto a line of their own.
const head = document.createElement("span");
head.className = "season-head";
const actions = document.createElement("span");
actions.className = "season-actions";
const deckBtn = document.createElement("button");
deckBtn.type = "button";
@@ -3689,7 +3704,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
});
});
line.append(disclose, name, track, seasonCountsChip(season));
head.append(name, track, seasonCountsChip(season));
// #227: the season the operator's report is about read `0/10` with
// nothing saying a pack had been grabbed, downloaded in full and thrown
// out at import. The counts chip beside this one is exactly the number
@@ -3699,7 +3714,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
const kind = blacklistClass(failure.reason);
const when = failure.failed_at === null ? "" : ` ${formatSweepAge(failure.failed_at)}`;
const told = `a pack for this season downloaded in full and failed at import${when} — ${failure.release} · ${blacklistAdvice(failure.reason)}`;
line.append(
head.append(
chip(`import failed · ${blacklistReasonLabel(failure.reason)}`, (span) => {
span.dataset.flag = "import-failed";
span.dataset.blacklist = kind;
@@ -3711,13 +3726,12 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
// The season-level twin of the episode flag above: gone upstream while
// files under it remained.
if (season.vanished) {
line.append(
head.append(
chip("vanished", (span) => {
span.dataset.flag = "vanished";
}),
);
}
line.append(space, deckBtn);
// #174: files go, episodes stop being wanted, the season stays listed.
// Only offered with files on disk — intent alone is the tracked toggle.
let clear: HTMLButtonElement | null = null;
@@ -3746,7 +3760,8 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView {
);
}
// rows without a delete still reserve its slot, so search keeps its column
line.append(space, deckBtn, clear ?? placeholderSlot());
actions.append(deckBtn, clear ?? placeholderSlot());
line.append(disclose, head, actions);
item.append(line);
const episodes = document.createElement("ul");
+44 -10
View File
@@ -1516,16 +1516,34 @@ body {
border-bottom: 1px solid oklch(from var(--line) l c h / 45%);
}
/* the line never wraps: the head shrinks and wraps inside itself, so the
controls keep the right edge on every viewport */
.season-line {
display: flex;
align-items: center;
flex-wrap: wrap;
flex-wrap: nowrap;
gap: var(--space-2) var(--space-3);
padding: var(--space-2) var(--space-1);
cursor: pointer;
transition: background 150ms var(--ease-out);
}
.season-head {
display: flex;
align-items: center;
flex-wrap: wrap;
flex: 1;
min-width: 0;
gap: var(--space-2) var(--space-3);
}
.season-actions {
display: flex;
align-items: center;
flex: none;
gap: var(--space-3);
}
/* a row under the pointer is visibly the row the click will land on —
the same lift a search result row gets */
.season-line:hover,
@@ -1568,6 +1586,7 @@ body {
}
.season-name {
flex: none;
font-family: var(--font-display);
font-size: var(--text-base);
font-weight: 700;
@@ -1576,16 +1595,19 @@ body {
color: var(--ink);
}
/* one name, two lengths — the wide one reads as prose, the narrow one is the
SxxEyy tag used everywhere else. display:none keeps the unused one out of
the accessibility tree, so the row is never announced twice. */
.season-name-short {
display: none;
}
/* the tracked toggle reads as the rule it is; pressed = on */
.season-line .control,
.ep-actions .control {
padding: 0 var(--space-3);
}
.season-space {
flex: 1;
}
.episodes {
margin: 0;
padding: 0 0 var(--space-2) var(--space-8);
@@ -1968,8 +1990,10 @@ body {
margin-left: auto;
}
/* no side gutter on a phone: the width belongs to the content, and the
hero reaches both edges the way the rail already does */
.deck {
padding: var(--space-6) var(--space-4) var(--space-8);
padding: var(--space-6) 0 var(--space-8);
}
.row-chips {
@@ -1977,6 +2001,20 @@ body {
flex-basis: 100%;
}
/* the season name shrinks to its tag so the head keeps fitting beside the
controls; the long form has nowhere to go on a phone */
.season-name-long {
display: none;
}
.season-name-short {
display: inline;
}
.episodes {
padding-left: var(--space-4);
}
/* an episode row wraps: identity first, chips full-width, actions right */
.ep-chips {
margin-left: 0;
@@ -1987,10 +2025,6 @@ body {
margin-left: auto;
}
.episodes {
padding-left: var(--space-4);
}
.releases-id {
order: -1;
flex-basis: 100%;