Skip to content

fix(plugin-detail): record:related_list stops requiring columns, so the page compile accepts a node that lists none (objectui#11613) - #11621

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11613-related-list-columns
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11613-related-list-columns

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11613
Clause-②: yes

What changes

The record:related_list registration in @object-ui/plugin-detail (ComponentRegistry.register('related_list', …)) no longer declares required: true on its columns input. The input's description now says where the columns come from when the key is absent. The ledger row record:related_list.columns in objectui#11605's enumeration pin is deleted, so that pin now covers this member like the others.

This is triage's first grade on the card (comment 5980741977), under objectui#11605's ruling (a): the spec row ComponentPropsMap['record:related_list'].columns is optional, and the registration may not be stricter than the row it publishes. The spec row's own describe says what an omitted list means: "Optional: when omitted, columns derive from the related object's highlightFields / default list columns".

No new manifest vocabulary, no new compile rule, no runtime change. packages/types, sdui-parser and the renderer are not edited. Landing site as dispatched: the registration in packages/plugin-detail/src/index.tsx, the ledger row, and tests beside them.

Accept-set changes (for the contract review)

Measured against the manifest the console ships: emitSduiManifest over dev/manifest-registry.ts, read back from the written sdui.manifest.json. "Before" is the registration with required: true (base 2abec3a, re-measured on this branch by ablation 1 below); "after" is this head. Nodes are written as JSON keys here; the pins write the same nodes as JSX.

type: "record:related_list" node with Before After
objectName, relationshipField, dataSource: { object, view }, no columns ok: false, one missing-required-prop error naming columns ok: true, no diagnostics, the binding recorded
objectName, relationshipField, no columns, no binding ok: false, the same error ok: true, no diagnostics
objectName, relationshipField, dataSource: { object } (no view), no columns ok: false, the same error ok: true, no diagnostics
objectName, relationshipField, columns, dataSource: { object, view } (control) ok: true the same
objectName, columns, no relationshipField (control) refused, one error naming relationshipField the same
relationshipField, columns, no objectName, bound by dataSource: { object } or not (control) refused, one error naming objectName the same; the spec row requires it

What the accept set gains: exactly the nodes that omit columns and carry everything else the gate already required (objectName and relationshipField), with or without a dataSource binding.

What stays refused: a node without objectName, bound or not, and a node without relationshipField.

Unchanged, and not refusals before or after: a columns value of the wrong kind (a number or a string) draws a type-mismatch warning with ok: true; a spec-legal column object draws a member-type-mismatch warning with ok: true (Acceptance notes). Neither check reads required.

What a columns-less node draws (measured, not assumed)

Triage's premise, that the neither node auto-derives its columns, is now a measurement. RecordRelatedListRenderer.columnsOptional-11613.test.tsx renders through the real renderer, the real ElementDataSourceGate, the real RelatedList and the real table, and reads header and body cells:

  • The neither node draws columns derived from the related object (Subject, Status, Priority, and not the foreign key back to the parent). This holds unbound and bound by object alone. No hint, no blank, no throw.
  • The view-bound node draws the view's columns only (Subject).
  • Authored columns win over the view and over the derivation.

Who reads the registration's required for this input

Enumerated by grepping non-test sources for reads of required off registration or manifest inputs:

  • @object-ui/sdui-parser validateTree, the missing-required-prop check (input.required && !(input.name in node)). It is the page compile and its ok, the save gate. It now accepts the nodes above.
  • @object-ui/components' page renderer, the kind: 'html' page compile (manifestFromConfigs over the live registry). The same validator at render time, so the same widening. Source reading, not separately measured.
  • @object-ui/sdui-parser codegen. generateDts now writes columns?: string[] on RecordRelatedListProps, and generateBlockList's required column for the tag reads objectName, relationshipField. Measured once on this branch by a throwaway probe, not committed.
  • Downstream: the objectstack tools that load the shipped sdui.manifest.json (the CLI's compile, lint and validate, and metadata-protocol's runtime authoring gate) inherit the widening when the next console manifest ships. Not measured here.
  • Studio's designer and the AI prompt keys: no reader of the registration's required found. check:prompt-keys reads the key bullets in .github/prompts/**, not required, and no prompt teaches columns as required.

Generated artifacts

sdui.manifest.json, sdui-intrinsics.d.ts and sdui-blocks.md are not tracked: no tracked file has those names, and the positive control apps/console/scripts/emit-sdui-manifest.ts is tracked. They regenerate from the registration in the console build, as for objectui#11605. This diff has no regeneration step.

Pins

  • New apps/console/src/__tests__/related-list-columns-optional-11613.test.ts, 7 rows, judged against the shipped manifest:
    • The published columns is not required, and its description names dataSource and highlightFields. objectName and relationshipField stay required.
    • The view-bound node, the neither node, and the neither node bound by object alone each compile ok with no diagnostic.
    • Three controls: authored columns beside a view; no relationshipField; no objectName, bound or not.
  • New packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.columnsOptional-11613.test.tsx, 5 rows, as described above.
  • apps/console/src/__tests__/objectname-binding-required-11605.test.ts: the LEDGER is now empty and says why. Its stale-row check stays, so a future row is still judged.

Ablations on the committed fix (0624118), through objectstack's scripts/ablation-replace.mjs. The console project aliases @object-ui/plugin-detail and @object-ui/sdui-parser to src, so a mutation reaches the tests without a build.

  1. required: true put back on columns (packages/plugin-detail/src/index.tsx, blob 62fba905fb9f became 23df5500cb78), with the two console files run. Exactly 5 rows went red and 50 passed. Red: the enumeration row for record:related_list (its message names "columns"), the published-input row, and the three columns-less compile rows, each with a missing-required-prop naming columns. Every control stayed green.
  2. RelatedList's derivation switched off (the if (!objectSchema?.fields) return []; guard made unconditional in RelatedList.tsx, blob 3c5bbfc1b87d became e5a0270e381b), with the render file run. Exactly the 2 neither-node rows went red and 3 passed.

Each restore is proven by the tool: blob equals HEAD and git diff HEAD is empty. The render rows need no "before" run: the runtime is not changed, and they read triage's premise.

Gates

Final head 305c738, which merges main at 6e9090c. Every run below is on that head, from the repo root.

  • pnpm exec vitest run packages/plugin-detail/, run as two path-scoped halves to stay under the foreground time cap. src/__tests__/: 149 files passed, 1 skipped; 1335 tests passed, 8 skipped. src/renderers/, src/synth/ and the 7 top-level test files: 86 files, 991 tests passed. The 236 files are all of the package's tracked test files.
  • pnpm exec vitest run apps/console/, as two halves. src/__tests__/: 51 files, 880 tests passed. The rest: 97 files, 888 tests passed. The 148 files are all of the console's tracked test files.
  • pnpm --filter @object-ui/plugin-detail type-check (echoed tsc --noEmit && tsc -p tsconfig.test.json): exit 0, after turbo run build --filter='@object-ui/console^...' rebuilt the dependency closure on this head. --listFiles, run once before the merge, shows the new render test is in the test program.
  • pnpm --filter @object-ui/console type-check (echoed tsc --noEmit && tsc -b tsconfig.node.json --force): exit 0. Its tsconfig includes src, so the new console test is in the program.
  • Lint, narrowed to the 4 touched code files with eslint --format json: 4 files, 0 errors, 34 warnings. index.tsx carries 32, the same as at base 2abec3a read through --stdin. The new render test carries 2 no-explicit-any, the pattern its sibling redactedDerivation-9053 test uses. The narrowing is a measurement because: each file is linted by the root eslint.config.js (--print-config exit 0 for each); the count comes from the JSON output; and eslint.config.js enables no type-aware linting (no parserOptions.project, no projectService), and no rule under eslint-rules/ reads the disk, so this diff cannot move any untouched file's verdict.
  • Root checks, each exit 0: check-changeset-presence.mjs, check-changeset-no-major.mjs, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations (0 new citations), check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:element-data-source-declaration, check:prompt-keys, check:component-surface-parity (report-only), check:unreferenced-sources.
  • The changeset scripts' own suites (scripts/__tests__/ no-major, presence, claims, fixed, overwrite, polarity census): 6 files, 277 tests passed.

CI runs the full farm.

Changeset

.changeset/11613-plugin-detail-related-list-columns-optional.md: @object-ui/plugin-detail minor, with the widening paragraph opening **Clause-②: yes (widening)**.

Acceptance notes

  • The member kind on columns. Measured on this head: a spec-legal column object ({ field, label }) compiles ok: true with a member-type-mismatch warning, because the registration declares of: 'string' while the spec row admits string or ListColumn members. This is already recorded in-tree as a gap, in registry-inputs-spec-parity.test.ts's record:related_list.columns entry. This PR does not change it.
  • A real producer of the neither node. The platform's own default-record-page synthesizer (buildDefaultPageSchema in this package, fed by app-shell's deriveRelatedLists) emits record:related_list nodes with no columns whenever the foreign key declares no relatedListColumns. Whether such a node ever reached the save gate was not measured.
  • The dispatch brief's churn note. It said PR objectui#11612 last touched both files. Measured: that PR touched the enumeration pin only. packages/plugin-detail/src/index.tsx was last changed by objectui#11531 (6158e4c).
  • Docs. The binding doc's paragraph on the objectName exception stays true. No doc or README says columns is required (grep over content/docs and the package README), so no doc edit is owed.

Generated by Claude Code

claude added 3 commits October 4, 2026 15:56
…s at the compile and at render; drop the columns ledger row (objectui#11613)

The compile pins (`related-list-columns-optional-11613.test.ts`) are red on
this commit: the registration still declares `columns` required. The render
pins measure what a columns-less node draws through the real renderer, gate
and table.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
… the page compile accepts a node that lists none (objectui#11613)

The spec row leaves `columns` optional. A view-bound node draws the view's
columns, and a node with neither draws columns derived from the related
object, so the registration may not be stricter than the row it publishes.
The published description now says where the columns come from without it.

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

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3314.3 KB 3330.4 KB
Main entry chunk (gzip) 151.6 KB 350 KB
Entry file index-Mj3N6kVM.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
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.17KB 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) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 573.73KB 137.60KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.97KB 64.67KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.65KB 45.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
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.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
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.36KB 2.51KB
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 (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.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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: 305c7384ec1e3e694db99dc3c9c8d7b7013018b2
Local-runs: none

① Derived judgments

Inputs: card objectui#11613 (body and all four comments: the triage grade 5980741977, the claim 5981630431, the dev report 5982668365, the seat's content ACCEPT 5982687449); PR objectui#11621 (body, file list, net diff against main); the check-runs on the head. Source statements below are read off main and the head through git show. Nothing was built, run or re-run.

The diff is five files. One runtime file, packages/plugin-detail/src/index.tsx: the related_list registration's columns input drops required: true and gains a description saying where columns come from when the key is absent. The 11605 enumeration pin's LEDGER loses its one row (record:related_list.columns) and is empty with the reason written in. Two new pin files (console compile, plugin-detail render). One changeset. No edit to packages/types, packages/sdui-parser, the renderer, the gate or RelatedList.

  1. Accept-set widening at the page compile: RIGHT. The compile's only reader of required is validateTree's check input.required && !(input.name in node), which emits missing-required-prop. With the flag gone, a record:related_list node that omits columns is accepted whether or not a dataSource binding is present or names a view. Nothing else moves: objectName and relationshipField keep required: true, and the console pin's two refusal controls read them unchanged. No node accepted before is refused now, so this is a pure widening.
  2. The widening follows the spec row: RIGHT. RecordRelatedListProps in @objectstack/spec declares columns as .optional(), with the describe "Optional: when omitted, columns derive from the related object's highlightFields / default list columns". The family's ruling (a) applies (the registration may not be stricter than the row it publishes), and the triage grade on this card answers the card's three questions the same way. The diff implements each: required dropped, the no-columns case measured, the ledger row removed.
  3. The runtime answer for a columns-less node is measured, not assumed: RIGHT. Read at the head: RecordRelatedListRenderer reads schema.columns through Array.isArray(schema.columns) ? schema.columns : [], so an absent key never throws. RelatedList reads Array.isArray(columns) ? columns : [] on every path and, with nothing authored, derives from the child object's highlightFields or the heuristic field walk, minus the parent FK. ElementDataSourceGate's columns: true mapping copies a named view's columns onto the node only when the authored list is absent or empty (if (!authored && composed.columns !== undefined)), so authored columns win over the view. The five render rows pin exactly these three statements through the real renderer, gate, RelatedList and table. No hint is owed: unlike the objectName family, the node still draws a list.
  4. Public-surface changes implied by the registration edit: RIGHT, and all derived at build. The shipped sdui.manifest.json entry for record:related_list publishes columns not required, with the new description; generateDts writes columns?: string[] on RecordRelatedListProps; generateBlockList's required column for the tag reads objectName, relationshipField. None of these files is tracked, so the diff owes no regeneration. The console pin judges the compile against the manifest emitSduiManifest writes, the file a host registers as the save gate's manifest.
  5. The ledger edit: RIGHT. The 11605 pin's row 2 filters LEDGER[tag]?.[name] === undefined; with columns no longer required, record:related_list has no contradiction, so the row is deleted rather than kept, as triage item 3 requires. The stale-row check stays in place over the now-empty ledger. The old row's reason ("supplied only by a named view, not by dataSource.object") is answered by judgment 3: the object-only node derives its columns and never needed authoring.
  6. The new description is a runtime string: RIGHT. It carries no tracker number and no model identifier; it names dataSource, highlightFields and the authored-wins rule, each asserted by content in the console pin. The objectui#11613 citation sits in the code comment beside it, this repo's existing convention.
  7. Unchanged diagnostics: RIGHT to leave alone. A columns value of the wrong kind draws type-mismatch with ok: true, and a spec-legal ListColumn object member draws member-type-mismatch with ok: true, before and after; neither check reads required. The of: 'string' versus union gap is already asserted in-tree as a gap (registry-inputs-spec-parity.test.ts, entry record:related_list.columns) and is outside this card's defect class (see ③).
  8. No other reader of the registration's required: confirmed by grep at the head. Readers are sdui-parser only (validate.ts, codegen.ts, the manifest emitter in index.ts). No designer, app-shell or prompt file hard-codes columns as required for this tag, and no doc or README states it. The binding doc's record:related_list row already lists columns among what the binding supplies.
  9. Shape: RIGHT. Head repo equals base repo (not a fork). No governed path in the file list (Governed Surface Queue Guard is success). Commit trailers on the three branch commits are the model-free pair. No model identifier in the PR title, body, changeset or code comments. No cross-file path:line citation in the diff (Line Citation Gate is success).

② Semver level

.changeset/11613-plugin-detail-related-list-columns-optional.md: '@object-ui/plugin-detail': minor, with the paragraph opening **Clause-②: yes (widening)**. Judged RIGHT:

  • The diff publishes a behaviour change from one released package, @object-ui/plugin-detail (its src/index.tsx). The widening is in the accept set of a published contract (the registration the manifest emits), so Clause-②: yes is the true declaration, and yes takes at least minor. Nothing accepted before is refused now, so the (narrowing) arm would be false; patch would understate a widening; major is forbidden in this fixed group; skip-changeset does not apply.
  • apps/console changes are test-only, and @object-ui/console bumps with the fixed group. The family precedent (11569-line-items-child-object-optional.md and the seven 11605-* changesets) names only the package whose registration moved and carries the same **Clause-②: yes (widening)** form; this changeset matches it.
  • The changeset's sentences hold against the diff and the source read above: a node with no columns compiles and saves with or without a view; authored columns win over both; objectName and relationshipField stay required; the published input carries the new description.
  • Clause-② line: the PR body carries Clause-②: yes at the start of its own line, matching the claim (5981630431) and the changeset arm. Gate verdicts on the head: Changeset Declaration, Changeset Claim Re-read, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Overwrite Report are all success.

③ Boundary flags

open_questions: none declared in the dev report. Every flag the report and the PR raise, with its answer:

  • Out-of-scope (b): a spec-legal ListColumn member draws member-type-mismatch (ok: true) because the registration declares of: 'string'. Answered by the seat (5982687449): kept as an Acceptance note, not filed, because the gap is already pinned in-tree as a gap and the family card objectui#8067 is closed; the seat's dedupe found no open carrier. Judged a correct boundary: a warning with ok: true is not a refusal, so it does not bear on this PR's accept set, and of is a different key property than required. Noted for the seat: that in-tree pin is the gap's only carrier today.
  • Out-of-scope note: the default record-page synthesizer (buildDefaultPageSchema, fed by deriveRelatedLists) emits record:related_list nodes with no columns when the FK declares no relatedListColumns. Answered: evidence for this change (a real producer of the shape the gate now accepts), recorded in the PR's Acceptance notes. Whether such a node reached the save gate before is unmeasured and not needed for the verdict.
  • Deviation, verify lock: pin files, ablations and probes ran without the shared lock after a 30-minute wait; every full suite, build and type-check went through it. Process-level; the check-runs on the head are the gate verdicts here.
  • Deviation, halves and narrowed lint: suites ran as path-scoped halves whose union is every tracked test file of each package; lint narrowed to the four touched code files with a stated invariance argument. Both superseded by CI: Lint and Type Check are success on the head, and the test shards are the farm's run.
  • Deviation, check-half-states.mjs not found: correctly identified as an objectstack board sweeper, not an objectui gate.
  • Correction: the dispatch brief's churn note (PR objectui#11612 touched both surface files) was wrong; the dev measured fd060f0 touched the 11605 pin only and index.tsx was last changed by 6158e4c. Owned by the seat in 5982687449. No bearing on the diff.
  • Commit trailers: the model-free pair per objectui AGENTS.md, which takes precedence over the harness reminder. Confirmed on all three branch commits.
  • Nothing escalated beyond the seat; nothing left open.

Check-runs on the head, read at 2026-10-04T17:49Z: 42 runs. 34 success, 3 skipped (the coverage matrix and dependabot, expected), 5 in_progress (Test shards 1, 2, 4, 5 and 6 of 8; shards 3, 7 and 8 are success). No run has failed. Lint, Type Check, Spec Main Shape Gate, Build & E2E, Test (dist pins), Line Citation Gate, Governed Surface Queue Guard and the five changeset gates are success. The in-progress shards are recorded as such; landing waits on every check green, which the seat enforces separately.

Implemented-by: claude/issue-11613-related-list-columns
Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

needs:contract-review removed: a conforming PASS is on record for this head.

  • Record: 5982768686 (## Contract review, Served-tier: CONTRACT_REVIEW_TIER, Local-runs: none, VERDICT PASS).
  • Judged head: 305c7384ec1e3e694db99dc3c9c8d7b7013018b2, the PR's head at this write.
  • Independence pair: Implemented-by: claude/issue-11613-related-list-columns, Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL. That is an isolated reviewer at the review tier, adopted by the dispatching seat.
  • Checks on that head: 40 success and 3 expected skips (dependabot, the two coverage jobs).

The seat now marks this ready and arms auto-merge through the merge queue.

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL · 2026-10-04T17:53Z.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 17:54
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 17:54
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 4c127cd Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11613-related-list-columns branch October 4, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants