feat(176): visual rule builder for the SmartCollection editor #433

Merged
timothy merged 22 commits from feat/176-smartcollection-rule-builder into main 2026-07-18 03:30:24 +02:00
Owner

Closes the remaining leg of #176 — the visual rule builder in the SmartCollection editor. (Autotune #69 and the composite Create-Channel wizard #63 already shipped; this is the third leg.)

What shipped

  • Backend: read-only GET /api/v1/search/fields — a curated, typed, labeled field catalog (name/label/type/group/values) derived from the Lucene index; single source of truth for the builder, MCP-introspectable. No DB change, no write-path change.
  • Client (web/src/builder/rules/): a Kodi-style one-level-nesting rule model that compiles to a closed subset of the Lucene grammar and parses exactly that back (exact inverses; out-of-subset → raw-text fallback). Round-trip proven by a 500-tree property test incl. Lucene special chars. useSearchFields hook + controlled RuleBuilder component.
  • Integration: SmartDialog gains a Builder | Advanced toggle; the compiled Lucene string stays the single submitted value (SmartCollection still stores a plain query string).

Design (see docs/decisions.md 2026-07-18)

Compile-only, no stored AST, no schema change — an authoritative AST would still need a Lucene→rules parser for every pre-existing/Auto-Tune query, buying little for a dual-provider migration cost.

Verification

  • Full local: dotnet build 0/0, backend handler tests 3/3, web suite 826/826, tsc/lint/check:api green, no BOM. Rebased on origin/main; OpenAPI artifacts regenerated (no delta).
  • Adversarial whole-branch review (Opus) → MERGEABLE-WITH-FIXES; the one Important (date fields compiled yyyy-MM-dd vs the index's yyyyMMdd lexicographic format) is fixed (compile→yyyyMMdd, parse reformats) + re-reviewed CLEAN.

Deferred (tracked as separate follow-ups)

facet-value typeahead · relative dates · nesting >1 level · ChannelBuilder/Auto-Tune inline adoption · rule-builder input validation/polish.

fixes #176

🤖 Generated with Claude Code

Closes the remaining leg of **#176** — the **visual rule builder** in the SmartCollection editor. (Autotune #69 and the composite Create-Channel wizard #63 already shipped; this is the third leg.) ## What shipped - **Backend**: read-only `GET /api/v1/search/fields` — a curated, typed, labeled field catalog (name/label/type/group/values) derived from the Lucene index; single source of truth for the builder, MCP-introspectable. No DB change, no write-path change. - **Client** (`web/src/builder/rules/`): a Kodi-style **one-level-nesting** rule model that **compiles to a closed subset** of the Lucene grammar and **parses exactly that back** (exact inverses; out-of-subset → raw-text fallback). Round-trip proven by a 500-tree property test incl. Lucene special chars. `useSearchFields` hook + controlled `RuleBuilder` component. - **Integration**: `SmartDialog` gains a `Builder | Advanced` toggle; the compiled Lucene string stays the single submitted value (SmartCollection still stores a plain query string). ## Design (see docs/decisions.md 2026-07-18) Compile-only, **no stored AST, no schema change** — an authoritative AST would still need a Lucene→rules parser for every pre-existing/Auto-Tune query, buying little for a dual-provider migration cost. ## Verification - Full local: dotnet build 0/0, backend handler tests 3/3, web suite 826/826, tsc/lint/check:api green, no BOM. Rebased on origin/main; OpenAPI artifacts regenerated (no delta). - Adversarial whole-branch review (Opus) → MERGEABLE-WITH-FIXES; the one Important (date fields compiled `yyyy-MM-dd` vs the index's `yyyyMMdd` lexicographic format) is **fixed** (compile→`yyyyMMdd`, parse reformats) + re-reviewed CLEAN. ## Deferred (tracked as separate follow-ups) facet-value typeahead · relative dates · nesting >1 level · ChannelBuilder/Auto-Tune inline adoption · rule-builder input validation/polish. fixes #176 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Review-verdict: MERGEABLE @ e38019c46e297206b9db7525ea30f905b141b530

Adversarial whole-branch review (Opus, cold context) → MERGEABLE-WITH-FIXES; the one Important finding (date fields compiled yyyy-MM-dd while the Lucene index stores yyyyMMdd with lexicographic range matching, silently excluding same-year items) was fixed (compile→yyyyMMdd, parse reformats only for date fields) and re-reviewed CLEAN. Minor items (empty-startsWith fallback, single-child-group cosmetic match flip, between value2 validation, pure-negative queries) deferred to a tracked follow-up. Per-task adversarial reviews passed for all 8 tasks (one Critical caught+fixed in the compile/parse layer: fulltext operator split + total escaping). Rebased onto origin/main with no code delta; OpenAPI artifacts regenerated identical. Full local verification green (build 0/0, backend 3/3, web 826/826, tsc/lint/check:api).

Review-verdict: MERGEABLE @ e38019c46e297206b9db7525ea30f905b141b530 Adversarial whole-branch review (Opus, cold context) → MERGEABLE-WITH-FIXES; the one Important finding (date fields compiled `yyyy-MM-dd` while the Lucene index stores `yyyyMMdd` with lexicographic range matching, silently excluding same-year items) was **fixed** (compile→`yyyyMMdd`, parse reformats only for date fields) and **re-reviewed CLEAN**. Minor items (empty-startsWith fallback, single-child-group cosmetic match flip, between value2 validation, pure-negative queries) deferred to a tracked follow-up. Per-task adversarial reviews passed for all 8 tasks (one Critical caught+fixed in the compile/parse layer: fulltext operator split + total escaping). Rebased onto origin/main with no code delta; OpenAPI artifacts regenerated identical. Full local verification green (build 0/0, backend 3/3, web 826/826, tsc/lint/check:api).
timothy force-pushed feat/176-smartcollection-rule-builder from e38019c46e to 3b5a78a08f 2026-07-18 02:44:37 +02:00 Compare
Author
Owner

Review-verdict: MERGEABLE @ 3b5a78a08f

Re-based onto the advanced origin/main (6204e30c); the only conflicts were both-added doc entries in decisions.md and spa-conventions.md (the parallel #386 session appended too) — resolved by keeping both and renumbering my rule-builder section to spa-conventions §12. No code changed — the feature commits are byte-identical to the previously-reviewed head (whole-branch Opus review MERGEABLE + date-fix re-review CLEAN still stand). Local re-verify green (tsc/lint/check:api, no BOM, no markers).

Review-verdict: MERGEABLE @ 3b5a78a08f36f9f4883bb2d67bb3f6e98374c5cf Re-based onto the advanced `origin/main` (6204e30c); the only conflicts were **both-added doc entries** in `decisions.md` and `spa-conventions.md` (the parallel #386 session appended too) — resolved by keeping both and renumbering my rule-builder section to spa-conventions §12. **No code changed** — the feature commits are byte-identical to the previously-reviewed head (whole-branch Opus review MERGEABLE + date-fix re-review CLEAN still stand). Local re-verify green (tsc/lint/check:api, no BOM, no markers).
timothy merged commit 0a418a7c53 into main 2026-07-18 03:30:24 +02:00
timothy deleted branch feat/176-smartcollection-rule-builder 2026-07-18 03:30:27 +02:00
Sign in to join this conversation.