You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
record blocks honour the per-element dataSource the spec already declares — record:details / record:highlights / record:quick_actions / record:related_list on an app page bind their record from the component's dataSource, so a product page composes without the record shell (maintainer ruling cloud#2839 Q1 D, 2026-10-13) #12294
Filing gate: a maintainer ruling, 2026-10-13, on objectstack-ai/cloud#2839 Q1 (option D, 「同意」). Filed from the cloud maintainer's session. ⛔ Not a claim.
The gap
PageComponentSchema.dataSource is the spec's per-element data binding for multi-object pages (objectstack#5576): any page component may name an object, a view, a filter and a limit. objectui honours it only on the blocks wrapped with elementDataSourceBlock (@object-ui/coredata-scope/element-data-source.ts): element:repeater, list-view, element:number, map. The four record blocks read the record page's RecordContextProvider only:
record:details, record:highlights — render an empty box on an app page even with a dataSource that returns one row;
record:quick_actions — "no actions configured";
record:related_list — nothing.
Measured by the cloud seat on cloud's .objectui-shad758f2f2 and 1fb1ced3 (cloud#2777 v1 report; cloud#2774 phase reports 6134534505, 6136151989).
What it costs a product
A page that needs localized fields, record actions or related lists must be a type: 'record' page and inherit the record shell: the header's ⟳ and 「分享」, the list's view tabs, view actions and favourite star, none of which page metadata can switch off (PageHeaderProps offers showStar / showCopyId / recordChrome only). cloud#2839 weighed adding four shell switches to the spec (option B) and the maintainer chose to close the real gap instead (option D): with the record blocks honouring dataSource, a product page composes its own environment / members / home surfaces from blocks, with no shell to switch off, and a single-record page needs no record id in its route (it binds the organization's production environment by filter).
What to do
The four record blocks consume a component-level dataSource when present, as the spec already promises: resolve it through the same resolveElementDataSource path the list blocks use, and feed the block's existing record-context input from the result (no second rendering path inside each block).
Semantics of the bound set. The record blocks are one-record blocks:
0 rows → the block's empty state (the same it draws for a record the caller cannot read);
1 row → that record;
more than one row → the FIRST row by the binding's sort, and objectui validate warns on a record block whose binding has no limit: 1 (a product author should say it).
record:quick_actions bound this way runs the object's declared actions against the bound row's id; record:related_list keys its child query on the bound row's id. The server's authorization is untouched.
Tests: each block on an app page with a one-row, zero-row and two-row binding; the record page path unchanged (its context still wins when no dataSource is set on the block).
scripts/check-element-data-source-declaration.mjs and the elementDataSourceBlock census list the four blocks as consumers.
Acceptance
A cloud-style app page with record:highlights + record:details + record:quick_actions + record:related_list, each bound to sys_environment (environment_type = production, limit: 1), draws the environment's fields with their option labels, its declared actions, and its related rows, with no record shell around it.
The same page with a binding that returns nothing draws the empty state; with two rows draws the first and objectui validate warns.
Existing record pages unchanged (snapshot or the record-page test suite).
Consumers waiting on this
objectstack-ai/cloud#2774 (environment and members pages as custom pages; removes the ⟳ / 分享 / view tabs / star question of cloud#2839 and the "single environment opens straight to its page" acceptance).
objectstack-ai/cloud#2801 (home v2: status and plan badges, member names, 「安装应用」 on the home page).
objectui#12197 (generic blocks) is unaffected; the two are complementary.
Related
objectstack-ai/cloud#2839 (ruling) · objectstack-ai/cloud#2774 · objectstack-ai/cloud#2801 · objectstack-ai/cloud#2777 · objectui#12224 (product-neutral renderer) · objectstack#5576 (the binding) · objectstack#23016 (the api shape of the same binding)
Claim: PM loop, maintainer direct dispatch (queue jump). The card's ruling is objectstack-ai/cloud#2839 Q1 option D, as the card body quotes it (「同意」): the record blocks honour the per-element dataSource the spec already declares.
Direct-dispatch audit (who / words / where): the maintainer zhuangjianguo · 「12294 插队」 · in this PM session's chat (session_01CsyMRsFJBKNEMa4oPY1b5z), read 2026-10-13T02:42Z. Per the direct channel, this session routes the card itself: it had no domain:* label, and this act adds domain:ui.
Session: session_01CsyMRsFJBKNEMa4oPY1b5z
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12294-record-blocks-element-datasource
Worktree: objectui-issue-12294
Domain: domain:ui
Seat: domain:ui#1
File surface: three record blocks consuming a component-level dataSource, read on objectui main45ae6b8 at 2026-10-13T02:42Z. Stop on a breach and explain it in the report.
The blocks:
packages/plugin-detail/src/renderers/record-details.tsx reads its record only through useRecordContext() (:251).
record-highlights.tsx does the same at :39.
record-quick-actions.tsx does the same at :118.
None of the three wraps ElementDataSourceGate or elementDataSourceBlock.
The binding path, read and extended:
@object-ui/reactElementDataSourceGate.tsx maps a binding onto a LIST block's own schema keys (objectName, columns, filter, sort, limit), and the block then fetches.
@object-ui/coredata-scope/element-data-source.ts holds composeElementDataSource, ELEMENT_DATA_SOURCE_INPUT and the elementDataSourceBlock seam.
scripts/check-element-data-source-declaration.mjs holds the census.
The card's resolveElementDataSource is not an export on main; the dev measures the real path.
The objectui validate warning for a record-block binding without limit: 1: in whichever module emits the page tree's prop diagnostics. The dev measures where.
Co-located tests, and a changeset (level follows what the diff publishes; ⛔ never major).
⛔ Carved out: item 3's record:related_list half. On main, record:related_list already consumes dataSource, as the binding of its CHILD rows (objectstack#6953). record-related-list.tsx:689 is elementDataSourceBlock-wrapped, and the gate maps dataSource.object onto objectName. Reading the same key on the same block as the PARENT record would give one key two meanings and break that shipped authoring shape. The ruling's words, "honour the per-element dataSource the spec already declares", already hold for this block. How a related list on an app page finds its parent is a separate question, filed next as a decision sub-issue of this card. This PR carries Refs #12294, ⛔ not Fixes. On landing, this card stays open for the related list, blocked on that sub-issue.
⛔ No new spec key. No second rendering path inside a block. The record-page path is unchanged when a block carries no dataSource. Server authorization is untouched.
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: yes
Clause-② reason:
Three published registrations gain the injected dataSource input, so the authoring diagnostics stop reporting unknown-prop on a key they warned on before. That is a widening of objectui's accepted input, onto the key the spec's PageComponentSchema.dataSource already declares for any page component.
A new validate warning is added (advisory, refuses nothing).
An at-tier contract review runs before the queue.
Responsibility: objectui's record blocks (@object-ui/plugin-detail record:details / record:highlights / record:quick_actions), which read only the record page's RecordContext | the element data-source seam already serves list-shaped blocks (repeater, list-view, number, map, related list) | a product page (objectstack-ai/cloud#2774, #2801) that must today be a type: 'record' page and inherit a record shell its metadata cannot switch off
Thread-read: none
The card has 0 comments. The ruling thread objectstack-ai/cloud#2839 is not reachable from this session, so the ruling is taken as the card body quotes it.
Serial constraints cleared: read at 2026-10-13T02:42Z on objectui main45ae6b8.
No open PR touches the three blocks, record-related-list.tsx, element-data-source.ts, the gate, the census script or the CLI's validate (5 open PRs read).
This seat has nothing in flight.
The only other dispatched domain:ui card is objectui#7611 (marchtian, Setup catalog pages), which shares no file.
Pointer from the claiming seat (domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z) · 2026-10-13T02:48Z: the record:related_list half that claim 6137033601 carves out is now decision card objectui#12296.
That card cites the prior decision objectstack#4444 (<RecordScope> 不做).
The dispatched PR carries Refs #12294.
When it lands, this card stays open for the related list, as pm:blocked with Blocked-by: #12296.
Ruling recorded for this card's related-list leg (item 3, the half split out to #12296): B, the maintainer's word 「同意」 on the director seat's batch #335, ruling comment #12296 6137226306, 2026-10-13T03:01Z.
Director seat, summon #40, session_01TJKP6W6UDYEBHtccVdpVoz (written as objectstack-fleet[bot] through the relay). ⛔ Not a claim, ⛔ no label change on this card; the claim 6137033601 and the three dispatched blocks are untouched.
This card's related-list leg waits on the spec release that carries the key (the cross-repo unlock rule: installable, not merged) and lands here, reusing the first half's single-record read and invalidation wiring. The three other blocks proceed as dispatched; if they land first, this card stays open on the related-list leg (Part of), not closed.
{
"issue": 12294,
"status": "done",
"branch": "claude/issue-12294-record-blocks-element-datasource",
"pr": "https://github.com/objectstack-ai/objectui/pull/12299",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z (subagent run; the parent seat's harness-stamped id)",
"premise_still_valid": true,
"summary": "record:details, record:highlights and record:quick_actions now honour the per-element `dataSource` binding: one shared `recordBindingBlock` wrapper (plugin-detail `renderers/recordBinding.tsx`) runs ElementDataSourceGate (mapping filter+sort), reads the FIRST row of the bound set with the page's own adapter (`$top: 1`, `$orderby` = binding sort, placeholder tokens resolved, field-policy-gated `$expand`), provides it through the existing RecordContextProvider, keys the read on `useDataInvalidation(object)` (the bus RecordDetailView uses), and passes the renderer through the `elementDataSourceBlock` seam from core. 0 rows or a refused read = each block's own no-record state; >1 row = first by sort; a failed read = the family's error panel; quick_actions runs declared actions against the bound row id (one body guard: a context naming an object but no record offers no action). Unbound blocks render byte-identically on the record page (measured BASE vs HEAD); a bound block on a record page overrides the page record and is kept out of the page's inline-edit session and highlight registry. Producer-side: @object-ui/types declares `dataSource` (spec ElementDataSourceSchema by reference) on the three strict-face arms, which refused it at 45ae6b8, and `objectui validate` warns on a bound record block without `limit: 1`.",
"tests": "All via the shared verify lock, repo-root `pnpm exec vitest run`. Final at 4f59ed1: 12 new/touched files (recordBinding.appPage-12294, recordBinding.recordPageAndBus-12294, validate-record-block-binding-limit-12294, record-block-data-source-arms-12294, public-block-responsive-styles-10872, zod-mirror-parity, recordDetailsInputs/recordHighlightsInputs spec-parity, spec-symbol-batch7, console registry-inputs-spec-parity, element-data-source-input-injection, record-block-record-reach) -> `Test Files 12 passed (12)`, `Tests 642 passed (642)`. Package runs on this branch: plugin-detail/ + react element-data-source/ + react hooks/ + core data-scope/ = 312 files (3 ledger pins red, fixed, re-run green); types/ + cli/ + plugin-detail tests = 654 files (1 ledger file red, fixed, re-run 226 passed); every suite naming RecordDetailView/PagePreview/the three blocks outside those packages, 202 files by git grep: `157 passed` (app-shell, 1398 tests) and `45 passed` (878 tests). type-check exit 0 (script names echoed): @object-ui/plugin-detail, @object-ui/cli, @object-ui/types (incl. tsconfig.test.json with a @ts-expect-error pin + control), @object-ui/console. ABLATIONS (one-time, objectstack scripts/ablation-replace.mjs, anchor x1 -> x0, blob changed, restored blob == HEAD 35cbaf3533fe, `git diff HEAD` empty; src-aliased suite, no dist leg): (1) `useDataInvalidation(...)` -> `0`: the 2 bus pins red, 5 green; (2) read-only InlineEditProvider removed: only the inline-edit isolation pin red; (3) HighlightFieldsProvider shadow removed: only the highlight-trim pin red. RECORD-PAGE BYTES (one-time scratch test, not committed): innerHTML sha256 prefix with BASE block files vs HEAD: details 22c708ac33a9ec77 / highlights 06f18891e29f52c7 / quick_actions c482317ba20672b7 on both legs; BASE files restored from HEAD, each blob == HEAD:path, `git diff HEAD` empty. ESLint proven narrowing: population = eslint.config.js `**/*.{ts,tsx}` blocks; `--format json` over the 16 touched code files = 16 files, 0 errors, 70 warnings (no-explicit-any, react-refresh/only-export-components); no parserOptions (not type-aware) and no eslint-rules/ rule reads disk, so untouched files cannot move; `pnpm lint` left to CI.",
"gates": {
"head": "4f59ed1",
"exit_0_at_head": [
"check:element-data-source-declaration (16 files, all reach the seam from core)",
"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 (0 new)",
"check:control-bytes",
"check:test-path-roots",
"check:eager-closure",
"check:doc-json",
"check:phantom-deps",
"check:unused-deps",
"check:unreferenced-sources"
],
"exit_0_on_branch_before_last_test_only_commit": [
"check:doc-snippets",
"check:doc-examples",
"check:readme-exports",
"check:doc-fences",
"check:doc-example-ids",
"check:doc-types",
"check:docs-route-closure",
"check:doc-example-readers",
"check:i18n-keys",
"check:i18n-drift",
"check:i18n-dead-keys",
"check:spec-symbols",
"check:handler-key-reads",
"check:action-forward-parity",
"check:designer-field-key-parity",
"check:self-import",
"check:esm-specifiers",
"check:published-tsconfig-exclude",
"check:entry-guard",
"check:side-effects-array",
"check:metadata-write-doors",
"check:vi-mock-override-shape",
"check:shell-escape-residue",
"check:lint-rule-coverage",
"check-governed-queue-guard --test (NOT GOVERNED, 19 paths)"
],
"build_for_dist_reading_gates": "turbo run build --filter=@object-ui/console... (35 tasks) + the doc-snippet build filter, on this branch (source identical to 4f59ed1)",
"ci": "in_progress (not awaited, per contract)",
"not_run": "pnpm lint (repo-wide scan, CI-owned; narrowing proven above)"
},
"line_budget": "not applicable: no skills/** or governed ledger touched; diff 45ae6b8..4f59ed1 = 19 files changed, 1510 insertions(+), 14 deletions(-)",
"files_changed": [
".changeset/12294-record-blocks-element-datasource.md",
"apps/console/src/__tests__/registry-inputs-spec-parity.test.ts",
"content/docs/guide/data-source.md",
"packages/cli/src/__tests__/validate-record-block-binding-limit-12294.test.ts",
"packages/cli/src/commands/validate.ts",
"packages/cli/src/utils/record-block-binding-hint.ts",
"packages/plugin-detail/README.md",
"packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts",
"packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts",
"packages/plugin-detail/src/renderers/__tests__/recordBinding.appPage-12294.test.tsx",
"packages/plugin-detail/src/renderers/__tests__/recordBinding.recordPageAndBus-12294.test.tsx",
"packages/plugin-detail/src/renderers/record-details.tsx",
"packages/plugin-detail/src/renderers/record-highlights.tsx",
"packages/plugin-detail/src/renderers/record-quick-actions.tsx",
"packages/plugin-detail/src/renderers/recordBinding.tsx",
"packages/types/src/__tests__/public-block-responsive-styles-10872.test.ts",
"packages/types/src/__tests__/record-block-data-source-arms-12294.test.ts",
"packages/types/src/__tests__/zod-mirror-parity.test.ts",
"packages/types/src/zod/public-blocks.zod.ts"
],
"deviations": [
"File surface widened beyond the claim's list, producer-side as the dispatch allows: packages/types/src/zod/public-blocks.zod.ts declares `dataSource` on the three arms, because `objectui validate` (strict face) refused the binding at 45ae6b8 (measured: exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:details\")`), which also made the card's validate warning unreachable; plus the ledgers that pin who declares what (types zod-mirror-parity prose, public-block-responsive-styles DECLARES_DATA_SOURCE, console registry-inputs-spec-parity MEMBER_PINS, plugin-detail recordDetails/recordHighlights spec-parity node-level keys) and the docs (data-source guide, plugin-detail README, AGENTS.md commandment #2).",
"The shared wrapper lives in @object-ui/plugin-detail (renderers/recordBinding.tsx), not in @object-ui/react or @object-ui/core: it needs @object-ui/permissions for the field-policy-gated `$expand`/readable row, which @object-ui/react does not depend on. No new package export.",
"Static census lists the wrapper file (recordBinding.tsx), not the three block files: the gate, the scope and the seam call live once in `recordBindingBlock`, so a record block cannot take the binding without the seam. The three registrations' declaration is asserted per block in recordBinding.appPage-12294 and by the live-registry probe apps/console element-data-source-input-injection.",
"Zone 3 said block bodies stay unchanged; record:quick_actions gained one guard (a context naming an object but no record offers no action), because the 0-row context would otherwise offer declared actions with no record id to run against.",
"Binding `limit` is not mapped: the read always asks for one row (`$top: 1`), the same first row under the same sort.",
"Bound scope mounts a read-only InlineEditProvider and its own HighlightFieldsProvider: not in the dispatch, added because measurement showed a bound block on a record page would otherwise join the page record's inline-edit draft (whose Save bar writes the page record) and trim the page's details body; both pinned and ablated.",
"Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + `Co-authored-by: Claude`), not the harness reminder's model-named trailer.",
"First relay attempt was a --dry-run refused locally (`actions must be an array`, zero writes, nothing sent); re-packed and sent once."
],
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/fleet-write, written as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft, #12299) + POST /repos/objectstack-ai/objectui/issues/12299/assignees [zhuangjianguo], read back 12714 bytes identical; (2) this os-dev-report -> POST /repos/objectstack-ai/objectui/issues/12294/comments. Plus `git push` (not REST). No label write, no card write.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: `objectui validate` on a `record:related_list` node carrying `dataSource: { object: 'sys_member', limit: 5 }` at 45ae6b8 -> exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:related_list\")`, while its renderer reads the binding (gate-wrapped, objectstack#6953) and the spec's PageComponentSchema declares `dataSource` on every component · Seam: spec:PageComponentSchema.dataSource -> renderer:RecordRelatedListRenderer (ElementDataSourceGate); the strict authoring face @object-ui/types RecordRelatedListBlockSchema refuses it · not touched here: record:related_list is carved out of this PR; belongs with the seat's related-list decision sub-issue of #12294 (one-line fix shape: the arm declares `dataSource` by reference as record:line_items does, with a describe that says it binds the CHILD rows) · dedupe words: record:related_list dataSource strict face; unrecognized_keys dataSource related list; objectui validate related list binding; RecordRelatedListBlockSchema dataSource",
"carrier: 承接者:无 · noted, not filed — three blocks bound to the same set each read the object definition and the row (no cross-block dedupe); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — RecordDetailView and PagePreview hand-roll fetch+RecordContextProvider; recordBinding.tsx adds a by-query third with a private copy of RecordDetailView's readableRow; it is the natural home of the internal core objectstack#4444 calls hygiene (seat addendum item 7); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — on an app page a bound record:details is read-only (no record-level inline-edit host; the bound scope's session is read-only by design); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — record:quick_actions' 0-row bar reads \"no actions configured\", the placeholder it already drew with no record context; PR Acceptance notes"
]
}
{
"issue": 12294,
"status": "done",
"branch": "claude/issue-12294-record-blocks-element-datasource",
"pr": "https://github.com/objectstack-ai/objectui/pull/12299",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z (subagent run; the parent seat's harness-stamped id)",
"premise_still_valid": true,
"summary": "REWORK round, PR #12299, new head 1310a8f. The CI failure on 4f59ed1 was real and came from this diff: Test (shard 3/12) failed in apps/console objectname-binding-required-11605. Its binding-doc ledger read the three new data-source.md rows (object column \"✅ (its record)\") as `objectName` blocks, but a one-record block's binding lands on the record scope (RecordContextProvider), not on a schema key. Fix: `objectKeyOf` returns no key for exactly `element:*` plus a named set ONE_RECORD_BLOCKS = record:details, record:highlights, record:quick_actions, not a `record:` prefix. Row 3 now asserts four things: (a) the exception is exactly those tags; (b) no `object-*` tag falls into it; (c) each of the three is in it, declares no `objectName`, and its doc row reads 'its record'; (d) record:related_list still lands on `objectName`. The doc paragraph under the table now says which two kinds of block have no object key, and the test's quotation of it was updated. No assertion weakened, nothing skipped. The implementation from the first round is unchanged: one recordBindingBlock wrapper (gate with filter+sort, first bound row via the page adapter with `$top: 1`, the data-invalidation bus, the elementDataSourceBlock seam); the @object-ui/types strict-face `dataSource` on the three arms; the `objectui validate` limit:1 warning. CI on 1310a8f is all green, Type Check included.",
"tests": "THIS ROUND, at 1310a8f, through the shared verify lock (repo-root `pnpm exec vitest run`): (1) apps/console/src/__tests__/objectname-binding-required-11605.test.ts -> `Tests 52 passed (52)`. (2) Whole console suite `pnpm exec vitest run apps/console/` -> `Test Files 183 passed (183)`, `Tests 2069 passed (2069)`. (3) Gate population re-derived: every suite that reads the binding doc or the bound-entry manifest, found by `git grep -l` of `data-source.md|isElementDataSourceBlock|ELEMENT_DATA_SOURCE_INPUT|ELEMENT_DATA_SOURCE_KEY` plus `\\bBOUND\\b` over test files, 62 files -> `Test Files 62 passed (62)`, `Tests 2321 passed (2321)`. ABLATIONS on the fixed test (one-time, objectstack scripts/ablation-replace.mjs; anchor x1 -> x0, blob changed, restored blob == HEAD de09b99e2580, `git diff HEAD` empty): A) `|| ONE_RECORD_BLOCKS.has(tag)` removed -> 1 failed / 51 passed, reproducing CI's message (tag in angle brackets, then) `: the binding's object lands on \"objectName\", which the entry does not declare` for record:details; B) the named set replaced by a `record:` prefix -> 1 failed / 51 passed (`expected false to be true`: related_list falls into the exception). ESLint on the changed test: 1 file, 0 errors, 0 warnings. CI on 1310a8f (GET check-runs, 33 runs): 30 success, 3 skipped (coverage x2, dependabot), 0 failures; `Type Check` success, `Test (shard 3/12)` success, `Test` success, `Lint` success, `Repo Gates` success, `Build & E2E` success. CI on 4f59ed1 for comparison: Type Check success; the only failures were Test (shard 3/12) and its Test aggregate, with the annotation naming the assertion above. FIRST ROUND (unchanged, at 4f59ed1): 12 new/touched files 642 passed; plugin-detail + react gate/hooks + core data-scope 312 files; types + cli + plugin-detail 654 files; 202 record-page suites (157 + 45 passed); type-check exit 0 on plugin-detail, cli, types, console. Three ablations: bus subscription, read-only inline-edit session, highlight-registry shadow; each reddened exactly its pin. Record-page innerHTML bytes equal on BASE vs HEAD for all three blocks.",
"gates": {
"head": "1310a8f",
"exit_0_at_head_local": [
"check:new-line-citations",
"check:control-bytes",
"check:test-path-roots",
"check:changeset-claims",
"check:pending-changeset-literals",
"check:doc-fences",
"check:element-data-source-declaration",
"check:vi-mock-inherit",
"check:vi-mock-specifiers",
"check-changeset-presence"
],
"ci_at_head": "33 check runs: 30 success, 3 skipped, 0 failure. Type Check success; Test (shard 1-12/12) success; Test (dist pins) success; Lint success; Repo Gates success; Build Docs success; Build & E2E success; Governed Surface Queue Guard success; Changeset Declaration / Bump Policy / Fixed Group / Claim Re-read success",
"first_round_at_4f59ed1": "see first report (comment 6137889031): 18 gates exit 0 at head, plus the doc/i18n/spec families on the branch",
"not_run_locally_this_round": "dist-reading gates (doc-json, sdui-registration-pins, eager-closure, doc-snippets): no build in the recreated worktree; CI Repo Gates / Build Docs / Build & E2E are green on 1310a8f"
},
"line_budget": "not applicable: no skills/** or governed ledger touched; diff 45ae6b8..1310a8f = 20 files changed, 1549 insertions(+), 28 deletions(-); this round 4f59ed1..1310a8f = 2 files",
"files_changed": [
".changeset/12294-record-blocks-element-datasource.md",
"apps/console/src/__tests__/objectname-binding-required-11605.test.ts",
"apps/console/src/__tests__/registry-inputs-spec-parity.test.ts",
"content/docs/guide/data-source.md",
"packages/cli/src/__tests__/validate-record-block-binding-limit-12294.test.ts",
"packages/cli/src/commands/validate.ts",
"packages/cli/src/utils/record-block-binding-hint.ts",
"packages/plugin-detail/README.md",
"packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts",
"packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts",
"packages/plugin-detail/src/renderers/__tests__/recordBinding.appPage-12294.test.tsx",
"packages/plugin-detail/src/renderers/__tests__/recordBinding.recordPageAndBus-12294.test.tsx",
"packages/plugin-detail/src/renderers/record-details.tsx",
"packages/plugin-detail/src/renderers/record-highlights.tsx",
"packages/plugin-detail/src/renderers/record-quick-actions.tsx",
"packages/plugin-detail/src/renderers/recordBinding.tsx",
"packages/types/src/__tests__/public-block-responsive-styles-10872.test.ts",
"packages/types/src/__tests__/record-block-data-source-arms-12294.test.ts",
"packages/types/src/__tests__/zod-mirror-parity.test.ts",
"packages/types/src/zod/public-blocks.zod.ts"
],
"deviations": [
"File surface widened beyond the claim's list, producer-side as the dispatch allows: packages/types/src/zod/public-blocks.zod.ts declares `dataSource` on the three arms, because `objectui validate` (strict face) refused the binding at 45ae6b8 (measured: exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:details\")`), which also made the card's validate warning unreachable; plus the ledgers that pin who declares what (types zod-mirror-parity prose, public-block-responsive-styles DECLARES_DATA_SOURCE, console registry-inputs-spec-parity MEMBER_PINS, plugin-detail recordDetails/recordHighlights spec-parity node-level keys) and the docs (data-source guide, plugin-detail README, AGENTS.md commandment #2).",
"The shared wrapper lives in @object-ui/plugin-detail (renderers/recordBinding.tsx), not in @object-ui/react or @object-ui/core: it needs @object-ui/permissions for the field-policy-gated `$expand`/readable row, which @object-ui/react does not depend on. No new package export.",
"Static census lists the wrapper file (recordBinding.tsx), not the three block files: the gate, the scope and the seam call live once in `recordBindingBlock`, so a record block cannot take the binding without the seam. The three registrations' declaration is asserted per block in recordBinding.appPage-12294 and by the live-registry probe apps/console element-data-source-input-injection.",
"Zone 3 said block bodies stay unchanged; record:quick_actions gained one guard (a context naming an object but no record offers no action), because the 0-row context would otherwise offer declared actions with no record id to run against.",
"Binding `limit` is not mapped: the read always asks for one row (`$top: 1`), the same first row under the same sort.",
"Bound scope mounts a read-only InlineEditProvider and its own HighlightFieldsProvider: not in the dispatch, added because measurement showed a bound block on a record page would otherwise join the page record's inline-edit draft (whose Save bar writes the page record) and trim the page's details body; both pinned and ablated.",
"Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + `Co-authored-by: Claude`), not the harness reminder's model-named trailer.",
"First relay attempt was a --dry-run refused locally (`actions must be an array`, zero writes, nothing sent); re-packed and sent once.",
"REWORK round: the worktree removed after round 1 was recreated on the existing local branch at 4f59ed1 (no merge of origin/main: it moved 3 commits, none touching this diff's files), and removed again after the push.",
"Round 1's gate derivation missed apps/console objectname-binding-required-11605: it names neither the blocks nor the record page; it reads data-source.md and the bound manifest. This round's population is derived from those readers (62 files) plus the whole console suite."
],
"mcp_calls": "0",
"api_writes": "This round: 1 relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/post-stamped, as objectstack-fleet[bot]) -> POST /repos/objectstack-ai/objectui/issues/12294/comments (this updated os-dev-report), plus `git push` (not REST). No label write, no PR body edit, no card write. Round 1: 2 relay dispatches (pr_create #12299 + assignees; os-dev-report comment 6137889031).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: `objectui validate` on a `record:related_list` node carrying `dataSource: { object: 'sys_member', limit: 5 }` at 45ae6b8 -> exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:related_list\")`, while its renderer reads the binding (gate-wrapped, objectstack#6953) and the spec's PageComponentSchema declares `dataSource` on every component · Seam: spec:PageComponentSchema.dataSource -> renderer:RecordRelatedListRenderer (ElementDataSourceGate); the strict authoring face @object-ui/types RecordRelatedListBlockSchema refuses it · not touched here: record:related_list is carved out of this PR; belongs with the seat's related-list decision sub-issue of #12294 (one-line fix shape: the arm declares `dataSource` by reference as record:line_items does, with a describe that says it binds the CHILD rows) · dedupe words: record:related_list dataSource strict face; unrecognized_keys dataSource related list; objectui validate related list binding; RecordRelatedListBlockSchema dataSource",
"carrier: 承接者:无 · noted, not filed — three blocks bound to the same set each read the object definition and the row (no cross-block dedupe); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — RecordDetailView and PagePreview hand-roll fetch+RecordContextProvider; recordBinding.tsx adds a by-query third with a private copy of RecordDetailView's readableRow; it is the natural home of the internal core objectstack#4444 calls hygiene (seat addendum item 7); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — on an app page a bound record:details is read-only (no record-level inline-edit host; the bound scope's session is read-only by design); PR Acceptance notes",
"carrier: 承接者:无 · noted, not filed — record:quick_actions' 0-row bar reads \"no actions configured\", the placeholder it already drew with no record context; PR Acceptance notes"
]
}
Merged through the merge queue. The PR carried Refs #12294, not Fixes, so this card stays open on its related-list leg, as the claim 6137033601 and the recorded ruling 6137246293 (letter B) say.
Landed by content, not by sha: all 20 of the PR's files on main82e5f53 are blob-identical to the head the contract review judged, 1310a8f (record 6138316821, PASS, nothing escalated).
What is on main:
record:details, record:highlights and record:quick_actions honour the per-element dataSource on an app page.
Each block reads the first row by the binding's sort, under the caller's identity, and keeps it fresh on the data-invalidation bus.
@object-ui/types declares the binding on the three strict-face arms, and objectui validate warns on a binding without limit: 1.
The changeset (minor for @object-ui/plugin-detail, @object-ui/types and @object-ui/cli) rides the next objectui release.
The review record's non-blocking note: a bound record:details on an app page is read-only. There is no record-level inline-edit host there, and the bound scope mounts a read-only session by design. The changeset says so for the record page only; no sentence in it is false.
Release, in this act:pm:dispatched → pm:blocked, and the assignee is cleared. Whichever seat restarts the related-list leg claims it afresh.
The remaining leg:record:related_list binds its parent record through the key ruling B adds to @objectstack/spec. It reuses this PR's one-record read and its invalidation wiring.
Cross-repo unlock rule: when objectstack#23134 merges, the card moves to pm:on-hold with an install-surface Restart-when: probe. It is dispatched only once objectui resolves a release that carries the key.
The derivative objectui#12302 (the strict face refuses the child dataSource on record:related_list) is separate. It is dispatched now that this PR's types file is free.
Cross-repo rule: the criterion is that objectui can install the fix, not that it merged upstream.
Restart-when: an @objectstack/specnext prerelease that contains objectstack-ai/objectstack#23134's landing is published on npm, and objectui resolves it (#12030's bump to the next line, p1 and queued, is the other half).
The record blocks that do not need the key (details, highlights, quick actions) are this card's other half, under its existing claim history. ⛔ They do not wait on this hold if a seat splits them out. The related-list half is what waits.
Activity
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop, maintainer direct dispatch (queue jump). The card's ruling is objectstack-ai/cloud#2839 Q1 option D, as the card body quotes it (「同意」): the record blocks honour the per-element
dataSourcethe spec already declares.Direct-dispatch audit (who / words / where): the maintainer
zhuangjianguo· 「12294 插队」 · in this PM session's chat (session_01CsyMRsFJBKNEMa4oPY1b5z), read 2026-10-13T02:42Z. Per the direct channel, this session routes the card itself: it had nodomain:*label, and this act addsdomain:ui.Session:
session_01CsyMRsFJBKNEMa4oPY1b5zAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12294-record-blocks-element-datasourceWorktree:
objectui-issue-12294Domain:
domain:uiSeat:
domain:ui#1File surface: three record blocks consuming a component-level
dataSource, read on objectuimain45ae6b8at 2026-10-13T02:42Z. Stop on a breach and explain it in the report.packages/plugin-detail/src/renderers/record-details.tsxreads its record only throughuseRecordContext()(:251).record-highlights.tsxdoes the same at:39.record-quick-actions.tsxdoes the same at:118.ElementDataSourceGateorelementDataSourceBlock.@object-ui/reactElementDataSourceGate.tsxmaps a binding onto a LIST block's own schema keys (objectName,columns,filter,sort,limit), and the block then fetches.@object-ui/coredata-scope/element-data-source.tsholdscomposeElementDataSource,ELEMENT_DATA_SOURCE_INPUTand theelementDataSourceBlockseam.scripts/check-element-data-source-declaration.mjsholds the census.resolveElementDataSourceis not an export onmain; the dev measures the real path.objectui validatewarning for a record-block binding withoutlimit: 1: in whichever module emits the page tree's prop diagnostics. The dev measures where.major).record:related_listhalf. Onmain,record:related_listalready consumesdataSource, as the binding of its CHILD rows (objectstack#6953).record-related-list.tsx:689iselementDataSourceBlock-wrapped, and the gate mapsdataSource.objectontoobjectName. Reading the same key on the same block as the PARENT record would give one key two meanings and break that shipped authoring shape. The ruling's words, "honour the per-elementdataSourcethe spec already declares", already hold for this block. How a related list on anapppage finds its parent is a separate question, filed next as a decision sub-issue of this card. This PR carriesRefs #12294, ⛔ notFixes. On landing, this card stays open for the related list, blocked on that sub-issue.dataSource. Server authorization is untouched.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Clause-② reason:
dataSourceinput, so the authoring diagnostics stop reportingunknown-propon a key they warned on before. That is a widening of objectui's accepted input, onto the key the spec'sPageComponentSchema.dataSourcealready declares for any page component.validatewarning is added (advisory, refuses nothing).Responsibility:
objectui's record blocks (@object-ui/plugin-detail record:details / record:highlights / record:quick_actions), which read only the record page's RecordContext | the element data-source seam already serves list-shaped blocks (repeater, list-view, number, map, related list) | a product page (objectstack-ai/cloud#2774, #2801) that must today be a type: 'record' page and inherit a record shell its metadata cannot switch offThread-read: none
Serial constraints cleared: read at 2026-10-13T02:42Z on objectui
main45ae6b8.record-related-list.tsx,element-data-source.ts, the gate, the census script or the CLI's validate (5 open PRs read).domain:uicard is objectui#7611 (marchtian, Setup catalog pages), which shares no file.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Pointer from the claiming seat (
domain:uiseat 1 ·session_01CsyMRsFJBKNEMa4oPY1b5z) · 2026-10-13T02:48Z: therecord:related_listhalf that claim6137033601carves out is now decision card objectui#12296.<RecordScope>不做).Refs #12294.pm:blockedwithBlocked-by: #12296.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Ruling recorded for this card's related-list leg (item 3, the half split out to #12296): B, the maintainer's word 「同意」 on the director seat's batch #335, ruling comment #12296 6137226306, 2026-10-13T03:01Z.
Director seat, summon #40,
session_01TJKP6W6UDYEBHtccVdpVoz(written asobjectstack-fleet[bot]through the relay). ⛔ Not a claim, ⛔ no label change on this card; the claim 6137033601 and the three dispatched blocks are untouched.record:related_listgains a parent-record binding key of its own in@objectstack/spec(the shape ofdataSource, the single-record rule, a loud absence when no row answers);dataSourceon the block keeps its published child-row meaning (objectstack#6953). The spec execution card is filed in objectstack from the ruling and named on decision: on an app page, how doesrecord:related_listfind its parent record, given that itsdataSourcealready binds its child rows? (the related-list half of objectui#12294) #12296.Part of), not closed.dataSourceas the parent, or leaving the related list to record pages.objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 12294, "status": "done", "branch": "claude/issue-12294-record-blocks-element-datasource", "pr": "https://github.com/objectstack-ai/objectui/pull/12299", "session": "session_01CsyMRsFJBKNEMa4oPY1b5z (subagent run; the parent seat's harness-stamped id)", "premise_still_valid": true, "summary": "record:details, record:highlights and record:quick_actions now honour the per-element `dataSource` binding: one shared `recordBindingBlock` wrapper (plugin-detail `renderers/recordBinding.tsx`) runs ElementDataSourceGate (mapping filter+sort), reads the FIRST row of the bound set with the page's own adapter (`$top: 1`, `$orderby` = binding sort, placeholder tokens resolved, field-policy-gated `$expand`), provides it through the existing RecordContextProvider, keys the read on `useDataInvalidation(object)` (the bus RecordDetailView uses), and passes the renderer through the `elementDataSourceBlock` seam from core. 0 rows or a refused read = each block's own no-record state; >1 row = first by sort; a failed read = the family's error panel; quick_actions runs declared actions against the bound row id (one body guard: a context naming an object but no record offers no action). Unbound blocks render byte-identically on the record page (measured BASE vs HEAD); a bound block on a record page overrides the page record and is kept out of the page's inline-edit session and highlight registry. Producer-side: @object-ui/types declares `dataSource` (spec ElementDataSourceSchema by reference) on the three strict-face arms, which refused it at 45ae6b8, and `objectui validate` warns on a bound record block without `limit: 1`.", "tests": "All via the shared verify lock, repo-root `pnpm exec vitest run`. Final at 4f59ed1: 12 new/touched files (recordBinding.appPage-12294, recordBinding.recordPageAndBus-12294, validate-record-block-binding-limit-12294, record-block-data-source-arms-12294, public-block-responsive-styles-10872, zod-mirror-parity, recordDetailsInputs/recordHighlightsInputs spec-parity, spec-symbol-batch7, console registry-inputs-spec-parity, element-data-source-input-injection, record-block-record-reach) -> `Test Files 12 passed (12)`, `Tests 642 passed (642)`. Package runs on this branch: plugin-detail/ + react element-data-source/ + react hooks/ + core data-scope/ = 312 files (3 ledger pins red, fixed, re-run green); types/ + cli/ + plugin-detail tests = 654 files (1 ledger file red, fixed, re-run 226 passed); every suite naming RecordDetailView/PagePreview/the three blocks outside those packages, 202 files by git grep: `157 passed` (app-shell, 1398 tests) and `45 passed` (878 tests). type-check exit 0 (script names echoed): @object-ui/plugin-detail, @object-ui/cli, @object-ui/types (incl. tsconfig.test.json with a @ts-expect-error pin + control), @object-ui/console. ABLATIONS (one-time, objectstack scripts/ablation-replace.mjs, anchor x1 -> x0, blob changed, restored blob == HEAD 35cbaf3533fe, `git diff HEAD` empty; src-aliased suite, no dist leg): (1) `useDataInvalidation(...)` -> `0`: the 2 bus pins red, 5 green; (2) read-only InlineEditProvider removed: only the inline-edit isolation pin red; (3) HighlightFieldsProvider shadow removed: only the highlight-trim pin red. RECORD-PAGE BYTES (one-time scratch test, not committed): innerHTML sha256 prefix with BASE block files vs HEAD: details 22c708ac33a9ec77 / highlights 06f18891e29f52c7 / quick_actions c482317ba20672b7 on both legs; BASE files restored from HEAD, each blob == HEAD:path, `git diff HEAD` empty. ESLint proven narrowing: population = eslint.config.js `**/*.{ts,tsx}` blocks; `--format json` over the 16 touched code files = 16 files, 0 errors, 70 warnings (no-explicit-any, react-refresh/only-export-components); no parserOptions (not type-aware) and no eslint-rules/ rule reads disk, so untouched files cannot move; `pnpm lint` left to CI.", "gates": { "head": "4f59ed1", "exit_0_at_head": [ "check:element-data-source-declaration (16 files, all reach the seam from core)", "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 (0 new)", "check:control-bytes", "check:test-path-roots", "check:eager-closure", "check:doc-json", "check:phantom-deps", "check:unused-deps", "check:unreferenced-sources" ], "exit_0_on_branch_before_last_test_only_commit": [ "check:doc-snippets", "check:doc-examples", "check:readme-exports", "check:doc-fences", "check:doc-example-ids", "check:doc-types", "check:docs-route-closure", "check:doc-example-readers", "check:i18n-keys", "check:i18n-drift", "check:i18n-dead-keys", "check:spec-symbols", "check:handler-key-reads", "check:action-forward-parity", "check:designer-field-key-parity", "check:self-import", "check:esm-specifiers", "check:published-tsconfig-exclude", "check:entry-guard", "check:side-effects-array", "check:metadata-write-doors", "check:vi-mock-override-shape", "check:shell-escape-residue", "check:lint-rule-coverage", "check-governed-queue-guard --test (NOT GOVERNED, 19 paths)" ], "build_for_dist_reading_gates": "turbo run build --filter=@object-ui/console... (35 tasks) + the doc-snippet build filter, on this branch (source identical to 4f59ed1)", "ci": "in_progress (not awaited, per contract)", "not_run": "pnpm lint (repo-wide scan, CI-owned; narrowing proven above)" }, "line_budget": "not applicable: no skills/** or governed ledger touched; diff 45ae6b8..4f59ed1 = 19 files changed, 1510 insertions(+), 14 deletions(-)", "files_changed": [ ".changeset/12294-record-blocks-element-datasource.md", "apps/console/src/__tests__/registry-inputs-spec-parity.test.ts", "content/docs/guide/data-source.md", "packages/cli/src/__tests__/validate-record-block-binding-limit-12294.test.ts", "packages/cli/src/commands/validate.ts", "packages/cli/src/utils/record-block-binding-hint.ts", "packages/plugin-detail/README.md", "packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts", "packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts", "packages/plugin-detail/src/renderers/__tests__/recordBinding.appPage-12294.test.tsx", "packages/plugin-detail/src/renderers/__tests__/recordBinding.recordPageAndBus-12294.test.tsx", "packages/plugin-detail/src/renderers/record-details.tsx", "packages/plugin-detail/src/renderers/record-highlights.tsx", "packages/plugin-detail/src/renderers/record-quick-actions.tsx", "packages/plugin-detail/src/renderers/recordBinding.tsx", "packages/types/src/__tests__/public-block-responsive-styles-10872.test.ts", "packages/types/src/__tests__/record-block-data-source-arms-12294.test.ts", "packages/types/src/__tests__/zod-mirror-parity.test.ts", "packages/types/src/zod/public-blocks.zod.ts" ], "deviations": [ "File surface widened beyond the claim's list, producer-side as the dispatch allows: packages/types/src/zod/public-blocks.zod.ts declares `dataSource` on the three arms, because `objectui validate` (strict face) refused the binding at 45ae6b8 (measured: exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:details\")`), which also made the card's validate warning unreachable; plus the ledgers that pin who declares what (types zod-mirror-parity prose, public-block-responsive-styles DECLARES_DATA_SOURCE, console registry-inputs-spec-parity MEMBER_PINS, plugin-detail recordDetails/recordHighlights spec-parity node-level keys) and the docs (data-source guide, plugin-detail README, AGENTS.md commandment #2).", "The shared wrapper lives in @object-ui/plugin-detail (renderers/recordBinding.tsx), not in @object-ui/react or @object-ui/core: it needs @object-ui/permissions for the field-policy-gated `$expand`/readable row, which @object-ui/react does not depend on. No new package export.", "Static census lists the wrapper file (recordBinding.tsx), not the three block files: the gate, the scope and the seam call live once in `recordBindingBlock`, so a record block cannot take the binding without the seam. The three registrations' declaration is asserted per block in recordBinding.appPage-12294 and by the live-registry probe apps/console element-data-source-input-injection.", "Zone 3 said block bodies stay unchanged; record:quick_actions gained one guard (a context naming an object but no record offers no action), because the 0-row context would otherwise offer declared actions with no record id to run against.", "Binding `limit` is not mapped: the read always asks for one row (`$top: 1`), the same first row under the same sort.", "Bound scope mounts a read-only InlineEditProvider and its own HighlightFieldsProvider: not in the dispatch, added because measurement showed a bound block on a record page would otherwise join the page record's inline-edit draft (whose Save bar writes the page record) and trim the page's details body; both pinned and ablated.", "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + `Co-authored-by: Claude`), not the harness reminder's model-named trailer.", "First relay attempt was a --dry-run refused locally (`actions must be an array`, zero writes, nothing sent); re-packed and sent once." ], "mcp_calls": "0", "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/fleet-write, written as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft, #12299) + POST /repos/objectstack-ai/objectui/issues/12299/assignees [zhuangjianguo], read back 12714 bytes identical; (2) this os-dev-report -> POST /repos/objectstack-ai/objectui/issues/12294/comments. Plus `git push` (not REST). No label write, no card write.", "open_questions": [], "out_of_scope_findings": [ "class: b · reach: `objectui validate` on a `record:related_list` node carrying `dataSource: { object: 'sys_member', limit: 5 }` at 45ae6b8 -> exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:related_list\")`, while its renderer reads the binding (gate-wrapped, objectstack#6953) and the spec's PageComponentSchema declares `dataSource` on every component · Seam: spec:PageComponentSchema.dataSource -> renderer:RecordRelatedListRenderer (ElementDataSourceGate); the strict authoring face @object-ui/types RecordRelatedListBlockSchema refuses it · not touched here: record:related_list is carved out of this PR; belongs with the seat's related-list decision sub-issue of #12294 (one-line fix shape: the arm declares `dataSource` by reference as record:line_items does, with a describe that says it binds the CHILD rows) · dedupe words: record:related_list dataSource strict face; unrecognized_keys dataSource related list; objectui validate related list binding; RecordRelatedListBlockSchema dataSource", "carrier: 承接者:无 · noted, not filed — three blocks bound to the same set each read the object definition and the row (no cross-block dedupe); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — RecordDetailView and PagePreview hand-roll fetch+RecordContextProvider; recordBinding.tsx adds a by-query third with a private copy of RecordDetailView's readableRow; it is the natural home of the internal core objectstack#4444 calls hygiene (seat addendum item 7); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — on an app page a bound record:details is read-only (no record-level inline-edit host; the bound scope's session is read-only by design); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — record:quick_actions' 0-row bar reads \"no actions configured\", the placeholder it already drew with no record context; PR Acceptance notes" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 12294, "status": "done", "branch": "claude/issue-12294-record-blocks-element-datasource", "pr": "https://github.com/objectstack-ai/objectui/pull/12299", "session": "session_01CsyMRsFJBKNEMa4oPY1b5z (subagent run; the parent seat's harness-stamped id)", "premise_still_valid": true, "summary": "REWORK round, PR #12299, new head 1310a8f. The CI failure on 4f59ed1 was real and came from this diff: Test (shard 3/12) failed in apps/console objectname-binding-required-11605. Its binding-doc ledger read the three new data-source.md rows (object column \"✅ (its record)\") as `objectName` blocks, but a one-record block's binding lands on the record scope (RecordContextProvider), not on a schema key. Fix: `objectKeyOf` returns no key for exactly `element:*` plus a named set ONE_RECORD_BLOCKS = record:details, record:highlights, record:quick_actions, not a `record:` prefix. Row 3 now asserts four things: (a) the exception is exactly those tags; (b) no `object-*` tag falls into it; (c) each of the three is in it, declares no `objectName`, and its doc row reads 'its record'; (d) record:related_list still lands on `objectName`. The doc paragraph under the table now says which two kinds of block have no object key, and the test's quotation of it was updated. No assertion weakened, nothing skipped. The implementation from the first round is unchanged: one recordBindingBlock wrapper (gate with filter+sort, first bound row via the page adapter with `$top: 1`, the data-invalidation bus, the elementDataSourceBlock seam); the @object-ui/types strict-face `dataSource` on the three arms; the `objectui validate` limit:1 warning. CI on 1310a8f is all green, Type Check included.", "tests": "THIS ROUND, at 1310a8f, through the shared verify lock (repo-root `pnpm exec vitest run`): (1) apps/console/src/__tests__/objectname-binding-required-11605.test.ts -> `Tests 52 passed (52)`. (2) Whole console suite `pnpm exec vitest run apps/console/` -> `Test Files 183 passed (183)`, `Tests 2069 passed (2069)`. (3) Gate population re-derived: every suite that reads the binding doc or the bound-entry manifest, found by `git grep -l` of `data-source.md|isElementDataSourceBlock|ELEMENT_DATA_SOURCE_INPUT|ELEMENT_DATA_SOURCE_KEY` plus `\\bBOUND\\b` over test files, 62 files -> `Test Files 62 passed (62)`, `Tests 2321 passed (2321)`. ABLATIONS on the fixed test (one-time, objectstack scripts/ablation-replace.mjs; anchor x1 -> x0, blob changed, restored blob == HEAD de09b99e2580, `git diff HEAD` empty): A) `|| ONE_RECORD_BLOCKS.has(tag)` removed -> 1 failed / 51 passed, reproducing CI's message (tag in angle brackets, then) `: the binding's object lands on \"objectName\", which the entry does not declare` for record:details; B) the named set replaced by a `record:` prefix -> 1 failed / 51 passed (`expected false to be true`: related_list falls into the exception). ESLint on the changed test: 1 file, 0 errors, 0 warnings. CI on 1310a8f (GET check-runs, 33 runs): 30 success, 3 skipped (coverage x2, dependabot), 0 failures; `Type Check` success, `Test (shard 3/12)` success, `Test` success, `Lint` success, `Repo Gates` success, `Build & E2E` success. CI on 4f59ed1 for comparison: Type Check success; the only failures were Test (shard 3/12) and its Test aggregate, with the annotation naming the assertion above. FIRST ROUND (unchanged, at 4f59ed1): 12 new/touched files 642 passed; plugin-detail + react gate/hooks + core data-scope 312 files; types + cli + plugin-detail 654 files; 202 record-page suites (157 + 45 passed); type-check exit 0 on plugin-detail, cli, types, console. Three ablations: bus subscription, read-only inline-edit session, highlight-registry shadow; each reddened exactly its pin. Record-page innerHTML bytes equal on BASE vs HEAD for all three blocks.", "gates": { "head": "1310a8f", "exit_0_at_head_local": [ "check:new-line-citations", "check:control-bytes", "check:test-path-roots", "check:changeset-claims", "check:pending-changeset-literals", "check:doc-fences", "check:element-data-source-declaration", "check:vi-mock-inherit", "check:vi-mock-specifiers", "check-changeset-presence" ], "ci_at_head": "33 check runs: 30 success, 3 skipped, 0 failure. Type Check success; Test (shard 1-12/12) success; Test (dist pins) success; Lint success; Repo Gates success; Build Docs success; Build & E2E success; Governed Surface Queue Guard success; Changeset Declaration / Bump Policy / Fixed Group / Claim Re-read success", "first_round_at_4f59ed1": "see first report (comment 6137889031): 18 gates exit 0 at head, plus the doc/i18n/spec families on the branch", "not_run_locally_this_round": "dist-reading gates (doc-json, sdui-registration-pins, eager-closure, doc-snippets): no build in the recreated worktree; CI Repo Gates / Build Docs / Build & E2E are green on 1310a8f" }, "line_budget": "not applicable: no skills/** or governed ledger touched; diff 45ae6b8..1310a8f = 20 files changed, 1549 insertions(+), 28 deletions(-); this round 4f59ed1..1310a8f = 2 files", "files_changed": [ ".changeset/12294-record-blocks-element-datasource.md", "apps/console/src/__tests__/objectname-binding-required-11605.test.ts", "apps/console/src/__tests__/registry-inputs-spec-parity.test.ts", "content/docs/guide/data-source.md", "packages/cli/src/__tests__/validate-record-block-binding-limit-12294.test.ts", "packages/cli/src/commands/validate.ts", "packages/cli/src/utils/record-block-binding-hint.ts", "packages/plugin-detail/README.md", "packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts", "packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts", "packages/plugin-detail/src/renderers/__tests__/recordBinding.appPage-12294.test.tsx", "packages/plugin-detail/src/renderers/__tests__/recordBinding.recordPageAndBus-12294.test.tsx", "packages/plugin-detail/src/renderers/record-details.tsx", "packages/plugin-detail/src/renderers/record-highlights.tsx", "packages/plugin-detail/src/renderers/record-quick-actions.tsx", "packages/plugin-detail/src/renderers/recordBinding.tsx", "packages/types/src/__tests__/public-block-responsive-styles-10872.test.ts", "packages/types/src/__tests__/record-block-data-source-arms-12294.test.ts", "packages/types/src/__tests__/zod-mirror-parity.test.ts", "packages/types/src/zod/public-blocks.zod.ts" ], "deviations": [ "File surface widened beyond the claim's list, producer-side as the dispatch allows: packages/types/src/zod/public-blocks.zod.ts declares `dataSource` on the three arms, because `objectui validate` (strict face) refused the binding at 45ae6b8 (measured: exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:details\")`), which also made the card's validate warning unreachable; plus the ledgers that pin who declares what (types zod-mirror-parity prose, public-block-responsive-styles DECLARES_DATA_SOURCE, console registry-inputs-spec-parity MEMBER_PINS, plugin-detail recordDetails/recordHighlights spec-parity node-level keys) and the docs (data-source guide, plugin-detail README, AGENTS.md commandment #2).", "The shared wrapper lives in @object-ui/plugin-detail (renderers/recordBinding.tsx), not in @object-ui/react or @object-ui/core: it needs @object-ui/permissions for the field-policy-gated `$expand`/readable row, which @object-ui/react does not depend on. No new package export.", "Static census lists the wrapper file (recordBinding.tsx), not the three block files: the gate, the scope and the seam call live once in `recordBindingBlock`, so a record block cannot take the binding without the seam. The three registrations' declaration is asserted per block in recordBinding.appPage-12294 and by the live-registry probe apps/console element-data-source-input-injection.", "Zone 3 said block bodies stay unchanged; record:quick_actions gained one guard (a context naming an object but no record offers no action), because the 0-row context would otherwise offer declared actions with no record id to run against.", "Binding `limit` is not mapped: the read always asks for one row (`$top: 1`), the same first row under the same sort.", "Bound scope mounts a read-only InlineEditProvider and its own HighlightFieldsProvider: not in the dispatch, added because measurement showed a bound block on a record page would otherwise join the page record's inline-edit draft (whose Save bar writes the page record) and trim the page's details body; both pinned and ablated.", "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + `Co-authored-by: Claude`), not the harness reminder's model-named trailer.", "First relay attempt was a --dry-run refused locally (`actions must be an array`, zero writes, nothing sent); re-packed and sent once.", "REWORK round: the worktree removed after round 1 was recreated on the existing local branch at 4f59ed1 (no merge of origin/main: it moved 3 commits, none touching this diff's files), and removed again after the push.", "Round 1's gate derivation missed apps/console objectname-binding-required-11605: it names neither the blocks nor the record page; it reads data-source.md and the bound manifest. This round's population is derived from those readers (62 files) plus the whole console suite." ], "mcp_calls": "0", "api_writes": "This round: 1 relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/post-stamped, as objectstack-fleet[bot]) -> POST /repos/objectstack-ai/objectui/issues/12294/comments (this updated os-dev-report), plus `git push` (not REST). No label write, no PR body edit, no card write. Round 1: 2 relay dispatches (pr_create #12299 + assignees; os-dev-report comment 6137889031).", "open_questions": [], "out_of_scope_findings": [ "class: b · reach: `objectui validate` on a `record:related_list` node carrying `dataSource: { object: 'sys_member', limit: 5 }` at 45ae6b8 -> exit 1, `Undeclared key \"dataSource\" at (root) (type \"record:related_list\")`, while its renderer reads the binding (gate-wrapped, objectstack#6953) and the spec's PageComponentSchema declares `dataSource` on every component · Seam: spec:PageComponentSchema.dataSource -> renderer:RecordRelatedListRenderer (ElementDataSourceGate); the strict authoring face @object-ui/types RecordRelatedListBlockSchema refuses it · not touched here: record:related_list is carved out of this PR; belongs with the seat's related-list decision sub-issue of #12294 (one-line fix shape: the arm declares `dataSource` by reference as record:line_items does, with a describe that says it binds the CHILD rows) · dedupe words: record:related_list dataSource strict face; unrecognized_keys dataSource related list; objectui validate related list binding; RecordRelatedListBlockSchema dataSource", "carrier: 承接者:无 · noted, not filed — three blocks bound to the same set each read the object definition and the row (no cross-block dedupe); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — RecordDetailView and PagePreview hand-roll fetch+RecordContextProvider; recordBinding.tsx adds a by-query third with a private copy of RecordDetailView's readableRow; it is the natural home of the internal core objectstack#4444 calls hygiene (seat addendum item 7); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — on an app page a bound record:details is read-only (no record-level inline-edit host; the bound scope's session is read-only by design); PR Acceptance notes", "carrier: 承接者:无 · noted, not filed — record:quick_actions' 0-row bar reads \"no actions configured\", the placeholder it already drew with no record context; PR Acceptance notes" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed (part): objectui PR #12299 →
main82e5f53·domain:uiseat 1 ·session_01CsyMRsFJBKNEMa4oPY1b5z· 2026-10-13T04:52ZRefs #12294, notFixes, so this card stays open on its related-list leg, as the claim6137033601and the recorded ruling6137246293(letter B) say.main82e5f53are blob-identical to the head the contract review judged,1310a8f(record6138316821, PASS, nothing escalated).main:record:details,record:highlightsandrecord:quick_actionshonour the per-elementdataSourceon anapppage.sort, under the caller's identity, and keeps it fresh on the data-invalidation bus.@object-ui/typesdeclares the binding on the three strict-face arms, andobjectui validatewarns on a binding withoutlimit: 1.minorfor@object-ui/plugin-detail,@object-ui/typesand@object-ui/cli) rides the next objectui release.record:detailson anapppage is read-only. There is no record-level inline-edit host there, and the bound scope mounts a read-only session by design. The changeset says so for the record page only; no sentence in it is false.pm:dispatched→pm:blocked, and the assignee is cleared. Whichever seat restarts the related-list leg claims it afresh.Blocked-by: objectstack-ai/objectstack#23134
record:related_listbinds its parent record through the key ruling B adds to@objectstack/spec. It reuses this PR's one-record read and its invalidation wiring.pm:on-holdwith an install-surfaceRestart-when:probe. It is dispatched only once objectui resolves a release that carries the key.dataSourceonrecord:related_list) is separate. It is dispatched now that this PR's types file is free.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Unlock scan:
pm:blocked→pm:on-hold. Upstream landed, but it is not in a published release objectui resolves yetTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T10:03Z. ⛔ Not a claim, ⛔ not a dispatch.record:related_listdeclares its parent record with a binding key of its own, beside the child-rowdataSource— the spec half of objectstack-ai/objectui#12294's related-list leg (ruled B on objectstack-ai/objectui#12296) objectstack#23134 (therecord:related_listparent-binding key, ruling B on decision: on an app page, how doesrecord:related_listfind its parent record, given that itsdataSourcealready binds its child rows? (the related-list half of objectui#12294) #12296) closedcompletedat 2026-10-13T09:47Z. Its landing6138848338is not an ancestor of the@objectstack/spec@18.0.0-next.0tag, read now. So no published version carries the key.Restart-when: an
@objectstack/specnextprerelease that contains objectstack-ai/objectstack#23134's landing is published on npm, and objectui resolves it (#12030's bump to thenextline, p1 and queued, is the other half).priority:p1).