Skip to content

feat(plugin-detail,types,cli): record blocks honour the per-element dataSource binding (objectui#12294) - #12299

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12294-record-blocks-element-datasource
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-12294-record-blocks-element-datasource

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Refs #12294 (record:details, record:highlights, record:quick_actions; the record:related_list half is carved out, see the claim)
Clause-②: yes (widening)
What widens: the three record-block registrations publish the injected dataSource input, and @object-ui/types declares dataSource on their three strict-authoring-face arms (the spec's ElementDataSourceSchema, by reference), so a binding both the html tier and objectui validate refused before is now accepted on these three blocks.

What this does

Maintainer ruling on objectstack-ai/cloud#2839 Q1, option D, as the card quotes it: close the real gap. record:details, record:highlights and record:quick_actions now honour the per-element dataSource binding the spec already declares on every page component, so a product app page composes a record's surfaces from blocks, with no record shell to switch off.

  • One shared wrapper, no second rendering path. recordBindingBlock (packages/plugin-detail/src/renderers/recordBinding.tsx) wraps each block body in ElementDataSourceGate (the binding resolves exactly as on every bound block: named view, AND-combined filter, sort, the family's configuration panels) and a one-record scope, and passes the result through the elementDataSourceBlock seam from @object-ui/core. The block bodies are unchanged except one guard in record:quick_actions (below).
  • The one-record scope reads the bound set with the page's own adapter, so under the caller's identity: find(object, { $filter, $orderby, $top: 1, $expand }), with {current_user_id}-style tokens resolved (useResolvedFilter) and reference fields expanded only where the loaded field policy lets the caller read them, as the record page asks. The FIRST row is provided through the existing RecordContextProvider (object name, record id, readable row, object definition, adapter).
  • Semantics (the card's): 0 rows, or a read the server refuses (classifyLoadError reports forbidden), mount the block under a context that names the object and no record, so each block draws its own no-record state: record:details DetailView's "record not found", record:highlights nothing, record:quick_actions its empty bar. More than one row: the first by the binding's sort (one row is ever asked for). A read that fails any other way is reported in the block's place with the family's data-source error panel, not drawn as an absent record.
  • record:quick_actions runs the object's declared actions against the bound row's id. Its one body change: a record context that names an object but no record offers no record action (the 0-row state); the record page always names its record.
  • Freshness (objectstack#4444's bar, the seat addendum). The read is keyed on useDataInvalidation(object) from @object-ui/react data-invalidation.ts, the same bus RecordDetailView keys its record read on. Object-scoped, because the binding is a query: a write to any row can change which row is first. A refetch replaces the row in place, and an equal answer keeps the row object it replaces (AGENTS.md [WIP] Enhance every detail of the designer #10). No second bus.
  • The record page path is unchanged. Unbound, BoundRecordScope renders the body as it is: no provider, no element, no read. Bound and placed on a record page, the binding wins for that block ("Overrides the page-level object context for this block"), and the bound scope is kept out of the page record's inline-edit session (it mounts a read-only InlineEditProvider, so the page's Save bar can never write bound fields onto the page record) and out of the page's highlight-field registry (its own HighlightFieldsProvider).
  • objectui validate warning. Measured: objectui validate runs the strict authoring face in packages/cli (validateAuthoredDocument), not sdui-parser's validateTree. The warning lives there, once, in the success branch's Warnings: block (packages/cli/src/utils/record-block-binding-hint.ts): a bound record block whose binding has no limit: 1 is named by path and type. record:related_list is never named.
  • Producer-side fix in @object-ui/types. Measured at the merge base: the strict face refused dataSource on all three blocks (Undeclared key "dataSource" at (root) (type "record:details"), exit 1), so the warning above was unreachable and authors were told the one key that now works does not exist. Each arm now declares the binding by reference, as record:line_items does; the TypeScript authoring face (PublicBlockNode) follows from the arms.

Zone 2 measurements (PM mechanism assumptions)

  1. Confirmed at 45ae6b8: the three bodies read the record only via useRecordContext(); none wrapped the gate or the seam.
  2. Confirmed: the gate maps a binding onto a LIST block's keys and composeElementDataSource composes; neither reads a row. The card's resolveElementDataSource exists only as ViewDataProvider.resolveElementDataSource in @object-ui/core, which the render path never calls (ListViewBlock's docblock records it "had no caller outside its own test"). Used path: ElementDataSourceGate (useElementDataSource + composeElementDataSource) with the mapping { filter: true, sort: true }, plus the new one-record scope. The scope lives in @object-ui/plugin-detail, not in @object-ui/react or @object-ui/core: it needs @object-ui/permissions for the field-policy-gated $expand and readable row (@object-ui/react does not depend on it), and the whole record:* family lives there. No new export from any package entry.
  3. The census (check:element-data-source-declaration) discovers by file, with no roster to edit. The shared wrapper file consumes the gate and calls the seam from core, so the static census lists recordBinding.tsx; that the three registrations declare the key is asserted per block in recordBinding.appPage-12294.test.tsx and by the live-registry probe apps/console/src/__tests__/element-data-source-input-injection.test.tsx. Measured: all 309 vi.mock('@object-ui/react', …) call sites on main spread the actual module, so the hand-listed-mock hazard has no live population today; this change adds no @object-ui/react export anyway.
  4. See the objectui validate bullet above.
  5. Byte-identical, measured once: each of the three blocks rendered under a record page's providers with no dataSource, innerHTML SHA-256 prefix at 45ae6b8's block files and at this branch's: details 22c708ac33a9ec77, highlights 06f18891e29f52c7, quick_actions c482317ba20672b7 on both legs (scratch test, not committed; BASE files checked out over the committed implementation and restored from HEAD, git diff HEAD empty after). The override direction is pinned in recordBinding.recordPageAndBus-12294.test.tsx.

Published surface

  • @object-ui/plugin-detail: the record:details, record:highlights and record:quick_actions registrations gain the injected dataSource input (ELEMENT_DATA_SOURCE_INPUT, through elementDataSourceBlock). No new export; recordBindingBlock is package-internal.
  • @object-ui/types: RecordDetailsBlockSchema, RecordHighlightsBlockSchema and RecordQuickActionsBlockSchema gain an optional dataSource member, the spec's ElementDataSourceSchema by reference; PublicBlockNode gains it on those three node types by derivation. No new export.
  • @object-ui/cli: objectui validate prints one new advisory warning kind; exit codes unchanged.
  • Changeset: .changeset/12294-record-blocks-element-datasource.md, minor for the three packages.

Tests

All at 4f59ed1, repo-root pnpm exec vitest run, through the shared verify lock.

  • New: packages/plugin-detail/src/renderers/__tests__/recordBinding.appPage-12294.test.tsx (each block through the real registration and SchemaRenderer on an app page: one row, zero rows, a refused read, two rows stored out of order, a failed read, an unresolvable view, the declared input; record:path as the control), recordBinding.recordPageAndBus-12294.test.tsx (unbound reads the page record and reads nothing; bound overrides; bound details out of the page's inline-edit session; bound highlights does not trim the page's details; bus re-read after an adapter write and after a hand-announced write; no re-read for another object), packages/cli/src/__tests__/validate-record-block-binding-limit-12294.test.ts, packages/types/src/__tests__/record-block-data-source-arms-12294.test.ts.
  • Ledgers updated: apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (three MEMBER_PINS), recordDetailsInputs.spec-parity / recordHighlightsInputs.spec-parity (node-level spec keys, as recordRelatedListInputs.spec-parity already takes them), public-block-responsive-styles-10872 (DECLARES_DATA_SOURCE), zod-mirror-parity (exclusion prose).
  • Final run of the new and touched suites at 4f59ed1: Test Files 12 passed (12), Tests 642 passed (642).
  • Package runs (same branch, before the last test-only commits): packages/plugin-detail/ + packages/react/src/element-data-source/ + packages/react/src/hooks/ + packages/core/src/data-scope/: 312 files; packages/types/ + packages/cli/ + plugin-detail tests: 654 files, all green after the ledger updates above. Every suite that names RecordDetailView, PagePreview or one of the three blocks outside those packages (202 files, enumerated by git grep): 157 passed (app-shell) and 45 passed (the rest).
  • Type-check (script name echoed): @object-ui/plugin-detail, @object-ui/cli, @object-ui/types, @object-ui/console type-check, exit 0.

Ablations (one-time, ablation-replace.mjs, anchor hit once, blob changed, restored to HEAD with git diff HEAD empty)

  • Bus subscription removed (useDataInvalidation(…) replaced by 0): the two bus pins red (re-reads the bound row after a write through the adapter…, …announced on the bus by hand), the other five green.
  • Read-only inline-edit session removed: only a bound record:details does not join the page record's inline-edit session red.
  • Highlight-registry shadow removed: only a bound record:highlights does not trim the page record's details body red.

Gates

Exit 0 at 4f59ed1: check:element-data-source-declaration, check:component-surface-parity, check:sdui-registration-pins, check:vi-mock-specifiers, check:vi-mock-inherit, check:registry-bare-names, check-changeset-presence, check-changeset-no-major, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:control-bytes, check:test-path-roots, check:eager-closure, check:doc-json, check:phantom-deps, check:unused-deps, check:unreferenced-sources. Also exit 0 on this branch: check:doc-snippets, check:doc-examples, check:readme-exports, check:doc-fences, check:doc-types, check:i18n-keys, check:spec-symbols, check:handler-key-reads, check:governed-queue-guard --test (NOT GOVERNED). The build-reading gates ran over a console-closure build of this branch.

ESLint, proven narrowing: the population is eslint.config.js's **/*.{ts,tsx} blocks; --format json over the 16 touched code files reports 16 files, 0 errors (70 warnings, no-explicit-any and react-refresh/only-export-components, the families these files already carry); the config sets no parserOptions (no type-aware linting) and no rule in eslint-rules/ reads the disk, so this diff cannot move a verdict on an untouched file. pnpm lint is left to CI.

Acceptance notes (observed, not filed)

  • Each bound block reads the object definition and its row on its own, so three blocks bound to the same set make three reads; nothing dedupes them across blocks.
  • RecordDetailView and PagePreview each hand-roll "fetch a record and feed RecordContextProvider", and this change adds a third, by-query reader with a private copy of RecordDetailView's readableRow (module-private in @object-ui/app-shell, which depends on this package, not the reverse). recordBinding.tsx is a natural home for the one internal core objectstack#4444 calls hygiene; neither host is refactored here.
  • On an app page a bound record:details is read-only: there is no record-level inline-edit host there, and the bound scope mounts a read-only session on purpose.
  • record:quick_actions' 0-row bar says "no actions configured", the placeholder it already drew with no record context at all.

#12294 remains open for the record:related_list half, as the claim states. Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z.


Generated by Claude Code

…ataSource binding

record:details, record:highlights and record:quick_actions now read their
record from the node's `dataSource` binding when one is set: the gate resolves
the binding as on every bound block, and a shared one-record scope reads the
first row of the bound set under the caller's identity and provides it through
RecordContextProvider. Unbound, the record page path is unchanged.

The strict authoring face declares the binding on the three arms by reference
to the spec's ElementDataSourceSchema, and `objectui validate` warns on a bound
record block without `limit: 1`.

Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z
Co-authored-by: Claude <noreply@anthropic.com>
…locks; tests

The gate, the one-record scope and the declaration seam move into one
`recordBindingBlock` wrapper, so a record block takes the binding one way and
cannot forget the seam. Tests pin each block on an app page with one, zero and
two bound rows, a refused and a failed read, the record page path, the bound
scope's isolation from the page's inline-edit session and highlight registry,
the data-invalidation bus, the validate warning and the strict face.

Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z
Co-authored-by: Claude <noreply@anthropic.com>
The data-source guide's consumer table gains the three one-record blocks and
a paragraph on their semantics; the plugin-detail README documents the binding.

Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z
Co-authored-by: Claude <noreply@anthropic.com>
… ledgers

The repo-wide registry parity test gains member pins for record:details,
record:highlights and record:quick_actions `dataSource`; the two per-block
parity tests take the node-level spec keys the related list's already takes;
the binding wrapper names its props type the renderer-props way.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 72 chunks) 3132.3 KB 3204.6 KB
Main entry chunk (gzip) 73.5 KB 350 KB
Entry file index-Wsq1LvNx.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) 20.69KB 7.62KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 279.76KB 71.33KB
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) 15.35KB 5.51KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 258.42KB 68.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.28KB 46.22KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.12KB 70.93KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.35KB 31.54KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 45.65KB 12.81KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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 ACCEPT: objectui PR #12299 at 4f59ed1 · domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z · 2026-10-13T04:02Z

Verdict: ACCEPT. Reviewed on GitHub against the claim 6137033601, the seat addendum (objectstack#4444's bar), and the related-list ruling recorded on objectui#12294 (6137246293, letter B). Not reviewed against the report 6137889031. It is gated on the at-tier contract review that the claim's Clause-②: yes owes; in this act the seat hangs needs:contract-review on the PR, and the review runs once CI on this head has converged.

PR form.

  • Draft, base main, assignee zhuangjianguo.
  • Line 1 is Refs #12294 (…), with no closing verb, as the claim requires: objectui#12294 stays open for the related-list leg. Line 2 is Clause-②: yes (widening), naming what widens.
  • check-governed-merges --pr reads NOT governed, 1524 lines.

The diff, read.

  • One wrapper, block bodies unchanged. recordBindingBlock (plugin-detail/src/renderers/recordBinding.tsx) wraps each body in ElementDataSourceGate (mapping { filter, sort }) and a one-record scope, and passes through the elementDataSourceBlock seam imported from @object-ui/core. The one body change is record:quick_actions' guard: a context naming an object but no record offers no action. Accepted, because without it the 0-row state would offer actions with no record id to run against.
  • The read. find(object, { $filter, $orderby, $top: 1, $expand }) goes through the page's own adapter, under the caller's identity.
    • $expand is limited to fields the loaded policy lets the caller read, and the row passes readableRow.
    • A forbidden read draws the block's no-record state, as the card asks ("the same it draws for a record the caller cannot read"). Any other failure draws the family's error panel.
    • Effects key on the query's string data, not on object identity, and an equal answer keeps the same row object (AGENTS.md [WIP] Enhance every detail of the designer #10).
    • Not mapping limit is accepted: the first row under the same sort is the same row.
  • objectstack#4444's bar 1 is met. The read keys on useDataInvalidation(object), the bus RecordDetailView uses, with no second bus. The bus pins go red when the subscription is ablated.
  • The record page. Unbound, the scope renders the body with no provider, element or read; byte-identity was measured on all three blocks. Bound, the binding overrides, as the spec's description of the key says. Bound blocks are kept out of the page record's inline-edit session and highlight registry, each pinned and ablated.
    • Accepted as an addition beyond the dispatch: without them, a bound block on a record page would join the page record's draft, whose Save bar writes the page record.
  • objectui validate. The warning lives once, in the CLI's strict-face success branch (record-block-binding-hint.ts), and never names record:related_list.
  • The producer-side widening in @object-ui/types is accepted as in scope. The strict face refused dataSource on all three arms (exit 1, as measured in the PR), which made the card's warning unreachable. Each arm now declares the spec's ElementDataSourceSchema by reference, as record:line_items does. This is the declared widening; the at-tier review judges it.

The changeset, sentence by sentence. @object-ui/plugin-detail, @object-ui/types and @object-ui/cli, each minor.

  • The JSON example's filter is the spec's ViewFilterRule array form, the orthography ElementDataSourceSchema declares, so the example teaches an accepted shape.
  • "One record per block", "runs against the bound row", "resolves as on every bound block", "stays fresh", "The record page is unchanged", "The registrations declare the key", the @object-ui/types bullet and the @object-ui/cli bullet are each true of the diff.
  • "record:related_list is unchanged" is true.

Docs. content/docs/guide/data-source.md gains the three rows, and the plugin-detail README is updated (commandment #2).

Evidence, as reported.

  • New and touched suites: 12 files, 642 tests. Package runs: 312 and 654 files.
  • 202 outside suites that name the record page or the blocks: 157 and 45 files passed.
  • type-check passed for four packages, with script names echoed. Three ablations each turned only their own pin red.

Out-of-scope finding 1 (class b, reach measured). objectui validate refuses dataSource on record:related_list (exit 1, Undeclared key "dataSource"), the child binding objectstack#6953 shipped and the renderer reads.

  • This falls inside objectui#12294's own scope ("honour the per-element dataSource the spec already declares"), so the seat files it as an in-flight derivative of objectui#12294: domain and priority are inherited, and this seat owns it.
  • It is not the parent-binding leg that ruling B sends to objectstack#23134.

The other four findings are Acceptance notes with no carrier, and the seat agrees with each.

Next (the seat's).

  • CI on 4f59ed1 is converging. Then an isolated at-tier contract review renders its record on this head.
  • On PASS: strip needs:contract-review, then pr_ready + automerge_enable.
  • On merge: objectui#12294 stays open on the related-list leg, as pm:blocked with Blocked-by: objectstack-ai/objectstack#23134.

Implemented-by: claude/issue-12294-record-blocks-element-datasource (mode:subagent dev of this seat)
Reviewed-by: session_01CsyMRsFJBKNEMa4oPY1b5z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat REWORK: objectui PR #12299 at 4f59ed1 · domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z · 2026-10-13T04:07Z

The ACCEPT 6137962819 stands on the design. CI on this head is red with a real failure caused by this diff, so a patch round goes back to the same dev before the contract review.

  • Failing check: Test (shard 3/12); the aggregate Test is red with it. apps/console/src/__tests__/objectname-binding-required-11605.test.ts, case "the binding doc names every bound entry, and each object key is one the entry declares": <record:details>: the binding's object lands on "objectName", which the entry does not declare.
  • Why: the ledger's objectKeyOf reads the binding doc's object column. A key named in backticks, such as (childObject), is the landing key. element:* lands on no key. Everything else falls back to objectName.
    • The three one-record blocks are now bound entries, and their doc rows say "✅ (its record)", so the ledger takes them as objectName blocks.
    • Their binding lands on the record scope (RecordContextProvider), not on a schema key.
  • Wanted:
    • The ledger and the doc row agree on that.
    • The "lands on no key" exception stays CLOSED: exactly element:* plus these three named record blocks, with a control that an object-* block still cannot hide in it.
    • ⛔ The assertion is not weakened, and the test is not skipped or quarantined.
  • Also: run every suite that reads the binding doc or the bound-entry manifest (data-source.md, isElementDataSourceBlock, ELEMENT_DATA_SOURCE_INPUT), and the whole apps/console suite. Report Type Check on the new head too; it was still running at this read.

Generated by Claude Code

… key

The binding-doc ledger in objectname-binding-required-11605 read the three
one-record rows ("its record") as objectName blocks. Their binding lands on
the record scope, not on a schema key, so the "lands on no key" exception is
now exactly `element:*` plus record:details, record:highlights and
record:quick_actions, named one by one; record:related_list keeps objectName,
no `object-*` block can fall into it, and the doc says why.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 72 chunks) 3132.3 KB 3204.6 KB
Main entry chunk (gzip) 73.5 KB 350 KB
Entry file index-Wsq1LvNx.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) 20.69KB 7.62KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 279.76KB 71.33KB
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) 15.35KB 5.51KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 258.42KB 68.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.28KB 46.22KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.12KB 70.93KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.35KB 31.54KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 45.65KB 12.81KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1310a8f5989eee717174b0d066c427a9914b08c4
Local-runs: none

Inputs: card objectui#12294 (body and all five comments: claim 6137033601, pointer 6137101274, ruling B 6137246293, dev reports 6137889031 and 6138204025), PR #12299 (body, 20-file list, net diff against main), the 33 check-runs on this head (30 success, 3 skipped — dependabot and the two coverage legs — 0 failure, 0 in progress; Type Check, Lint, Repo Gates, Test and all 12 shards, Test (dist pins), Build & E2E, Build Docs, Changeset Declaration / Bump Policy / Fixed Group Check / Claim Re-read, Governed Surface Queue Guard, Spec Main Shape Gate, Line Citation Gate all success), objectstack#4444, and the spec's ElementDataSourceSchema / PageComponentSchema.dataSource in packages/spec/src/ui/page.zod.ts. No build is available, so the published surface is judged from each package's entry source and package.json exports at this head: packages/types/src/index.ts and src/zod/index.zod.ts (the . and ./zod entries), packages/plugin-detail/src/index.tsx (the one . entry), packages/cli/src/index.ts (the . entry).

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong.

  1. @object-ui/plugin-detail — the record:details, record:highlights and record:quick_actions registrations gain the injected dataSource input — RIGHT. recordBindingBlock (renderers/recordBinding.tsx) returns elementDataSourceBlock(Bound), imported from @object-ui/core as the census requires, and index.tsx registers the wrapped exports (register('details', RecordDetailsRenderer…), register('highlights', …), register('quick_actions', …)), so Registry.register injects ELEMENT_DATA_SOURCE_INPUT from the seam, not by hand. record:path stays undeclared and is pinned as the control; the live-registry probe (apps/console element-data-source-input-injection) quantifies over getAllConfigs() and is green on this head.
  2. The three exported renderer components widen too — RIGHT, and named here because the PR's "Published surface" lists only the registrations. RecordDetailsRenderer, RecordHighlightsRenderer and RecordQuickActionsRenderer are re-exported from plugin-detail's . entry, and the wrap is on the exported name (as record:related_list already does), so a host importing a renderer directly now has its schema.dataSource honoured. Unbound, BoundRecordScope returns the children with no provider, element or read, and the gate carries requiresDataSource={false}; the dev measured byte-identical record-page output on all three, and every outside suite naming them is green. No new export from any package entry (the plugin-detail export list is unchanged; recordBindingBlock is package-internal).
  3. @object-ui/types — RecordDetailsBlockSchema, RecordHighlightsBlockSchema, RecordQuickActionsBlockSchema gain optional dataSource, the spec's ElementDataSourceSchema by reference — RIGHT. One constant, stripImportedDefaults(SpecElementDataSourceSchema).optional(), the exact spelling the record:line_items, element:number and element:repeater arms already use; the test pins the member's def identical to the spec's through the import boundary. Reached by consumers through the ./zod entry (index.zod.ts exports the three arms and PublicBlockComponentSchema), so StrictAnyComponentSchema and safeValidateSchema accept the binding on exactly these three arms; PublicBlockNode is ClosedArmInput over PublicBlockComponentSchema.options, so the . entry's TypeScript face gains it by derivation (pinned with a ts-expect-error control on record:path). NODE_ENVELOPE (not on BaseSchema) is untouched, so no other arm widens.
  4. The widening stays inside the spec's declared key — RIGHT. The key is PageComponentSchema.dataSource; its members are the spec's own (object, view, filter as the ViewFilterRule array, sort, limit), and a value the spec refuses is refused at dataSource on both faces (pinned). No objectui-only key, no spec edit.
  5. @object-ui/cli — objectui validate gains one advisory warning — RIGHT. validate.ts prints it in the success branch's Warnings: block only; findRecordBindingsWithoutLimitOne walks the document for ONE_RECORD_BLOCK_TYPES (the three) with a binding naming an object and limit !== 1; record:related_list is never named (pinned, with the one-record control). Exit codes unchanged. packages/cli/src/index.ts exports serve and init only — no new export.
  6. The one body change, record:quick_actions' guard — RIGHT. noRecord = ctx != null && ctx.recordId == null hides record actions. RecordContextProviderProps.recordId is string | number | null | undefined, so a host can mount a context naming an object and no record; at the merge base such a context offered declared actions with no record id to run them against, and now draws the empty bar. That is the 0-row semantics the card asks for and the only pre-existing shape it touches; RecordDetailView always names its record, so the record page's bar is unchanged.
  7. record:related_list's dataSource meaning is unchanged — RIGHT, as the claim's carve-out and ruling B require. record-related-list.tsx is not in the diff; at the head it is still elementDataSourceBlock-wrapped with the binding's object landing on objectName (its child rows, objectstack#6953); its types arm is untouched; the CLI hint excludes it by name; the console ledger pins objectKeyOf('record:related_list') === 'objectName'; the changeset says so in its last line.
  8. The one-record scope's behaviour is consistent with the card, ruling B and objectstack#4444's bar — RIGHT. The read is find(object, { $filter, $orderby, $top: 1, $expand }) through the page's own adapter (useResolvedDataSource), filter composed by the gate (named view AND the binding's filter, filter: true, sort: true mapping) with placeholder tokens resolved by useResolvedFilter; the first row is provided through the existing RecordContextProvider. 0 rows, and a read classifyLoadError reports forbidden (403 / PERMISSION_DENIED / FORBIDDEN), mount a context naming the object and no record, so each block draws its own no-record state — the card's "the same it draws for a record the caller cannot read"; any other failure is reported in the block's place (ElementDataSourceErrorPanel), which is the right side of "absence must be loud". More than one row: the first by the binding's sort, one row ever asked for. useDataInvalidation(object) (object-scoped, @object-ui/react data-invalidation.ts, the bus RecordDetailView keys on) is in the read effect's deps; both the adapter-mutation bridge and a hand notifyDataChanged are pinned and the subscription was ablated. That is chore(config): retire the dead tailwind.config.js eslint ignore; reword the postcss blind-spot docblock (#4350) #4444's bar 1 (失效总线集成); its bars 2–4 concern a RecordScope member of the react-page scope, which ruling B leaves not taken and this diff does not add — nothing enters buildComponentScope, no spec key, no react-page export. On a record page a bound block gets a read-only InlineEditProvider and its own HighlightFieldsProvider: right, since otherwise the page's Save bar could write bound fields onto the page record and a bound highlights strip would trim the page record's details; both pinned and ablated.
  9. Binding limit left unmapped — RIGHT. The read is always $top: 1; the first row of an authored cap under the same sort is the same row; the binding doc row says "one row is read", and the validate warning asks for limit: 1 (and a sort when several rows can match).
  10. Nothing else published moves. No @object-ui/react or @object-ui/core export; no spec change; apps/console moves only under src/__tests__; content/docs/guide/data-source.md and packages/plugin-detail/README.md (in the tarball's files) are prose matching the behaviour above; no committed manifest in this repo changes (the objectstack-side SDUI manifest is regenerated at the pin bump, outside this PR).

② Semver level

  • .changeset/12294-record-blocks-element-datasource.md: @object-ui/plugin-detail: minor, @object-ui/types: minor, @object-ui/cli: minor — matches what the diff publishes: the three packages whose published source moves, each a widening; apps/console changes are tests only and rides the fixed group. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read are success on this head.
  • Clause-②: yes (widening) — the PR's line 2 and the claim's Clause-②: yes agree with the diff: accepted input widens on the html tier (injected input), the strict face and the TypeScript face; nothing narrows, nothing is removed or renamed, so no migration text is owed. yes takes at least minor; objectui's version policy (AGENTS.md §9 at the head) forbids major in any changeset, so minor is both the floor and the ceiling — right for all three.
  • The changeset's sentences are each true of the diff: one record per block (first by sort, $top: 1, under the caller's identity); forbidden reads look absent; other failures reported; record:quick_actions runs against the bound row's id; the binding resolves as on every bound block; the bus re-read; the record page unchanged when unbound and read-only when bound; the registrations declare the key; the types arms declare the spec's schema; the validate warning; record:related_list unchanged. The JSON example's filter is the spec's ViewFilterRule array form.

③ Boundary flags

Every deviation in the latest os-dev-report (6138204025), answered; open_questions is empty, so nothing to escalate.

  1. Producer-side @object-ui/types widening, its ledgers and docs — answered, in scope: at the claim's main reading the strict face refused dataSource on all three arms (exit 1, Undeclared key), which made the card's item 2 (the validate warning) unreachable; the PR declares it on line 2 and in its published surface; judged in ① items 3–4.
  2. Wrapper lives in @object-ui/plugin-detail, not react/core — answered: it needs @object-ui/permissions (withoutDeniedFields row gating, policy-gated $expand), which is already a peerDependency of plugin-detail and not a dependency of @object-ui/react; no new package export.
  3. Static census lists recordBinding.tsx, not the three block files — answered: check-element-data-source-declaration.mjs is file-granular by design and holds no roster ("a source file that CONSUMES the gate must also call elementDataSourceBlock("); the block files consume no gate symbol, the wrapper does and reaches the seam from core, so a fourth record block cannot take the binding without it. The card's item 5 is met in substance (three blocks; the fourth is carved out by the claim), with the per-registration declaration pinned in recordBinding.appPage-12294 and the live probe.
  4. record:quick_actions guard beyond "bodies unchanged" — answered in ① item 6; accepted as the necessary half of the 0-row semantics.
  5. limit not mapped — answered in ① item 9.
  6. Read-only inline-edit session and highlight-registry shadow — answered in ① item 8; each pinned and ablated to its own pin.
  7. Model-free commit trailers — objectui AGENTS.md §9's rule, applied; not a contract matter.
  8. First relay attempt refused at --dry-run — zero writes; process note only.
  9. REWORK worktree recreated at 4f59ed1 without merging origin/main — the check-runs on this head are green and the queue rebuilds on current main; no action.
  10. Round-1 gate derivation missed the console binding-doc ledger — closed in the patch round (4f59ed1..1310a8f): objectKeyOf's "lands on no key" exception is element:* plus the named ONE_RECORD_BLOCKS set, ⛔ not a record: prefix; controls pinned (no object-* tag in it, record:related_list → objectName, record:line_items → childObject, each doc row reads "its record"); the doc paragraph names the two kinds of block with no object key; no assertion weakened. Right.

Noted, not blocking (the dev's acceptance notes, agreed): a bound record:details on an app page is read-only (no record-level inline-edit host there; the bound scope mounts canEdit={false}), which the changeset states for the record page only — a sentence the seat may add at landing, no claim is false; three blocks bound to one set make three reads; readableRow is a private copy of RecordDetailView's, the #4444 零暴露准备 hygiene this change does not perform. The class-b out-of-scope finding (the strict face refuses dataSource on record:related_list at the merge base) is pre-existing, outside this diff, and carried by the seat's derivative card.

Implemented-by: claude/issue-12294-record-blocks-element-datasource
Reviewed-by: session_01CsyMRsFJBKNEMa4oPY1b5z

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

needs:contract-review removed by the dispatching seat (domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z) · 2026-10-13T04:35Z.

  • Record: ## Contract review comment 6138316821 on this PR. Served tier CONTRACT_REVIEW_TIER, judged head 1310a8f5989eee717174b0d066c427a9914b08c4 (the current head), Local-runs: none, independence pair claude/issue-12294-record-blocks-element-datasource / session_01CsyMRsFJBKNEMa4oPY1b5z, VERDICT: PASS, nothing escalated.
  • At this head: 33 check-runs, each success or an expected skip (dependabot, the two Test (coverage) jobs); mergeable_state clean; check-governed-merges --pr NOT governed, 1577 lines.
  • The seat's ACCEPT is 6137962819. The REWORK 6138015532 (the shard-3 ledger) was answered by 1310a8f. Landing follows in the same act: pr_ready + automerge_enable (the merge queue).
  • The record's non-blocking note (a bound record:details on an app page is read-only, while the changeset states read-only for the record page only) is carried into the landing note on objectui#12294. No sentence in the changeset is false.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants