refactor(series): unify series-gaps toolbar with shared books_select_toolbar (bookshelf-v1ap) #1197

Merged
zombor merged 1 commit from bd-bookshelf-v1ap into main 2026-07-22 01:15:50 +00:00
Owner

Summary

  • Replaces the bespoke inline emoji-button bulk toolbar in templates/pages/series_show.html (the series-gaps mixed-grid branch) with {{template "books_select_toolbar" ...}} — the same canonical SVG-icon partial introduced by bookshelf-rzlf and already used by the grid and table views.
  • Adds books_select_filter_controller.js to the page_scripts block in series_show.html (required by the canonical toolbar's books-select-filter Stimulus controller).
  • All three views (grid, table, series-gaps) now share one identical toolbar.

Test plan

  • make build — template renders without parse errors
  • make test — all unit tests pass
  • go build -tags e2e ./e2e/browser/... — e2e compiles
  • Journey 5 It 8 (series-gaps page: checking a book reveals the canonical SVG-icon toolbar): seeds a series with missing entries, checks the owned book, asserts .books-select-bar becomes visible and .bst-trigger[aria-label="Metadata actions"] is present, posts PR screenshot
  • Screenshot of series-gaps page with unified toolbar visible posted to PR (via CI e2e run)

Closes bead bookshelf-v1ap on merge.

## Summary - Replaces the bespoke inline emoji-button bulk toolbar in `templates/pages/series_show.html` (the series-gaps mixed-grid branch) with `{{template "books_select_toolbar" ...}}` — the same canonical SVG-icon partial introduced by bookshelf-rzlf and already used by the grid and table views. - Adds `books_select_filter_controller.js` to the `page_scripts` block in `series_show.html` (required by the canonical toolbar's `books-select-filter` Stimulus controller). - All three views (grid, table, series-gaps) now share one identical toolbar. ## Test plan - [x] `make build` — template renders without parse errors - [x] `make test` — all unit tests pass - [x] `go build -tags e2e ./e2e/browser/...` — e2e compiles - [x] Journey 5 It 8 (`series-gaps page: checking a book reveals the canonical SVG-icon toolbar`): seeds a series with missing entries, checks the owned book, asserts `.books-select-bar` becomes visible and `.bst-trigger[aria-label="Metadata actions"]` is present, posts PR screenshot - [x] Screenshot of series-gaps page with unified toolbar visible posted to PR (via CI e2e run) Closes bead bookshelf-v1ap on merge.
refactor(series): unify series-gaps toolbar with shared books_select_toolbar partial (bookshelf-v1ap)
All checks were successful
/ E2E API (pull_request) Successful in 2m13s
/ Integration (pull_request) Successful in 4m9s
/ E2E Browser (pull_request) Successful in 4m7s
/ Test Race (pull_request) Successful in 6m28s
/ Coverage (pull_request) Successful in 7m16s
/ Lint (pull_request) Successful in 8m25s
/ JS Unit Tests (pull_request) Successful in 16m30s
e3f76a2742
Replace the bespoke inline emoji-button bulk toolbar in the series_show.html
series-gaps branch with {{template "books_select_toolbar" ...}}, the same
canonical SVG-icon partial used by the grid and table views since bookshelf-rzlf.

Also adds books_select_filter_controller.js to the page_scripts block (required
by the canonical toolbar's books-select-filter controller).

Adds It 8 to Journey 5 (Bulk Operations) e2e: navigates to a series page with
missing entries, checks a book, and asserts the canonical SVG-icon toolbar
appears (proving the unified partial wires up correctly in the series-gaps DOM).

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

Security Review — PR #1197 (series-gaps canonical bulk toolbar)

Scope: presentation-only refactor swapping the bespoke inline emoji toolbar in templates/pages/series_show.html (MissingEntries branch) for the canonical books_select_toolbar partial, plus a browser e2e step.

Findings:

No security findings.

Verification notes:

  • XSS / template-injection: The books_select_toolbar partial emits only static SVG markup, fixed /books/bulk/* action URLs, and numeric/{{with .Filter}} data-attributes. No template.HTML / safeHTML / | safe introduced. User-controlled fields (book titles, series names) continue to render through standard auto-escaped {{.Title}} pipelines already on the page — unchanged. html/template auto-escaping intact.
  • Authorization / new surface: The swap changes no endpoints — same /books/bulk/{delete,locks,attach,move,enrich,custom-fetch,metadata,generate-covers,llm-vision} URLs the removed bespoke bar already wired. No new privileged action is exposed: the partial's all-matching "Select all books" button renders only {{if .HasMoreBooks}}, and HasMoreBooks is not passed in the series_show dict (nil .Filter -> filter data-values resolve to 0/empty), so the books-select-filter all-matching mode is inert here. Bulk endpoints enforce user scoping server-side (unchanged by this PR). Presentation only.
  • CSP: No inline style= and no inline onclick= added — all handlers use Stimulus data-action="click->...". Compliant with production style-src 'self'.
  • Secrets/PII: none in the template or JS.
  • The partial is correctly mounted inside the required data-controller="books-select shelf-assign" wrapper.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — PR #1197 (series-gaps canonical bulk toolbar) Scope: presentation-only refactor swapping the bespoke inline emoji toolbar in `templates/pages/series_show.html` (MissingEntries branch) for the canonical `books_select_toolbar` partial, plus a browser e2e step. Findings: No security findings. Verification notes: - **XSS / template-injection:** The `books_select_toolbar` partial emits only static SVG markup, fixed `/books/bulk/*` action URLs, and numeric/`{{with .Filter}}` data-attributes. No `template.HTML` / `safeHTML` / `| safe` introduced. User-controlled fields (book titles, series names) continue to render through standard auto-escaped `{{.Title}}` pipelines already on the page — unchanged. html/template auto-escaping intact. - **Authorization / new surface:** The swap changes no endpoints — same `/books/bulk/{delete,locks,attach,move,enrich,custom-fetch,metadata,generate-covers,llm-vision}` URLs the removed bespoke bar already wired. No new privileged action is exposed: the partial's all-matching "Select all books" button renders only `{{if .HasMoreBooks}}`, and `HasMoreBooks` is not passed in the series_show dict (nil `.Filter` -> filter data-values resolve to 0/empty), so the `books-select-filter` all-matching mode is inert here. Bulk endpoints enforce user scoping server-side (unchanged by this PR). Presentation only. - **CSP:** No inline `style=` and no inline `onclick=` added — all handlers use Stimulus `data-action="click->..."`. Compliant with production `style-src 'self'`. - **Secrets/PII:** none in the template or JS. - The partial is correctly mounted inside the required `data-controller="books-select shelf-assign"` wrapper. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

[MINOR] static/css/main.css — dead CSS rule .books-select-bar-meta

The old emoji toolbar in series_show.html used this rule to set font-size: 1.1rem on the Metadata button. With the switch to the canonical SVG-icon toolbar (which uses different button classes like .bst-trigger), this rule is no longer referenced anywhere in the codebase (confirmed via git grep on the PR branch). No other templates use .books-select-bar-meta.

Suggested fix: remove the .books-select-bar-meta { font-size: 1.1rem; } rule from main.css as dead code.


Behavior & wiring verification:

  • ✓ Template invocation passes correct parameters (SidebarShelves, LLMVisionAvailable, EnabledLLMProviders)
  • ✓ Parent wrapper has correct data-controller="books-select shelf-assign" for toolbar to read selection state
  • ✓ books_select_filter_controller.js loaded in page_scripts and registered in app.js
  • ✓ Filter controller safely handles missing selectAllBtn target (HasMoreBooks not passed for series detail)
  • ✓ All toolbar actions preserved: Metadata submenu (Auto Fetch/Custom Fetch/Bulk Edit/Step-through/Regenerate/LLM Vision), Assign, Lock, Organize, Attach, More menu (Show selected), Select all/Clear, Delete — all with identical URLs as original toolbar
  • ✓ E2E test correctly verifies toolbar appears on book selection, gap cards render, canonical SVG-icon toolbar is present
  • ✓ CSP compliant: no inline style= attributes; toolbar visibility controlled via hidden attribute and classes
  • ✓ No regression: old bespoke toolbar fully removed, no duplicate controllers or dead HTML

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

[MINOR] static/css/main.css — dead CSS rule `.books-select-bar-meta` The old emoji toolbar in series_show.html used this rule to set `font-size: 1.1rem` on the Metadata button. With the switch to the canonical SVG-icon toolbar (which uses different button classes like `.bst-trigger`), this rule is no longer referenced anywhere in the codebase (confirmed via git grep on the PR branch). No other templates use `.books-select-bar-meta`. Suggested fix: remove the `.books-select-bar-meta { font-size: 1.1rem; }` rule from main.css as dead code. --- **Behavior & wiring verification:** - ✓ Template invocation passes correct parameters (SidebarShelves, LLMVisionAvailable, EnabledLLMProviders) - ✓ Parent wrapper has correct `data-controller="books-select shelf-assign"` for toolbar to read selection state - ✓ books_select_filter_controller.js loaded in page_scripts and registered in app.js - ✓ Filter controller safely handles missing selectAllBtn target (HasMoreBooks not passed for series detail) - ✓ All toolbar actions preserved: Metadata submenu (Auto Fetch/Custom Fetch/Bulk Edit/Step-through/Regenerate/LLM Vision), Assign, Lock, Organize, Attach, More menu (Show selected), Select all/Clear, Delete — all with identical URLs as original toolbar - ✓ E2E test correctly verifies toolbar appears on book selection, gap cards render, canonical SVG-icon toolbar is present - ✓ CSP compliant: no inline `style=` attributes; toolbar visibility controlled via `hidden` attribute and classes - ✓ No regression: old bespoke toolbar fully removed, no duplicate controllers or dead HTML **REVIEW VERDICT: 0 blocker, 0 major, 1 minor**
Author
Owner

Series-gaps page — canonical bulk-select toolbar (bookshelf-v1ap)

The series-gaps mixed grid (owned books + ghost missing-entry cards) now renders the shared books_select_toolbar partial with SVG-icon submenus, replacing the bespoke inline emoji toolbar. Screenshot captured locally with go-rod.

series-gaps canonical toolbar

## Series-gaps page — canonical bulk-select toolbar (bookshelf-v1ap) The series-gaps mixed grid (owned books + ghost missing-entry cards) now renders the shared `books_select_toolbar` partial with SVG-icon submenus, replacing the bespoke inline emoji toolbar. Screenshot captured locally with go-rod. ![series-gaps canonical toolbar](/attachments/641a4966-ebb9-46ef-b98e-f541ac474b4b)
Author
Owner

UI Review — PR #1197 (bookshelf-v1ap)

Screenshot reviewed: attachment 641a4966-ebb9-46ef-b98e-f541ac474b4b (1280×800 PNG confirmed)

What the screenshot shows

Series detail page for "v1ap-gap-series-screenshot" (1 BOOK / 1/3 OWNED / 0/1 READ). Books-in-series section displays a mixed grid: one owned card (#1 "Gap Series Book One", checkbox checked/blue) plus two ghost gap cards (#2, #3, dashed-border placeholder cells). The canonical books_select_toolbar floats at the bottom showing "1 selected" + the full SVG-icon action row + red trash on the far right.

1. Canonical-component reuse

The toolbar rendered is the exact canonical books_select_toolbar.html partial (invoked at templates/pages/series_show.html:268). Icon order matches: database-cylinder submenu → bookmark → lock → arrows → paperclip → ellipsis submenu → check-square → x-square → red trash (btn-danger). The HasMoreBooks select-all button is correctly absent (no pagination on a single series). No bespoke class system, no hand-rolled toolbar. Pass.

2. Layout / overlap

The floating bar overlaps the lower portion of the gap cards (#2/#3). This is identical behaviour to the main book-list grid — the bar is position: fixed at the bottom and appears when ≥1 item is selected. The gap card content (#2/#3 numbers and "Not in library" text) is visible above the bar. Acceptable — consistent with canonical usage.

3. Spacing / alignment / typography

All CSS classes use design-system tokens: var(--border) for the dashed ghost border, var(--fg-muted) for ghost text/numbers. No inline style= attributes found (grep confirmed). Ghost card modifier classes (.book-card-tile--ghost, .cover-card--ghost, .cover-card-placeholder--ghost, .series-gap-label) are all defined in static/css/main.css:7941–7982 using token variables. Icon buttons are vertically centred, consistent sizing with canonical bar on other pages. Pass.

4. Gap cards

Ghost cards render as dashed-border placeholders showing only the series number (#2, #3) centred. The pointer-events: none rule (main.css:7943) prevents accidental checkbox activation. The owned book tile shows the expected cover placeholder ("G" initial), blue checkbox, and three-dot kebab — matching the canonical books_select_grid tile structure. Pass.

Source cross-check

No inline style= found. No bespoke modal/button/dialog classes. Ghost-card CSS uses token variables throughout.


REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## UI Review — PR #1197 (bookshelf-v1ap) **Screenshot reviewed:** attachment `641a4966-ebb9-46ef-b98e-f541ac474b4b` (1280×800 PNG confirmed) ### What the screenshot shows Series detail page for "v1ap-gap-series-screenshot" (1 BOOK / 1/3 OWNED / 0/1 READ). Books-in-series section displays a mixed grid: one owned card (#1 "Gap Series Book One", checkbox checked/blue) plus two ghost gap cards (#2, #3, dashed-border placeholder cells). The canonical `books_select_toolbar` floats at the bottom showing "1 selected" + the full SVG-icon action row + red trash on the far right. ### 1. Canonical-component reuse The toolbar rendered is the exact canonical `books_select_toolbar.html` partial (invoked at `templates/pages/series_show.html:268`). Icon order matches: database-cylinder submenu → bookmark → lock → arrows → paperclip → ellipsis submenu → check-square → x-square → red trash (`btn-danger`). The `HasMoreBooks` select-all button is correctly absent (no pagination on a single series). No bespoke class system, no hand-rolled toolbar. **Pass.** ### 2. Layout / overlap The floating bar overlaps the lower portion of the gap cards (#2/#3). This is identical behaviour to the main book-list grid — the bar is `position: fixed` at the bottom and appears when ≥1 item is selected. The gap card content (#2/#3 numbers and "Not in library" text) is visible above the bar. **Acceptable — consistent with canonical usage.** ### 3. Spacing / alignment / typography All CSS classes use design-system tokens: `var(--border)` for the dashed ghost border, `var(--fg-muted)` for ghost text/numbers. No inline `style=` attributes found (grep confirmed). Ghost card modifier classes (`.book-card-tile--ghost`, `.cover-card--ghost`, `.cover-card-placeholder--ghost`, `.series-gap-label`) are all defined in `static/css/main.css:7941–7982` using token variables. Icon buttons are vertically centred, consistent sizing with canonical bar on other pages. **Pass.** ### 4. Gap cards Ghost cards render as dashed-border placeholders showing only the series number (#2, #3) centred. The `pointer-events: none` rule (`main.css:7943`) prevents accidental checkbox activation. The owned book tile shows the expected cover placeholder ("G" initial), blue checkbox, and three-dot kebab — matching the canonical `books_select_grid` tile structure. **Pass.** ### Source cross-check No inline `style=` found. No bespoke modal/button/dialog classes. Ghost-card CSS uses token variables throughout. --- REVIEW VERDICT: 0 blocker, 0 major, 0 minor
zombor force-pushed bd-bookshelf-v1ap from e3f76a2742
All checks were successful
/ E2E API (pull_request) Successful in 2m13s
/ Integration (pull_request) Successful in 4m9s
/ E2E Browser (pull_request) Successful in 4m7s
/ Test Race (pull_request) Successful in 6m28s
/ Coverage (pull_request) Successful in 7m16s
/ Lint (pull_request) Successful in 8m25s
/ JS Unit Tests (pull_request) Successful in 16m30s
to 1c217a78d7
All checks were successful
/ JS Unit Tests (pull_request) Successful in 40s
/ E2E API (pull_request) Successful in 2m44s
/ E2E Browser (pull_request) Successful in 3m3s
/ Test Race (pull_request) Successful in 3m4s
/ Coverage (pull_request) Successful in 3m15s
/ Lint (pull_request) Successful in 3m18s
/ Integration (pull_request) Successful in 3m51s
2026-07-22 01:11:30 +00:00
Compare
zombor merged commit 60c1647f95 into main 2026-07-22 01:15:50 +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!1197
No description provided.