Skip to content

fix(components): element:repeater and element:number publish the dataSource binding as their only query input, and objectui compiles against spec main again (objectui#12085, objectui#12093) - #12100

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12085-element-binding-inputs
Oct 10, 2026

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12085
Fixes #12093
Clause-②: no

Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z (domain:ui seat 3, claim 6097673277 on objectui#12085, fold claim 6097776869 on objectui#12093).

objectui#12085: the element registrations publish the binding as the only query door

  • element:repeater (packages/components/src/renderers/basic/data-list.tsx): the registration drops its flat object (which was required: true), filter, sort and limit inputs. The injected dataSource input (the elementDataSourceBlock seam) is unchanged, so the repeater now requires nothing.
  • element:number (packages/components/src/renderers/basic/elements.tsx): the registration drops its flat object and filter inputs. The metric has not read them since objectui#11880.
  • Mirrored from the spec, not recalled. On objectstack main (read at 243dd3c6, still so at d8830c28), ElementRepeaterPropsSchema declares object, filter, sort and limit as retiredKey(elementFlatBindingRetired('element:repeater', KEY)). ElementNumberPropsSchema does the same for object and filter. Both landed with feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) objectstack#22421 (317cddd40d).
  • The renderer's fallback read of the repeater's flat keys is kept, on purpose. Evidence:
    • The ruling on objectstack#11509 (comment 6051498447, sub-question 2) has record_picker and number lose their fallbacks, and has the repeater "take dataSource". It does not retire the repeater's read.
    • The spec row that landed says, in the ElementRepeaterPropsSchema docblock: "The renderer's fallback read of them, recorded above, is the console's tolerance for metadata written before this release, not a door this row keeps". The repeater's tombstone prescription says the same ("keeping this key only as a fallback for metadata written before it").
    • objectui still pins @objectstack/spec 17.7.0. There, ComponentPropsMap['element:repeater'] requires a flat object, and @object-ui/types' repeater arm requires properties.object wherever no dataSource.object names one. Dropping the read now would make objectui's own validator accept a node whose list the renderer leaves empty.
    • So the read stays, and it is not published: a back-compat read is not an authoring surface. Its retirement belongs with objectui's spec-18 pin bump (see Acceptance notes).
  • The pin, at the manifest level. These are the same manifestFromConfigs + validateTree pair that feed sdui.manifest.json:
    • a repeater bound only through dataSource draws no diagnostic (it drew missing-required-prop);
    • each flat object / filter / sort / limit beside the binding draws unknown-prop, with the bound node as the control;
    • the same holds for element:number's flat object / filter.
  • Pins flipped, load-bearing:
    • data-list.elementDataSource-11880.test.tsx and elementNumber.dataSourceBinding-10909.test.tsx carry the registration pins.
    • element-list-inputs-11168.test.tsx mounts the repeater's query rows through dataSource, and its required-input control moves to element:number.aggregate.
    • apps/console registry-inputs-spec-parity.test.ts: the three member pins on keys no longer published are struck, and the six keys installed 17.7.0 still declares get reasoned UNPUBLISHED_EXEMPTIONS. Each entry says it goes stale at the pin that carries objectstack#11509's tombstones.
    • objectname-binding-required-11605.test.ts: the element:* blocks land the binding on no key of theirs.
  • Docs: content/docs/guide/data-source.md says the repeater and the metric offer the binding alone. No code fence changed.

Pin-window order

  1. objectstack's spec retirement is already on its main (feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) objectstack#22421).
  2. This objectui change lands.
  3. objectstack's next console pin bump regenerates sdui.manifest.json from these registrations, through gen-sdui-manifest-node.mjs.

That bump is objectstack's own card. This PR does not touch that file. Until the bump, objectstack's manifest keeps the old registrations.

objectui#12093: objectui compiles against spec main again

Every fix compiles against BOTH specs: the installed 17.7.0 that the pull-request type-check uses, and the Spec Main Shape Gate's build of objectstack main. No behaviour changes on the installed spec.

  • The nine named sites:
    • Fixtures that only carried a flat object now name it through dataSource: record-picker-label-association, page-variables, authoring-nodes-11364 pickerStyled, and element-repeater-data-source-11880 bound / aliased.
    • Three pins asserted that a retired key compiles. Each now asserts that the face's verdict IS the installed row's verdict:
      • authoring-nodes-11364, the record picker's bag object;
      • element-repeater-data-source-11880, the repeater's bag object;
      • grid-default-filters-...-round10-6152, defaultFilters on the grid and on the table slot, plus two by-reference Equals.
    • Each pin holds on both specs, and asserts REFUSED on main.
  • Further signatures found by re-running the gate in full (the annotations are capped):
    • overlayScope. feat(metadata-protocol,objectql,rest,spec)!: every metadata read is environment → code; legacy organization rows and sealed overlays are reported at boot, not served (#15206 S5) objectstack#22628 narrows it to env. The alias pin in data-objectstack now reads the expected set off the schema VALUE in both directions, plus a label-table ceiling. LayeredDiff.overlayScope's en-US case renders env. app-shell's LAYER_SCOPE_ZH keys org beside the spec union. The type is internal and the behaviour is unchanged.
    • object-grid.defaultFilters. plugin-view's ObjectView hands its legacy table.defaultFilters rung to the grid as filter. On main the by-reference type is the tombstone, so plugin-view's build failed, and that cascaded TS7016 into app-shell, console and examples/byo-backend-console. The read now goes through legacyTableDefaults, which carries ONE explicit unknown-to-rule-array assertion with its reason. The value read and the rung are unchanged. The plugin-grid and plugin-view tests that pin that legacy read build their fixtures the same way. This assertion is a review point; see open question 2 in the report.

Measured

Heads cited are this branch's commits. Turbo's cache is shared across worktrees in this container, so every type-check below ran with TURBO_FORCE=true.

Spec Main Shape Gate reproduction. objectstack main was archived read-only and built from source. The tarball was injected with scripts/spec-main-shape-gate.mjs inject into a separate worktree, and both installs were checked by markers:

head objectstack main scope result
base de302c73 243dd3c6 types + components RED: the ten diagnostics on the card
c70f848db 96469912 full RED: 8 of 81 tasks; the overlayScope and defaultFilters signatures above
c4e01fb39 d8830c28 full (pnpm type-check --continue, 81 tasks force-executed) GREEN: TYPECHECK_EXIT=0; the gate's own report prints "objectui type-checks against @objectstack/spec at that commit"

Installed spec, c4e01fb39. TURBO_FORCE=true pnpm type-check --continue: 81 of 81 tasks, force-executed 81, cache hits 0, exit 0.

vitest, c4e01fb39, from the repo root:

  • 55 files and 1245 tests pass: the components registration and element pins, the console parity / 11605 / html-tier / public-contract set, the three types files, and check-doc-links.
  • 82 files and 744 tests pass: all of plugin-view, four plugin-grid filter files, the data-objectstack overlay pin, and three app-shell metadata-admin files.

Reverse verification. The two registration files were checked out from base with the fix committed. That gives 13 failed tests in 5 files, among them the binding-only "no diagnostic" and the flat-key unknown-prop rows. Restored from HEAD: blob equals HEAD, and git diff HEAD is empty.

Ablations. All five ran through ablation-replace.mjs; each mutation was proven on disk and each restore was proven by blob hash.

  • On main, the record picker's bag object re-forked to a string turns the new authoring-nodes pin red.
  • On main, ObjectGridSchema.defaultFilters re-forked to the rule array turns red both round-10 differentials and both by-reference Equals.
  • On the installed spec, the record picker's bag losing its object turns the same pin red.
  • On the installed spec, the overlay alias widened to string | null turns _noWider, _labelled and the @ts-expect-error red.
  • On main, the alias restated as 'org' | 'env' | null turns _noWider red.

Gates, at c4e01fb39 unless noted. All exit 0:

  • check:new-line-citations, check:control-bytes, check:element-data-source-declaration;
  • check:changeset-claims, check:pending-changeset-literals, check:installed-pin-claims;
  • check:spec-symbols, check:test-path-roots, check:component-surface-parity, check:doc-types, check:doc-fences;
  • check-changeset-presence (2 changesets), check-changeset-no-major;
  • check:sdui-registration-pins at c70f848db, after a console build there; no registration moved after that commit.

eslint, --no-inline-config, on the 19 touched TS files:

  • The population is read from eslint.config.js, and the file count from --format json.
  • 0 errors. The warnings are identical per file to base (97 and 97).
  • The config enables no type-aware linting, so this diff cannot move a verdict on an untouched file.

NOT MEASURED:

  • check:doc-snippets / check:doc-examples: prerequisite not met (packages outside the type-check closure are unbuilt). The doc diff changes no fenced block.
  • Repo-wide pnpm lint: CI.

Clause-② (both claims say no)

Acceptance notes

  • element:record_picker's registration still publishes its four flat binding inputs, each marked NOT READ. objectstack main tombstones them too. They are the same family as this card, outside its ruling, and left untouched; reported to the seat.
  • Four things move together at objectui's @objectstack/spec 18 pin bump:
    • the renderer's back-compat reads of retired keys: the repeater's flat four, ObjectGrid's legacy defaultFilters, and ObjectView's legacyTableDefaults rung;
    • the runtime pins that read the installed spec literally: the overlay enum, the round-10 precondition legs, and the 11168 spec-row probes;
    • the six new parity exemptions;
    • LAYER_SCOPE_ZH's org entry.

Generated by Claude Code

…Source binding as their only query input

The repeater stops requiring a flat `object` and drops the flat
`object` / `filter` / `sort` / `limit` inputs; `element:number` drops its
flat `object` / `filter`. `@objectstack/spec` retires all six as tombstones
(objectstack#11509), and the published `required: true` made the html tier
refuse a repeater bound through `dataSource` alone (objectui#12085).

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
… and the acceptance pins follow the installed row (objectui#12093)

objectstack PR #22421 (objectstack#11509) tombstones the element-layer flat
binding keys and `object-grid.defaultFilters` on objectstack `main`, so the
Spec Main Shape Gate stopped compiling five test files. Fixtures that only
carried a flat `object` now name it through `dataSource`. The three pins that
asserted a retired key compiles now assert the face's verdict IS the row's:
they compile against the installed release and against `main`, and refuse
the key wherever the row does.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…e binding is the one published query door (objectui#12085)

The repeater requires nothing and publishes none of its flat query keys;
the html tier accepts a repeater bound through `dataSource` alone and
reports each flat key as `unknown-prop`. `element:number` publishes no flat
`object` / `filter`. The repeater's query rows mount through the binding.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…llow the narrowed element registrations (objectui#12085)

The six flat keys the installed spec still declares get reasoned
unpublished-key exemptions that go stale at the pin carrying
objectstack#11509's tombstones; the three member pins on keys no longer
published are struck. objectui#11605's doc reading learns that the element
blocks land the binding on no key of theirs. The data-source guide says the
repeater and the metric offer the binding alone.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…t registrations (objectui#12085)

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…c's value set, and the zh label table keys `org` beside it (objectui#12093)

objectstack PR #22628 (objectstack#15206 S5) narrows `overlayScope` to `env`
on objectstack `main`, while the installed release declares `org | env`.
The alias pin now reads the expected set off the schema value in both
directions, so it holds and stays load-bearing on either spec; the en-US
raw-value case renders `env`, a scope on both. `LAYER_SCOPE_ZH` keys `org`
beside the spec union, so a server on the installed release keeps its label
and the table still fails to compile when the spec adds a scope.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…ultFilters read compiles against the spec that retires the key (objectui#12093)

objectstack PR #22421 tombstones `object-grid.defaultFilters` on objectstack
`main`, so ObjectView's legacy filter rung, which hands the deprecated
`table.defaultFilters` to the grid as its `filter`, stopped compiling there
and its build failure cascaded into app-shell, console and an example. The
read is now typed by the rule-array shape legacy metadata carries; the value
and the rung are unchanged. The grid and view tests that pin that legacy read
build their `defaultFilters` fixtures the same way.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3169.3 KB 3204.6 KB
Main entry chunk (gzip) 73.7 KB 350 KB
Entry file index-_2xvUn4D.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 19.75KB 7.29KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 14.32KB 5.17KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.30KB 45.92KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.09KB 30.26KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.97KB 23.24KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat answer to the report's open question · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T18:41Z

Report: os-dev-report 6100892115 on objectui#12085.

The question: when do the renderers' back-compat reads of retired keys retire? There are three: the repeater's flat object / filter / sort / limit fallback, ObjectGrid's legacy defaultFilters read, and ObjectView's legacyTableDefaults rung.

Answer: A, as landed. They retire at objectui's @objectstack/spec 18 pin bump, together with the types arms, the six parity exemptions and LAYER_SCOPE_ZH's org entry. Three reasons:

  • The ruling covers only part of it. The objectstack#11509 ruling (6051498447, sub-question 2) retires the element:number and record_picker fallbacks. It does not retire the repeater read, and this PR keeps element:number reading dataSource only.
  • B would let the validator accept keys the renderer ignores. objectui pins @objectstack/spec 17.7.0, which still declares those keys, so objectui's own validate accepts them. Dropping the reads now would silently empty a list that still carries them.
  • A is bounded. The by-reference tombstone typing forces the reads out at the bump. The single unknown → rule-array assertion in legacyTableDefaults is the one review point, and it is a type-only edit with no runtime effect.

The record_picker family remainder (out_of_scope_findings[0]) and the spec-18 pins that read the installed spec literally ([2]) belong to that same pin bump. They are written into the ledger and not filed separately. Finding [1], turbo's worktree-shared cache replaying main-spec verdicts in a local gate reproduction, goes to the seat's clock-out ledger.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c4e01fb39cb01b103a10e8e3dbc3699fadc24cb8
Local-runs: none

① Derived judgments

Read against objectstack origin/main c63028e5 (packages/spec/src/ui/component.zod.ts), objectui origin/main de302c73 (the merge base), the net diff, both cards with every comment, the ruling 6051498447, and the 43 check-runs on the head.

  • element:repeater registration narrowing — right. The diff removes object (required: true), filter, sort, limit from inputs in data-list.tsx. On objectstack main ElementRepeaterPropsSchema declares exactly those four as retiredKey(elementFlatBindingRetired('element:repeater', KEY)) and nothing else as a tombstone. What stays published (titleField, fields, emptyText, divided, plus the injected dataSource) is live on the row. No input added, no description widened. The narrowing is exactly the spec's retirement set.
  • element:number registration narrowing — right. The diff removes object and filter from inputs in elements.tsx; on main ElementNumberPropsSchema tombstones exactly those two. What stays (aggregate required, field, format, prefix, suffix, injected dataSource) is live on the row. Nothing added.
  • The required: true drop — right, and it is the one relaxation in the diff, judged as the rule asks. A repeater bound through dataSource alone was refused by the manifest (missing-required-prop) and is accepted now. execution-duties: deleting a mis-refusal that the published contract text itself denies is no, and that no must cite the text; a mismatching citation downgrades to yes. Claim 6097673277 cites object: retiredKey(elementFlatBindingRetired('element:repeater', 'object')) on objectstack main; verified at c63028e5. The ruling (sub-questions 3 and 4) puts the one requirement on dataSource.object in validate-component-props, "not a new gate", so a manifest that requires nothing matches element:number and element:record_picker (neither ever required object) and leaves a bare repeater refused by objectstack's os validate (the spec gate) and by objectui's typed face on 17.7.0. Not a widening of the contract. The tension, named: the installed 17.7.0 row still requires a flat object, so against the npm snapshot the drop reads as a relaxation; the repo's criterion is the declared contract (the landed row plus the ruling), and the installed lag is bridged by the six reasoned UNPUBLISHED_EXEMPTIONS, each deleted by the parity file's own carries no stale unpublished-key exemption at the pin that carries the tombstones.
  • Kept repeater read — consistent with the ruling and with answer A. The ruling's order step has record_picker and number lose their fallbacks and the repeater "take dataSource"; it does not retire the repeater's read. The landed row's docblock calls that read "the console's tolerance for metadata written before this release, not a door this row keeps", and the tombstone prescription says "keeping this key only as a fallback for metadata written before it". The diff neither adds nor removes the read (it predates this PR, from objectui#11880); element:number keeps reading dataSource only. On 17.7.0 objectui's own validator still accepts the flat keys, so dropping the read now would reopen the trap in reverse (accepted, rendered empty). Unpublished, the read is not an authoring surface; the parity file has that category already (page:card.body, page-header-subtitle-alias).
  • Merge queue: Spec Main Shape Gate fails every merge group since objectstack#22421 retired the flat element binding keys; five objectui test files still use them #12093 gate fixes — each holds on both specs, none vacuous, neither source edit a runtime change.
    • overlayScope pin (metadata-client.overlayScope.test.ts): the expected set is read off GetMetaItemLayeredResponseSchema.shape.overlayScope (the schema VALUE route) and compared with the alias MetadataOverlayScope = GetMetaItemLayeredResponse['overlayScope'] (the TYPE route). _specCovers and _noWider bite on a hand-restated or widened alias (the report's ablations A4 on installed, A5 on main); the old literal ceiling survives as _labelled, so a spec ADDITION still reds on either spec. A spec REMOVAL is now seen only by the runtime literal on the installed spec, which reds at the pin bump; the acceptance notes name it. The _org literal is gone because it cannot hold on main; _env, _none and the @ts-expect-error control stay. Load-bearing on both specs.
    • LAYER_SCOPE_ZH keyed Record of NonNullable alias | 'org': on the installed spec the key type is what it was ('org' | 'env'); on main it is 'env' | 'org'. The object literal is unchanged, the completeness check (a new spec scope with no label stops compiling) holds on both. Type-only.
    • ObjectView legacyTableDefaults: the same property read (table?.defaultFilters), the same value, hopped through one unknown to ObjectGridSchema['filter']; AuthoredFilterSegments.tableDefaults is retyped from ['defaultFilters'] to ['filter'], which on the installed spec are the same ViewFilterRule[] | undefined (the round-10 by-reference pin held that equality before this PR). The rung and its precedence are untouched. Type-only. One correction to the PR's and the seat's prose: this unknown hop is precisely what lets the read compile against the tombstone, so the tombstone typing cannot force this read out at the bump; a read of a never-valued key compiles anywhere (so do ObjectGrid's schema.defaultFilters read and the repeater's schema: any read, and the green gate proves it). Only WRITES red at the bump. The three reads are bounded by the acceptance-notes ledger, not by the typing. Flagged under ③.
    • types-test fixtures moved to dataSource (record-picker-label-association, page-variables, authoring-nodes pickerStyled, element-repeater-data-source bound / aliased): the binding is what those renderers read since objectui#11880, so each test still exercises what it pinned. The three flipped pins assert Equal of the face's verdict and the row's verdict (FaceTakesFlatObject vs RowTakesFlatObject on the picker and the repeater; GridTakesRules / SlotTakesRules vs RowTakesRules, plus the by-reference Expect on defaultFilters for grid and table slot) rather than either spec's answer; they red when the face re-forks a key (ablations A1, A2, A3). Not vacuous. The grid and view fixtures that build a legacy defaultFilters document through as unknown as ObjectGridSchema are test-only constructions of metadata the face refuses on main, each stated in its header; no authorable key is added, so AGENTS.md #0.1 is not touched.
    • 11168: the required-input control moved from the bare repeater to element:number without aggregate (missing-required-prop), so the empty verdict still has a control that can fail; mountRepeater mounts through dataSource, and the flat-key read stays pinned by the 11880 file's "the flat keys alone read exactly as before".
    • Manifest-level pins in data-list.elementDataSource-11880 and elementNumber.dataSourceBinding-10909 use the same manifestFromConfigs + validateTree pair that feeds sdui.manifest.json: binding-only draws nothing, each flat key beside the binding draws unknown-prop, with the bound node as the control. Right instrument for the card's filing gate.
  • Check-runs on the head are the gate verdicts. 43 runs; every non-skipped one success (Test (coverage) and dependabot skipped by design). Spec Main Shape Gate run 114284269180: success, 13 steps all success (resolve objectstack main, sparse fetch, build spec, pack, install, inject, forced type-check, report). The job log is not readable from this container (the Actions log host is refused by the egress proxy) and a green run writes no sha into the check output; the commit it compiled against is therefore inferred from the workflow's resolve step (GET /repos/objectstack-ai/objectstack/commits/main at job start, 2026-10-10T18:36:11Z) and objectstack origin/main: c63028e5 (committed 18:11:21Z, nothing newer before the resolve), a descendant of the dev's d8830c28; the four rows above were read at that commit. Type Check success is the installed 17.7.0 verdict (the lockfile at the head pins @objectstack/spec@17.7.0); Test plus eight shards and Test (dist pins) success; Lint success with ten any warnings all in e2e/live/**, no file of this diff; Governed Surface Queue Guard success, and the 22-file list touches no governed path; 540 changed lines. The registration-pins gate the report ran at c70f848db stays valid: the two later commits touch no file under packages/components/src/renderers/.

② Semver level

  • .changeset/12085-element-binding-inputs.md: @object-ui/components: minor. The diff narrows a published registration (six inputs leave the manifest the package publishes; after objectstack's pin bump each draws unknown-prop in os validate). objectui's rule: a changeset never declares major; the fixed group marks its own breaking changes minor and states the breaking semantics in the body. The body does: "Narrowing, no widening", the FROM (the six flat keys, object formerly required) to TO (the dataSource binding as the only query input), the new verdicts, and that rendering is unchanged. Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read and Changeset Fixed Group Check are all success. Consistent.
  • .changeset/12093-spec-main-compile.md: empty frontmatter, the explicit no-release form objectui's rule allows for internal change. The two source edits are behaviour-neutral on the installed spec (judged above), and the fixed group releases plugin-view and app-shell at the components minor anyway, so nothing ships un-versioned. Consistent.
  • Clause-②: no on both claims and on the PR body: a narrowing of a published accept set is no by rule and owes one contract-review-tier review before the queue (this record); the single mis-refusal deletion is no with its text cited and verified. no is right.

③ Boundary flags

  • Deviation 1 (file surface beyond both claims) — answered. The console parity and 11605 tests and data-source.md are the registration change's own pins and doc; ObjectView.tsx and i18n.ts with their tests are the further gate signatures the amendment 6098412168 opened ("any further fixture the same gate names on the PR head"), accepted by the seat in 6100912606, both type-only.
  • Deviation 2 (Clause-② stays no) — judged right in ① and ②.
  • Deviations 3 to 6 (foreground-waited long runs under the verify lock; the detached gate worktree removed; refs/scratch-12085/* fetched into owned refs and deleted; the resume note's commit placement) — process discipline, each closed in the report; none reaches the diff or a gate.
  • Deviation 7 (session-URL footer on the PR body, model-free trailer pair on commits) — the AGENTS.md form; correct.
  • Open question (when the three back-compat reads retire; A vs B) — answered A by the seat in 6100912606, and A is right: the ruling retires the number and record_picker fallbacks only, the landed row records the repeater read as tolerance, and B would let objectui's 17.7.0 validator accept what the renderer ignores. One correction to the answer's third reason, so the pin-bump card reads the reads off the ledger and not off a red: the by-reference tombstone typing forces out WRITES of a retired key (fixtures, authoring) but not READS of one, and legacyTableDefaults's unknown hop is built to compile against the tombstone. What binds A to the bump is the acceptance-notes ledger (the repeater's flat four, ObjectGrid's legacy defaultFilters, legacyTableDefaults, LAYER_SCOPE_ZH's org) together with the pins that red on their own at the bump: the six parity exemptions (carries no stale unpublished-key exemption), the overlayScope runtime literal, the round-10 precondition legs, the 11168 spec-row probes. Those reds name the ledger; the reads themselves need the bump card to act.
  • Out-of-scope finding [0] (element:record_picker still publishes four NOT READ flat inputs that main tombstones) — answered: ledgered to the spec-18 pin bump by the seat. Outside the card's ruling (triage 6094448002 named the repeater and the metric), and a lower class than the card's defect: no required flag, so no refusal of the prescribed shape, and the spec's tombstone refuses the flat spelling at os validate regardless. Acceptable in the ledger.
  • Out-of-scope finding [1] (turbo's worktree-shared cache replaying main-spec verdicts in a local gate reproduction) — answered: the seat's clock-out ledger. No bearing on CI, which runs the gate with TURBO_FORCE=true and pins that in scripts/__tests__/spec-main-shape-gate.test.ts.
  • Out-of-scope finding [2] (pins that read the installed spec literally and red at the pin bump) — answered: named in the PR's acceptance notes, and listed above as the reds that lead to the ledger.
  • PR state read: draft, no auto-merge, no needs:contract-review label; the fold's two Fixes lines name the two cards, one each.
  • Reviewer-side disclosure: this review fetched the PR branch as directed and refreshed origin/main in both sibling checkouts (remote-tracking refs advanced; nothing checked out, no file written, nothing built or run).

Implemented-by: claude/issue-12085-element-binding-inputs
Reviewed-by: session_01B1gHb9baeX7oioD5sHVm7z

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 19:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 10, 2026 19:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit ce991bd Oct 10, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12085-element-binding-inputs branch October 10, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment