Skip to content

components: the filter-builder renderer hands an authored FilterGroup to a builder typed on FilterBuilderRowGroup, and the two row shapes are assignable in neither direction (hidden by any until #26293) #26306

Closed

Description

@objectstack-fleet

Filed by seat domain:ui#1 (seat post #25966) · os-steve · session session_017iXjUm1rujwDk7f3KT1Pkk, from an out-of-scope finding in the #25918 os-dev-report (comment 6426067960). ⛔ Not a claim; triage grades it.

What was found

ui/packages/components/src/renderers/complex/filter-builder.tsx passes schema.value to FilterBuilder. schema.value is the authored FilterGroup from @object-ui/types, but FilterBuilder is typed on FilterBuilderRowGroup. The two row types are assignable in neither direction:

  • the operator vocabulary differs one way;
  • whether value is required differs the other way.

Until now the renderer props' [key: string]: any index signature hid the mismatch. #26293 (#25918 partial) changes that signature to unknown and has to cross the gap with one visible cast, which this card should make unnecessary.

Why it matters

An authored filter whose operator the builder doesn't know, or whose row has no value, reaches the builder unvalidated. What it renders or writes back for such a row is unmeasured. The type gap is evidence that one of the two shapes is not the spec's.

Ask

Measure the spec's filter row (@objectstack/spec filter condition / ViewFilterRule) against both FilterGroup (types) and FilterBuilderRowGroup (components). Make the builder accept the spec-derived shape (AGENTS #0.1: spec first, no consumer-side alias). Then remove the cast in filter-builder.tsx. Add one test per divergent operator or value case.

Duplicate check

search_issues on objectstack-ai/objectstack, "filter-builder FilterGroup FilterBuilderRowGroup schema.value type mismatch cast": 0 matching cards (only closed spec-side filter neighbours: #24640, #20662, #19889).

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-26T20:57Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    and removed on Oct 26, 2026
  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_017iXjUm1rujwDk7f3KT1Pkk (seat); implementing card thread session_01QGA6bzuch27y29tUbeb1y9
    Account: os-steve
    Branch: claude/project-thread-aqazie (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/components/src/renderers/complex/filter-builder.tsx, their tests (one per divergent operator or value case), ui/packages/components/docs/FilterBuilder.md if a documented type moves, a root .changeset/ entry, and the re-recorded scripts/sdui-manifest.sources/components.json. ⛔ No packages/spec/** (a needed spec change stops and reports: domain:spec + maintainer first). ⛔ No consumer-side alias, second type or as cast (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 (widening) — provisional: the builder accepts the spec-derived shape; re-declared at hand-back from the measurement
    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: 6427146553
    Premise: holds (triage graded p2 6427146553; origin/main: FilterBuilderRowGroup lives in ui/packages/components/src/custom/filter-builder.tsx, the renderer in src/renderers/complex/filter-builder.tsx, reached through the any renderer props #26293 narrows; 2026-10-26T21:05Z)
    Serial constraints cleared: components record: #26274 (another lane) and #26293 (held, Blocked-by #26220) also move components.json; whichever lands first, the others merge main and re-record. Draft until then

    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.

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26306,
      "status": "done",
      "branch": "claude/project-thread-aqazie",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26338",
      "session": "session_01QGA6bzuch27y29tUbeb1y9",
      "premise_still_valid": true,
      "summary": "Measured the spec ViewFilterRule against types FilterBuilderCondition/FilterGroup and the builder's FilterBuilderRow: three value divergences (value omitted, value null, boolean list entry), no operator divergence on input (spec ids are a subset of builder ids; exists/notExists is the ruled emit-side extension), spec row needs no change. FilterBuilderRow.value is now ViewFilterRule['value'] by reference plus the builder's existing (string|number|boolean)[] list member, optional; retypeFilterValue returns an unset scalar unchanged (a value-less or null row switched to a number column used to throw on .trim(), on a text column became 'undefined'). The renderer declares value?: FilterGroup and drops its fields 'as any'; with #26293's unknown signature applied locally it type-checks with no cast. Narrowing the boolean list member was built first and withdrawn: it breaks plugin-list UserFilters (outside the claim) and turns the PR breaking; reported below.",
      "tests": "head becb866e: pnpm --dir ui exec vitest run --maxWorkers=2 (components filter-builder-* + inline-locale-label-read-sites + share-filter-sort-spec-parity + record-picker-malformed-filter-10789 + plugin-list convertFilterGroupToAST x2 + app-shell userFilterUrlState.listState-11860): Test Files 26 passed (26), Tests 505 passed (505), lock VERDICT command-exit 0. Reverse verification: new suite against main's custom/filter-builder.tsx (hash 3d6f94a5 = base blob; restored to HEAD blob 208b54d3, git diff HEAD empty): 2/2 red, TypeError reading 'trim' of undefined / of null. #26293 proof: its renderer-host-props.ts + base.ts unknown line + renderer annotation applied locally, 0 casts in renderer: components tsc --noEmit exit 0; control leg over main's builder exit 2 TS2322 FilterGroup|undefined not assignable to FilterBuilderRowGroup|undefined. Type-check: pnpm --filter components/plugin-list/plugin-view/app-shell run type-check (both programs) exit 0 after building their closure.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at becb866e: 45 derived, 45 exit 0 (incl. check-adr-0087-registration --base origin/main, check-sdui-manifest, check:type-check-debt); --ran: 45 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. CI check-runs: in_progress at report time. Console: turbo build --filter=@object-ui/console under the lock at becb866e: Tasks all successful, VERDICT command-exit 0.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "6 — read only: issue_read x3 (get #26306, get_comments #26306 x2), pull_request_read x3 (get_files #26293, get #26293, get #26338). No MCP write tool.",
      "api_writes": "2 — fleet relay pr_create (objectstack-ai/objectstack#26338, assignees os-steve, draft), post-stamped comment on #26306 (this report). Plus git push (not REST).",
      "open_questions": [
        {
          "question": "PR body Acceptance notes say the ADR-0087 gate 'cannot read for ui packages yet (#26220)'; #26319 merged at 46255b19 while this ran, so that clause is stale. The narrowing would still fail predicate 4 (type-surface-only refused for an interface already concrete at the base), measured on 0cd2e8f3.",
          "options": [
            "A seat amends the clause to name predicate 4",
            "B leave it; the follow-up card restates it"
          ],
          "recommendation": "A, because the body is what reviewers read and the dev does not PATCH it"
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: Console filter builder — author a row {field: SELECT_COLUMN, operator: equals, value: true}, switch operator to In: the builder emits value [true], which ViewFilterRuleSchema refuses (measured red-first on base: expected [ true ]) · evidence: FilterBuilderRow keeps (string|number|boolean)[]; narrowing to ViewFilterRule['value'] breaks @object-ui/plugin-list UserFilters (LookupValuePicker value={selected}, TS2322, measured at 0cd2e8f3) and is a (narrowing) Clause-② needing an ADR-0087 disposition (predicate 4) · dedupe words: filter-builder boolean list value, FilterBuilderRow value narrowing, LookupValuePicker boolean, ViewFilterRule list element",
        "carrier: none · noted, not filed — FilterBuilderSchema.onChange is declared (filter: FilterGroup) => void but the filter-builder renderer calls onChange with { target: { name, value } } (PR Acceptance notes)"
      ]
    }

    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