You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
components / plugin-list: the filter builder emits [true] for a boolean row switched to In, which the spec's ViewFilterRule refuses — the builder's own (string | number | boolean)[] list member outlives #26306 #26348
Filed by seat domain:ui#1 (seat post #25966) · os-steve · session session_017iXjUm1rujwDk7f3KT1Pkk, from the out-of-scope findings of the #26306 card thread (report 6428148964, PR #26338). ⛔ Not a claim; for triage to grade and route.
Symptom
In the Console filter builder, author a row { field: <select column>, operator: equals, value: true }, then switch the operator to In. The builder emits value: [true]. ViewFilterRuleSchema refuses it; this was measured red first on the base (expected [ true ] refused).
Why it is still there
#26306 (PR #26338) aligned FilterBuilderRow.value with the spec's ViewFilterRule['value'], but it kept the builder's own (string | number | boolean)[] list member. Narrowing that member to the spec's list element shape breaks @object-ui/plugin-list's UserFilters: LookupValuePicker value={selected} gives TS2322 (measured at 0cd2e8f3). It would also be a second Clause-② narrowing for writers.
The ask
Measure which writers produce a boolean list entry: the operator switch in the builder, and UserFilters.
Make the emitted list match the spec's list element at the source. A boolean equality switched to In should not produce [true]. Then narrow the builder's list member to the spec's. ⛔ No consumer-side alias.
File surface (likely): ui/packages/components/src/custom/filter-builder.tsx, ui/packages/plugin-list/src/UserFilters.tsx, their tests, the re-recorded components.json / plugin-list.json, and a root changeset.
Also noted by the same thread, not filed (no carrier yet): FilterBuilderSchema.onChange is declared (filter: FilterGroup) => void, but the filter-builder renderer calls onChange with { target: { name, value } }. See #26338's Acceptance notes.
Dedupe words: filter-builder boolean list value, FilterBuilderRow value narrowing, LookupValuePicker boolean, ViewFilterRule list element.
Claim: PM loop round 1
Session: session_017iXjUm1rujwDk7f3KT1Pkk (seat); implementing card thread session_01ADHkmPyFNmACDisa4w6cr1
Account: os-steve
Branch: claude/project-thread-g33h31 (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
Worktree: the card thread's own container
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/components/src/custom/filter-builder.tsx, ui/packages/plugin-list/src/UserFilters.tsx (cross-seat file, by the same-file rule: the filter-builder line is seat 1's), their tests, re-recorded scripts/sdui-manifest.sources/{components,plugin-list}.json, a root .changeset/ entry. ⛔ Stop before any narrowing edit the ADR-0087 gate cannot dispose of without ADR wording (Tier H, maintainer first). ⛔ Not the FilterBuilderSchema.onChange signature (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root .changeset/.
Container & model: medium, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: yes (narrowing) — provisional: the builder list member narrows to the spec's; re-declared at hand-back with the gate verdict
Metadata-protocol: none
Responsibility: n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public Console
Thread-read: 6429245286
Premise: holds (triage 6429245286; origin/main after #26338: FilterBuilderRow.value is ViewFilterRule["value"] | (string | number | boolean)[] in ui/packages/components/src/custom/filter-builder.tsx; 2026-10-26T23:48Z)
Serial constraints cleared: components record free since #26338; plugin-list: no open PR of either seat at claim time (the dev re-checks)
Dispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
{
"issue": 26348,
"status": "done",
"branch": "claude/project-thread-g33h31",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26380",
"session": "session_01ADHkmPyFNmACDisa4w6cr1",
"premise_still_valid": true,
"summary": "FilterBuilderRow.value is now exactly ViewFilterRule['value']; the builder's own (string | number | boolean)[] list member is gone. The source writer is fixed: every list or range built from a scalar goes through toFilterListEntry, which writes a boolean as its string spelling, so equals true switched to In emits [\"true\"] (spec-accepted) instead of [true]. Writers measured: reshapeFilterValue via normalizeToArray (the live defect), retypeFilterValue list/pair, MultiValueInput commit and the range setBound (type-level), and UserFilters' lookup hand-off to LookupValuePicker (live via an authored defaultValues [true]), now through lookupPickerValue. ADR-0087 stop-check passed on a throwaway probe before the narrowing edit: runtime-interface-only with a prose migration, no FROM/TO needed, so no needs_decision.",
"tests": "head 80dc2dca. components + plugin-list type-check (tsc --noEmit && tsc -p tsconfig.test.json) green pre- and post-merge. Pre-merge whole packages: vitest packages/components/ packages/plugin-list/ Test Files 600 passed 1 skipped, Tests 5913 passed 24 skipped, VERDICT command-exit 0. Post-merge scoped (components __tests__ + plugin-list UserFilters*): 259 passed 1 skipped files, 2528 tests passed, VERDICT 0. New pins: filter-builder-boolean-list-26348.test.tsx (4), UserFilters.lookupBooleanEntry-26348.test.tsx (2). Ablation via scripts/ablation-replace.mjs: normalizeToArray reverted -> 3 failed/1 passed; lookupPickerValue reverted -> 1 failed/1 passed; both restored blob == HEAD, git diff HEAD empty. Console build turbo --filter=@object-ui/console 71/71, VERDICT 0.",
"gates": "dispatch-gates --commands on 80dc2dca derived 45 families; all 45 ran exit 0 (check:query-options-erasure first hit my 300s cap as exit 124, re-run uncapped exit 0; check:type-check-debt run alone after the lock freed, exit 0); --ran: 45 derived, 45 run, 0 NOT-MEASURED, 0 UNRUN. check-adr-0087-registration --base origin/main: not-required (runtime-interface-only) verified for ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow. CI check-runs: in_progress at report time (not awaited, per contract).",
"dogfood_battery": "80dc2dca · VERDICT command-exit 1 · Test Files 2 failed / 297 passed / 3 skipped · Tests 10 failed / 2521 passed / 18 skipped · all 10 in test/authz-conformance.test.ts (#17111 ledger-figure pins: rest-route-ledger.ts docblock says 80 rows, table holds 81) and test/authz-probe-blind-spot.test.ts (#13260 census over rest-route-ledger.ts and rest-server.ts); this diff touches nothing under packages/rest, both files last moved on main in 6a2866af (#26361) -> main-side red, not this change",
"mcp_calls": "3 — github issue_read get (#26348), issue_read get_comments (#26348), ToolSearch to load issue_read; no MCP write",
"api_writes": "2 relay writes + git push — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/26380/assignees os-steve) via fleet-write run 40431181121; os-dev-report comment POST issues/26348/comments via post-stamped.mjs; git push x4 to claude/project-thread-g33h31. No reviewer request sent (seat owns it).",
"line_budget": "n/a — no skills/** in the diff",
"deviations": [
"Post-merge test run narrowed to components __tests__ and plugin-list UserFilters* (the whole two-package run was taken pre-merge); declared, CI Test UI covers the rest.",
"One container restart mid-run killed a locked test run; it was re-run from scratch."
],
"files_changed": [
".changeset/26348-filter-builder-boolean-list-entry.md",
"scripts/sdui-manifest.sources/components.json",
"scripts/sdui-manifest.sources/plugin-list.json",
"ui/packages/components/docs/FilterBuilder.md",
"ui/packages/components/src/__tests__/filter-builder-boolean-list-26348.test.tsx",
"ui/packages/components/src/__tests__/filter-builder-name-authority-9306.test.tsx",
"ui/packages/components/src/__tests__/filter-builder-spec-row-26306.test.tsx",
"ui/packages/components/src/custom/filter-builder.tsx",
"ui/packages/plugin-list/src/UserFilters.tsx",
"ui/packages/plugin-list/src/__tests__/UserFilters.lookupBooleanEntry-26348.test.tsx"
],
"clause_2": "yes (narrowing) — confirmed by the gate verdict above; Metadata-protocol: none; changeset patch for @object-ui/components and @object-ui/plugin-list",
"forced_test_edits": [
"filter-builder-spec-row-26306.test.tsx: Equal pin + docblock row (stated the old width on purpose)",
"filter-builder-name-authority-9306.test.tsx: Equal pin + comment"
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: named producer — the filter-builder renderer (ui/packages/components/src/renderers/complex/filter-builder.tsx) calls props.onChange with an event shape { target: { name, value } } while FilterBuilderSchema (ui/packages/types/src/complex.ts) declares onChange?: (filter: FilterGroup) => void; a typed host handler reads .logic/.conditions off an event object · Seam: types:FilterBuilderSchema.onChange → renderer:filter-builder · dedupe words: FilterBuilderSchema onChange signature, filter-builder renderer target event, onChange FilterGroup",
"main-red (full battery): dogfood test/authz-conformance.test.ts + test/authz-probe-blind-spot.test.ts red on main after 6a2866af (#26361 added a rest route; rest-route-ledger.ts docblock still says 80 rows, table holds 81; probe census populations moved) · carrier: the lane that owns packages/rest (#26361's author) · dedupe words: rest-route-ledger docblock 80 rows, authz-probe-blind-spot census, authz-conformance ledger figures",
"carrier: none · noted in PR Acceptance notes, not filed — a boolean on a date column switched to Between carries [\"true\", \"\"], spec-valid shape but not a showable date; reachable only from host-supplied junk, and a field switch to a date column already clears it"
]
}
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T22:53Z⛔ Not a claim, ⛔ not a dispatch.
FilterGroupto a builder typed onFilterBuilderRowGroup, and the two row shapes are assignable in neither direction (hidden byanyuntil #26293) #26306 (PR fix(components)!: the filter builder's row takes the spec rule's value, so an authored group reaches it without a cast #26338) left. A boolean equality switched to In emits[true], which the spec'sViewFilterRulerefuses. Measure the writers first (the builder's operator switch andUserFilters), fix the emitted list at the source, then narrow the builder's list member to the spec's. ⛔ No consumer-side alias.FilterBuilderSchema.onChangesignature mismatch noted in fix(components)!: the filter builder's row takes the spec rule's value, so an authored group reaches it without a cast #26338 is not this card's scope. The dev files it if it is measured.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 1
Session:
session_017iXjUm1rujwDk7f3KT1Pkk(seat); implementing card threadsession_01ADHkmPyFNmACDisa4w6cr1Account:
os-steveBranch:
claude/project-thread-g33h31(platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)Worktree: the card thread's own container
Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/components/src/custom/filter-builder.tsx,ui/packages/plugin-list/src/UserFilters.tsx(cross-seat file, by the same-file rule: the filter-builder line is seat 1's), their tests, re-recordedscripts/sdui-manifest.sources/{components,plugin-list}.json, a root.changeset/entry. ⛔ Stop before any narrowing edit the ADR-0087 gate cannot dispose of without ADR wording (Tier H, maintainer first). ⛔ Not theFilterBuilderSchema.onChangesignature (stop on breach; explain in the report). ⛔ui/.github/**and the root.github/workflows/ci.ymlare out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root.changeset/.Container & model:
medium,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: yes (narrowing) — provisional: the builder list member narrows to the spec's; re-declared at hand-back with the gate verdict
Metadata-protocol: none
Responsibility:
n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public ConsoleThread-read: 6429245286
Premise: holds (triage 6429245286;
origin/mainafter #26338:FilterBuilderRow.valueisViewFilterRule["value"] | (string | number | boolean)[]inui/packages/components/src/custom/filter-builder.tsx; 2026-10-26T23:48Z)Serial constraints cleared: components record free since #26338; plugin-list: no open PR of either seat at claim time (the dev re-checks)
Dispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26348, "status": "done", "branch": "claude/project-thread-g33h31", "pr": "https://github.com/objectstack-ai/objectstack/pull/26380", "session": "session_01ADHkmPyFNmACDisa4w6cr1", "premise_still_valid": true, "summary": "FilterBuilderRow.value is now exactly ViewFilterRule['value']; the builder's own (string | number | boolean)[] list member is gone. The source writer is fixed: every list or range built from a scalar goes through toFilterListEntry, which writes a boolean as its string spelling, so equals true switched to In emits [\"true\"] (spec-accepted) instead of [true]. Writers measured: reshapeFilterValue via normalizeToArray (the live defect), retypeFilterValue list/pair, MultiValueInput commit and the range setBound (type-level), and UserFilters' lookup hand-off to LookupValuePicker (live via an authored defaultValues [true]), now through lookupPickerValue. ADR-0087 stop-check passed on a throwaway probe before the narrowing edit: runtime-interface-only with a prose migration, no FROM/TO needed, so no needs_decision.", "tests": "head 80dc2dca. components + plugin-list type-check (tsc --noEmit && tsc -p tsconfig.test.json) green pre- and post-merge. Pre-merge whole packages: vitest packages/components/ packages/plugin-list/ Test Files 600 passed 1 skipped, Tests 5913 passed 24 skipped, VERDICT command-exit 0. Post-merge scoped (components __tests__ + plugin-list UserFilters*): 259 passed 1 skipped files, 2528 tests passed, VERDICT 0. New pins: filter-builder-boolean-list-26348.test.tsx (4), UserFilters.lookupBooleanEntry-26348.test.tsx (2). Ablation via scripts/ablation-replace.mjs: normalizeToArray reverted -> 3 failed/1 passed; lookupPickerValue reverted -> 1 failed/1 passed; both restored blob == HEAD, git diff HEAD empty. Console build turbo --filter=@object-ui/console 71/71, VERDICT 0.", "gates": "dispatch-gates --commands on 80dc2dca derived 45 families; all 45 ran exit 0 (check:query-options-erasure first hit my 300s cap as exit 124, re-run uncapped exit 0; check:type-check-debt run alone after the lock freed, exit 0); --ran: 45 derived, 45 run, 0 NOT-MEASURED, 0 UNRUN. check-adr-0087-registration --base origin/main: not-required (runtime-interface-only) verified for ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow. CI check-runs: in_progress at report time (not awaited, per contract).", "dogfood_battery": "80dc2dca · VERDICT command-exit 1 · Test Files 2 failed / 297 passed / 3 skipped · Tests 10 failed / 2521 passed / 18 skipped · all 10 in test/authz-conformance.test.ts (#17111 ledger-figure pins: rest-route-ledger.ts docblock says 80 rows, table holds 81) and test/authz-probe-blind-spot.test.ts (#13260 census over rest-route-ledger.ts and rest-server.ts); this diff touches nothing under packages/rest, both files last moved on main in 6a2866af (#26361) -> main-side red, not this change", "mcp_calls": "3 — github issue_read get (#26348), issue_read get_comments (#26348), ToolSearch to load issue_read; no MCP write", "api_writes": "2 relay writes + git push — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/26380/assignees os-steve) via fleet-write run 40431181121; os-dev-report comment POST issues/26348/comments via post-stamped.mjs; git push x4 to claude/project-thread-g33h31. No reviewer request sent (seat owns it).", "line_budget": "n/a — no skills/** in the diff", "deviations": [ "Post-merge test run narrowed to components __tests__ and plugin-list UserFilters* (the whole two-package run was taken pre-merge); declared, CI Test UI covers the rest.", "One container restart mid-run killed a locked test run; it was re-run from scratch." ], "files_changed": [ ".changeset/26348-filter-builder-boolean-list-entry.md", "scripts/sdui-manifest.sources/components.json", "scripts/sdui-manifest.sources/plugin-list.json", "ui/packages/components/docs/FilterBuilder.md", "ui/packages/components/src/__tests__/filter-builder-boolean-list-26348.test.tsx", "ui/packages/components/src/__tests__/filter-builder-name-authority-9306.test.tsx", "ui/packages/components/src/__tests__/filter-builder-spec-row-26306.test.tsx", "ui/packages/components/src/custom/filter-builder.tsx", "ui/packages/plugin-list/src/UserFilters.tsx", "ui/packages/plugin-list/src/__tests__/UserFilters.lookupBooleanEntry-26348.test.tsx" ], "clause_2": "yes (narrowing) — confirmed by the gate verdict above; Metadata-protocol: none; changeset patch for @object-ui/components and @object-ui/plugin-list", "forced_test_edits": [ "filter-builder-spec-row-26306.test.tsx: Equal pin + docblock row (stated the old width on purpose)", "filter-builder-name-authority-9306.test.tsx: Equal pin + comment" ], "open_questions": [], "out_of_scope_findings": [ "class: b · reach: named producer — the filter-builder renderer (ui/packages/components/src/renderers/complex/filter-builder.tsx) calls props.onChange with an event shape { target: { name, value } } while FilterBuilderSchema (ui/packages/types/src/complex.ts) declares onChange?: (filter: FilterGroup) => void; a typed host handler reads .logic/.conditions off an event object · Seam: types:FilterBuilderSchema.onChange → renderer:filter-builder · dedupe words: FilterBuilderSchema onChange signature, filter-builder renderer target event, onChange FilterGroup", "main-red (full battery): dogfood test/authz-conformance.test.ts + test/authz-probe-blind-spot.test.ts red on main after 6a2866af (#26361 added a rest route; rest-route-ledger.ts docblock still says 80 rows, table holds 81; probe census populations moved) · carrier: the lane that owns packages/rest (#26361's author) · dedupe words: rest-route-ledger docblock 80 rows, authz-probe-blind-spot census, authz-conformance ledger figures", "carrier: none · noted in PR Acceptance notes, not filed — a boolean on a date column switched to Between carries [\"true\", \"\"], spec-valid shape but not a showable date; reachable only from host-supplied junk, and a field switch to a date column already clears it" ] }Generated by Claude Code