fix(books): remove redundant Shelf Status facet from filter drawer [shot:filter-drawer] (bookshelf-onx3v) #1215

Merged
zombor merged 2 commits from bd-bookshelf-onx3v into main 2026-07-24 00:54:00 +00:00
Owner

Summary

  • The filter drawer showed two nearly identical facets — "Read Status" and "Shelf Status" — both exposing UNREAD/READING/FINISHED counts.
  • Root cause: FacetShelfStatusCounts delegated to FacetStatusCounts and buildShelfStatusGroup literally called buildStatusGroup, producing the same SQL predicate. The "Shelf Status" facet was entirely redundant.
  • Fix: remove fshelfstatus URL param, FacetShelfStatuses/ShelfStatuses fields from all structs, FacetShelfStatusCounts function, buildShelfStatusGroup, the template section, and all JS references (fshelfstatus, FACET_PARAMS entry, _paramName mapping).

Before: Filter drawer had "Read Status" and "Shelf Status" sections showing identical UNREAD/READING/FINISHED counts.
After: Filter drawer has only "Read Status" — the authoritative per-user read status filter.

Test plan

  • make test passes — all unit tests green
  • make build passes — no compile errors
  • make lint passes — no new lint issues in this worktree
  • internal/books tests: removed FacetShelfStatusCounts, FacetShelfStatuses, ShelfStatuses, fshelfstatus test cases; kept all other facet tests
  • JS Vitest: removed "maps shelfstatus → fshelfstatus" test and fshelfstatus=UNREAD from the all-params test
  • Screenshot: CI auto-posts via [shot:filter-drawer] title marker using existing filter drawer journey

Closes bead bookshelf-onx3v on merge.

## Summary - The filter drawer showed two nearly identical facets — "Read Status" and "Shelf Status" — both exposing UNREAD/READING/FINISHED counts. - Root cause: `FacetShelfStatusCounts` delegated to `FacetStatusCounts` and `buildShelfStatusGroup` literally called `buildStatusGroup`, producing the same SQL predicate. The "Shelf Status" facet was entirely redundant. - Fix: remove `fshelfstatus` URL param, `FacetShelfStatuses`/`ShelfStatuses` fields from all structs, `FacetShelfStatusCounts` function, `buildShelfStatusGroup`, the template section, and all JS references (`fshelfstatus`, `FACET_PARAMS` entry, `_paramName` mapping). **Before:** Filter drawer had "Read Status" and "Shelf Status" sections showing identical UNREAD/READING/FINISHED counts. **After:** Filter drawer has only "Read Status" — the authoritative per-user read status filter. ## Test plan - [x] `make test` passes — all unit tests green - [x] `make build` passes — no compile errors - [x] `make lint` passes — no new lint issues in this worktree - [x] `internal/books` tests: removed `FacetShelfStatusCounts`, `FacetShelfStatuses`, `ShelfStatuses`, `fshelfstatus` test cases; kept all other facet tests - [x] JS Vitest: removed `"maps shelfstatus → fshelfstatus"` test and `fshelfstatus=UNREAD` from the all-params test - [x] Screenshot: CI auto-posts via `[shot:filter-drawer]` title marker using existing filter drawer journey Closes bead bookshelf-onx3v on merge.
fix(books): remove redundant Shelf Status facet from filter drawer
Some checks failed
/ Test Race (pull_request) Successful in 3m50s
/ JS Unit Tests (pull_request) Successful in 1m41s
/ Coverage (pull_request) Successful in 4m48s
/ E2E API (pull_request) Successful in 2m58s
/ Lint (pull_request) Successful in 6m16s
/ Integration (pull_request) Successful in 6m50s
/ E2E Browser (pull_request) Failing after 5m18s
7ca434bf85
FacetShelfStatusCounts / buildShelfStatusGroup / FacetShelfStatuses were
semantically identical to FacetStatusCounts / buildStatusGroup / FacetStatuses:
buildShelfStatusGroup literally called buildStatusGroup with the same args,
and appendScopePredicates scoped both to the current shelf anyway. The "Shelf
Status" drawer facet (fshelfstatus URL param) showed UNREAD/READING/FINISHED
counts that perfectly matched the existing "Read Status" facet — confusing users
who saw two near-identical panels.

