feat(filters): panel removal + All-Books field-values fix (faceted plan Phase 8)

- Remove the Phase 7 facet-panel UI: SSR route, facet_panel.templ,
  FacetCountsForView, drawer panel block, panel TS + .facet-* CSS.
  The ENGINE stays (FacetCountsUnderFilters, all-facets endpoint,
  drift guard) — it becomes the Phase 9 builder's machinery.
- Fix the All-Books bug: field-values autocomplete (author/genre/
  tags/narrator/series/language) treats library_id as OPTIONAL —
  absent aggregates every VISIBLE library (family lens in-query);
  explicit out-of-set library_id is now 403, never a silent pass.
  The four scalar Search*Values queries get the nullable shape.
- toast.ts: suggestion fetches are exempt from error toasts
  (console-only) — a failed background hint never interrupts.
- TestFacetPanelPartial deleted with the behavior; TestFieldValuesAll-
  Libraries pins the fix (200 + cross-library aggregation, family
  lens, 403). Contract-pins changelog entry added.
This commit is contained in:
John O'Keefe committed 2026-10-20 12:20:51 -04:00
1 parent f52bb6f073
commit ebb7f4a5b0
14 files changed
+192 -643

No files matched your search

+21 -16
View File
@@ -2583,24 +2583,29 @@ func (mh *MediaHandler) GetFacets(c *echo.Context) error {
})
}
// FacetCountsForView is the SSR facet panel's data path (faceted plan
// Phase 7): the all-facets counts under the active filters for one
// library — or across every visible library when libraryID is NULL.
func (mh *MediaHandler) FacetCountsForView(ctx context.Context, userID, libraryID pgtype.UUID, params services.SearchParams, limit int) (map[string][]services.FacetValue, error) {
return mh.searchService.FacetCounts(ctx, userID, libraryID, params, limit)
}
// handleFieldValuesSearch handles autocomplete queries (author=value, genre=value, etc.)
// Returns distinct field values with counts and similarity scores for dropdown population
//
// library_id is OPTIONAL: absent means "every library the requesting user
// can see" — the per-user family lens is enforced inside the query (the
// library_visibility join), so the All-Books view's suggestions aggregate
// the visible world. An explicit library_id outside the visible set is a
// 403 (AGENTS standing rule: never silently pass).
func (mh *MediaHandler) handleFieldValuesSearch(c *echo.Context, userID pgtype.UUID, fieldType, searchQuery string) error {
libraryID := c.QueryParam("library_id")
if libraryID == "" {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "library_id is required"})
}
libUUID, err := uuid.Parse(libraryID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
var libPg pgtype.UUID
if libraryID := c.QueryParam("library_id"); libraryID != "" {
libUUID, err := uuid.Parse(libraryID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
}
libPg = pgtype.UUID{Bytes: libUUID, Valid: true}
visible, err := mh.libraryVisible(c, libPg)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
if !visible {
return c.JSON(http.StatusForbidden, map[string]string{"error": "library not visible"})
}
}
limit, _ := strconv.Atoi(c.QueryParam("limit"))
@@ -2611,7 +2616,7 @@ func (mh *MediaHandler) handleFieldValuesSearch(c *echo.Context, userID pgtype.U
params := services.FieldSearchParams{
UserID: userID,
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
LibraryID: libPg,
FieldType: fieldType,
SearchQuery: searchQuery,
Limit: limit,