8aec80cbb7ca0586f5303323286ea61c1d2f2f34
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4f71eda6be |
feat(podcasts): the rules page retires — the collection builder IS the filter builder (item 3.4)
The smart-collection editor at /collections/:id/rules now hosts the SAME builder island as All-Books (FilterBuilderRows — the rows markup extracted into one shared templ both mounts render; the shared filterBuilderExtra component). One vocabulary, one engine, one row UI; the collection differences are exactly three: the payload is the collection's scope (episode trees get the episode fields — books trees hide them), Apply writes the tree to the collection (PUT filter_tree through the validated write path), and a legacy rules array preloads as its CONVERTED tree (RulesToFilterTree rendered server-side) so the upgrade lands at the first Apply — read-compat never mutates data silently. Retired with the page: the per-rule CRUD endpoints and test-rules (their handler bodies, routes, api-docs rows, and the dead collections.ts island functions — the file's LIVE halves, the collection modal/navigate/ grid-refresh, stay; the dead rules half went). match-mode endpoint and preview survive (legacy channel + the dashboard custom-section-builder, flagged for its own migration). Contract-pin changelog documents the deletions per the asymmetry rule. The builder component gains collection mounts: scopeKey (the row universe for counts/apply), data-tree prefill, treeJSON (the apply wire with scope riding the top level), and an apply hook (the page owns the write; All-Books never sets one). ensureReadyRule defaults to the scope's first field instead of hardcoding author. Tests migrated onto trees: TestSmartCollections' live-membership / reject-mutations / include-auto / system-collection subtests now drive TREE collections; the deleted-endpoint subtests die with them. |
||
|
|
abb7c84833 |
feat(podcasts): smart episode collections — ALL/ANY match modes + episode rule fields (item 2.4)
The daily briefing needs AND: today's rules engine was OR-only. The
match semantics become a per-collection column
(collections.rule_match_mode, 'any' legacy default | 'all'), flipped on
its own endpoint (PUT /api/collections/:id/match-mode, owner-only —
nothing else on the row changes) and surfaced in the rules page as an
ANY/ALL toggle with matching copy.
Episode rule fields join the vocabulary (plan item 2.4): feed (the
multi-select rides the new `in` operator — comma-separated feed ids),
played (the canonical finished predicate — viewer progress ≥ 0.999,
same as hide_finished), downloaded, published_within (N days), plus
title/description contains. The rules candidate query now carries the
viewer's progress (LEFT JOIN reading_progress) so played/unplayed is
per-user, and TestRules/preview speak the SAME evaluator: the handler's
duplicated field switch is deleted and delegates to
CollectionService.FieldMatches — one vocabulary, one semantics.
Legitimate semantics note: in ANY mode `played:false` matches unplayed
BOOKS too (everything with no progress row) — correct per-rule
behavior, and exactly why the briefing mode exists.
Backward compatibility: every legacy row defaults to 'any' (the old
OR), UpdateCollection keeps the stored mode when the form doesn't send
one, and the evaluator treats absent data honestly (no date → no
match; no progress → unplayed).
Pinned by TestPodcastSmartCollections: the AND-mode briefing
(feed∈{A} ∧ unplayed ∧ <7d = exactly the new episode), OR-mode
widening, owner-only flips, the preview honoring the mode, and
downloaded/contains rules. Also fixes a latent bug the new query
surfaced: UpdateCollectionViewSettings referenced a nonexistent
updated_at column (never exercised until now — the new sibling query
was written without it).
|
||
|
|
1dbb0ce406 |
feat(collections): playlist auto-creation, position ordering, hide_finished, Up Next, dashboard eligibility
Phase 2b + 3 of the metadata overhaul (plan §2.4, §3.1-3.7). Auto-playlist collections (§2.4): the scanner turns obviously-playlist sidecar series (explicit '(Reading Order)'/chronological/publication/ in-order/timeline markers only — precision over recall) into curated static collections: upsert by view_settings.imported_from_series then name (rename-safe), position set ONLY on insert (clean integers; 1.5/1a stay unpositioned rather than guessing), membership add-if-missing, never removed or re-ordered by rescans, provenance in description + view_settings, show_on_dashboard true (dashboard-eligible by design), 'scanner.auto_playlist_collections' setting (default on, Settings Registry + schema seed). Ordering (§3.1-3.2): collection listings order position ASC NULLS LAST then added_at ASC (was added_at DESC — walked playlists backwards); PUT /api/collections/:id/items/order applies full-order rewrites (owner-only, service-gated). hide_finished + Up Next (§3.3-3.4): GetCollectionItemsForDashboard gains viewer_user_id + hide_finished (LEFT JOIN reading_progress, COALESCE percentage < 0.999) and viewer_percentage rides the row for card graying; GetCollectionUpNext serves the collection's reading-queue head with the family-lens visible-library array enforced. Dashboard (§3.5): user collections are dashboard-ELIGIBLE BY DEFAULT — the show_on_dashboard gate (never writable for user rows since the column shipped) is gone from GetUserCollectionsForDashboard; hiding stays dashboard-prefs (hidden_collections) territory; hide_finished filtered-everything now emits a Complete section instead of silently vanishing; sections JSON carries is_playlist/complete. Series page ordering hardening (§3.6): GetSeriesBooks tiebreaks by title ASC — NULL/tied numbers previously returned heap (scan) order, which is what the user saw. Frontend custom-section-builder note: the builder has sent show_on_dashboard: true since its creation; the API never bound it. User-row visibility is now intentionally prefs-governed, so the field stays unbound by design (documented in code). Pins widened (docs/contract_pins.md): collection detail + up_next; dashboard sections + is_playlist/complete; new reorder endpoint recorded. Verified: TestPlaylistCollectionsEndToEnd (new, full e2e: sidecar series split, collection auto-creation with position 7, rescan idempotency, owner-only reorder, finished-filter + completion marker + Up Next drain) plus the full affected integration sweep green. |
||
|
|
0a7fbd6f48 |
feat(collections): smart (dynamic) collections — rules evaluated live at render
Type is DERIVED, not stored: a saved ruleset makes a collection SMART,
no rules = STATIC (stored collection_items links, unchanged behavior).
No schema change — collections.auto_assign_rules JSONB already carried
the config; user's decision 2026-10-14 (handoff: live-at-render, not
refresh-on-scan; create-time Static/Smart toggle; v1 NO hybrid).
- Rule gains an enabled flag (absent = enabled, legacy rules unaffected).
- Rule CRUD: POST/PUT(DELETE) /api/collections/:id/rules[/:ruleId] — the
survivor dead effort of the rules-builder UI finally has real
endpoints (rules were never editable after creation; nothing ever
re-evaluated them). First rule converts a user collection to smart;
removing the last reverts it to static. UpdateCollectionRulesJSON
edits ONLY the rules doc so rule CRUD cannot clobber metadata.
- Live evaluation: candidates from GetVisibleLiveMediaForRules —
visible-library set (family lens enforced at source) + archived IS
NULL AND missing_scan_count = 0 — any enabled rule matches, title
order. Zero refresh hooks by design: always current by construction.
- Membership mutations (add/remove/bulk-add) are 400 on smart — v1 no
hybrid; collection_items.excluded stays the reserved future hook.
- Rules builder page (/collections/:id/rules) first time ever routable;
templ whole-attribute interpolation ("{ expr }" inside a quoted attr
value is literal text, as theme-{ user.Theme } silently proved).
- UI: create modal Static/⚡Smart cards (smart create redirects into the
rules builder), ⚡ badge + real per-card counts on the grid (smart =
live matches, static = stored-link count), smart detail renders
matched books with no hand-pick actions + an Edit Rules button.
- API: GET /:id gains is_smart + live books; GET /: keeps the Android
app's default exclusion, include_auto=true surfaces smart rows with
live counts; TestRules previews against the SAME candidates the live
render uses (visible + live, was raw first-1000).
- FIXED pre-existing regression from
|
||
|
|
29e9f66c71 |
feat(dashboard): implement Phase 4.5 Collections Preview Endpoint
Add preview endpoint for custom section builder and rule evaluation: Handler Implementation (internal/handlers/collections.go): - PreviewCollection method: Evaluates filter rules and returns matching items without saving * Accepts library_id, rules array, manual_book_ids array, and limit * Evaluates rules against all library items using collectionService.EvaluateRules * Adds manually selected books to results * Deduplicates manual books (avoids adding same book twice) * Applies limit (default: 20, max: 100) * Returns array of BookInfo with matching items - Helper function: mediaItemsToListMediaItemsRow * Converts database.MediaItems to database.ListMediaItemsRow * Required for EvaluateRules which expects ListMediaItemsRow type Route Registration (internal/router/collections.go): - POST /api/collections/preview - Protected by JWT middleware - Part of collections API group Why This Endpoint is Necessary: - Allows users to see what books match their filter rules BEFORE saving - Avoids creating incorrect collections - Enables testing different rule combinations quickly - Reuses existing service logic (collectionService.EvaluateRules) - Client-side preview would require downloading entire library (10,000+ books) - Would duplicate 500+ lines of rule evaluation logic in TypeScript - Would create maintenance nightmare keeping Go and TypeScript in sync Bruno Test (bruno/collections/preview-collection.bru): - Tests POST /api/collections/preview endpoint - Validates status 200 response - Validates items array in response - Example request with genre filter rule This endpoint is required for both the web UI Custom Section Builder and future mobile apps. |
||
|
|
296c45e870 |
refactor(router): Phase 5 - update router package with new handlers
- Update Config struct to replace EbookHandler with MediaHandler, SearchHandler, MatchingHandler - Add CollectionHandler to Config struct - Update RegisterRoutes to call new registration functions - Create registerCollectionsRoutes for 15 collection endpoints - Create registerSearchRoutes for search endpoints (uses MediaHandler.SearchMediaItems, MatchingHandler.QueryBooks) - Create registerMatchingRoutes for 8 matching/linking endpoints - Update registerMediaRoutes to use cfg.MediaHandler (adds 24 media endpoints) - Create registerProgressRoutes for 3 universal progress endpoints All 57 routes preserved and properly registered with correct handlers. No functionality lost, all endpoints work identically. This is Phase 5 of the ebook.go refactoring plan. |