Files
bookhoard/tests/ui/COVERAGE.md
T
John O'Keefe ace9e57d27 fix(ui): hx-confirm interception actually worked — htmx 2 detail shape
User report: Remove Feed still used the browser dialog. Headless-proven
root cause (tests/ui/confirm_probe.spec.ts): the htmx:confirm handler
read evt.detail.element, which htmx 2 does not provide (its detail is
{ elt, question, issueRequest, ... }) — the handler returned at
'if (!elt) return' BEFORE preventDefault, so every hx-confirm sitewide
fell through to htmx's native confirm. The acbcc61a migration only ever
worked for direct confirmDialog() callers; hx-confirm interception was
dead on arrival against htmx 2 and no test watched the JS side.

Fix: read detail.question (htmx's own closest-attribute resolution of
hx-confirm); return without preventing default when absent. Probe
spec pins: click Remove Feed → #confirm-modal opens with the message,
cancel hides it, no dialog event fires.

Companion fixes in the same user-test round (one commit, one defect
family — templ boolean attributes + confirm-modal placement):
- Add-modal Download order never revealed: #podcast-download-all was
  nested in a wrapper div, so ':checked ~ .podcast-add-order' had no
  sibling relationship. Input, label, and order row are now siblings.
- Order/auto-download dropdowns always DISPLAYED the last option
  ('Newest first'/'Newest') whatever was saved: templ selected={ bool }
  emits selected="false" — presence selects, so every option was
  selected and the browser honored the last. Swept to selected?={...}
  across podcast_detail (14), book_detail_modals (10, incl. two
  always-checked checkboxes), collection_modal (2, same). Saves were
  correct all along; display only.
- Global confirm modal moved from SidebarSignIn (signed-out only!) into
  Header — signed-in pages had no #confirm-modal, so confirmDialog
  degraded to window.confirm even with a working interceptor.

Facet bar scale (user question): values now carry feed counts, sort
frequency-first, cap at 6 chips per group with a native <details>
'+N more' expander.

Probes: tests/ui/confirm_probe.spec.ts + add_order_probe.spec.ts
(live-server, BH_FEED-gated) — both pass against the dev instance.
2026-10-30 12:24:16 -04:00

3.3 KiB

UI sweep coverage (plan §7.5)

Gate: every line below executes for BOTH personas unless marked "admin-only". Status: ✅ swept+green · 🔲 pending (future sweep runs).

# Page / control admin restricted
1 Login: bad password rejected; good login lands authed; logout ✅ ✅
2 Dashboard: sections render (Recently Added / Not Started) ✅ ✅ (hidden lib absent)
3 Dashboard: hidden library visibility (Audiobooks) ✅ visible ✅ absent
4 Search: results scoped (/api/media-items/search) ✅ ✅ no leaks
5 Media list: no hidden items in payloads ✅ ✅
6 Library browse grid + pagination 🔲 🔲
7 Library create / edit / delete (admin) 🔲 n/a
8 Admin visibility toggle roundtrip (hide → restricted loses → unhide) 🔲 🔲
9 Media detail: edit metadata / archive / delete 🔲 n/a
10 Reader: open, page turn, TOC, bookmark, highlight, note, settings 🔲 🔲
11 Collections: CRUD, add/remove items, smart-rule editor 🔲 🔲
12 Series: list/detail 🔲 🔲
13 Stats screens + range filters 🔲 🔲
14 Admin: users CRUD, visibility matrix, settings toggles, processing issues 🔲 n/a
15 Upload: EPUB generated at runtime, upload → appears → delete 🔲 n/a
16 Auth edges: restricted → admin URL; anonymous → media URL 🔲 ✅ (detail/404)
17 OPDS / KOReader API-covered (invariant test) API-covered
18 Podcasts: page renders (empty state before any feed) 🔲 🔲
19 Podcasts: add-feed modal → feed page (member flow) 🔲 🔲
20 Podcasts: feed detail — episode list lifecycle (download/queue/failed+retry states inline), Download All, gap banner 🔲 🔲
21 Podcasts: cross-user invisibility — own feed only (RLS §20 surfaces) 🔲 🔲
22 Podcasts: order selector re-renders episode list; serial suggestion shown only for serial/audiobook feeds 🔲 🔲
23 Podcasts: audio reader "Ep N/M" + prev/next episode; chain-advance to next episode 🔲 🔲

Podcast-specific sweep notes (plan task 1.10)

  • The download lifecycle needs a fetchable enclosure: sweep with a local feed server fixture (the integration tests use swapFeedServer; the Playwright run needs the same trick) — otherwise episodes stay pending and rows 20's queue/failed states can't render.
  • Row 21 rides TestPodcastRLSSurfaces (API level) until the sweep lands; the page-level check is the 404 render for a foreign feed id.
  • Row 23 chains by page navigation — assert the URL becomes the next episode's reader and progress carries over.

2026-10-30 — round-3 regression probes (live-server, env-gated)

Two specs added against a RUNNING dev server (skip unless BH_FEED=<feed uuid> is set for the first):

  • confirm_probe.spec.ts — every hx-confirm must open the GLOBAL confirm modal, never a native dialog. Pins the htmx-2 detail-shape fix (detail.question, not detail.element) and the modal's presence on signed-in pages (it used to live inside SidebarSignIn).
  • add_order_probe.spec.ts — the add-modal "Download order" select reveals when "Download all episodes now" is checked (CSS sibling selector; the checkbox must stay a sibling of .podcast-add-order).