Repository navigation
feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) - #22421
Conversation
…t-grid defaultFilters (#11509) Tombstones, the two protocol-18 conversions, the registry entries and the absorbed step-18 narrowings. Tests and generated artifacts follow. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…e three data-source-bound elements Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…e retirement moves Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ter pins follow the binding Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ement-binding-retirement
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 864718bb7754351336c85bc26812058890dafc90 && git checkout 864718bb7754351336c85bc26812058890dafc90
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 35ef501e1301a2e992f4f785a9ef469ce79fc086 d8e0996f4f95b99951d65548657aa323c98d35eb && git checkout -B drift-repro 35ef501e1301a2e992f4f785a9ef469ce79fc086 && git merge --no-ff d8e0996f4f95b99951d65548657aa323c98d35eb
node scripts/docs-audit/affected-docs.mjs --json 35ef501e1301a2e992f4f785a9ef469ce79fc086
|
…ement-binding-retirement # Conflicts: # packages/spec/dropped-refinements.baseline.json # packages/spec/src/ui/component.zod.ts
… first and keeps its flat keys as a fallback Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ording Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…18 entries this retirement absorbs The REWRITTEN floor in migrate-meta-engine-guidance.test.ts still named element-number-filter-rule-array, element-record-picker-filter-rule-array and object-grid-default-filters-rule-array. The element flat binding retirement absorbs all three into its own D3 entries, so none of them is in MIGRATIONS_BY_MAJOR any more and the floor case read "family lost element-number-filter-rule-array". The list's own rule admits only entries rewritten when their family was brought to the no-tracker-id line; an entry born without a tracker id needs no row, because the whole directory is held by the printed-block case. The two absorbing entries (element-flat-data-binding-retired, object-grid-default-filters-retired) were born that way, so they are not added. The assertion is unchanged. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ement-binding-retirement
Fixes #11509
Clause-②: no (narrowing: the ten element-layer flat data-binding keys and
object-grid.defaultFiltersleave the accept set, and the component-props gate'sdataSource.objectwaiver becomes a refusal)ADR-0087 disposition, as the changeset's marker carries it:
registered element-flat-data-binding-retired, object-grid-default-filters-retired(.changeset/11509-element-flat-binding-retired.md).Consumer pin rule (triage
6071460039): a consumer that pins both repositories moves its objectui pin past objectui#11880 no later than its objectstack pin moves past this retirement. The converted shape is one only objectui after objectui#11880 reads.Landing gate (claim
6072058120, ruling sub-question 2):.objectui-shamust cover objectui5bc55c0c5a1e(the merge of objectui PR #12010) before this lands. The pin bump is its own card, #22385 (claimed by the devx seat); this PR does not move.objectui-sha. At head756fa0b33,.objectui-shaisf0268ad78485, and REST compare5bc55c0c5a1e...f0268ad78485readsaheadby 8,behindby 0: the pin covers5bc55c0c5a1e. #22385 landed. This branch mergedorigin/main3ca71b6e0withos-regen-merge.sh, kept the re-measured repeater citation verbatim, and added the retirement on top.Held again (seat
6080485027): the gate moves to #22476. At that pin, Studio's page-block inspector still writes the repeater's flatproperties.objectandproperties.limit(block-config.ts:338–:345), the keys this PR retires. The ruling's order is that the designer writesdataSourcefirst. The fix is objectstack-ai/objectui#12056, and #22476 bumps the pin past it. This PR stays a held draft until.objectui-shacovers that fix; then it takes a merge round onto the then-currentmainand a contract review.What this does
Ruling
6051498447(A-narrow, all four sub-questions at their recommendation), carried out as the claim states it:element:record_pickerobject/filter/sort/limit,element:numberobject/filter,element:repeaterobject/filter/sort/limit, andobject-grid.defaultFiltersareretiredKey()tombstones inpackages/spec/src/ui/component.zod.ts. Each is refused at its key with the prescription (usedataSource.KEYon the node; what to do where the binding already sets it, by that element's old rule), and its input type isnever. The repeater's other spellings (objectName,filters,where,orderBy,sortBy,top,pageSize) were aliases of the flat keys and now point at the binding. Theobject-*block layer and the relationship-scoped blocks are unchanged.page-component-filter-record-to-rule-arrayat 36).element-flat-data-binding-to-data-sourcefollows each element's old rule: a key the binding lacks moves there; a key the binding already set is deleted where the binding always won (the picker, andelement:number'sobject);element:number's flat filter is appended to the binding's (the two always AND-combined). It reports a TODO and leaves the key in three cases: a picker key beside adataSource.viewthat sets no such key of its own, a repeater key the binding sets to a different value or beside aview(the repeater read neither until the console put its binding first, so what it applied depends on the console version), and anelement:numberfilter pair that is not two rule arrays.object-grid-default-filters-removedis the shapedefaultSort's retirement took. Whenfilteris empty (absent,null,[]or{}), the rules move into it. Whenfilterhas content, the key is deleted. Whenfilteris a value no lowering reads (a bare string), it reports a TODO rather than overwrite it.filteranddefaultFiltersdoors ofpage-component-filter-record-to-rule-array) are dropped from that entry's reach.packages/lint/src/validate-component-props.tsthe type-blindsuppliedByDataSourcewaiver is gone. One of the three elements with nodataSource.objectis the rule's existingcomponent-props-invalidfinding (warning tier, at…dataSource.object); this is not a new rule. The docs gate's twin waiver (packages/spec/scripts/check-yaml-examples.ts) turns with it.dataSourceis the clean shape: zero findings, and objectui reads it since objectui#11880. A repeater aimed by a flatobjectis refused twice: the tombstone at the key and the missing binding. Pinned invalidate-component-props.test.tsand in the docs gate's self-test.RETIRED_KEYS_BY_MAJOR[18]rows. There are two D3 entries (element-flat-data-binding-retired,object-grid-default-filters-retired), each with a step-18 rationale fragment.authorable-surface/ui.json(11 rows[RETIRED]) andcontent/docs/references/ui/component.mdxare regenerated by the repo's generators. The hand-editeddropped-refinements.baseline.jsonloses three rows whose only dropped refinement was a retiredfilter.Premises, verified on
origin/mainbb4f5cc00before the first editobjectkeys were atcomponent.zod.ts:2602(element:number),:3015(element:record_picker) and:4134(element:repeater). The repeater docblock's "NOT read at the pin" is at:4117.suppliedByDataSource(validate-component-props.ts:169-185) checks the issue path anddataSource.object, never the component type.defaultFiltersisz.array(ViewFilterRuleSchema)(:4563), and thedefaultSortprecedent isobjectGridDefaultSortRemoved(conversions/registry.ts:9264).examples/**,skills/**and the hand-writtencontent/docs/**. The one element-layer author, the showcase picker (page-variables.page.ts:59), already writesdataSource. The spec, lint and metadata-protocol tests that authored the flat keys as fixtures are re-bound here, and the tree-scoped absence pin found two of them that grep had missed (page.test.ts,i18n-resolver.test.ts).skills/objectstack-ui/rules/pages.mdnameselement:number/element:repeateronly as printable blocks and teaches no key, so noskills/**edit is needed and no hand-written docs page needed an edit.Decisions the reviewer should check
object-grid-default-filters-rule-array.element-number-filter-rule-arrayandelement-record-picker-filter-rule-arraynarrowed the two element flatfilterkeys this PR retires in the same unreleased major, and their acceptance criteria assert parses this PR refuses (safeParse({ object, aggregate, filter })succeeds). That is the same-major bookkeeping of thespec-property-retirementplaybook (§0) and the measurement's "same-major bookkeeping against … its semantic entries". The record-form guidance they carried is the binding's own entry,element-data-source-and-object-block-filter-rule-array. The CLI's migrate-meta guidance pin drops the three absorbed ids from itsREWRITTENfloor. The absorbing entries are not added there, because the list admits only entries rewritten off a tracker id and these two were born without one; the printed-block case holds them with the whole directory.component.zod.ts. The conversion registry and the lint rule each keep a copy, and each copy is pinned against the tombstones by probingComponentPropsMap. Publishing it would have widened a narrowing-only retirement.@objectstack/spec 17, not 18.check:future-spec-major(ADR-0087, amended 2026-09-13) refuses a tombstone that names the next major. The migrate sentence is--from 17, as withdefaultSort.XParsedaliases of the three element props stay, although their two shapes now coincide. Deleting a published type name would be an export removal of its own.Acceptance notes (not filed)
packages/lint/src/page-walk.ts:137still resolves a component's object asdataSource.object ?? properties.object ?? page.objectfor the field-binding rules, andcontent/docs/deployment/validating-metadata.mdx:157describes that chain. After this PR the flatobjecton the three elements is refused by its tombstone, so the fallback only ever reads a refused key (andelement:metadata_viewer's metadata owner). This PR does not change it: the file is outside the claim. Carrier: none named; the next card that touchespage-walk.ts.propertiesmapping, so a baretype: element:repeaterfence with neitherpropertiesnordataSourceis not reported there. The lint rule judges it. This is pre-existing dispatch behaviour.Tests and gates
All readings below are at head
d8e0996f4:c1d688804(the CLI pin fix), thenorigin/main35ef501e1merged byscripts/pm/os-regen-merge.sh, with no conflict and no generated path to regenerate (check:generated15/15 current). Taken on a shared box underscripts/pm/os-verify-lock.sh.@objectstack/clias Test Core runs it (pnpm turbo run test test:repo --filter=@objectstack/cli,VITEST_MAX_WORKERSfromvitest-worker-cap.mjs): 373 files, 4964 passed, 2 skipped, both tiers.test/migrate-meta-engine-guidance.test.ts: 3 passed. At756fa0b33it read 1 failed / 2 passed, with "family lost element-number-filter-rule-array".@objectstack/specunit tiers in full (vitest run --project local --project repo): 684 files, 19731 passed, 1 todo, 0 failed.pnpm typecheck: exit 0.756fa0b33; CI Test Core 1/6 to 6/6 is green atd8e0996f4)@objectstack/lintfull (vitest run): 129 files, 5912 passed.pnpm typecheck: exit 0.756fa0b33; CI Test Core 1/6 to 6/6 is green atd8e0996f4)@objectstack/metadata-protocolsrc/protocol.stored-migration.test.ts: 40 passed.pnpm typecheck: exit 0.756fa0b33; CI Test Core 1/6 to 6/6 is green atd8e0996f4)examples/app-showcasetest/my-work-visibility.test.ts: 7 passed.pnpm typecheck: exit 0.scripts/ablation-replace.mjs, run from the committed head): the guard was mutated toif (false && …), the mutation was proven on disk (anchor 1 → 0, blob5c1af3370366→30bf2ab540c7), andvalidate-component-props.test.tswent to 9 failed / 56 passed. Restored: the blob equals HEAD5c1af3370366andgit diff HEADis empty. The lint subject is imported from source, so no build leg was involved.dist(the lint test reads@objectstack/spec/ui). It is proven fresh by the pin that derives the three element types fromComponentPropsMap: against a pre-retirementdistthat set is empty, and the pin is red.dispatch-gates --commandsderived 120 at this head (last round's 119 pluscheck:cli-test-child-env), and all 120 were run.--ran: "120 run, 0 NOT-MEASURED". All 120 exit 0, after a locked full build.check:objectui-pin-citations: 55 asserting citations matchf0268ad78, and its 13 anchor content assertions are verified against objectuif0268ad78, fetched in isolation.check:pm-dispatch-gates: 2011 cases pass.d8e0996f4. That is over the 3000-line human-merge thresholdmainset in chore(governance): the human-merge line threshold is 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22490 (2026-10-09), so this PR lands by a human merge.d8e0996f4: all 35 check runs completed, 33 success and 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), none red.Generated by Claude Code