The pre-tree rules machinery is deleted: no row ever carried a rules array or a non-default match mode (verified in the dev DB), the web never read the envelope keys, and the Android app consumes none of it. Dead code gets no documentation. Removed: - PUT /api/collections/:id/match-mode — endpoint, route, and the UpdateCollectionMatchMode query. The tree's root mode owns any|all. - auto_assign_rules / rule_match_mode from every envelope (create/list/detail/update), from the create/update request shapes, and from the schema: both columns DROPPED (schema.sql carries idempotent DROP COLUMN IF EXISTS heals for existing DBs; sqlc regenerated). - The rules CRUD service layer (AddRule/SetRuleEnabled/RemoveRule + validateRule + UpdateCollectionRulesJSON), ParseRules / EnabledRules / IsSmartCollection, RulesToFilterTree (+ the SSR preload branch), the legacy branch of EvaluateSmartMembership, and SmartEvalOptions.MatchMode. IsCollectionSmart = tree presence; create-path isSmart = collection_type=smart OR a filter_tree. Kept (live consumers): POST /api/collections/preview still evaluates rules payloads in memory (the custom-section builder's preview), and include_auto stays on the list (the Android app sends it). The custom-section builder's SAVE was already broken — its stringified auto_assign_rules payload failed request binding (probed live: 400 invalid request). Create now rejects that payload loudly with a 400 naming the retirement, instead of silently creating a hollow static section; custom-section-builder.md carries the flag. The builder's tree migration remains the flagged follow-up. Test-side (asymmetry rule — deleted behavior, deleted tests): TestPodcastSmartCollections rewritten onto trees (mode lives in the tree root), match-mode subtests and tree_wins_over_legacy_rules deleted, TestRulesToFilterTree deleted, contract-pin envelope keys retracted per the contract_pins 2026-10-31 retirement entry. Mirrors hand-synced: openapi.yaml, bruno create/update, docs/api/*.md, docs/schema.go, api.d.ts. Full container suite green (155s).
4.1 KiB
Custom Section Builder API
The Custom Section Builder allows users to create personalized dashboard sections by defining filter rules or manually selecting books.
Preview Collection
Evaluates filter rules and returns matching items without saving the collection.
Endpoint: POST /api/collections/preview
Request Body:
{
"library_id": "uuid",
"rules": [
{
"id": "rule1",
"field": "genre",
"operator": "equals",
"value": "Sci-Fi",
"priority": 1
}
],
"manual_book_ids": ["uuid1", "uuid2"],
"limit": 20
}
Available Filter Fields:
| Field | Type | Operators |
|---|---|---|
title |
text | contains, equals, starts_with, ends_with, regex |
author |
text | contains, equals |
genre |
select | equals, not_equals, in, not_in |
series |
text | is_set, is_not_set, equals, contains |
progress |
number | equals, not_equals, greater_than, less_than, between, is_set, is_not_set |
rating |
number | equals, not_equals, greater_than, less_than, is_set, is_not_set |
date_added |
date | equals, not_equals, before, after, between, last_x_days |
last_read |
date | equals, before, after, between, last_x_days, is_set, is_not_set |
publisher |
text | contains, equals |
language |
select | equals, not_equals, in |
format |
select | equals, in |
tags |
text | contains, not_contains, equals |
narrators |
text | contains, equals, is_set, is_not_set |
Response:
{
"items": [
{
"media_item_id": "uuid",
"title": "Book Title",
"author": "Author Name",
"cover_image_path": "/path/to/cover.jpg"
}
]
}
Create Custom Section
Creates a new custom collection with filter rules and/or manual book selection.
Endpoint: POST /api/collections
Request Body:
Status (2026-10-31): the rules-array storage this payload targets was retired — collections carry filter trees now. The server rejects this save with a 400 naming the retired fields; the builder needs its flagged tree migration (§12) before saving works again.
{
"name": "My Custom Section",
"description": "My favorite Sci-Fi books",
"icon": "📚",
"color": "#9333ea",
"show_on_dashboard": true,
"auto_assign_rules": "[{\"id\":\"rule1\",\"field\":\"genre\",\"operator\":\"equals\",\"value\":\"Sci-Fi\",\"priority\":1}]",
"manual_book_ids": ["uuid1", "uuid2"],
"view_settings": {}
}
Response: Returns the created collection object.
Frontend Implementation
Route: /custom-section
Template: templates/custom_section.templ
TypeScript: web/src/custom-section-builder.ts
Key features:
- 14 filter fields with various operators
- Live preview functionality
- Search + multi-select for manual book addition
- AND/OR logic support for combining rules
Example Use Cases
Sci-Fi Favorites
{
"rules": [
{
"field": "genre",
"operator": "equals",
"value": "Sci-Fi"
}
]
}
High Rated Books
{
"rules": [
{
"field": "rating",
"operator": "greater_than",
"value": "4"
}
]
}
Long Books (Manual Selection)
{
"manual_book_ids": ["uuid1", "uuid2", "uuid3"]
}
Recently Finished Audiobooks
{
"rules": [
{
"field": "format",
"operator": "equals",
"value": "Audiobook"
},
{
"field": "last_read",
"operator": "last_x_days",
"value": "30"
}
]
}