Skip to content

fix(components)!: the filter builder's list value holds the spec list's entries, so a boolean switched to In is a rule the spec accepts - #26380

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-g33h31
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-g33h31

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #26348

Clause-②: yes (narrowing)
Metadata-protocol: none

维护者速读

What changes

  • FilterBuilderRow.value (ui/packages/components/src/custom/filter-builder.tsx) is now exactly ViewFilterRule['value']. The builder's own wider list member (string | number | boolean)[] is gone. A module-private FilterListEntry reads the list element from the spec's value type instead of restating it.
  • The source writer. Every list or range the builder builds from a scalar goes through one conversion, toFilterListEntry: a boolean becomes its string spelling (true becomes "true"). It is called by normalizeToArray (so reshapeFilterValue's list and pair arms), by retypeFilterValue's list and pair arms, by MultiValueInput's commit and by the range input's setBound. That is the conversion the builder already made for a boolean that reaches a text column, and every bucket offering a list operator is a text-family column (select / lookup). The list controls compare entries with String(), so the option whose value is "true" stays ticked.
  • UserFilters (ui/packages/plugin-list/src/UserFilters.tsx) hands a lookup chip's selection to LookupValuePicker through lookupPickerValue, the same conversion. The selection map stays typed for every control kind, because a boolean chip's selection really is [true]. The two casts in the picker's onChange are gone, since the narrowed value no longer needs them.
  • docs/FilterBuilder.md shows the new member. No sdui record. The per-package records were retired by ci(sdui): retire the per-package SDUI manifest records; CI's regeneration holds freshness (#26137 PR B) #26367 while this PR was open. The main merge took the modify/delete with git rm, and CI's Build & E2E UI regenerates the manifest. Changeset: .changeset/26348-filter-builder-boolean-list-entry.md (patch for both packages, BREAKING banner, prose migration for writers).

Writers measured (the card's step 1)

writer produced a boolean list entry? now
operator switch, changeOperator → reshapeFilterValue → normalizeToArray yes: equals true → in gave [true]; → between gave [true, ""] ["true"], ["true", ""]
field switch, retypeFilterValue list / pair arms type-level only: a text target already stringified, the boolean family has no list operator typed through toFilterListEntry
MultiValueInput commit, range setBound type-level only: their inputs hand back strings and numbers typed through toFilterListEntry
checkbox list, LookupValuePicker.toggleId no: option values and ids are strings and numbers unchanged
UserFilters lookup chip → LookupValuePicker yes: an authored defaultValues: [true] on a lookup filter field reached the query as [true, id] ["true", id]

The compiler confirmed the list: narrowing the member first left exactly one more site red (the range writer, TS2322), fixed here.

ADR-0087 stop-check (the card's step 3)

Run before the narrowing edit, against a throwaway probe commit (the narrowing line plus a probe changeset with a prose migration), with node scripts/check-adr-0087-registration.mjs --base origin/main --head PROBE_SHA: exit 0, not-required (runtime-interface-only) -- verified: ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow (interface). No literal FROM → TO is needed for writers, so no ADR wording and no decision were needed. The real changeset carries the same disposition and passes the gate on this head.

Tests

  • New ui/packages/components/src/__tests__/filter-builder-boolean-list-26348.test.tsx: type pins (FilterBuilderRow['value'] equals the spec value; a boolean list is a @ts-expect-error; a boolean scalar stays admitted), the schema refusing [true] and accepting ["true"] (non-vacuity), the real operator dropdown driving equals true → In and equals false → Not in with the emitted rule parsed by ViewFilterRuleSchema, and the helpers.
  • New ui/packages/plugin-list/src/__tests__/UserFilters.lookupBooleanEntry-26348.test.tsx: a lookup chip with an authored true default; a picked id joins it as ["true", "acc_1"]; string and number ids pass through untouched.
  • Forced test edits, both type pins that stated the old width on purpose so a narrowing would have to change them: filter-builder-spec-row-26306.test.tsx (the Equal pin and its docblock row) and filter-builder-name-authority-9306.test.tsx (the Equal pin and its comment).

Acceptance notes

  • A boolean on a date column switched to Between now carries ["true", ""]: a spec-valid shape, but not a date the range input can show. Reaching it needs a host to hand a date column a boolean in the first place, and a field switch to a date column already clears it. Noted, not filed (no defect class: the builder never authors that row).
  • FilterBuilderSchema.onChange signature mismatch (out of scope by the card). Measured on this head: FilterBuilderSchema in ui/packages/types/src/complex.ts declares onChange?: (filter: FilterGroup) => void, while the filter-builder renderer calls onChange with a { target: { name, value } } event shape. Reported to the seat in the report's out_of_scope_findings; not touched here.

Verification

All readings on head 80dc2dca (after merging origin/main at 6390fe52), under scripts/pm/os-verify-lock.sh.

Type-check and tests
  • pnpm --filter @object-ui/components type-check and pnpm --filter @object-ui/plugin-list type-check (both tsc --noEmit && tsc -p tsconfig.test.json): green before and after the merge. The new @ts-expect-error pin compiles only because the boolean list is refused.
  • Before the merge, both whole packages: pnpm --dir ui exec vitest run --maxWorkers=2 packages/components/ packages/plugin-list/: Test Files 600 passed, 1 skipped; Tests 5913 passed, 24 skipped; VERDICT command-exit 0.
  • After the merge, scoped (main moved components renderers): packages/components/src/__tests__/ plus packages/plugin-list/src/__tests__/UserFilters*: Test Files 259 passed, 1 skipped; Tests 2528 passed; VERDICT command-exit 0.
  • Console: pnpm exec turbo run build --filter=@object-ui/console --concurrency=2: 71 of 71 tasks, VERDICT command-exit 0.
Ablation (one-shot, not kept)

Through node scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty:

  • normalizeToArray back to keeping the boolean: filter-builder-boolean-list-26348.test.tsx goes 3 failed, 1 passed (the schema non-vacuity case stays green).
  • lookupPickerValue back to the identity: UserFilters.lookupBooleanEntry-26348.test.tsx goes 1 failed, 1 passed (the pass-through case stays green).
Gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 45 families on this head; all 45 ran with exit 0, and --ran reconciles 45 run, 0 NOT-MEASURED, 0 UNRUN. Among them node scripts/check-adr-0087-registration.mjs --base origin/main verifies the runtime-interface-only disposition, and node scripts/check-sdui-manifest.mjs is green on the re-recorded sources.
  • Every family CI runs is read from this PR's check-runs, not re-run here.
Dogfood battery (owed by the narrowing arm)

pnpm --filter @objectstack/dogfood test after building its closure: Test Files 2 failed, 297 passed, 3 skipped; Tests 10 failed, 2521 passed, 18 skipped. Every failure is in test/authz-conformance.test.ts (the #17111 ledger-figure pins: "packages/rest/src/rest-route-ledger.ts: docblock says 80 rows, the table holds 81") and test/authz-probe-blind-spot.test.ts (the #13260 census over rest-route-ledger.ts and rest-server.ts). This diff touches no file under packages/rest; both files last changed on main in #26361 (the record-anchored approval lock read), so this is a main-side red, reported to the seat, not fixed here.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmPyFNmACDisa4w6cr1

…'s entries, so a boolean switched to In is a rule the spec accepts

WIP: implementation and pins; changeset and manifest records follow.

Claude-Session: https://claude.ai/code/session_01ADHkmPyFNmACDisa4w6cr1
Co-authored-by: Claude <noreply@anthropic.com>
…-g33h31

# Conflicts:
#	scripts/sdui-manifest.sources/components.json
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-27T01:28Z

Head 80dc2dca (read from the PR object).

Landing: ready + auto-merge through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 01:29
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 01:29
…-g33h31

# Conflicts:
#	scripts/sdui-manifest.sources/components.json
#	scripts/sdui-manifest.sources/plugin-list.json
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-27T02:33Z

Head 2dfbe0bc (read from the PR object). It is 80dc2dca plus a merge of main abb90460, which takes #26367's retirement of the per-package records with git rm on components.json and plugin-list.json. Nothing was regenerated. Re-verified on this head: Console build 71/71; components and plugin-list type-check; scoped vitest 259 files / 2528 tests; check-sdui-manifest exit 0.

  • Shape. Draft on main, Fixes #26348. 9 files after the merge (no records):
    • filter-builder.tsx. FilterBuilderRow.value is now exactly ViewFilterRule['value']. The new FilterListEntry type reads the spec's list element rather than restating it. toFilterListEntry writes a boolean list or range entry as its string spelling at the source, so equals true switched to in now emits ["true"], which ViewFilterRuleSchema accepts. Before, it emitted [true], which the schema refuses.
    • UserFilters.tsx hands the lookup picker spec entries through lookupPickerValue.
    • The docs page, two new regression tests, two forced width pins (the 26306 and 9306 tests, named in the body) and a root changeset (patch for both packages with a BREAKING type note, as fix(components)!: the filter builder's row takes the spec rule's value, so an authored group reaches it without a cast #26338 did).
    • Clause-②: yes (narrowing), Metadata-protocol: none.
  • Checks pinned to the head. All check runs completed: 0 failing, 0 pending. The seat's landing script checked this before posting.
  • Governed surface. check-governed-merges.mjs --pr 26380: not governed.
  • ADR-0087. The claim's stop-check passed on a throwaway probe before the edit: runtime-interface-only, with a prose migration, so no ADR wording is owed.
  • Seat review. The diff only swaps the list member for the spec's element type and routes every list or range write through one converter. The string spelling matches what the builder already does for a boolean on a text column. Every list control compares entries with String(), so what is drawn doesn't change. The FilterBuilderSchema.onChange signature was kept out of bounds; the dev's finding is filed as components: the filter-builder renderer calls onChange with a { target: { name, value } } event, but FilterBuilderSchema.onChange declares (filter: FilterGroup) => void #26382.
  • Evidence (dev).
  • Serial constraints. With the records retired, only a source-file overlap serialises, and fix(types,components)!: renderer props index signatures map to unknown, not any #26293 (held on ADR-0087 D8) is the only open PR on a filter-builder file. The ⛔ paths are untouched.

Landing: ready + auto-merge through the relay.

Merged via the queue into main with commit 57ab06b Oct 27, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-g33h31 branch October 27, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants