Series list page: eight hygiene filters, Site and Library narrowing, paging #142

Closed
opened 2026-08-21 16:39:21 +07:00 by sulthan · 1 comment
Owner

Parent

Spec #134.

What to build

The owner gets a filterable, bookmarkable list of every Series across every Reader's library at once, so broken rows can be found without asking about one Reader at a time. Filter state lives in the query string -- filter, Site, Library, page -- so "the Series with no cover" is a bookmark to come back to, not a click path to repeat.

Eight named hygiene filters, one at a time, each labelled as the repair it needs: no series URL, never read a chapter, no Readers, never checked, not checked in the window, no cover, latest from a Reader's report, all Series. On top of a hygiene filter the owner can narrow by Site and by Library without losing the filter. The filter control is a select whose option labels carry their counts, not a row of eight plain links -- eight hygiene labels do not read as a chip row. Site is a second select; Library is a three-way segmented row marked active in patina.

The heading states the count and the filter, so the number the landing page promised and the number the list shows come from one query. An empty filtered list names the filter it is empty for, so an empty hygiene list reads as good news rather than as a broken page. Paging is 50 rows with a stable order; asking for a page past the end re-reads at page 1 rather than showing an empty table.

Rows are the two-line form: a subgrid row with the title on its own full-width line and the facts on the line under it, so a long title never breaks column rhythm. Separation is banding rather than hairlines, so a title and its facts read as one record -- and banding is a class, not an nth-of-type, because the confirm rows later tickets add are row siblings and would throw the alternation off. Notes chips cap at two plus a faint tail rather than stacking.

Nothing new on the frontend. The vendored htmx already carries every primitive this needs, and URL-addressable filters are the same pushed-URL pattern the reading tabs already prove. The existing client-side filter script is not reusable: it is a pure title filter over an already-rendered list, it issues no requests, it changes no URL, and it is not loaded on admin at all -- and client-side filter state cannot be bookmarkable without hand-written history calls, which is exactly what these routes must avoid. So filtering is server-side. Delete branch research/htmx-series-list once this lands.

Acceptance criteria

  • Each of the eight filters returns and renders the rows it names and no others, asserted through a request and its rendered response
  • The heading's stated count agrees with the rendered row count
  • Each filter's empty state names that filter
  • Site and Library narrowing stack on a hygiene filter without dropping it, and survive in the URL
  • Page 2 of a one-page result re-reads at page 1
  • Row order is stable across pages with several rows sharing a zero check stamp: no row repeats or vanishes
  • Filter option labels carry their counts; Library renders as a segmented row with the active branch marked in patina
  • Rows are two-line and banded by class; no ember token appears
  • Branch research/htmx-series-list is deleted
  • go test ./... green

Blocked by

  • #138 — Split the admin surface into four bookmarkable pages with a nav row
  • #140 — Cross-Series admin read model, with the privacy boundary in the projection
