Files
arr/.impeccable/surfaces/web-src-main-ts.md
T
Miguel Palhas 9fee07f080 feat: let a size rejection be waived
A release below §5.5's floor was rejected with no way through, so a
policy wrong about one title left three Rick and Morty S09 packs
visible and none grabbable.

`allow_below_floor` relaxes the floor for one title into a soft fail,
never a pass: the release is waived, so automatic grabbing still skips
it and the import records a §5.7 waiver. The deck offers the one click
on a rejected row where the rule has an override, which is exactly what
§9.3's override is for.

Stored verdicts are re-derived when a title's overrides change — the
deck and the daemon's grab gate both read that column, so without it
the row the operator just acted on would keep reading `rejected`.

Closes #210

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:15:14 +01:00

81 lines
5.1 KiB
Markdown

---
version: 1
slug: "web-src-main-ts"
primary_target: "web/src/main.ts"
related_targets: ["web/index.html","web/src/search.ts","web/src/releases.ts","web/src/library.ts","web/src/style.css"]
---
# Surface: unified search + release deck + library (web/src/main.ts, §9.2 + §9.3 + §4.2)
Mode: Operate. The operator finds or adds a title without knowing which of
the two they are doing, picks a release from decisions, not name dumps, and
reads the library as derived status, not flags.
- Audience: the household operator; desktop mostly, phone sometimes.
- Task: one rail-mounted command input. Typing swaps the current surface for
the search deck; clearing or Esc returns to the board. "/" focuses from
anywhere.
- Structure (pinned by DESIGN.md §9.2): IN LIBRARY group first, ON TMDB below,
engraved micro-label headings, machined rows, chips on the right.
- Add flow: inline panel under a TMDB row of either kind — root radio cards
(audience name, policy name, path), main pre-selected, one confirm. Never a
separate page. A series adds the one §4.1 add-time question: an auto-track
pressed control (default on) with the plain line "future seasons are tracked
automatically; past seasons are not", echoed in the added confirmation.
- Release deck (§9.3, issue #31): a library row opens the buckets view.
ELIGIBLE leads with fixed-width attribute chips in aligned mono columns
(rem-width `.cw-*` classes shared by chips and the column header, so the
pseudo-table survives flex wrapping); WAIVED and REJECTED collapse to
counts behind cyan show/hide toggles. Release name is a secondary
truncated line, full string plus indexer/date/bytes on row expand. Every
waived/rejected row carries a verdict chip naming the rule. Grab is its
own control per row; where the rule that failed has an override it reads
"waive + grab" and writes the per-title override (rule→override mapping in
releases.ts — bookkeeping, never policy; verdicts always come from the
API). A rejected row offers it too — §9.3's one click exists precisely for
the rule the operator disagrees with, and a size rejection (#210) is only
ever rejected. What the click produces stays a waiver: the row re-reads as
a dashed WAIVED, never eligible. When ELIGIBLE is empty and something is
waivable, its "none" line carries the count, so the way out is not folded
inside a collapsed bucket. Esc steps back one
layer: deck → search results → board.
- Removal (§7.4, issue 104): REMOVE is a quiet control in the deck head,
never a row affordance — a dense list is the wrong place for a delete.
It opens one inline confirmation carrying the evidence first (file count,
total size, the §7.4 folder as reported by `/api/movies/{id}/files`), then
the decision, then the cost. "delete files from disk" is a separate toggle
that starts OFF and stays disabled when nothing is on disk or the file list
cannot be read; armed, the panel, its toggle word, the note and the confirm
button all turn caution amber, and the confirm reads "remove and delete
files" instead of "remove from library". The note says what does not happen
either: the torrent keeps seeding (§7.3). Esc abandons the confirmation
before it abandons the deck. On success the deck closes and the surface it
opened over repaints, so a removed title never lingers in a list.
- Library view (§4.2, issue #32): the LIBRARY rail control opens the library
deck — SERIES then MOVIES groups. Status is derived and only displayed
(§4.2 status chips in the channel-violet/neutral family for series, state
chips for movies); intent is `wanted` at the leaf and no surface offers a
monitored flag. Default set = airing + incomplete series and
wanted-and-missing movies; everything satisfied collapses behind ONE cyan
toggle carrying its count. A waived import reads honestly as a dashed
amber chip ("english, no dub"). A movie row opens the release deck over
the library and Esc returns there; series rows are inert until #39. Esc
layering uses capture + stopImmediatePropagation — three surfaces listen
for Escape on the same window.
- Manual intake: magnet / .torrent input routes to a MANUAL GRAB panel showing
parsed display name and btih; magnet grab wiring is issue #22, and the
panel says so honestly.
- Constraints: 250 ms debounce, AbortController cancellation, token-gated CSS,
cyan for interaction only, dead-air rule (no idle animation), no horizontal
scroll at any viewport (§9.3 acceptance criterion).
- Unified search covers both kinds (issue 130): in-library hits read as
whatever they are — a movie row, a series hit (display-only until the
detail view of issue 129 exists) and an episode hit rendered as its series,
an `SxxEyy` chip and the episode title in sans beside it. TMDB rows show TV
alongside film; the "in library" dedup spans movies and series.
- Unresolved: releases land in the persisted store only once the daemon
consumes search commands (#68-adjacent); episode hits have no link target
until issue 129's series detail view exists — they render display-only for
now; per-season and per-episode library actions are #39; attention queues
surface is #33.