fix(plugin-detail): record:related_list stops requiring columns, so the page compile accepts a node that lists none (objectui#11613) - #11621
Conversation
…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>
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsInputs: card objectui#11613 (body and all four comments: the triage grade The diff is five files. One runtime file,
② Semver level
③ Boundary flags
Check-runs on the head, read at 2026-10-04T17:49Z: 42 runs. 34 Implemented-by: VERDICT: PASS Generated by Claude Code |
|
The seat now marks this ready and arms auto-merge through the merge queue.
Generated by Claude Code |
Fixes #11613
Clause-②: yes
What changes
The
record:related_listregistration in@object-ui/plugin-detail(ComponentRegistry.register('related_list', …)) no longer declaresrequired: trueon itscolumnsinput. The input's description now says where the columns come from when the key is absent. The ledger rowrecord:related_list.columnsin 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 rowComponentPropsMap['record:related_list'].columnsis 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-parserand the renderer are not edited. Landing site as dispatched: the registration inpackages/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:
emitSduiManifestoverdev/manifest-registry.ts, read back from the writtensdui.manifest.json. "Before" is the registration withrequired: true(base2abec3a, 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 withobjectName,relationshipField,dataSource: { object, view }, nocolumnsok: false, onemissing-required-properror namingcolumnsok: true, no diagnostics, the binding recordedobjectName,relationshipField, nocolumns, no bindingok: false, the same errorok: true, no diagnosticsobjectName,relationshipField,dataSource: { object }(no view), nocolumnsok: false, the same errorok: true, no diagnosticsobjectName,relationshipField,columns,dataSource: { object, view }(control)ok: trueobjectName,columns, norelationshipField(control)relationshipFieldrelationshipField,columns, noobjectName, bound bydataSource: { object }or not (control)objectNameWhat the accept set gains: exactly the nodes that omit
columnsand carry everything else the gate already required (objectNameandrelationshipField), with or without adataSourcebinding.What stays refused: a node without
objectName, bound or not, and a node withoutrelationshipField.Unchanged, and not refusals before or after: a
columnsvalue of the wrong kind (a number or a string) draws atype-mismatchwarning withok: true; a spec-legal column object draws amember-type-mismatchwarning withok: true(Acceptance notes). Neither check readsrequired.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.tsxrenders through the real renderer, the realElementDataSourceGate, the realRelatedListand the real table, and reads header and body cells: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.Subject).columnswin over the view and over the derivation.Who reads the registration's
requiredfor this inputEnumerated by grepping non-test sources for reads of
requiredoff registration or manifestinputs:@object-ui/sdui-parservalidateTree, themissing-required-propcheck (input.required && !(input.name in node)). It is the page compile and itsok, the save gate. It now accepts the nodes above.@object-ui/components' page renderer, thekind: 'html'page compile (manifestFromConfigsover the live registry). The same validator at render time, so the same widening. Source reading, not separately measured.@object-ui/sdui-parsercodegen.generateDtsnow writescolumns?: string[]onRecordRelatedListProps, andgenerateBlockList's required column for the tag readsobjectName, relationshipField. Measured once on this branch by a throwaway probe, not committed.sdui.manifest.json(the CLI'scompile,lintandvalidate, andmetadata-protocol's runtime authoring gate) inherit the widening when the next console manifest ships. Not measured here.requiredfound.check:prompt-keysreads the key bullets in.github/prompts/**, notrequired, and no prompt teachescolumnsas required.Generated artifacts
sdui.manifest.json,sdui-intrinsics.d.tsandsdui-blocks.mdare not tracked: no tracked file has those names, and the positive controlapps/console/scripts/emit-sdui-manifest.tsis tracked. They regenerate from the registration in the console build, as for objectui#11605. This diff has no regeneration step.Pins
apps/console/src/__tests__/related-list-columns-optional-11613.test.ts, 7 rows, judged against the shipped manifest:columnsis not required, and its description namesdataSourceandhighlightFields.objectNameandrelationshipFieldstay required.okwith no diagnostic.relationshipField; noobjectName, bound or not.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: theLEDGERis 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'sscripts/ablation-replace.mjs. The console project aliases@object-ui/plugin-detailand@object-ui/sdui-parsertosrc, so a mutation reaches the tests without a build.required: trueput back oncolumns(packages/plugin-detail/src/index.tsx, blob62fba905fb9fbecame23df5500cb78), with the two console files run. Exactly 5 rows went red and 50 passed. Red: the enumeration row forrecord:related_list(its message names"columns"), the published-input row, and the three columns-less compile rows, each with amissing-required-propnamingcolumns. Every control stayed green.RelatedList's derivation switched off (theif (!objectSchema?.fields) return [];guard made unconditional inRelatedList.tsx, blob3c5bbfc1b87dbecamee5a0270e381b), 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 HEADis empty. The render rows need no "before" run: the runtime is not changed, and they read triage's premise.Gates
Final head
305c738, which mergesmainat6e9090c. 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(echoedtsc --noEmit && tsc -p tsconfig.test.json): exit 0, afterturbo 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(echoedtsc --noEmit && tsc -b tsconfig.node.json --force): exit 0. Its tsconfig includessrc, so the new console test is in the program.eslint --format json: 4 files, 0 errors, 34 warnings.index.tsxcarries 32, the same as at base2abec3aread through--stdin. The new render test carries 2no-explicit-any, the pattern its siblingredactedDerivation-9053test uses. The narrowing is a measurement because: each file is linted by the rooteslint.config.js(--print-configexit 0 for each); the count comes from the JSON output; andeslint.config.jsenables no type-aware linting (noparserOptions.project, noprojectService), and no rule undereslint-rules/reads the disk, so this diff cannot move any untouched file's verdict.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.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-detailminor, with the widening paragraph opening**Clause-②: yes (widening)**.Acceptance notes
columns. Measured on this head: a spec-legal column object ({ field, label }) compilesok: truewith amember-type-mismatchwarning, because the registration declaresof: 'string'while the spec row admits string orListColumnmembers. This is already recorded in-tree as a gap, inregistry-inputs-spec-parity.test.ts'srecord:related_list.columnsentry. This PR does not change it.buildDefaultPageSchemain this package, fed by app-shell'sderiveRelatedLists) emitsrecord:related_listnodes with nocolumnswhenever the foreign key declares norelatedListColumns. Whether such a node ever reached the save gate was not measured.packages/plugin-detail/src/index.tsxwas last changed by objectui#11531 (6158e4c).objectNameexception stays true. No doc or README sayscolumnsis required (grep overcontent/docsand the package README), so no doc edit is owed.Generated by Claude Code