## Parent Spec #134. ## What to build The owner gets a filterable, bookmarkable list of every Series across every Reader's library at once, so broken rows can be found without asking about one Reader at a time. Filter state lives in the query string -- filter, Site, Library, page -- so "the Series with no cover" is a bookmark to come back to, not a click path to repeat. Eight named hygiene filters, one at a time, each labelled as the repair it needs: no series URL, never read a chapter, no Readers, never checked, not checked in the window, no cover, latest from a Reader's report, all Series. On top of a hygiene filter the owner can narrow by Site and by Library without losing the filter. The filter control is **a select whose option labels carry their counts**, not a row of eight plain links -- eight hygiene labels do not read as a chip row. Site is a second select; Library is a three-way segmented row marked active in patina. The heading states the count and the filter, so the number the landing page promised and the number the list shows come from one query. An empty filtered list **names the filter it is empty for**, so an empty hygiene list reads as good news rather than as a broken page. Paging is 50 rows with a stable order; asking for a page past the end re-reads at page 1 rather than showing an empty table. Rows are **the two-line form**: a subgrid row with the title on its own full-width line and the facts on the line under it, so a long title never breaks column rhythm. Separation is **banding** rather than hairlines, so a title and its facts read as one record -- and banding is a class, not an nth-of-type, because the confirm rows later tickets add are row siblings and would throw the alternation off. Notes chips cap at two plus a faint tail rather than stacking. **Nothing new on the frontend.** The vendored htmx already carries every primitive this needs, and URL-addressable filters are the same pushed-URL pattern the reading tabs already prove. The existing client-side filter script is not reusable: it is a pure title filter over an already-rendered list, it issues no requests, it changes no URL, and it is not loaded on admin at all -- and client-side filter state cannot be bookmarkable without hand-written history calls, which is exactly what these routes must avoid. So filtering is server-side. Delete branch `research/htmx-series-list` once this lands. ## Acceptance criteria - [ ] Each of the eight filters returns and renders the rows it names and no others, asserted through a request and its rendered response - [ ] The heading's stated count agrees with the rendered row count - [ ] Each filter's empty state names that filter - [ ] Site and Library narrowing stack on a hygiene filter without dropping it, and survive in the URL - [ ] Page 2 of a one-page result re-reads at page 1 - [ ] Row order is stable across pages with several rows sharing a zero check stamp: no row repeats or vanishes - [ ] Filter option labels carry their counts; Library renders as a segmented row with the active branch marked in patina - [ ] Rows are two-line and banded by class; no ember token appears - [ ] Branch `research/htmx-series-list` is deleted - [ ] `go test ./...` green ## Blocked by - #138 — Split the admin surface into four bookmarkable pages with a nav row - #140 — Cross-Series admin read model, with the privacy boundary in the projection
sulthan added the ready-for-agent label 2026-08-21 16:39:21 +07:00
sulthan self-assigned this 2026-08-21 19:23:27 +07:00
Author
Owner

Merged into spec-134 as bf08d6e (branch ticket/142-series-list, commits e8a3c5f, 134c930).

Series list ships with the eight hygiene filters over the landed store.SeriesFilter* vocabulary, Site and Library narrowing, stable paging off SeriesPage.Total, and the two-line banded .tbl.series row. ownerWindow (12h) declared once in internal/web/admin.go; latest.SiteNames() added for the Site select. Site colour lands as a site-* class rather than an inline style attribute — site is client-supplied and unvalidated, and a class attribute is not a CSS context.

Review: spec axis found one blocker (stale-count cutoff) plus two minors, all fixed; standards axis clean.

Two orchestrator notes:

  • The AC to delete research/htmx-series-list was left to me and is done at merge time.
  • Seven new tests passed the now-removed lane-reporter argument to newRouter; #145 deleted that parameter in the same wave, so I dropped the argument during the merge. Suite green after.

go test ./... green on the merged base.

Merged into `spec-134` as bf08d6e (branch `ticket/142-series-list`, commits e8a3c5f, 134c930). Series list ships with the eight hygiene filters over the landed `store.SeriesFilter*` vocabulary, Site and Library narrowing, stable paging off `SeriesPage.Total`, and the two-line banded `.tbl.series` row. `ownerWindow` (12h) declared once in `internal/web/admin.go`; `latest.SiteNames()` added for the Site select. Site colour lands as a `site-*` class rather than an inline `style` attribute — `site` is client-supplied and unvalidated, and a class attribute is not a CSS context. Review: spec axis found one blocker (stale-count cutoff) plus two minors, all fixed; standards axis clean. Two orchestrator notes: - The AC to delete `research/htmx-series-list` was left to me and is done at merge time. - Seven new tests passed the now-removed lane-reporter argument to `newRouter`; #145 deleted that parameter in the same wave, so I dropped the argument during the merge. Suite green after. `go test ./...` green on the merged base.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#142