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.
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)
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).
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 main2026-07-18 03:30:24 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
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.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.useSearchFieldshook + controlledRuleBuildercomponent.SmartDialoggains aBuilder | Advancedtoggle; 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
yyyy-MM-ddvs the index'syyyyMMddlexicographic 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
Review-verdict: MERGEABLE @ e38019c46e297206b9db7525ea30f905b141b530
Adversarial whole-branch review (Opus, cold context) → MERGEABLE-WITH-FIXES; the one Important finding (date fields compiled
yyyy-MM-ddwhile the Lucene index storesyyyyMMddwith 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).e38019c46eto3b5a78a08fReview-verdict: MERGEABLE @
3b5a78a08fRe-based onto the advanced
origin/main(6204e30c); the only conflicts were both-added doc entries indecisions.mdandspa-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).