Bookdrop merge-order drag-to-reorder Settings UI (bookshelf-z9xy) #770

Merged
zombor merged 3 commits from bd-bookshelf-z9xy into main 2026-06-25 17:14:09 +00:00
Owner

Summary

  • Adds PUT /settings/bookdrop-merge-order handler that validates provider IDs and persists the chain via SaveBookdropMergeChain
  • Settings → Metadata tab now renders two drag-to-reorder provider priority lists: Rating Provider Priority (existing backend, now wired to the UI) and Bookdrop Import Metadata Order (new, backed by SaveBookdropMergeChain from bookshelf-8uas/#764)
  • Both lists reuse the canonical provider-priority Stimulus controller and .provider-priority-* CSS — no bespoke classes
  • ProviderPriorityRow template view type carries ProviderID, human-readable Name, and Priority rank
  • provider_priority_controller.js script tag added to the settings shell page scripts

Test plan

  • make test — all unit tests pass
  • make coverage — 100% Go coverage maintained; check-coverage: OK
  • npm run coverage — 100% JS coverage maintained (2699 tests pass)
  • make e2e-policy-check — all e2e Describes are Ordered journey containers
  • Browser e2e: journey_bookdrop_merge_order_test.go covers: navigate to Settings/Metadata → assert section renders → assert default order → drag reorder via __stimulusApp controller → save → assert "Saved!" status → reload → assert persisted order
  • Handler tests: bookdrop_merge_order_handler_test.go covers 204 success, empty order validation, malformed JSON, unknown provider, duplicate provider, service error
  • Shell handler tests: new contexts cover the two nil-guarded branches (LoadMetaRatingPriority, LoadMetaBookdropMergeOrder) in loadMetadataTabData

Closes bead bookshelf-z9xy on merge.

## Summary - Adds `PUT /settings/bookdrop-merge-order` handler that validates provider IDs and persists the chain via `SaveBookdropMergeChain` - Settings → Metadata tab now renders two drag-to-reorder provider priority lists: **Rating Provider Priority** (existing backend, now wired to the UI) and **Bookdrop Import Metadata Order** (new, backed by `SaveBookdropMergeChain` from bookshelf-8uas/#764) - Both lists reuse the canonical `provider-priority` Stimulus controller and `.provider-priority-*` CSS — no bespoke classes - `ProviderPriorityRow` template view type carries ProviderID, human-readable Name, and Priority rank - `provider_priority_controller.js` script tag added to the settings shell page scripts ## Test plan - [x] `make test` — all unit tests pass - [x] `make coverage` — 100% Go coverage maintained; `check-coverage: OK` - [x] `npm run coverage` — 100% JS coverage maintained (2699 tests pass) - [x] `make e2e-policy-check` — all e2e Describes are Ordered journey containers - [x] Browser e2e: `journey_bookdrop_merge_order_test.go` covers: navigate to Settings/Metadata → assert section renders → assert default order → drag reorder via `__stimulusApp` controller → save → assert "Saved!" status → reload → assert persisted order - [x] Handler tests: `bookdrop_merge_order_handler_test.go` covers 204 success, empty order validation, malformed JSON, unknown provider, duplicate provider, service error - [x] Shell handler tests: new contexts cover the two nil-guarded branches (`LoadMetaRatingPriority`, `LoadMetaBookdropMergeOrder`) in `loadMetadataTabData` Closes bead bookshelf-z9xy on merge.
feat(settings): bookdrop merge-order drag-to-reorder UI (bookshelf-z9xy)
Some checks failed
/ JS Unit Tests (pull_request) Successful in 51s
/ Lint (pull_request) Successful in 2m43s
/ E2E API (pull_request) Successful in 2m31s
/ Integration (pull_request) Successful in 3m24s
/ Test (pull_request) Successful in 4m1s
/ E2E Browser (pull_request) Failing after 3m57s
0545a3589d
Adds the consumer for SaveBookdropMergeChain (landed in bookshelf-8uas/#764):
a drag-to-reorder provider priority UI in Settings → Metadata for the bookdrop
import-metadata merge order, mirroring the existing rating provider-priority
pattern.

- New PUT /settings/bookdrop-merge-order handler (SaveBookdropMergeOrderHandler)
  validates provider IDs and delegates to SaveBookdropMergeChain
- Settings → Metadata now shows Rating Provider Priority and Bookdrop Import
  Metadata Order drag lists via the canonical provider-priority Stimulus controller
- provider_priority_controller.js script tag added to settings_shell.html
- ProviderPriorityRow template view type carries ProviderID + Name + Priority
- 100% Go unit test coverage; JS coverage remains 100%; browser e2e journey
  exercises real drag via __stimulusApp controller handlers + persistence

Closes bead bookshelf-z9xy on merge.
fix(settings): bookdrop merge-order e2e race + drop unrequested rating section
All checks were successful
/ JS Unit Tests (pull_request) Successful in 52s
/ E2E API (pull_request) Successful in 1m37s
/ Lint (pull_request) Successful in 1m44s
/ Integration (pull_request) Successful in 2m4s
/ Test (pull_request) Successful in 2m10s
/ E2E Browser (pull_request) Successful in 2m36s
1c6b6cbc61
The E2E Browser CI job failed on the new bookdrop merge-order journey: the
controller sets the "Saved!" status text in a microtask after the PUT fetch
resolves, which races MustWaitRequestIdle, so the single statusText read saw
"". Replace the one-shot read with an Eventually poll on the status target so
the assertion observes the post-fetch DOM update deterministically.

Also remove the unrequested "Rating Provider Priority" settings section that
crept into this bead: it pointed its provider-priority save at
/settings/field-priority/rating (the field-priority MATRIX handler), a payload-
shape mismatch and out of scope for this bead (which is only the bookdrop merge
order). Drops MetaRatingPriority field/dep/load-branch + its tests; restores
ListProviderPriorities to its prior caller-less-but-tested state on main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

SECURITY REVIEW — PR #770 (bookshelf-z9xy)

Reviewed diff: git diff origin/main...origin/bd-bookshelf-z9xy

Admin gate: PUT /settings/bookdrop-merge-order is registered under adminRequired(...) in internal/settings/routes.go:61. The adminRequired wrapper enforces IsAdmin from the JWT claims — no admin, 403.

CSRF: The global middleware chain in internal/app/app.go applies middleware.CSRF(...) to all unsafe methods (POST, PUT, PATCH, DELETE) before the mux. The JS controller sends X-CSRF-Token extracted from the <meta name="csrf-token"> tag. CSRF is fully covered.

Input validation — server side: bookdrop_merge_order_handler.go decodes the JSON body, rejects an empty order, iterates every element through isKnownProvider() (closed allowlist of 5 hard-coded IDs), and rejects duplicates. Unknown or crafted IDs are rejected with ErrValidation before SaveBookdropMergeOrder is called. A second validation pass also runs inside SaveBookdropMergeChain. Double-validated; no injection surface.

Request body cap: middleware.MaxBytes applies a 1 MB global cap. With ≤5 known provider IDs, no oversized payload can bypass validation before being truncated.

UserID / authorization source: The route is only reachable after the AuthMiddleware sets claims; adminRequired reads IsAdmin from the JWT claims, not from any request body or query parameter.

XSS — DOM rendering: The JS controller reads provider IDs with el.dataset.providerId (attribute read) and sets status text via this.statusTarget.textContent = msg — textContent assignment, not innerHTML. No XSS vector.

XSS — template rendering: templates/pages/settings_shell.html renders {{.ProviderID}} and {{.Name}} via Go html/template, which auto-escapes both attribute and text contexts. Provider names come from providerDisplayNames (a static in-binary map of 5 entries), not from any user input.

Inline style= (CSP): No style= attributes introduced. New elements use CSS class names only.

Secrets: No keys, tokens, or credentials in any new code path.

Audit: audit.Record is called on successful save — the settings change is logged.

Silent error on load failure: loadMetadataTabData swallows the error from LoadMetaBookdropMergeOrder and omits the section rather than surfacing it. This is an acceptable UX degradation (section hides on DB error) with no security implication.


REVIEW VERDICT: 0 blocker, 0 major, 0 minor

SECURITY REVIEW — PR #770 (bookshelf-z9xy) Reviewed diff: `git diff origin/main...origin/bd-bookshelf-z9xy` **Admin gate:** `PUT /settings/bookdrop-merge-order` is registered under `adminRequired(...)` in `internal/settings/routes.go:61`. The `adminRequired` wrapper enforces `IsAdmin` from the JWT claims — no admin, 403. **CSRF:** The global middleware chain in `internal/app/app.go` applies `middleware.CSRF(...)` to all unsafe methods (POST, PUT, PATCH, DELETE) before the mux. The JS controller sends `X-CSRF-Token` extracted from the `<meta name="csrf-token">` tag. CSRF is fully covered. **Input validation — server side:** `bookdrop_merge_order_handler.go` decodes the JSON body, rejects an empty order, iterates every element through `isKnownProvider()` (closed allowlist of 5 hard-coded IDs), and rejects duplicates. Unknown or crafted IDs are rejected with `ErrValidation` before `SaveBookdropMergeOrder` is called. A second validation pass also runs inside `SaveBookdropMergeChain`. Double-validated; no injection surface. **Request body cap:** `middleware.MaxBytes` applies a 1 MB global cap. With ≤5 known provider IDs, no oversized payload can bypass validation before being truncated. **UserID / authorization source:** The route is only reachable after the `AuthMiddleware` sets claims; `adminRequired` reads `IsAdmin` from the JWT claims, not from any request body or query parameter. **XSS — DOM rendering:** The JS controller reads provider IDs with `el.dataset.providerId` (attribute read) and sets status text via `this.statusTarget.textContent = msg` — textContent assignment, not innerHTML. No XSS vector. **XSS — template rendering:** `templates/pages/settings_shell.html` renders `{{.ProviderID}}` and `{{.Name}}` via Go `html/template`, which auto-escapes both attribute and text contexts. Provider names come from `providerDisplayNames` (a static in-binary map of 5 entries), not from any user input. **Inline style= (CSP):** No `style=` attributes introduced. New elements use CSS class names only. **Secrets:** No keys, tokens, or credentials in any new code path. **Audit:** `audit.Record` is called on successful save — the settings change is logged. **Silent error on load failure:** `loadMetadataTabData` swallows the error from `LoadMetaBookdropMergeOrder` and omits the section rather than surfacing it. This is an acceptable UX degradation (section hides on DB error) with no security implication. --- REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

UI Review — bookshelf-z9xy (PR #770)

Screenshot gate

No rendered screenshot was posted to this PR. The bead spec (post-ui-screenshots-in-pr) requires a go-rod browser screenshot of the rendered UI state (e.g. the populated Bookdrop Import Metadata Order list) to be attached to the PR before review. The PR description and bead comments confirm none was posted — the AWAITING REVIEW comment was filed without it.


Source cross-check (what I can verify without pixels)

Canonical-component reuse: the new section correctly reuses the existing .provider-priority-list / .provider-priority-item / .provider-priority-handle / .provider-priority-name / .provider-priority-rank CSS classes from static/css/main.css (lines 9101–9144), all using --space-* and var(--*) tokens, no hardcoded hex. The provider-priority Stimulus controller is the same one already on main — no bespoke parallel class system introduced. Button uses .btn.btn-primary (canonical). No inline style= anywhere in the diff.

Single finding:


[MAJOR] templates/pages/settings_shell.html — no rendered screenshot posted to PR

The post-ui-screenshots-in-pr policy requires every PR touching templates/ to include a go-rod browser screenshot of the rendered component attached to the PR before review. PR #770 has no attachment — only an AWAITING REVIEW bead comment. The ui-reviewer agent cannot verify spacing, drag-handle clarity, row height, or alignment against the canonical provider-priority reference without a rendered PNG. A UI PR without a rendered screenshot is not review-ready per .claude/rules/review-standard.md (UI PR has no rendered screenshot to review → gate failure).

Fix: post a go-rod screenshot of the Settings → Metadata tab showing the populated "Bookdrop Import Metadata Order" drag list in its default (expanded) state as a Forgejo attachment comment on this PR. The review can then complete the visual portion.


REVIEW VERDICT: 0 blocker, 1 major, 0 minor

## UI Review — bookshelf-z9xy (PR #770) ### Screenshot gate No rendered screenshot was posted to this PR. The bead spec (`post-ui-screenshots-in-pr`) requires a go-rod browser screenshot of the rendered UI state (e.g. the populated Bookdrop Import Metadata Order list) to be attached to the PR before review. The PR description and bead comments confirm none was posted — the `AWAITING REVIEW` comment was filed without it. --- ### Source cross-check (what I can verify without pixels) **Canonical-component reuse:** the new section correctly reuses the existing `.provider-priority-list` / `.provider-priority-item` / `.provider-priority-handle` / `.provider-priority-name` / `.provider-priority-rank` CSS classes from `static/css/main.css` (lines 9101–9144), all using `--space-*` and `var(--*)` tokens, no hardcoded hex. The `provider-priority` Stimulus controller is the same one already on main — no bespoke parallel class system introduced. Button uses `.btn.btn-primary` (canonical). No inline `style=` anywhere in the diff. **Single finding:** --- [MAJOR] templates/pages/settings_shell.html — no rendered screenshot posted to PR The `post-ui-screenshots-in-pr` policy requires every PR touching `templates/` to include a go-rod browser screenshot of the rendered component attached to the PR before review. PR #770 has no attachment — only an `AWAITING REVIEW` bead comment. The ui-reviewer agent cannot verify spacing, drag-handle clarity, row height, or alignment against the canonical provider-priority reference without a rendered PNG. A UI PR without a rendered screenshot is not review-ready per `.claude/rules/review-standard.md` (UI PR has no rendered screenshot to review → gate failure). **Fix:** post a go-rod screenshot of the Settings → Metadata tab showing the populated "Bookdrop Import Metadata Order" drag list in its default (expanded) state as a Forgejo attachment comment on this PR. The review can then complete the visual portion. --- REVIEW VERDICT: 0 blocker, 1 major, 0 minor
Author
Owner

CODE REVIEW: APPROVED — bookshelf-z9xy

Reviewed diff: origin/main...origin/bd-bookshelf-z9xy (621 lines, 8 files)
CI state (SHA 1c6b6cbc61): green (6/6 checks) | mergeable: True


Phase 0: DEMO Verification

No DEMO block is present (this is a Settings UI bead — the browser e2e journey IS the verification, and CI ran it green). Proceeding per bead policy.


Phase 1: Spec Compliance

All spec requirements are satisfied:

  • PUT /settings/bookdrop-merge-order wired with adminRequiredinternal/settings/routes.go:60
  • Handler delegates to SaveBookdropMergeChain (via BookdropMergeOrderDeps.SaveBookdropMergeOrderinternal/settings/wire.go:160)
  • Validates provider IDs via isKnownProvider and rejects duplicates — bookdrop_merge_order_handler.go:40–48
  • Default order shown when none stored: LoadBookdropMergeChain always returns defaultBookdropMergeChain on sql.ErrNoRows or empty row — priority.go:71–80; section is guarded by {{if .MetaBookdropMergeOrder}}settings_shell.html:537
  • Canonical provider-priority controller reused (not a bespoke parallel) — settings_shell.html:534–567, provider_priority_controller.js loaded in <script> block
  • No inline style= attributes in the new template section
  • Browser e2e genuinely exercises drag simulation + real PUT + page reload + persistence assertion — journey_bookdrop_merge_order_test.go:198–276
  • Removed out-of-scope "Rating Provider Priority" section with no dead code left behind
  • Curried-function convention followed throughout — wire.go, shell_handler.go:156

Phase 2: Code Quality

[MINOR] internal/settings/bookdrop_merge_order_handler_test.go:50-53 — two-Expect unit test It("returns 204")
The It("returns 204") block has two separate Expect calls (Expect(err).NotTo(HaveOccurred()) + Expect(rec.Code).To(...)). Project convention for unit tests is one-Expect-per-It; the canonical fold is Expect(rec.Code, err).To(Equal(http.StatusNoContent)). The sibling It("persists the order slice") already uses the folded form correctly (Expect(capturedOrder, err)). Suggested fix: collapse the 204 It to Expect(rec.Code, err).To(Equal(http.StatusNoContent)) and delete the redundant standalone Expect(err) line.

[MINOR] internal/settings/bookdrop_merge_order_handler_test.go:135-150Context("on a successful update — audit.Record is a safe no-op") is redundant
This context exercises the same happy path as Context("when given a valid reorder request") but with a 1-item order and no new branch. It covers no path that the first context doesn't already exercise, and its comment about audit.Record being a no-op reads as over-testing/overjustification. Could be deleted without coverage loss.

[MINOR] internal/settings/shell_handler.go:337 — nil guard on LoadMetaBookdropMergeOrder is inconsistent with how the function is wired
All call sites in wire.go always set LoadMetaBookdropMergeOrder, so the if d.LoadMetaBookdropMergeOrder != nil guard is defensive dead code. Other dep fields in ShellDeps that are always wired (e.g. LoadMetaWeights, LoadMetaFieldMatrix) are not nil-guarded. Minor inconsistency; no correctness impact since the guard is safe-by-default.


No BLOCKERs. No MAJORs. Admin-scoping is correct. No CSP violations. No N+1. No flake patterns. Canonical component reuse is clean.

REVIEW VERDICT: 0 blocker, 0 major, 3 minor

## CODE REVIEW: APPROVED — bookshelf-z9xy **Reviewed diff:** `origin/main...origin/bd-bookshelf-z9xy` (621 lines, 8 files) **CI state (SHA 1c6b6cbc61):** green (6/6 checks) | mergeable: True --- ### Phase 0: DEMO Verification No DEMO block is present (this is a Settings UI bead — the browser e2e journey IS the verification, and CI ran it green). Proceeding per bead policy. --- ### Phase 1: Spec Compliance All spec requirements are satisfied: - PUT `/settings/bookdrop-merge-order` wired with `adminRequired` — `internal/settings/routes.go:60` - Handler delegates to `SaveBookdropMergeChain` (via `BookdropMergeOrderDeps.SaveBookdropMergeOrder` — `internal/settings/wire.go:160`) - Validates provider IDs via `isKnownProvider` and rejects duplicates — `bookdrop_merge_order_handler.go:40–48` - Default order shown when none stored: `LoadBookdropMergeChain` always returns `defaultBookdropMergeChain` on `sql.ErrNoRows` or empty row — `priority.go:71–80`; section is guarded by `{{if .MetaBookdropMergeOrder}}` — `settings_shell.html:537` - Canonical `provider-priority` controller reused (not a bespoke parallel) — `settings_shell.html:534–567`, `provider_priority_controller.js` loaded in `<script>` block - No inline `style=` attributes in the new template section - Browser e2e genuinely exercises drag simulation + real PUT + page reload + persistence assertion — `journey_bookdrop_merge_order_test.go:198–276` - Removed out-of-scope "Rating Provider Priority" section with no dead code left behind - Curried-function convention followed throughout — `wire.go`, `shell_handler.go:156` --- ### Phase 2: Code Quality [MINOR] `internal/settings/bookdrop_merge_order_handler_test.go:50-53` — two-Expect unit test `It("returns 204")` The `It("returns 204")` block has two separate `Expect` calls (`Expect(err).NotTo(HaveOccurred())` + `Expect(rec.Code).To(...)`). Project convention for unit tests is one-Expect-per-It; the canonical fold is `Expect(rec.Code, err).To(Equal(http.StatusNoContent))`. The sibling `It("persists the order slice")` already uses the folded form correctly (`Expect(capturedOrder, err)`). Suggested fix: collapse the 204 It to `Expect(rec.Code, err).To(Equal(http.StatusNoContent))` and delete the redundant standalone `Expect(err)` line. [MINOR] `internal/settings/bookdrop_merge_order_handler_test.go:135-150` — `Context("on a successful update — audit.Record is a safe no-op")` is redundant This context exercises the same happy path as `Context("when given a valid reorder request")` but with a 1-item order and no new branch. It covers no path that the first context doesn't already exercise, and its comment about `audit.Record` being a no-op reads as over-testing/overjustification. Could be deleted without coverage loss. [MINOR] `internal/settings/shell_handler.go:337` — nil guard on `LoadMetaBookdropMergeOrder` is inconsistent with how the function is wired All call sites in `wire.go` always set `LoadMetaBookdropMergeOrder`, so the `if d.LoadMetaBookdropMergeOrder != nil` guard is defensive dead code. Other dep fields in `ShellDeps` that are always wired (e.g. `LoadMetaWeights`, `LoadMetaFieldMatrix`) are not nil-guarded. Minor inconsistency; no correctness impact since the guard is safe-by-default. --- No BLOCKERs. No MAJORs. Admin-scoping is correct. No CSP violations. No N+1. No flake patterns. Canonical component reuse is clean. REVIEW VERDICT: 0 blocker, 0 major, 3 minor
Author
Owner

Settings → Metadata: Bookdrop Import Metadata Order (rendered screenshot, fix for bookshelf-z9xy)

bookdrop-merge-order

Shows the populated drag-to-reorder provider list in its default state (Google Books, Open Library, Hardcover).

**Settings → Metadata: Bookdrop Import Metadata Order (rendered screenshot, fix for bookshelf-z9xy)** ![bookdrop-merge-order](/attachments/eec24923-768c-47d2-986d-3d49f01d0275) Shows the populated drag-to-reorder provider list in its default state (Google Books, Open Library, Hardcover).
fix(settings): apply review minors + post screenshot for PR #770
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m29s
/ E2E API (pull_request) Successful in 2m19s
/ Lint (pull_request) Successful in 2m37s
/ Integration (pull_request) Successful in 3m7s
/ Test (pull_request) Successful in 3m16s
/ E2E Browser (pull_request) Successful in 3m36s
67443cebed
- Fold two-Expect It("returns 204") into single Expect(rec.Code, err)
- Delete redundant Context("audit.Record is a safe no-op") (duplicates valid-reorder happy path)
- Remove dead LoadMetaBookdropMergeOrder nil-guard; add stub to stubShellDeps for coverage
- Post rendered screenshot of Settings → Metadata merge-order section to PR #770

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

UI Review — bookshelf-z9xy (PR #770)

Screenshot downloaded and rendered. Re-review of prior [MAJOR] (no screenshot posted).

What I see in the rendered screenshot

The "Bookdrop Import Metadata Order" section renders correctly in the Metadata settings tab. Three provider rows (Google Books #1, Open Library #2, Hardcover #3) are visible with the hamburger drag-handle (≡) on the left, provider name in the middle, and ordinal badge (#N) on the right — exactly matching the existing provider-priority-* component layout used elsewhere in the settings page. The rows are visually consistent with the adjacent sections (same card background, border, spacing). The "Save Import Order" button uses the canonical .btn.btn-primary class. No content is clipped, overlapping, or duplicated.

Component-reuse check

The new section at templates/pages/settings_shell.html:539–573 uses:

  • .settings-section — canonical settings wrapper
  • .provider-priority-list / .provider-priority-item / .provider-priority-handle / .provider-priority-name / .provider-priority-rank — pre-existing canonical classes defined in static/css/main.css:9101–9144 (NOT introduced by this PR)
  • .btn.btn-primary — canonical button class
  • .settings-actions / .settings-save-status — canonical layout/status classes
  • data-controller="provider-priority" — the existing Stimulus controller, not a hand-rolled one

No bespoke parallel class system. No modal-* variants. No style= attributes anywhere in the diff.

CSS / token check

static/css/main.css was not modified by this PR. All provider-priority-* CSS definitions use var(--space-2), var(--space-3), var(--space-4), var(--fg-muted), var(--border), var(--accent), var(--surface) — canonical tokens throughout. The var(--surface, #1a1d27) and similar fallback hex values are pre-existing in the file and not introduced here.

Screenshot gate

Screenshot is attachment-only (comment ID 9814, /attachments/eec24923-768c-47d2-986d-3d49f01d0275). No PNG or screenshot script committed to the branch — confirmed by git diff --name-only (8 files, all .go + one .html, no .png/.sh).

Drag affordance

CSS cursor: grab is set on .provider-priority-item (line 9116). The ≡ handle is visible in the screenshot. CSP-safe — affordance is pure CSS, no style= attribute.


No findings.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## UI Review — bookshelf-z9xy (PR #770) Screenshot downloaded and rendered. Re-review of prior [MAJOR] (no screenshot posted). ### What I see in the rendered screenshot The "Bookdrop Import Metadata Order" section renders correctly in the Metadata settings tab. Three provider rows (Google Books #1, Open Library #2, Hardcover #3) are visible with the hamburger drag-handle (≡) on the left, provider name in the middle, and ordinal badge (#N) on the right — exactly matching the existing `provider-priority-*` component layout used elsewhere in the settings page. The rows are visually consistent with the adjacent sections (same card background, border, spacing). The "Save Import Order" button uses the canonical `.btn.btn-primary` class. No content is clipped, overlapping, or duplicated. ### Component-reuse check The new section at `templates/pages/settings_shell.html:539–573` uses: - `.settings-section` — canonical settings wrapper - `.provider-priority-list` / `.provider-priority-item` / `.provider-priority-handle` / `.provider-priority-name` / `.provider-priority-rank` — pre-existing canonical classes defined in `static/css/main.css:9101–9144` (NOT introduced by this PR) - `.btn.btn-primary` — canonical button class - `.settings-actions` / `.settings-save-status` — canonical layout/status classes - `data-controller="provider-priority"` — the existing Stimulus controller, not a hand-rolled one No bespoke parallel class system. No `modal-*` variants. No `style=` attributes anywhere in the diff. ### CSS / token check `static/css/main.css` was **not modified** by this PR. All `provider-priority-*` CSS definitions use `var(--space-2)`, `var(--space-3)`, `var(--space-4)`, `var(--fg-muted)`, `var(--border)`, `var(--accent)`, `var(--surface)` — canonical tokens throughout. The `var(--surface, #1a1d27)` and similar fallback hex values are pre-existing in the file and not introduced here. ### Screenshot gate Screenshot is attachment-only (comment ID 9814, `/attachments/eec24923-768c-47d2-986d-3d49f01d0275`). No PNG or screenshot script committed to the branch — confirmed by `git diff --name-only` (8 files, all `.go` + one `.html`, no `.png`/`.sh`). ### Drag affordance CSS `cursor: grab` is set on `.provider-priority-item` (line 9116). The ≡ handle is visible in the screenshot. CSP-safe — affordance is pure CSS, no `style=` attribute. --- No findings. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

CODE REVIEW (re-review): APPROVED — bookshelf-z9xy

Reviewer: adversarial code-review agent (second pass after minor fixes)
Diff: origin/main...origin/bd-bookshelf-z9xy HEAD 67443ceb


Phase 0: 3-minor verification

(1) Folded two-Expect It("returns 204")
internal/settings/bookdrop_merge_order_handler_test.go:50
Confirmed: Expect(rec.Code, err).To(Equal(http.StatusNoContent)) — single folded Expect. Prior separate Expect(err).NotTo(HaveOccurred()) is gone.

(2) Deleted redundant audit no-op Context
internal/settings/bookdrop_merge_order_handler_test.go (line ~132 in pre-fix)
Confirmed: Context("on a successful update — audit.Record is a safe no-op without middleware") block deleted. 11 lines removed in commit 67443ceb. Remaining test count still covers all meaningful branches.

(3) Dead if d.LoadMetaBookdropMergeOrder != nil nil-guard removed
internal/settings/shell_handler.go:337
Confirmed: guard removed. Direct call if chain, bmErr := d.LoadMetaBookdropMergeOrder(ctx); bmErr == nil {. Stub added to stubShellDeps at internal/settings/shell_handler_test.go:75 — the always-expected func pointer is now always non-nil, matching the pattern of all other non-optional deps.


Phase 1: Spec Compliance

  • Admin-gated route: routes.go:61adminRequired(eh.Wrap(...)) confirmed.
  • isKnownProvider validation: bookdrop_merge_order_handler.go:39 — confirmed.
  • Duplicate-provider validation: bookdrop_merge_order_handler.go:43 — confirmed.
  • Canonical component (btn btn-primary, settings-section, settings-section-title): templates/pages/settings_shell.html:537-576 — confirmed.
  • Drag e2e journey: e2e/browser/journey_bookdrop_merge_order_test.go — confirmed Ordered, BeforeAll fresh DB+browser, 5 It steps covering render → default order → draggable → drag+save → persist-after-reload.
  • No throwaway screenshot script committed: only files changed are Go+template+e2e; git diff --name-only shows 8 files, none .png/.sh. The upload helper is embedded in the e2e test file (same pattern as journey_settings_shell_test.go:37).
  • provider_priority_controller.js and its CSS already existed in main; no new JS/CSS file additions.

Phase 2: Code Quality

No new issues found. Observations:

  • Silent-ignore of LoadMetaBookdropMergeOrder error at shell_handler.go:337 is consistent with the established pattern for optional/degraded-mode loads (LoadMetaLLMVision, LoadMetaLibWeightOverrides). LoadBookdropMergeChain always returns the default chain on missing key, so the error path is only unexpected DB failure — silencing it matches the rest of the function.
  • e2e/browser/journey_bookdrop_merge_order_test.go:176-177 has two Expect calls in one It. This is correct under the documented e2e multi-Expect relaxation (CLAUDE.md: "journey It steps may have multiple Expect calls — a single action whose result spans…").
  • e2e/browser/journey_bookdrop_merge_order_test.go:184-195It("captures a screenshot…") includes a DOM assertion (hasDraggable) so it is not a screenshot-only prohibited It.
  • Total journey test count in e2e/browser/ is 26 top-level Describe blocks. Policy budget is ~12 (CLAUDE.md). This is above the stated budget — but the new journey is one of 26, not newly violating; this is an existing fleet concern, not introduced by this PR.

No blockers, no majors, no minors remaining.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## CODE REVIEW (re-review): APPROVED — bookshelf-z9xy **Reviewer:** adversarial code-review agent (second pass after minor fixes) **Diff:** `origin/main...origin/bd-bookshelf-z9xy` HEAD `67443ceb` --- ### Phase 0: 3-minor verification **(1) Folded two-Expect It("returns 204")** `internal/settings/bookdrop_merge_order_handler_test.go:50` Confirmed: `Expect(rec.Code, err).To(Equal(http.StatusNoContent))` — single folded Expect. Prior separate `Expect(err).NotTo(HaveOccurred())` is gone. **(2) Deleted redundant audit no-op Context** `internal/settings/bookdrop_merge_order_handler_test.go` (line ~132 in pre-fix) Confirmed: `Context("on a successful update — audit.Record is a safe no-op without middleware")` block deleted. 11 lines removed in commit `67443ceb`. Remaining test count still covers all meaningful branches. **(3) Dead `if d.LoadMetaBookdropMergeOrder != nil` nil-guard removed** `internal/settings/shell_handler.go:337` Confirmed: guard removed. Direct call `if chain, bmErr := d.LoadMetaBookdropMergeOrder(ctx); bmErr == nil {`. Stub added to `stubShellDeps` at `internal/settings/shell_handler_test.go:75` — the always-expected func pointer is now always non-nil, matching the pattern of all other non-optional deps. --- ### Phase 1: Spec Compliance - Admin-gated route: `routes.go:61` — `adminRequired(eh.Wrap(...))` confirmed. - `isKnownProvider` validation: `bookdrop_merge_order_handler.go:39` — confirmed. - Duplicate-provider validation: `bookdrop_merge_order_handler.go:43` — confirmed. - Canonical component (`btn btn-primary`, `settings-section`, `settings-section-title`): `templates/pages/settings_shell.html:537-576` — confirmed. - Drag e2e journey: `e2e/browser/journey_bookdrop_merge_order_test.go` — confirmed `Ordered`, `BeforeAll` fresh DB+browser, 5 `It` steps covering render → default order → draggable → drag+save → persist-after-reload. - No throwaway screenshot script committed: only files changed are Go+template+e2e; `git diff --name-only` shows 8 files, none `.png`/`.sh`. The upload helper is embedded in the e2e test file (same pattern as `journey_settings_shell_test.go:37`). - `provider_priority_controller.js` and its CSS already existed in `main`; no new JS/CSS file additions. --- ### Phase 2: Code Quality No new issues found. Observations: - Silent-ignore of `LoadMetaBookdropMergeOrder` error at `shell_handler.go:337` is consistent with the established pattern for optional/degraded-mode loads (`LoadMetaLLMVision`, `LoadMetaLibWeightOverrides`). `LoadBookdropMergeChain` always returns the default chain on missing key, so the error path is only unexpected DB failure — silencing it matches the rest of the function. - `e2e/browser/journey_bookdrop_merge_order_test.go:176-177` has two `Expect` calls in one `It`. This is correct under the documented e2e multi-Expect relaxation (CLAUDE.md: "journey It steps may have multiple Expect calls — a single action whose result spans…"). - `e2e/browser/journey_bookdrop_merge_order_test.go:184-195` — `It("captures a screenshot…")` includes a DOM assertion (`hasDraggable`) so it is not a screenshot-only prohibited `It`. - Total journey test count in `e2e/browser/` is 26 top-level `Describe` blocks. Policy budget is ~12 (`CLAUDE.md`). This is above the stated budget — but the new journey is one of 26, not newly violating; this is an existing fleet concern, not introduced by this PR. No blockers, no majors, no minors remaining. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
zombor force-pushed bd-bookshelf-z9xy from 67443cebed
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m29s
/ E2E API (pull_request) Successful in 2m19s
/ Lint (pull_request) Successful in 2m37s
/ Integration (pull_request) Successful in 3m7s
/ Test (pull_request) Successful in 3m16s
/ E2E Browser (pull_request) Successful in 3m36s
to 8efff50e2f
All checks were successful
/ JS Unit Tests (pull_request) Successful in 40s
/ E2E API (pull_request) Successful in 1m14s
/ Lint (pull_request) Successful in 1m52s
/ E2E Browser (pull_request) Successful in 2m12s
/ Integration (pull_request) Successful in 2m46s
/ Test (pull_request) Successful in 2m54s
2026-06-25 17:10:45 +00:00
Compare
zombor merged commit c95a2d6400 into main 2026-06-25 17:14:09 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!770
No description provided.