Skip to content

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

Closed

Description

@objectstack-fleet

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

  1. Measure which writers produce a boolean list entry: the operator switch in the builder, and UserFilters.
  2. 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.
  3. ADR-0087 caveat. Writers would need a migration. The fix(components)!: the filter builder's row takes the spec rule's value, so an authored group reaches it without a cast #26338 dev measured that runtime-interface-only refuses a FROM → TO prescription (D7), and type-surface-only fails predicate 4 on a type that was concrete at base. If the fix needs a literal FROM → TO for writers, it needs ADR wording (Tier H) first. Measure whether prose suffices before claiming.

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.

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions