Spec #135: owner data-correction actions — Latest Chapter, series_url, Cover, orphan removal #156

Merged
sulthan merged 15 commits from spec-135 into main 2026-08-22 12:10:41 +07:00
Owner

Implements spec #135 (spec 2 of 4, derived from wayfinder map #114; decisions settled in #120/#121/#125/#131). Blocked-by #134 is merged, so this lands on main.

Four owner actions the dashboard can now perform, one ticket each:

  • #149 — Latest Chapter correction: one numeric input, overwritten by the next machine write.
  • #151 — Series URL repair: owner-typed, gated by the poller's own fetch gate.
  • #150 / #153 / #154 — Cover replacement: addresses derived from bytes (#150), a Forced Poll replaces the Cover while an ordinary pass still only fills a blank one (#153), and byte reclamation is one guarded helper, file first / covers row last (#154).
  • #155 — Orphan removal: one Series at a time, with the foreign key as the guard.

Plus #152 — Latest Chapter provenance: one derived line naming the actor class, so an owner can tell a hand-edited number from a machine read.

  • Migration 0015_latest_correction.sql adds the correction/provenance columns; 0009 now derives cover addresses from bytes.
  • ADR 0014-cover-addresses-from-bytes.md records the address scheme.

Backend tests cover the store, poller, admin handlers, and web routes (go test ./..., needs Docker).

Implements spec #135 (spec 2 of 4, derived from wayfinder map #114; decisions settled in #120/#121/#125/#131). Blocked-by #134 is merged, so this lands on `main`. Four owner actions the dashboard can now perform, one ticket each: - **#149** — Latest Chapter correction: one numeric input, overwritten by the next machine write. - **#151** — Series URL repair: owner-typed, gated by the poller's own fetch gate. - **#150 / #153 / #154** — Cover replacement: addresses derived from bytes (`#150`), a Forced Poll replaces the Cover while an ordinary pass still only fills a blank one (`#153`), and byte reclamation is one guarded helper, file first / covers row last (`#154`). - **#155** — Orphan removal: one Series at a time, with the foreign key as the guard. Plus **#152** — Latest Chapter provenance: one derived line naming the actor class, so an owner can tell a hand-edited number from a machine read. - Migration `0015_latest_correction.sql` adds the correction/provenance columns; `0009` now derives cover addresses from bytes. - ADR `0014-cover-addresses-from-bytes.md` records the address scheme. Backend tests cover the store, poller, admin handlers, and web routes (`go test ./...`, needs Docker).
sulthan added 15 commits 2026-08-22 12:09:55 +07:00
# Conflicts:
#	backend/internal/store/store_test.go
(*Store).RemoveSeries deletes one series row by (site, series_id) via a
plain parameterized DELETE; a bookmarks_series_fk violation outside
23503 is translated into the ErrSeriesHasBookmarks sentinel so no
driver type escapes the store. The caller reads the row's cover
address before the delete and reclaims it after: ReclaimCover's guard
cannot pass while a series row still points at the address.

POST /admin/series/{key}/remove answers the list row with the removed
row's fragment plus the heading re-rendered out of band at the fresh
count (HX-Reswap: delete removes the row through the same button that
swaps the refusal back in), and navigates from the detail page to the
No-Readers list (HX-Redirect for htmx, a 303 for plain clients). A
removal that races a fresh Bookmark is a refusal, not a 500: the row
re-renders at its new count with the fact spelled out. The control
renders only at zero Reader count on both surfaces, gated by hx-confirm
with the brief's copy.
sulthan merged commit 4aaf1d4f91 into main 2026-08-22 12:10:41 +07:00
Sign in to join this conversation.