Repository navigation
fix(components)!: the filter builder's row takes the spec rule's value, so an authored group reaches it without a cast - #26338
Merged
Conversation
Claude-Session: https://claude.ai/code/session_01QGA6bzuch27y29tUbeb1y9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGA6bzuch27y29tUbeb1y9 Co-authored-by: Claude <noreply@anthropic.com>
…lue widens to the spec's Narrowing the list member to the spec's is a breaking change to a published type with a consumer in @object-ui/plugin-list (UserFilters hands LookupValuePicker a boolean-bearing list); it is reported, not made here. Claude-Session: https://claude.ai/code/session_01QGA6bzuch27y29tUbeb1y9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGA6bzuch27y29tUbeb1y9 Co-authored-by: Claude <noreply@anthropic.com>
…ow.value Claude-Session: https://claude.ai/code/session_01QGA6bzuch27y29tUbeb1y9 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对 · ACCEPT + contract review · seat
|
This was referenced Oct 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Steve · project thread
Fixes #26306
Clause-②: yes (narrowing)
Metadata-protocol: none
BREAKING type-surface change, compile-time only.
FilterBuilderRowis exported from@object-ui/components(export * from './custom'). What the builder accepts widens; what a reader ofrow.value(a host'sonChangehandler) may assume narrows, because the member is now optional and admitsnull. The changeset carries the reader's migration and the ADR-0087 dispositionnot-required (runtime-interface-only ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow). It shipspatch(Metadata-protocol: none).What changed
The
filter-builderrenderer handsschema.value(the authoredFilterGroupfrom@object-ui/types) toFilterBuilder, which is typed on its ownFilterBuilderRowGroup. The builder's row now takes the spec rule's value by reference, so the authored group is assignable to the builder's group and the renderer needs no cast.FilterBuilderRow.value(ui/packages/components/src/custom/filter-builder.tsx):ViewFilterRule['value']from@objectstack/spec/ui, plus the builder's own list member(string | number | boolean)[], and optional. It wasstring | number | boolean | (string | number | boolean)[], required.retypeFilterValue: a scalar with no value (undefined,null,"") is returned as it is instead of being converted.isValueUnsetbecame a type predicate, which also removes the cast insidenormalizeToArray.src/renderers/complex/filter-builder.tsx): declaresvalue?: FilterGroupon its props, because a host that binds the node forwards the group it holds. Both casts go:valuewas never cast onmain(theanyindex signature carried it), andfields={(schema.fields || []) as any}becomesfields={schema.fields || []}(FilterField[]was already assignable).docs/FilterBuilder.mdshows the new member. Changeset:.changeset/26306-filter-builder-spec-row-value.md(patch, BREAKING banner, migration for readers ofrow.value).Measurement: the spec's rule against both shapes
Spec:
ViewFilterRuleSchemainpackages/spec/src/ui/view.zod.ts, read at61ba04f68. Types:FilterBuilderCondition/FilterGroupinui/packages/types/src/complex.ts. Builder:FilterBuilderRow/FilterBuilderRowGroup.ViewFilterRuleFilterBuilderConditionFilterBuilderRowonmainvalue?value?: anynullany)any)VIEW_FILTER_OPERATORSViewFilterOperatorby referenceexists/notExistsid{ id?, logic, conditions }conditionsrestated onlyThe spec row itself needs no change, so nothing here goes to
domain:spec.The two value cases were not only type gaps. A field switch ran the absent value through
convertScalarToFamily: a value-less row switched to a number column threwTypeError: Cannot read properties of undefined (reading 'trim')(nulllikewise), and on a text column it came back as the string"undefined".Tests
New file
src/__tests__/filter-builder-spec-row-26306.test.tsx, one pin per case this PR changes, driven through the real field dropdown with an authored group typedFilterGroup(no cast):{ field: 'title', operator: 'is_null' }switched to a number column keeps no value.null:{ field: 'amount', operator: 'equals', value: null }draws a blank input and keepsnullthrough a switch to a currency column.tsc -p tsconfig.test.json):FilterBuilderRow['value']equals the spec's value plus the list member;FilterGroupandFilterField[]are assignable to the builder's props.filter-builder-name-authority-9306.test.tsx: the value-extension pin now states the new member; the@ts-expect-erroron a value-less row became a live fixture, and a new@ts-expect-errorholds that an object value is still refused (the member is notany).Measured (head
6a63ec93,origin/mainda99a4bdmerged in; the source under test is unchanged sincebecb866e)renderer-host-props.ts, itsbase.tsline ([key: string]: unknown) and its renderer annotation ([key: string]: unknown } & RendererHostProps) applied locally on top of this branch, with noasin the renderer:tsc --noEmitin@object-ui/componentsexits 0. Control leg, same mutation overmain's builder: exit 2,filter-builder.tsx(49,11): error TS2322: Type 'FilterGroup | undefined' is not assignable to type 'FilterBuilderRowGroup | undefined'. Neither change is pushed.main'scustom/filter-builder.tsx(byte-identical, hash checked; restored and checked against the HEAD blob): 2 of 2 red,TypeError: Cannot read properties of undefined (reading 'trim')and… of null (reading 'trim').pnpm --dir ui exec vitest run --maxWorkers=2over everypackages/components/src/__tests__/filter-builder-*file,inline-locale-label-read-sites,share-filter-sort-spec-parity,record-picker-malformed-filter-10789, plugin-list's twoconvertFilterGroupToASTsuites and app-shell'suserFilterUrlState.listState-11860: Test Files 26 passed (26), Tests 505 passed (505). Narrowed to the row's readers; the full package suites are CI'sTest UIlegs.pnpm --filter @object-ui/components --filter @object-ui/plugin-list --filter @object-ui/plugin-view --filter @object-ui/app-shell run type-check(both programs each, after building their dependency closure): exit 0. These are the packages that importFilterBuilderRow/FilterBuilderRowGroup.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 45 commands at6a63ec93; all 45 exit 0, and--ranreconciles 45 run, 0 NOT-MEASURED, 0 UNRUN. Among themcheck-sdui-manifest.mjs,check-changeset-no-major.mjs --base origin/mainandcheck-adr-0087-registration.mjs --base origin/main, whose verdict line reads:✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.([BREAKING+bang+clause-②-narrowing] not-required (runtime-interface-only) -- verified: ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow (interface)).3abe2733, gate run, commit dropped):runtime-interface-onlywith the prose migration: exit 0.runtime-interface-onlywith a FROM / TO code block: exit 1, refused as a migration prescription (D7 inherits that refusal).type-surface-only, with either migration: exit 1, predicate 4 (FilterBuilderRowwas already concrete at the merge base). So the migration for readers is prose, not a FROM / TO block.(narrowing)arm),6a63ec93,pnpm --filter @objectstack/dogfood testunder the lock after building its closure: Test Files 297 passed, 3 skipped (300); Tests 2528 passed, 18 skipped (2546);VERDICT command-exit 0.pnpm exec turbo run build --filter=@object-ui/console --concurrency=2atbecb866e: 71 tasks successful,VERDICT command-exit 0.node scripts/gen-sdui-manifest-node.mjsre-recordedscripts/sdui-manifest.sources/components.jsononly;sdui.manifest.jsonunchanged. fix(types,components)!: renderer props index signatures map to unknown, not any #26293 (held) and feat(types,react,sdui-parser)!: retire the node-level visibleOn / hiddenOn / disabledOn spellings #26274 also move that record, so whichever lands later mergesmainand re-records.Acceptance notes
equals truerow on a select column, switched toin, comes back as[true], whichViewFilterRuleSchemarefuses. Narrowing the member to the spec's was built and measured on this branch first: it is a breaking change to a published type, and@object-ui/plugin-list'sUserFiltersfails to compile against it (it handsLookupValuePickera(string | number | boolean)[]). It needs a file outside this claim, and its writers (UserFilters) would need a FROM → TO rewrite, which no ADR-0087 category admits for a type that was already concrete at the base (type-surface-onlyfails predicate 4,runtime-interface-onlyrefuses a prescription; both measured, see below). So it is reported for its own card rather than made here. The builder's doc comment and the new test name the width as it is.FilterBuilderSchema.onChangeis declared(filter: FilterGroup) => void, but the renderer calls the host'sonChangewith{ target: { name, value } }. Unchanged here; noted.as anyonvalueonce this lands; its renderer annotation then type-checks as measured above.Generated by Claude Code