Repository navigation
fix(components): element:number reads its object from dataSource.object, as the spec binding declares (objectui#10909) - #10944
Conversation
…ct (objectui#10909)
ElementNumberRenderer read its object only from `properties.object`, so a
spec-valid `{ dataSource: { object }, properties: { aggregate } }` metric
issued no query and painted the empty dash. It now resolves the binding
through `useElementDataSource`, the way `element:record_picker` does:
`dataSource.object ?? properties.object`, one value for the fetch guard,
`aggregate` / `find` and the `useDataInvalidation` key. A named `view`
scopes the aggregate (an unresolvable one reports and aggregates nothing),
a binding filter wins over `properties.filter`, and `sort` / `limit` are
not read. The registration goes through `elementDataSourceBlock`, so it
declares `dataSource`, and `object` is no longer required.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…d add its changeset (objectui#10909) Declaring `dataSource` on element:number adds an object-armed input to a covered block, so the registry-inputs parity census requires a member pin for it. The pin is the renderer file that asserts which binding members the metric reads and which it does not. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
… for the strict test project (objectui#10909) `tsc -p tsconfig.test.json` refused `adapter.aggregate` as possibly undefined on the optional-member fake. The aggregating fake and the find-only fake are now two factories. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…ctui#10909) The data-source guide's table of blocks that consume `dataSource` gains the `element:number` row (object, a view's filter, filter; no sort, no limit), and says how the two `element:*` rows read the binding: directly, with a binding member winning over the flat `properties` one. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsObject resolution: correct and complete.
Filter precedence. Ruling: B (AND-combine). This is not a FAIL ground on its own; adopt it in the fix round that is owed anyway.
Registration and manifest.
Pins and ablation.
Census: true against source.
Docs and changeset.
Compatibility with PR #10908.
② Semver levelClause-②: yes (widening). The
③ Boundary flagsLocal-runs: none
Implemented-by: VERDICT: FAIL Required fix round (text only):
Recommended in the same round, the seat's call:
|
…he binding's filter (objectui#10909, fix round 1) Seat ruling B on the open question: with a `dataSource` binding present, `properties.filter` is AND-combined with the binding's filter and its view's, lowered and merged the way `ElementDataSourceGate` does (`toFilterNodeSafely` + `mergeFilterNodes`). Neither is dropped; a refused merge takes the configuration-error panel and aggregates nothing. The properties form with no binding is unchanged. Pins: the precedence row flips to assert both filters reach the aggregate; new rows for a view filter beside properties.filter, a refused merge, and the unresolved-view guard beside a flat object. The registered `filter` description, the guide paragraph and the changeset say the same. The changeset becomes `minor` with `Clause-②: yes (widening)`, and its two sentences the review found false are replaced. The MEMBER_PINS prose says what the injected declaration does and does not carry. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsThis is a delta review of The merge is the gate's own merge.
Wire shape: measured by reading, and accepted everywhere
Refused-merge panel. A refused merge renders
Texts.
Pins and ablations, checked against the test source. There are 18 rows.
② Semver level
③ Boundary flagsLocal-runs: none
Implemented-by: VERDICT: PASS |
Fixes #10909
Clause-②: yes (widening)
Implemented on branch
claude/issue-10909-element-number-datasourceby theos-devrun dispatched from thedomain:uiseat 2 sessionsession_014mXUNuFomfj24w7s1pZzhN(claim comment 5866532141 on the card).What changed
ElementNumberRenderer(packages/components/src/renderers/basic/elements.tsx) now reads the spec's per-element binding (PageComponentSchema.dataSource) throughuseElementDataSource, the way its element twinelement:record_pickerdoes.dataSource.object ?? properties.object, and that one value is the fetch guard, theadapter.aggregate/adapter.findtarget and theuseDataInvalidationkey.dataSource.objectwins when both are set.properties.object.properties.filteris AND-combined with the binding's filter and with its view's. The lowering and merge are the onesElementDataSourceGateuses (toFilterNodeSafely+mergeFilterNodes), so neither filter is dropped. A merge the converter refuses shows the configuration-error panel and aggregates nothing. With no binding,properties.filterreaches the adapter exactly as authored.element:numberis registered throughelementDataSourceBlock(...)(from@object-ui/core), soRegistry.registerinjects the one shareddataSourcedeclaration (ELEMENT_DATA_SOURCE_INPUT,binding: 'object').objectis no longerrequired: true.objectinput's description states its precedence.filterinput's description is exactly: "Criteria the aggregate is scoped by. When a node-leveldataSourcebinding also supplies a filter (its own, or the saved view itsviewnames), the two are AND-combined: neither is dropped."packages/components/src/renderers/basic/__tests__/elementNumber.dataSourceBinding-10909.test.tsx(18 rows). It is registered as theelement:number.dataSourcemember pin inapps/console/src/__tests__/registry-inputs-spec-parity.test.ts, which the parity census requires once the block declares an object-armed input.content/docs/guide/data-source.mdgains theelement:numberrow in the per-element binding table. A paragraph says how the twoelement:*rows read the binding, and that they differ onfilter:record_pickersubstitutes,element:numberAND-combines..changeset/10909-element-number-datasource.md,'@object-ui/components': minor, withClause-②: yes (widening).H1: reproduced on
main(733fd5a)The probe used the real
SchemaRenderer, the real registry, and anAdapterCtxadapter withaggregate/findspies. The probe file was temporary and not committed.The same probe on this branch:
ElementDataSourceSchemamembers: which ones this renderer readsobjectdataSource.object ?? properties.object. There is no object at all while a named view is unresolved or unresolvable. A binding naming no object (empty or not a string) supplies nothing, and the flat object stands.viewuseElementDataSource: the saved view'sfilterscopes the aggregate. While the view resolves, the shared loading panel shows and nothing is read. If it cannot be resolved, the shared error panel (element-number-datasource-error) shows and nothing is aggregated.filterproperties.filterAND (view filter AND binding filter), lowered and merged asElementDataSourceGatedoes. Neither is dropped; a refused merge reports and aggregates nothing. Context tokens are handled by the existinguseResolvedFilteron the merged value. There is no record-context binding (objectui#7297 stays on hold), and the filter-shape note (objectui#8945) is untouched.sortaggregate()orfind(), and the whole options bag is asserted.limitpagination.pageSizereachesaggregate()or thefind()fallback.columns/viewType(view-only composed keys)No member is declared per member. The only declaration is the injected
dataSourceinput (type: 'object',binding: 'object'), whose description lists the binding's five members generically. Which of them this block reads is pinned at the read site.The registration change: what
elementDataSourceBlockchanges, exactlyelementDataSourceBlockis a marker. It adds the renderer to aWeakSetand returns it unchanged, so it adds no gate, no loading state and no fetch. The declaration is emitted bywithElementDataSourceInputinsideRegistry.register. Measured on the live registry:object,aggregate!,field,filter,format,prefix,suffix,dataSource(!= required). On base,object!carried the required mark and there was nodataSource;validateTreeon{ type: 'element:number', dataSource: { object: 'contact' }, aggregate: 'count' }returns no diagnostic. On base it returnedis missing required prop "object"andhas no prop "dataSource".The render-path changes come from
useElementDataSourceand from the filter merge, and they reach only a node that carries a binding:viewreads saved views and shows the shared loading panel while it resolves;view, or a filter refused in either merge, shows the shared error panel;dataSourceresolvesabsentsynchronously, so thepropertiesform issues no new read, shows no new panel, and sends its filter unlowered. This is pinned: the control row assertsgetObjectSchemais never called.Pins: red before, green on head
File:
elementNumber.dataSourceBinding-10909.test.tsx, 18 rows. The file was byte-identical in all three runs, and each base run used a temporary detached worktree that was removed afterwards.Tests 18 passed (18)Tests 16 failed | 2 passed (18)— the 2 green rows are theproperties-form control and the empty-objectbinding rowTests 6 failed | 12 passed (18)The 6 rows red on the previous head:
Rows:
element:textstill reportshas no prop "dataSource").Ablations at 37bc45f, through
objectstack/scripts/ablation-replace.mjsin wrap mode with its trap-armed restore. Each was run over the pin file plus the three pre-existingelement:numberfiles (elementNumberFilterMembers-8071,elementReaders.invalidationRefetch-10623,element-number.contractEnvelope-6726), 33 rows in all. Each restore printedok restored: blob == HEAD (3ab0d9a745a4) and git diff HEAD is empty.(composed?.object ?? props.object)becomes(/* ablation-10909 */ props.object); blob3ab0d9a745a4to875d8dfa0e4a11 failed | 22 passed (33): rows 1, 2, 4, 6–10, 12, 15 and 16, the dataSource-form rows that reach the resolution linemergeFilterNodes(own.node, bound.node)becomes(/* ablation-10909-and */ bound.node ?? own.node), the rule A semantics; blob3ab0d9a745a4to2a9bf06af87c2 failed | 31 passed (33): exactly the two AND rows, 9 and 10unresolved || filterRefusal ? undefined :becomes/* ablation-10909-guard */; blob3ab0d9a745a4toc249c85fa79b2 failed | 31 passed (33): exactly row 11 (refused merge) and row 14 (unresolved view beside a flat object)Census: element renderers and the
dataSourcebindingEvery
element:*registration is in@object-ui/components, and no other package registers one. The census probe rendered each registered element withdataSource: { object: 'contact' }and no flat object:dataSource?dataSourceelement:numberobject)aggregate(contact)element:record_pickerobject)find(contact)element:repeaterobject, required)has no prop "dataSource"and the missingobjectelement:metadata_viewerhas no prop "dataSource"element:text,element:divider,element:image,element:button,element:text_input,element:definition-listhas no prop "dataSource"element:formandelement:filterhaveComponentPropsMaprows but no objectui renderer.element:repeateris not a spec block: it has noPageComponentTypemember and noComponentPropsMaprow. Its declaration and its runtime agree, and the html tier reports the key, so this is an observation, not a finding.Consumer sweep
The file set is the
git grephits forelement:number,ElementNumberRenderer,elementDataSourceBlock,withElementDataSourceInputandELEMENT_DATA_SOURCE_INPUTin tests, plus:dataSourcedeclaration suites;element-data-source-input-injectionrender probe;record_pickerdata-source and malformed-filter pins;Fix round 1 adds the gate-family filter-merge tests this change reuses:
ElementDataSourceGate.malformedFilter-10789,ElementDataSourceGate.test, corefilter-source-merge.test, andObjectMetric.elementDataSource.test.At 37bc45f, 34 files, run under the shared verify lock:
Test Files 34 passed (34)·Tests 983 passed (983).The full
@object-ui/componentssuite ran at 75f027e (before this round) in three directory runs: 326 collected files, all green. This round changes onlyelements.tsxand its pin file inside that package. Both are covered above by the directories' ownelement:numberfiles and by the sweep.Gates
At 37bc45f. Each verdict is the gate's own line.
pnpm --filter @object-ui/components type-check(tsc --noEmit && tsc -p tsconfig.test.json), afterpnpm --workspace-concurrency=2 --filter '@object-ui/components^...' build(8 of 47 projects): exit 0.pnpm check:element-data-source-declaration:OK — 14 gate-consuming file(s) checked, 3 definition file(s) excluded; all reach the seam and take it from core.node scripts/check-changeset-presence.mjs:3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/10909-element-number-datasource.md.node scripts/check-changeset-no-major.mjs:No changeset declares a major bump.The frontmatter isminor.pnpm check:changeset-claims: report-only. The same three pending changesets name touched files (6726-find-envelope-records-arms,6783-readprops-degenerate-config-bag,8067-component-input-member-kind); each paragraph is still true.pnpm check:control-bytes:OK.pnpm check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).node scripts/check-doc-links.mjs:Links are valid across 17 scan roots.--no-inline-config --format jsonover the three changed source and test files):ESLint.isPathIgnoredanswersfalsefor all three.elements.tsxhas 24 warnings, the same per-rule split as base; the other two files have 0.parserOptions.projectorprojectService), and the one custom rule reads no file from disk, so untouched files cannot move.check:handler-key-reads,check:designer-field-key-parity,check:component-surface-parity,check:unreferenced-sources,check:test-path-roots,check:vi-mock-specifiers,check:registry-bare-names,check:i18n-keys,check:doc-types,check:doc-fences,check:doc-example-idsandcheck:spec-symbolswere green at 75f027e.apps/consoletype-check. Reason: its program needs every plugin's built declarations, and that closure build is beyond the foreground cap. The CIType Checkjob owns it. The console edit is oneMEMBER_PINSentry with two string fields.check:sdui-registration-pins. Reason: PREREQUISITE, no console bundle (exit 2). Theperformance-budgetworkflow runs it.check:doc-snippetsandcheck:doc-examples. Reason: PREREQUISITE, unbuilt packages (exit 2). The guide edit adds no fenced snippet.Serial
packages/components/src/renderers/basic/, the parity suite orcontent/docs/guide/data-source.md. Positive control: PR objectui#10908'spackages/types/files match the same grep.packages/coreis unchanged; this change only imports its existingtoFilterNodeSafelyandmergeFilterNodes.git merge-tree --write-treeagainst a freshorigin/main5c94589, fetched into a private ref: exit 0, tree4c51202bf5. Main moved 20 commits since the base, and none of them touches the renderer, the parity suite, the guide,useElementDataSource,useResolvedFilter, the gate, the core data-source module,filter-converter,Registry.tsorvalidate.ts.git merge-tree --write-treeagainst PR objectui#10908's head fe8f6a6: exit 0, tree4bb57ac81c.Fix round 1
This responds to the contract review FAIL at 48d755d, on text and one seat ruling. Object resolution, the unchanged properties form, the registration, the census and the pins held.
toFilterNodeSafely+mergeFilterNodes.properties.filter; a refused merge taking the error panel with no aggregate call.filterdescription is the ruling's text verbatim. The guide paragraph and the changeset bullet say AND-combined.record_pickeris untouched.minor;Clause-②: yes (widening)text verbatim;objectui validatewill once objectui#10908 arms the type";Clause-②: yes (widening).dataSource: { object: 'contact', view: 'no_such_view' }besideproperties.object: 'contact'asserts no aggregate call and the error panel. The guard ablation reddens exactly that row and the refused-merge row.MEMBER_PINSprose: rewritten for rule B. It no longer says the declaration carries "nothing about members"; it says the injected description lists the five members generically and the pin carries which of them this block reads.Acceptance notes
{ dataSource: { object } }, and PR objectui#10908's arm (which acceptsproperties.filterbeside a binding filter or view) now matches a renderer that honours both.element:numbermanifest entry declaresdataSource, andobjectis no longerrequired. The html tier, and the objectstack CLI's JSX gate that reads the manifest, now also accept a node that names neitherobjectnor a binding, which paints the empty dash. That case is still reported on the JSON door by the spec lint gate (advisory), and after PR objectui#10908 by its arm when a bag is present. A runtime "no object named" notice, or a one-of check at the html tier, would be a follow-up card. Precedent: objectui#10508 made the same trade on calendar and ganttobjectName.aggregate()/find(), asElementDataSourceGatedoes forobject-metric. Without a binding,properties.filteris sent as authored, as before.element:numberstays properties-only and offers nodataSourceeditor.record_pickershares the wording. This is a review nit, and the text is unchanged here.Generated by Claude Code