diff --git a/DESIGN.md b/DESIGN.md
index 06ad445..c6b6ace 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -164,10 +164,10 @@ Recomputed on metadata refresh and on file change:
| `complete` | everything wanted is on disk |
| `ended` | series finished upstream and complete |
-Default list view shows `airing` and `incomplete`. Everything else collapses
-behind one toggle. A one-off season grab therefore disappears from the default
-view by itself once satisfied, and a tracked show reappears by itself when a new
-season is announced. Nothing to remember to flip.
+The list view shows everything, ordered: `airing` and `incomplete` first, every
+other status after them. A one-off season grab therefore sinks to the bottom by
+itself once satisfied, and a tracked show rises again by itself when a new
+season is announced. Nothing is hidden and nothing has to be flipped.
Status chips carry state colour, not a single informational hue: green
(`--signal-ok`) for on disk or complete, amber (`--signal-warn`) for wanted but
diff --git a/web/index.html b/web/index.html
index 7c12e30..8b24aca 100644
--- a/web/index.html
+++ b/web/index.html
@@ -147,14 +147,6 @@
-
diff --git a/web/src/library.ts b/web/src/library.ts
index 9f83d01..c861cc6 100644
--- a/web/src/library.ts
+++ b/web/src/library.ts
@@ -51,8 +51,9 @@ export async function fetchLibrary(): Promise {
}
/**
- * §4.2: the default list shows `airing` and `incomplete`; everything else
- * collapses behind one toggle. A satisfied title leaves by itself.
+ * §4.2: `airing` and `incomplete` sort to the top of the list; everything
+ * else is satisfied and sinks below it. A satisfied title steps aside by
+ * itself, and is still there to click.
*/
export function seriesNeedsAttention(series: LibrarySeries): boolean {
return series.status === "airing" || series.status === "incomplete";
@@ -76,8 +77,8 @@ export function seriesStatusChipRedundant(series: {
}
/**
- * The movie analogue of §4.2's default set: wanted and not yet on disk.
- * Available and unwanted titles are satisfied and collapse with the rest.
+ * The movie analogue of §4.2's ordering: wanted and not yet on disk sorts
+ * first. Available and unwanted titles are satisfied and sink with the rest.
*/
export function movieNeedsAttention(movie: LibraryMovie): boolean {
return movie.wanted && movie.state !== "available";
diff --git a/web/src/main.ts b/web/src/main.ts
index 63132fd..55ef644 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -3651,6 +3651,14 @@ function loadLibraryShape(): LibraryShape {
}
}
+/**
+ * Stable partition: the items that still want something keep their order and
+ * come first, the satisfied ones keep theirs and follow.
+ */
+function attentionFirst(items: T[], wants: (item: T) => boolean): T[] {
+ return [...items.filter(wants), ...items.filter((item) => !wants(item))];
+}
+
function libraryMain(
movieDetail: MovieView,
seriesDetail: SeriesView,
@@ -3659,7 +3667,6 @@ function libraryMain(
const view = must("#library");
const deck = must("#deck");
const summary = must("#library-summary");
- const toggle = must("#library-toggle");
const status = must("#library-status");
const gridBtn = must("#library-view-grid");
const listBtn = must("#library-view-list");
@@ -3677,8 +3684,6 @@ function libraryMain(
},
};
- // §4.2: ONE toggle for everything the default view leaves out
- let showAll = false;
// §9.6 library view: the poster grid is default, per-person, localStorage
let shape = loadLibraryShape();
let data: { series: LibrarySeries[]; movies: LibraryMovie[] } | null = null;
@@ -3721,7 +3726,6 @@ function libraryMain(
group.rows.replaceChildren();
}
summary.textContent = "";
- toggle.hidden = true;
}
function renderGroup(group: LibraryGroup, items: T[], build: (item: T) => HTMLLIElement) {
@@ -3777,17 +3781,13 @@ function libraryMain(
return;
}
const total = library.series.length + library.movies.length;
- const satisfied =
- library.series.filter((series) => !seriesNeedsAttention(series)).length +
- library.movies.filter((movie) => !movieNeedsAttention(movie)).length;
summary.textContent = total === 0 ? "" : `${total} ${total === 1 ? "title" : "titles"}`;
- toggle.hidden = satisfied === 0;
- toggle.textContent = `${showAll ? "hide" : "show"} ${satisfied} satisfied`;
- toggle.setAttribute("aria-expanded", String(showAll));
- const seriesShown = showAll ? library.series : library.series.filter(seriesNeedsAttention);
- const moviesShown = showAll ? library.movies : library.movies.filter(movieNeedsAttention);
+ // §4.2: nothing is hidden. Satisfied titles sort last so what still
+ // wants something is what the eye lands on first.
+ const seriesShown = attentionFirst(library.series, seriesNeedsAttention);
+ const moviesShown = attentionFirst(library.movies, movieNeedsAttention);
const openSeries = (id: number, origin: HTMLElement) => {
navigate({ kind: "series", seriesId: id });
@@ -3801,16 +3801,26 @@ function libraryMain(
if (shape === "grid") {
groups.series.section.hidden = true;
groups.movies.section.hidden = true;
- renderLibraryRows([
- ...seriesShown.map((series) => ({
- root_id: series.root_id,
- item: seriesCard(series, openSeries),
- })),
- ...moviesShown.map((movie) => ({
- root_id: movie.root_id,
- item: movieCard(movie, openMovie),
- })),
- ]);
+ // series and movies share a root section, so the two lists interleave
+ // before the cards are built — otherwise a satisfied series would sit
+ // above a movie that still wants a file.
+ renderLibraryRows(
+ attentionFirst(
+ [
+ ...seriesShown.map((series) => ({
+ root_id: series.root_id,
+ wants: seriesNeedsAttention(series),
+ build: () => seriesCard(series, openSeries),
+ })),
+ ...moviesShown.map((movie) => ({
+ root_id: movie.root_id,
+ wants: movieNeedsAttention(movie),
+ build: () => movieCard(movie, openMovie),
+ })),
+ ],
+ (entry) => entry.wants,
+ ).map((entry) => ({ root_id: entry.root_id, item: entry.build() })),
+ );
} else {
libraryRows.replaceChildren();
libraryRows.hidden = true;
@@ -3820,8 +3830,6 @@ function libraryMain(
if (total === 0) {
setStatus("library is empty — the search box above adds titles");
- } else if (seriesShown.length + moviesShown.length === 0) {
- setStatus("nothing needs attention");
} else {
setStatus(null);
}
@@ -3862,11 +3870,6 @@ function libraryMain(
sequence += 1;
}
- toggle.addEventListener("click", () => {
- showAll = !showAll;
- render();
- });
-
function repaintDownloads() {
if (!view.hidden) {
render();