Remove FacetShelfStatusCounts, buildShelfStatusGroup, FacetShelfStatuses,
ShelfStatuses, the "shelfstatus" facet registration in wire.go, the template
section, and all JS references. Read Status continues to work unchanged.

Closes bead bookshelf-onx3v on merge.

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

Filter Drawer screenshot (filter-drawer-genre-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-genre-expanded

**Filter Drawer screenshot** (filter-drawer-genre-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-genre-expanded](/attachments/dc75dd22-5be3-4d62-9062-775e6cdae3f3)

Filter Drawer screenshot (filter-drawer-cross-facet-series-narrowed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-cross-facet-series-narrowed

**Filter Drawer screenshot** (filter-drawer-cross-facet-series-narrowed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-cross-facet-series-narrowed](/attachments/1b250fad-8e58-43e1-bc92-7587f0cfecbb)

Filter Drawer screenshot (filter-drawer-fantasy-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-fantasy-filtered

**Filter Drawer screenshot** (filter-drawer-fantasy-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-fantasy-filtered](/attachments/731a7cee-462d-4517-ab8d-20898a6504a0)

Filter Drawer screenshot (filter-drawer-range-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-expanded

**Filter Drawer screenshot** (filter-drawer-range-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-expanded](/attachments/8ae5be45-258c-4d1c-85f1-5e9dcd74f8f9)

Filter Drawer screenshot (filter-drawer-range-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-filtered

**Filter Drawer screenshot** (filter-drawer-range-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-filtered](/attachments/31048bcb-2811-4e28-bac7-e58fdc3b0222)

Filter Drawer screenshot (filter-drawer-value-search)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-value-search

**Filter Drawer screenshot** (filter-drawer-value-search) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-value-search](/attachments/9367d760-b634-4240-a9fc-1018848adb48)

Filter Drawer screenshot (filter-drawer-chips-combinator)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chips-combinator

**Filter Drawer screenshot** (filter-drawer-chips-combinator) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chips-combinator](/attachments/6331fd6c-94b7-447b-bfb7-aefbbe7faed5)

Filter Drawer screenshot (filter-drawer-chip-removed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chip-removed

**Filter Drawer screenshot** (filter-drawer-chip-removed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chip-removed](/attachments/7dcefc18-9fe8-443b-a8e1-83952a649454)

Filter Drawer screenshot (filter-drawer-shelf-open)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-open

**Filter Drawer screenshot** (filter-drawer-shelf-open) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-open](/attachments/ec91abb5-54eb-4779-9d3c-880610b1f313)

Filter Drawer screenshot (filter-drawer-shelf-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-filtered

**Filter Drawer screenshot** (filter-drawer-shelf-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-filtered](/attachments/71b95f3c-bc7e-4650-9604-71b71b494771)

Filter Drawer screenshot (filter-drawer-character-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-character-filtered

**Filter Drawer screenshot** (filter-drawer-character-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-character-filtered](/attachments/9b4219a9-a641-48ac-aa36-d1a6a5d3ff31)
fix(e2e): remove e2e browser tests for removed Shelf Status facet
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m28s
/ E2E API (pull_request) Successful in 2m42s
/ Test Race (pull_request) Successful in 3m47s
/ Coverage (pull_request) Successful in 3m44s
/ Lint (pull_request) Successful in 6m12s
/ Integration (pull_request) Successful in 6m2s
/ E2E Browser (pull_request) Successful in 5m46s
e49bcefe28
The Shelf Status facet was removed in the prior commit (bookshelf-onx3v).
Two e2e browser journey tests that exercised the shelfstatus facet endpoint
and DOM element now fail because the facet no longer exists in the template
or wire.go registration.

Remove:
- The "expanding Shelf Status facet does not show 'Could not load filters.'"
  It block from the lazy-fetch/localStorage/range journey
- The entire "Journey: Filter Drawer — shelf status facet scoped to selected shelf"
  Describe block (4 Its: opens drawer, expand-no-shelf, navigate-with-fshelf,
  per-shelf counts screenshot)

Update the lazy-fetch journey title and BeforeAll comment to drop stale
references to Shelf Status.

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

Filter Drawer screenshot (filter-drawer-chips-combinator)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chips-combinator

**Filter Drawer screenshot** (filter-drawer-chips-combinator) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chips-combinator](/attachments/d8b8d1ed-ae30-44f4-a559-ca1cfbda213f)

Filter Drawer screenshot (filter-drawer-genre-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-genre-expanded

**Filter Drawer screenshot** (filter-drawer-genre-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-genre-expanded](/attachments/73a5347a-78f8-4f89-b9c5-44a1a6ba77d5)

Filter Drawer screenshot (filter-drawer-chip-removed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chip-removed

**Filter Drawer screenshot** (filter-drawer-chip-removed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chip-removed](/attachments/56cd539c-640a-4b50-943c-18e20f39b63e)

Filter Drawer screenshot (filter-drawer-fantasy-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-fantasy-filtered

**Filter Drawer screenshot** (filter-drawer-fantasy-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-fantasy-filtered](/attachments/4b48d9a8-23c5-4482-a4d6-0e05414990b7)

Filter Drawer screenshot (filter-drawer-range-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-expanded

**Filter Drawer screenshot** (filter-drawer-range-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-expanded](/attachments/d8068033-0727-4723-8475-0eefbc913f49)

Filter Drawer screenshot (filter-drawer-range-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-filtered

**Filter Drawer screenshot** (filter-drawer-range-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-filtered](/attachments/ce4c06d8-8eb5-4d6f-8c63-55b59a7d006d)

Filter Drawer screenshot (filter-drawer-shelf-open)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-open

**Filter Drawer screenshot** (filter-drawer-shelf-open) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-open](/attachments/b5c3f5ef-d649-4eeb-9222-3cca05c0c79e)

Filter Drawer screenshot (filter-drawer-shelf-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-filtered

**Filter Drawer screenshot** (filter-drawer-shelf-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-filtered](/attachments/c5331e13-68f5-4e58-ac14-12eca6f9b73a)

Filter Drawer screenshot (filter-drawer-character-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-character-filtered

**Filter Drawer screenshot** (filter-drawer-character-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-character-filtered](/attachments/816da902-b8da-4cb0-831a-57b36ebda8c6)

Filter Drawer screenshot (filter-drawer-value-search)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-value-search

**Filter Drawer screenshot** (filter-drawer-value-search) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-value-search](/attachments/1bd6937f-baed-4ddd-9243-63a25681d61d)

Filter Drawer screenshot (filter-drawer-cross-facet-series-narrowed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-cross-facet-series-narrowed

**Filter Drawer screenshot** (filter-drawer-cross-facet-series-narrowed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-cross-facet-series-narrowed](/attachments/9ec404de-a8df-4148-a1b2-0612fc491d3a)
Author
Owner

UI Review — bookshelf-onx3v (PR #1215)

Reviewed rendered screenshots (second batch, comments 14922–14934) and the diff against main.

Screenshot observations

filter-drawer-shelf-open — Drawer open with Shelf facet expanded. Panel order reads: Read Status → File Format → Category → Author → Series → Publisher → Language → Tag → Rating → Page Count → Published Year → Metadata Score → Library → Shelf (expanded, MySuperShelf (2)) → Age Rating → Content Rating. No "Shelf Status" panel present. No orphaned heading, empty section, stray divider, or spacing anomaly where the panel used to sit. The Shelf→Age Rating transition is clean.

filter-drawer-genre-expanded — Category expanded. "Shelf Status" remains absent. Panel spacing and typography are visually uniform throughout. Shelf is visible (collapsed) at the foot of the drawer with no gap artifacts.

filter-drawer-chips-combinator — Active fantasy filter chip, Category expanded. Panel structure unchanged and correct. No double Read Status entry.

Source cross-check

Diff removes exactly:

  • The <div class="facet-category" data-facet="shelfstatus">…Shelf Status…</div> block from templates/pages/books_index.html
  • {{range .Filter.FacetShelfStatuses}}&fshelfstatus=…{{end}} from the two URL-building locations (infinite-scroll sentinel + load-more link)
  • params.delete("fshelfstatus"), the "fshelfstatus" entry from FACET_PARAMS, and the facet === "shelfstatus" mapping branch in filter_drawer_controller.js

No inline style= introduced. No bespoke classes introduced. All retained facet panels continue to use the existing canonical facet-category / facet-category-toggle / facet-values classes. No CSS changes in this PR.

Findings

None.


REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## UI Review — bookshelf-onx3v (PR #1215) Reviewed rendered screenshots (second batch, comments 14922–14934) and the diff against `main`. ### Screenshot observations **filter-drawer-shelf-open** — Drawer open with Shelf facet expanded. Panel order reads: Read Status → File Format → Category → Author → Series → Publisher → Language → Tag → Rating → Page Count → Published Year → Metadata Score → Library → **Shelf** (expanded, MySuperShelf (2)) → Age Rating → Content Rating. No "Shelf Status" panel present. No orphaned heading, empty section, stray divider, or spacing anomaly where the panel used to sit. The Shelf→Age Rating transition is clean. **filter-drawer-genre-expanded** — Category expanded. "Shelf Status" remains absent. Panel spacing and typography are visually uniform throughout. Shelf is visible (collapsed) at the foot of the drawer with no gap artifacts. **filter-drawer-chips-combinator** — Active fantasy filter chip, Category expanded. Panel structure unchanged and correct. No double Read Status entry. ### Source cross-check Diff removes exactly: - The `<div class="facet-category" data-facet="shelfstatus">…Shelf Status…</div>` block from `templates/pages/books_index.html` - `{{range .Filter.FacetShelfStatuses}}&fshelfstatus=…{{end}}` from the two URL-building locations (infinite-scroll sentinel + load-more link) - `params.delete("fshelfstatus")`, the `"fshelfstatus"` entry from `FACET_PARAMS`, and the `facet === "shelfstatus"` mapping branch in `filter_drawer_controller.js` No inline `style=` introduced. No bespoke classes introduced. All retained facet panels continue to use the existing canonical `facet-category` / `facet-category-toggle` / `facet-values` classes. No CSS changes in this PR. ### Findings None. --- REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Review: Remove Duplicate Shelf Status Facet (bookshelf-onx3v)

Verification Results

1. Removal Completeness ✓

  • FacetShelfStatuses field removed from Filter struct (books.go:171-176)
  • ShelfStatuses field removed from ActiveFacets struct (books.go:315)
  • FacetShelfStatusCounts() function completely removed (facet_store.go:397-420)
  • buildShelfStatusGroup() function completely removed (store.go:841-843)
  • fshelfstatus param validation removed (facet_handler.go:365-367)
  • shelfstatus facet mapping removed from wire (wire.go:893)
  • Template HTML element removed (templates/pages/books_index.html:988-993)
  • JS controller handling removed (filter_drawer_controller.js:906, 923)
  • All URL param forwarding for fshelfstatus removed
  • No remaining references to any removed symbols (verified: zero additions of ShelfStatus/shelfstatus)

2. Duplicate Verification ✓

Confirmed buildShelfStatusGroup was identical to buildStatusGroup:

// OLD:
func buildShelfStatusGroup(statuses []string) facetGroup {
    return buildStatusGroup(statuses)
}

100% duplicate logic — no user-facing distinction from regular Read Status facet.

3. Backward Compatibility (URL Degradation) ✓

  • Existing bookmarks/shares with fshelfstatus=... params will degrade gracefully
  • validateFacetParams() only processes explicitly listed param names; unknown params are silently ignored (no 400 error)
  • No SQL changes; filter logic unaffected

4. Other Facets Unaffected ✓

  • FacetShelves (shelf membership filter) remains intact and functional
  • FacetStatuses (read status facet) unchanged
  • Cross-facet narrowing logic correctly updated (buildCrossFacetGroups line 1031-1433)
  • bookFilterNeedsUBP() correctly updated to exclude FacetShelfStatuses check
  • No changes to store predicates, SQL, or indexes

5. Test Updates Correct ✓

Removed (appropriately):

  • 194-line browser e2e journey "shelf status facet scoped to selected shelf" (e2e/browser/journey_filter_drawer_test.go:79-265)
  • Shelf-status regression guard test from cross-facet journey (e2e/browser:33-71)
  • 120-line FacetShelfStatusCounts unit test suite (facet_store_test.go:492-612)
  • FacetShelfStatusCounts cross-facet test (facet_store_test.go:450-483)
  • FacetShelfStatuses from HasActiveFilter test (filter_has_active_test.go:621-623)
  • fshelfstatus param validation tests (handler_test.go:734-752)
  • FacetShelfStatuses from service forwarding test (service_test.go:2795-2814)
  • FacetShelfStatuses from filtered_ids parity test (filtered_ids_store_test.go:688-702)
  • All JS controller tests for shelfstatus (filter_drawer_controller.test.js:952-957, 944)

Retained & Passing:

  • All FacetStatusCounts tests (core predicate logic unchanged)
  • All shelf (FacetShelves) tests
  • All other facet tests
  • CI: all 7 checks passing (Unit/Race/Coverage/Lint/Integration/E2E API/E2E Browser)

6. Comment Consistency ✓

Stale reference updated in facet_handler.go:356:

// OLD: "fstatus/fshelfstatus: each value must be a valid read status"
// NEW: "fstatus: each value must be a valid read status"

Summary

This is a surgical removal of a 100% redundant facet. The logic was identical to the global Read Status facet; the shelf-status-specific wrapper only added shelf-context gating (return empty if no shelf selected). With that gate removed, the facet is just a duplicate UI control for the same predicate. The removal is complete, consistent, correctly tested, and backward-compatible.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

# Review: Remove Duplicate Shelf Status Facet (bookshelf-onx3v) ## Verification Results ### 1. Removal Completeness ✓ - `FacetShelfStatuses` field removed from Filter struct (books.go:171-176) - `ShelfStatuses` field removed from ActiveFacets struct (books.go:315) - `FacetShelfStatusCounts()` function completely removed (facet_store.go:397-420) - `buildShelfStatusGroup()` function completely removed (store.go:841-843) - `fshelfstatus` param validation removed (facet_handler.go:365-367) - `shelfstatus` facet mapping removed from wire (wire.go:893) - Template HTML element removed (templates/pages/books_index.html:988-993) - JS controller handling removed (filter_drawer_controller.js:906, 923) - All URL param forwarding for `fshelfstatus` removed - No remaining references to any removed symbols (verified: zero additions of ShelfStatus/shelfstatus) ### 2. Duplicate Verification ✓ Confirmed `buildShelfStatusGroup` was identical to `buildStatusGroup`: ```go // OLD: func buildShelfStatusGroup(statuses []string) facetGroup { return buildStatusGroup(statuses) } ``` 100% duplicate logic — no user-facing distinction from regular Read Status facet. ### 3. Backward Compatibility (URL Degradation) ✓ - Existing bookmarks/shares with `fshelfstatus=...` params will degrade gracefully - `validateFacetParams()` only processes explicitly listed param names; unknown params are silently ignored (no 400 error) - No SQL changes; filter logic unaffected ### 4. Other Facets Unaffected ✓ - `FacetShelves` (shelf membership filter) remains intact and functional - `FacetStatuses` (read status facet) unchanged - Cross-facet narrowing logic correctly updated (buildCrossFacetGroups line 1031-1433) - `bookFilterNeedsUBP()` correctly updated to exclude FacetShelfStatuses check - No changes to store predicates, SQL, or indexes ### 5. Test Updates Correct ✓ **Removed (appropriately):** - 194-line browser e2e journey "shelf status facet scoped to selected shelf" (e2e/browser/journey_filter_drawer_test.go:79-265) - Shelf-status regression guard test from cross-facet journey (e2e/browser:33-71) - 120-line FacetShelfStatusCounts unit test suite (facet_store_test.go:492-612) - FacetShelfStatusCounts cross-facet test (facet_store_test.go:450-483) - FacetShelfStatuses from HasActiveFilter test (filter_has_active_test.go:621-623) - fshelfstatus param validation tests (handler_test.go:734-752) - FacetShelfStatuses from service forwarding test (service_test.go:2795-2814) - FacetShelfStatuses from filtered_ids parity test (filtered_ids_store_test.go:688-702) - All JS controller tests for shelfstatus (filter_drawer_controller.test.js:952-957, 944) **Retained & Passing:** - All FacetStatusCounts tests (core predicate logic unchanged) - All shelf (FacetShelves) tests - All other facet tests - CI: all 7 checks passing (Unit/Race/Coverage/Lint/Integration/E2E API/E2E Browser) ### 6. Comment Consistency ✓ Stale reference updated in facet_handler.go:356: ``` // OLD: "fstatus/fshelfstatus: each value must be a valid read status" // NEW: "fstatus: each value must be a valid read status" ``` ## Summary This is a surgical removal of a 100% redundant facet. The logic was identical to the global Read Status facet; the shelf-status-specific wrapper only added shelf-context gating (return empty if no shelf selected). With that gate removed, the facet is just a duplicate UI control for the same predicate. The removal is complete, consistent, correctly tested, and backward-compatible. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Security Review — PR #1215 (bd-bookshelf-onx3v)

Scope: removal of the redundant "Shelf Status" facet (fshelfstatus param / FacetShelfStatuses / FacetShelfStatusCounts / buildShelfStatusGroup) across Go, template, and JS layers.

Multi-user scoping — intact. The removed FacetShelfStatusCounts was a thin wrapper delegating to FacetStatusCounts (identical per-user user_id/userLibraryIDs scoping); it added no unique scoping clause. The remaining facets keep their scoping: FacetHandler still resolves userLibraryIDs via resolveLibraryIDs(userID, ...) (facet_handler.go:77) fail-closed, and the shelf facet retains its ownership predicate buildShelfGroup(names, userID) (store.go) plus cross-facet buildCrossFacetStatusGroup(..., StatusUserID) / buildShelfGroup(..., ShelvesUserID). The removal drops only the duplicate status predicate, not any scoping clause.

Leftover fshelfstatus param — safe. validateFacetParams no longer reads q["fshelfstatus"], so on /books the param is now silently ignored (standard Go — unread query params never trigger a query or error). On /books/facets, an unknown ?facet=shelfstatus hits the guard at facet_handler.go:72 (facet != "status" && rangeCounts[facet]==nil && userRangeCounts[facet]==nil) → returns ErrValidation (400). No unscoped query, no existence/error leak.

Injection surface — unchanged. Remaining param parsing/validation (allowlist for fstatus/fformat, max-count/max-length caps for free-text facets, validateValueSearch) is untouched. No new dynamic SQL.

Ownership / shelf-visibility — no check removed. FacetShelfCounts (with its shelf.user_id = userID join) remains wired (wire.go). Only the read-status duplicate was removed.

Removal is symmetric and complete across all layers (Filter, ActiveFacets, params structs, validation, cross-facet builder, predicate builder, wire map, JS controller, template, tests); git grep finds no dangling shelfstatus/FacetShelfStatuses references in production code.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — PR #1215 (bd-bookshelf-onx3v) Scope: removal of the redundant "Shelf Status" facet (`fshelfstatus` param / `FacetShelfStatuses` / `FacetShelfStatusCounts` / `buildShelfStatusGroup`) across Go, template, and JS layers. **Multi-user scoping — intact.** The removed `FacetShelfStatusCounts` was a thin wrapper delegating to `FacetStatusCounts` (identical per-user `user_id`/`userLibraryIDs` scoping); it added no unique scoping clause. The remaining facets keep their scoping: `FacetHandler` still resolves `userLibraryIDs` via `resolveLibraryIDs(userID, ...)` (facet_handler.go:77) fail-closed, and the shelf facet retains its ownership predicate `buildShelfGroup(names, userID)` (store.go) plus cross-facet `buildCrossFacetStatusGroup(..., StatusUserID)` / `buildShelfGroup(..., ShelvesUserID)`. The removal drops only the duplicate status predicate, not any scoping clause. **Leftover `fshelfstatus` param — safe.** `validateFacetParams` no longer reads `q["fshelfstatus"]`, so on `/books` the param is now silently ignored (standard Go — unread query params never trigger a query or error). On `/books/facets`, an unknown `?facet=shelfstatus` hits the guard at facet_handler.go:72 (`facet != "status" && rangeCounts[facet]==nil && userRangeCounts[facet]==nil`) → returns `ErrValidation` (400). No unscoped query, no existence/error leak. **Injection surface — unchanged.** Remaining param parsing/validation (allowlist for `fstatus`/`fformat`, max-count/max-length caps for free-text facets, `validateValueSearch`) is untouched. No new dynamic SQL. **Ownership / shelf-visibility — no check removed.** `FacetShelfCounts` (with its `shelf.user_id = userID` join) remains wired (wire.go). Only the read-status duplicate was removed. Removal is symmetric and complete across all layers (Filter, ActiveFacets, params structs, validation, cross-facet builder, predicate builder, wire map, JS controller, template, tests); `git grep` finds no dangling `shelfstatus`/`FacetShelfStatuses` references in production code. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
zombor force-pushed bd-bookshelf-onx3v from e49bcefe28
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m28s
/ E2E API (pull_request) Successful in 2m42s
/ Test Race (pull_request) Successful in 3m47s
/ Coverage (pull_request) Successful in 3m44s
/ Lint (pull_request) Successful in 6m12s
/ Integration (pull_request) Successful in 6m2s
/ E2E Browser (pull_request) Successful in 5m46s
to 168f8a0d86
All checks were successful
/ Test Race (pull_request) Successful in 3m10s
/ JS Unit Tests (pull_request) Successful in 1m43s
/ E2E API (pull_request) Successful in 2m45s
/ Coverage (pull_request) Successful in 4m6s
/ Lint (pull_request) Successful in 6m3s
/ Integration (pull_request) Successful in 6m1s
/ E2E Browser (pull_request) Successful in 6m9s
2026-07-24 00:44:11 +00:00
Compare

Filter Drawer screenshot (filter-drawer-shelf-open)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-open

**Filter Drawer screenshot** (filter-drawer-shelf-open) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-open](/attachments/1e0c1fcf-a6e7-4b80-9492-9f0faab7cba6)

Filter Drawer screenshot (filter-drawer-genre-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-genre-expanded

**Filter Drawer screenshot** (filter-drawer-genre-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-genre-expanded](/attachments/1d40c2f4-0432-4776-afd6-7b2d0a10e206)

Filter Drawer screenshot (filter-drawer-shelf-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-shelf-filtered

**Filter Drawer screenshot** (filter-drawer-shelf-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-shelf-filtered](/attachments/dd36a9d2-f7e2-4582-857b-689cb42d4163)

Filter Drawer screenshot (filter-drawer-fantasy-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-fantasy-filtered

**Filter Drawer screenshot** (filter-drawer-fantasy-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-fantasy-filtered](/attachments/4287a7e6-e96c-4230-8639-f4307287d02f)

Filter Drawer screenshot (filter-drawer-character-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-character-filtered

**Filter Drawer screenshot** (filter-drawer-character-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-character-filtered](/attachments/fc3b7c75-5f03-48a8-abeb-9519067da8ff)

Filter Drawer screenshot (filter-drawer-range-expanded)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-expanded

**Filter Drawer screenshot** (filter-drawer-range-expanded) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-expanded](/attachments/833830ed-b524-44b3-a456-f1cb5cba9f03)

Filter Drawer screenshot (filter-drawer-range-filtered)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-range-filtered

**Filter Drawer screenshot** (filter-drawer-range-filtered) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-range-filtered](/attachments/3cea23dc-18e0-48e9-b500-2ed7040ad83f)

Filter Drawer screenshot (filter-drawer-chips-combinator)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chips-combinator

**Filter Drawer screenshot** (filter-drawer-chips-combinator) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chips-combinator](/attachments/e7b0d6aa-2d0f-4213-b13f-06e137ea2aa9)

Filter Drawer screenshot (filter-drawer-chip-removed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-chip-removed

**Filter Drawer screenshot** (filter-drawer-chip-removed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-chip-removed](/attachments/3b65c051-12ea-4e1a-9b97-e95ab7272e4c)

Filter Drawer screenshot (filter-drawer-cross-facet-series-narrowed)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-cross-facet-series-narrowed

**Filter Drawer screenshot** (filter-drawer-cross-facet-series-narrowed) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-cross-facet-series-narrowed](/attachments/d961e990-b322-4a83-b167-fec494c35fc4)

Filter Drawer screenshot (filter-drawer-value-search)

Books list filter drawer — Genre facet expanded, filter applied.

filter-drawer-value-search

**Filter Drawer screenshot** (filter-drawer-value-search) Books list filter drawer — Genre facet expanded, filter applied. ![filter-drawer-value-search](/attachments/44cd593b-420d-4d77-ab8e-701fdb6ecfeb)
zombor merged commit 890fcaa474 into main 2026-07-24 00:54:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
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!1215
No description provided.