diff --git a/web/index.html b/web/index.html
index d090179..26779e0 100644
--- a/web/index.html
+++ b/web/index.html
@@ -171,7 +171,8 @@
kids titles with no qualifying pt release — parked here, sometimes for months, by
- design. allow english writes this title's override and the normal search proceeds.
+ design. allow english writes a movie's override and its search proceeds; series wait
+ for the detail view.
@@ -182,7 +183,8 @@
- hard-failed twice on different releases — open the releases and decide by hand.
+ hard-failed twice on different releases — open a movie's releases and decide by hand;
+ series entries are display-only for now.
diff --git a/web/src/main.ts b/web/src/main.ts
index 344d843..1638353 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -13,6 +13,7 @@ import {
attemptsLabel,
attentionTotal,
fetchAttention,
+ type SeriesAttention,
} from "./queues";
import {
bucketOf,
@@ -1826,6 +1827,15 @@ function queuesMain(board: HTMLElement, releases: ReleasesView, views: HideableV
for (const movie of queues.needs_decision) {
groups.decision.rows.append(libraryRow(movie, roots, openReleases));
}
+ // the TV lanes share the two sections: one queue per reason, whatever
+ // the kind. Series entries are display-only — their release deck and
+ // overrides arrive with the series detail view (issues 129 and 39).
+ for (const series of queues.tv_no_pt_source) {
+ groups.noPt.rows.append(seriesAttentionRow(series));
+ }
+ for (const series of queues.tv_needs_decision) {
+ groups.decision.rows.append(seriesAttentionRow(series));
+ }
for (const group of [groups.noPt, groups.decision]) {
emptyLine(group);
}
@@ -1911,6 +1921,38 @@ function queuesMain(board: HTMLElement, releases: ReleasesView, views: HideableV
return { hide, open, refreshBadge };
}
+/**
+ * One TV lane entry: the series plus chips counting the episodes or
+ * seasons that put it there. The API carries internal row ids, not SxxEyy
+ * numbers, so counts are what can be named honestly. Inert — see above.
+ */
+function seriesAttentionRow(entry: SeriesAttention): HTMLLIElement {
+ const item = document.createElement("li");
+ const row = document.createElement("div");
+ row.className = "row";
+ const chips = document.createElement("span");
+ chips.className = "row-chips";
+ if (entry.episodes.length > 0) {
+ const n = entry.episodes.length;
+ chips.append(
+ chip(`${n} ${n === 1 ? "episode" : "episodes"}`, (span) => {
+ span.setAttribute("aria-label", `${n} ${n === 1 ? "episode" : "episodes"} waiting`);
+ }),
+ );
+ }
+ if (entry.seasons.length > 0) {
+ const n = entry.seasons.length;
+ chips.append(
+ chip(`${n} ${n === 1 ? "season" : "seasons"}`, (span) => {
+ span.setAttribute("aria-label", `${n} ${n === 1 ? "season" : "seasons"} waiting`);
+ }),
+ );
+ }
+ row.append(rowTitle(entry.title, entry.year), chips);
+ item.append(row);
+ return item;
+}
+
/**
* One no-PT-source entry: the line opens the release deck as evidence; the
* §5.2 one click sits beside it and empties the item.
diff --git a/web/src/queues.ts b/web/src/queues.ts
index 76af653..f14bda0 100644
--- a/web/src/queues.ts
+++ b/web/src/queues.ts
@@ -78,13 +78,14 @@ export async function allowEnglishAudio(movieId: number): Promise