Six specs driving the user-reported controls the way a human does,
encoding the sweep rules COVERAGE.md now states: menus/modals assert
VISIBILITY (never DOM text — hidden menus' text is why the 2026-10-30
panel sweep certified dead controls), geometry asserts the z-ladder
via elementFromPoint and viewport containment via boundingBox, every
click must produce an observable outcome (PUT fired, class flipped,
chip text swapped) and zero new console problems — the silent
CSP-throw family (theme buttons, connective chip, invisible op menu)
now fails a test instead of surviving to a user's mouse.
Covers: theme picker (interpolation regression from eb57b720),
pencil-glyph vs card-body contract, builder field menu / op menu
(full non-empty vocabulary) / connective both directions, the
Smart→Episodes create-modal seed, the picker overlay's ladder and
height, and the chain-order select's persistence round-trip.
Fixtures self-create/self-delete under test-prefixed names; localStorage
token + selectedLibrary=__all__ are seeded because the API login
establishes only the cookie; run the folder with --workers=1 (the login
route rate-limits and all suites share testuser — a parallel worker's
login revokes another's mid-run).
127 lines
7.2 KiB
Markdown
127 lines
7.2 KiB
Markdown
# 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`).
|
|
|
|
## 2026-10-30 — episode collections (plan item 2.3) — sweep candidates
|
|
|
|
New controls the Playwright sweep should cover once it lands:
|
|
|
|
- Collection picker modal: Books/Episodes tab pair — episodes tab loads
|
|
`?scope=episodes`, selection persists across tab switches and htmx
|
|
swaps (shared bookPicker store).
|
|
- Collection detail: episode rows (play link carries
|
|
`?queue=collection:<id>`; chain hops keep the param), "Not
|
|
downloaded" badge, "Unavailable / Remove entry" hint row for
|
|
collected-but-unsubscribed feeds (All Libraries view), header "Play
|
|
as Sequence" button.
|
|
- Player (collection mode): order dropdown + reverse button ABSENT
|
|
(feed-mode only); "Ep N/M" reflects collection order; auto-advance
|
|
follows the collection chain, and back-to-detail exits to the
|
|
collection page.
|
|
- API-level pins live in `TestPodcastEpisodeCollections` (7 subtests
|
|
incl. the family-lens auditor case) + `TestPodcastPlayerOrderControl`.
|
|
|
|
## 2026-10-30 — browser sweep executed (manual, OpenChamber panel)
|
|
|
|
All controls above plus the item-3 builder/editor surfaces were driven
|
|
in a real browser against the dev stack. Outcomes + reusable gotchas:
|
|
|
|
- Fixed during the sweep: templ has no `continue` STATEMENT (codegen
|
|
emits it as literal text — the library switcher's podcast exclusion
|
|
never worked); CSP attribute grammar (no backticks, no `?.`, no
|
|
if-statements in Alpine attrs); rules editor double-encoded payloads;
|
|
store hook must use the IMPORTED Alpine (window.Alpine?.store silently
|
|
no-ops). Pins: TestLibrarySwitcherExcludesPodcasts, templates CSP
|
|
source scan, TestPodcastEpisodeCollections/TestEpisodeCollectionsFilterTree.
|
|
- Panel limitation for future automated specs: `selectOption`-equivalent
|
|
via this panel sets the value WITHOUT a change event — htmx
|
|
change-triggers (player order dropdown, library switcher) must be
|
|
driven with Playwright-native interactions, not value assignment.
|
|
- Episode flows confirmed: picker Episodes tab → Add Selected → cards
|
|
with queue=collection: play links → chain hops keep the param; order
|
|
controls appear ONLY in feed mode (player + feed page share the
|
|
persisted override).
|
|
|
|
## 2026-10-31 — round 4: user-reported control defects → knob matrix (automated)
|
|
|
|
The panel sweep's blind spot, proven by the user's own two minutes:
|
|
DOM-text snapshots show HIDDEN menus' text — "the field menu opened"
|
|
was an illusion; the menus never VISIBLY opened, the op menu rendered
|
|
zero items, `closeCollectionModal` removed the hidden confirm dialog
|
|
instead of the modal, and the theme buttons died silently
|
|
(`selectTheme(opt.Name)` — templ gotcha #1, since the CSP migration).
|
|
|
|
Now automated: `knob_matrix.spec.ts` (6 specs — theme picker PUTs,
|
|
pencil-glyph vs card body at real coordinates, builder field/op menus
|
|
with toBeVisible + non-empty items + connective both directions, the
|
|
Smart→Episodes create-modal seed, picker modal ladder+geometry via
|
|
`elementFromPoint`/`boundingBox`, chain-order select persistence).
|
|
Run: `cd tests/ui && BH_FEED=<feed uuid> npx playwright test
|
|
--workers=1` — **`--workers=1` is load-bearing**: the login endpoint
|
|
rate-limits and every suite shares `testuser`, so a parallel worker's
|
|
login revokes another's mid-run (symptom: `session has expired` pages).
|
|
|
|
Standing rules for every sweep from now on:
|
|
- Menus/modals assert VISIBILITY (toBeVisible / computed display /
|
|
screenshot), never DOM text; every toggle click has an asserted
|
|
observable outcome (request fired, class flipped, value changed).
|
|
- Geometry asserts where geometry is the bug (z-ladder via
|
|
`elementFromPoint`, sheet within viewport via `boundingBox`).
|
|
- Zero new console errors per interaction; the only tolerated noise is
|
|
the two cancelled-transition shapes swallowed by main.ts's gate —
|
|
new noise gets fixed at the source, never filtered.
|
|
- API-mutating tests seed localStorage token + selectedLibrary=__all__
|
|
(the mutators read the bearer from localStorage; library-scoped views
|
|
correctly hide episodes).
|