Skip to content

fix(plugin-detail): an authored detail-section node renders its declared inputs - #9221

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-8626-detail-section-input-shape
Sep 12, 2026
Merged

fix(plugin-detail): an authored detail-section node renders its declared inputs#9221
os-tesla merged 2 commits into
mainfrom
claude/issue-8626-detail-section-input-shape

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Part of #8626

⚠️ Part of, not Fixes, and that is the stop condition firing — see "The measurement came before the choice" below. The card reserves ONE question for the maintainer (is detail-section meant to be authorable as a flat SDUI block at all, or should the registration be re-declared around the nested section shape). This PR takes the repair that invalidates nothing and leaves that question open rather than closing it by merge.


The measurement came before the choice

The triage's stop condition, verbatim:

Two repairs exist and they are not equivalent — name the one you take in the PR: teach the component to read the flat props, or change the registration to declare the nested section shape it actually reads. The second changes what authors may write; if the flat inputs have ever been authored anywhere, that is a compatibility question rather than a free choice. ⇒ check for authored usages first and say what you found; if any exist, stop and report rather than invalidating them.

The sweep is POSITIVE. The flat surface is authored, twice.

The complete universe is the 17 lines git grep detail-section returns on b775500af, hand-classified. Two of them are authored nodes:

# Site What it writes Reached how
1 packages/plugin-detail/README.md:158 content: { type: 'detail-section', fields: [...] } inside a detail-view tabs[] DetailTabs renders tab.content through SchemaRenderer — a live authored node, and one this package documents
2 packages/plugin-detail/src/__tests__/detailSectionHeaderColorEnum-6955.test.ts validateTree({ type: TAG, fields: [{ name: 'amount' }], ...props }) with TAG = 'detail-section', plus expect(inputs.map(i => i.name)).toContain('fields') objectui#6955's landed contract pin, ON the flat surface

The other 15 are prose (ROADMAP.md, four CHANGELOG.mds, the #6955 changeset), a comment in LookupField.tsx, the CLI's known-type list (packages/cli/src/utils/known-schema-types.ts:111,324), and the registration itself.

Lit controls, in the same command. The sweep counts type: 'TAG' literals, and is run at the merge-base and at HEAD so each control is individually checked as unmoved:

                   authored-node literals      all mentions
                   merge-base -> HEAD          merge-base -> HEAD
detail-section          1  ->  1                  13 -> 26   (subject; the +13 is this PR's own files)
detail-view            67  -> 67                 158 -> 158  (CONTROL — lit, unmoved)
activity-timeline       1  ->  1                   3 ->   3  (CONTROL — lit, unmoved)

Two candidate controls were tried and discarded rather than reported: related-list reads 0 on both sides (a zero is not a control), and grid moved 6660 -> 6668 (this PR's own grid-cols text), so it cannot witness anything.

⇒ re-declaring the registration around a nested section would invalidate both authored sites, and would delete the very surface objectui#6955 had just narrowed. That is the branch the stop condition forbids.

The repair taken, and what the other would have cost

TAKEN — adapt at the seam. detail-section is now registered against DetailSectionNode, which folds the eight declared inputs into the section object DetailSection reads. Same shape of repair this package already uses for field:permission-facet-link (withFieldCarrier, objectui#3307). The declared inputs array is byte-identical; every authored node keeps validating and starts rendering.

NOT TAKEN — declare the nested shape. It would have cost, concretely:

It would have BOUGHT a registration that describes DetailSection's props literally. That is the cheaper declaration, not the truer contract: inputs publishes what an AUTHOR may write, and the author has never been able to write a section object here.

M2 — the card's second half, EXECUTED (not restated)

The card flagged "a reading and not an execution". Executed, through the real SchemaRenderer and the real registry on b775500af, rendering a node authored exactly as the registration prescribes (type: 'detail-section' with title and fields):

It does throw — and the throw does not escape. SchemaErrorBoundary catches it and paints the block's slot orange:

role="alert"
  Component "detail-section" failed to render
  Cannot read properties of undefined (reading 'defaultCollapsed')
  [Retry]

⇒ the severity is worse than the card's "inert" and milder than "breaks the page": every authored key is lost AND the author is shown a raw JavaScript message where their section should be. errorBannerAbsent() in the pin asserts that exact face is gone.

M3 — the reach, verified rather than trusted

packages/components/src/renderers/layout/page.tsx (the _jsxManifest memo) builds the JSX-page compiler's manifest from ComponentRegistry.getKnownTypes() plus each entry's inputs, and sdui-parser's validateTree judges an authored page against it — the same reach apps/console/src/__tests__/registry-inputs-spec-parity.test.ts records for element:record_picker, likewise outside PUBLIC_BLOCKS.

Driven against a manifest built that same way, from the live registry:

Authored shape Verdict
the flat eight []zero diagnostics
{ section: { title, fields } } error missing-required-prop "fields" + warning unknown-prop "section"

⇒ the platform does not merely permit the flat shape; it refuses the nested one. That is the reading that decides between the two repairs, and it is pinned as this PR's control row.

Ablation matrix — three legs, every row covered

Each leg: mutate, prove the mutation reached disk (anchor count + git hash-object before/after), run, restore via git checkout HEAD -- PATH, prove restoration by blob-hash equality and an empty git diff HEAD. Restoration ran from a trap … EXIT INT TERM with absolute paths. No build step is involved: the pin imports the mutated ../index by relative path, so there is no dist between the mutation and the assertion.

Leg Mutation Rows that redden
A register the tag against DetailSection again (undo the repair) all 5 rendering rows, each with TypeError: Cannot read properties of undefined (reading 'defaultCollapsed')
B drop 'headerColor' from DETAIL_SECTION_NODE_INPUTS honours headerColor as the header tint class + folds exactly the inputs the registration declares
C rename the declared input fields to items folds exactly the inputs… + leaves the published authoring surface where authors already write it

Every one of the 7 rows reddens under at least one leg — no assertion survives deletion of the thing it names. The two rows that survive leg A survive it correctly: they name the DECLARATION, not the binding, and legs B and C are the mutations of what they do name.

The fold list was made load-bearing for leg B: DETAIL_SECTION_NODE_INPUTS was a list the pin merely compared against the registration while a hand-written destructure did the folding. Two copies, so the list could keep agreeing with the declaration while the fold drifted away from both. The component now folds by iterating that list.

CONTROL — what did not move, proven

packages/plugin-detail/src/DetailSection.tsx
  merge-base blob  ef5b7859084a73537a55140ab5303861d9fac8bc
  HEAD blob        ef5b7859084a73537a55140ab5303861d9fac8bc
  worktree         ef5b7859084a73537a55140ab5303861d9fac8bc   -> IDENTICAL

The registration's inputs array: git diff MERGE_BASE..HEAD -- packages/plugin-detail/src/index.tsx contains no line inside it. @object-ui/types is untouched. Five in-repo DetailSection call sites (DetailView x4, SectionGroup) pass section={…} as direct JSX children and go nowhere near the registry, so none of them is on this diff.

Verification

Diffed against git merge-base origin/main HEAD = b775500af (4 files, +424/-2).

What Command Result
the new pin pnpm exec vitest run packages/plugin-detail/src/__tests__/detailSectionAuthoredNode-8626.test.tsx Test Files 1 passed, Tests 7 passed
affected package pnpm exec vitest run packages/plugin-detail/ Test Files 173 passed (173), Tests 1602 passed (1602)
types pnpm --filter @object-ui/plugin-detail type-check exit 0 — and tsc -p tsconfig.test.json --listFiles lists BOTH new files, so the test leg really covers them
dependency closure pnpm --filter '@object-ui/plugin-detail^...' build exit 0
changeset node scripts/check-changeset-presence.mjs ✅ 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
control bytes pnpm check:control-bytes ✅ OK (scanned 7411 tracked text file(s))
line citations pnpm check:new-line-citations VERDICT new-cross-file-line-citations: 0 new citation(s) -> exit 0
documented types pnpm check:doc-types ✅ Every documented component type is registered.

Lint is narrowed, and the narrowing is declared. eslint --format json over the three touched source files: 3 files linted, 0 errors, 32 warnings, all react-refresh/only-export-components (31 pre-existing in index.tsx, 1 new for the exported fold list). The narrowing is safe to read as a measurement because type-aware linting is not enabledeslint.config.js declares no parserOptions.project and no projectService anywhere — so this diff cannot move the verdict on any file it does not contain. The repo-wide run (turbo run lint, which sets no --max-warnings by deliberate policy) is CI's.

Acceptance notes


🤖 Generated with Claude Code

https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

…lared inputs

The registration declares eight FLAT inputs — `title`, `description`, `fields`,
`collapsible`, `defaultCollapsed`, `columns`, `showBorder`, `headerColor` —
while `DetailSection` declares a single `section` OBJECT prop and reads
`section.*` only. `SchemaRenderer` spreads a node's non-metadata keys as React
props, so an authored node arrived as `title` / `fields` / … and `section`
arrived `undefined`.

Not merely inert. Measured end to end through the real `SchemaRenderer` and the
real registry: `DetailSection`'s first statement is
`React.useState(section.defaultCollapsed ?? false)`, so the render THREW,
`SchemaErrorBoundary` caught it, and the page showed

  Component "detail-section" failed to render
  Cannot read properties of undefined (reading 'defaultCollapsed')

in place of the block.

Register the tag against `DetailSectionNode`, a seam adapter that folds the
eight declared inputs into the `section` object the component reads — the same
shape of repair this package already uses for `field:permission-facet-link`
(`withFieldCarrier`, objectui#3307).

The authoring surface does not move, deliberately. The other repair — declaring
a nested `section` input — would change what authors may write, and the flat
shape is both published and authored: a manifest built the way `PageRenderer`
builds the JSX-page compiler's gives the flat eight ZERO diagnostics and REFUSES
`section`, this package's README documents a flat `detail-section` node inside
`tabs[].content`, and objectui#6955's landed pin asserts that same flat surface.

`DetailSection` is byte-identical: every in-repo caller passes `section={…}` as
a direct JSX child and goes nowhere near the registry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
…red list

`DETAIL_SECTION_NODE_INPUTS` was a list the pin compared against the
registration while a hand-written destructure did the actual folding — two
copies, so the list could go on agreeing with the declaration while the fold
drifted away from both. The component now folds by iterating that list, which
makes it the single source it claimed to be and gives the objectui#8626
ablation a per-input leg: drop a name from the list and that input stops
reaching `DetailSection`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3112.5 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-BylzvuQk.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) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 500.84KB 114.81KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.27KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.47KB 65.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
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.66KB 3.50KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3112.5 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-D-AdOrGX.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) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 500.84KB 114.81KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.27KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.38KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
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.66KB 3.50KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
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

Copy link
Copy Markdown
Collaborator Author

Contract review — head 23fbc79832PASS, and the needs_decision is routed, not resolved

Reviewed-by: domain:ui PM seat (os-tesla), R16, 2026-09-12T01:5xZ.

Clause-② yes--pair 9221 reads ✓ on both carriers (PR #9221 / card objectui#8626). ⛔ Not revised on delivery.

CI: 36 checks, 0 pending, 0 failing.

⭐ The stop condition fired, and the seat obeyed it. That is the headline.

I dispatched this with a stop condition worded verbatim: look for real authored usage first, and if it exists, stop and report — do not invalidate the eight declared inputs. The sweep came back positive:

  1. packages/plugin-detail/README.md:158 — a detail-section node inside a detail-view's tabs[].content, rendered through SchemaRenderer;
  2. detailSectionHeaderColorEnum-6955.test.ts — objectui#6955's landed pin, which authors flat fields + headerColor against validateTree and asserts the registration declares an input named fields.

⇒ The repair that re-declares the registration around a nested section is the branch the stop forbids, and it was not taken. Delivered instead: bind the tag to a DetailSectionNode seam adapter that folds the eight declared inputs into the section object the component reads — the shape this package already uses for field:permission-facet-link (withFieldCarrier, objectui#3307). DetailSection.tsx is byte-identical (blob ef5b7859 at merge-base, at HEAD and in the worktree) and the declared inputs array is byte-identical.

The card's un-executed half, executed

M2: rendering an authored node through the real SchemaRenderer and the real registry on b775500af does throw, and SchemaErrorBoundary catches it — role=alert carrying Component "detail-section" failed to render / Cannot read properties of undefined (reading 'defaultCollapsed') plus a Retry button. ⇒ worse than inert (every authored key lost AND a raw JS message shown), milder than a page crash. The card asserted inertness; the measurement is more specific and more damning.

M3, verified executably rather than trusted: a manifest built the way page.tsx's _jsxManifest memo builds the compiler's gives the flat eight zero diagnostics and gives the nested shape error missing-required-prop "fields" + warning unknown-prop "section". ⭐ The platform REFUSES the nested shape. That is the fact that decides the open question below, and it is measured rather than preferred.

Bump call — patch, agreed

The lane's test is does existing stored data render differently, and it does — dramatically, from a red error panel to a rendered section. But the affected configuration was already broken: it threw. Same reading as objectui#9200.

Ablation

Three legs, each with on-disk proof before and after and restore proven by blob-hash equality. Leg A (tag bound back to DetailSection): 5 failed / 2 passed, all five rendering rows red with the exact measured TypeError. Leg B (headerColor dropped from the fold list): 2 failed. Leg C (declared input fields renamed): 2 failed. Every one of the 7 rows reddens under at least one leg.

⭐ Worth naming: the seat made the fold list load-bearing — the component now folds by iterating it — precisely so that leg B exists. Before that it was a second copy the pin merely compared against, and dropping a name from it changed nothing. That is a seat noticing its own pin was decorative and fixing the code so the pin could bite.

⛔ The needs_decision is NOT a blocker on this PR, and it is not mine to answer

The card reserves a product question: should detail-section keep publishing the flat eight-input authoring surface, or should the surface migrate to a nested section object? The seat recommended A (keep flat) — which is the status quo this PR makes true — on the ground that the platform's own validator publishes the flat eight and refuses the nested shape, and that the flat shape is authored in two places, one of them a pin landed days ago.

⚠️ The seat also flagged, correctly, that it stated this without a four-axis analysis because the dispatch carried no {decision_frame} — and the standing clause forbids inventing axes. ⇒ That restraint is right and I am not papering over it.

This lands, the card stays open, and the question goes to the maintainer — the same shape as objectui#9202 / objectui#4044's aria. The PR body says Part of #8626, not Fixes, so merging closes nothing. ⭐ Option B remains available on top of A without rework; A does not foreclose it.

Out-of-scope findings — accepted

  • objectui#9218DetailSection's collapsible branch renders a bare Card and never reads section.showBorder, so collapsible: true + showBorder: false keeps its border. ⛔ Correctly not fixed here: it lives inside this PR's declared control, and fixing it would have destroyed the byte-identity proof.
  • ⚠️ Noted, not filed: the registration declares 8 of DetailViewSection's 11 members; name, icon and visible are undeclared and section.icon is read by the component. Not a defect — the validator warns rather than dropping silently — so it is a decision to WIDEN the authoring surface, and it belongs to whoever takes the reserved question above. Correctly parked there.
  • ⚠️ The GitHub body sanitizer dropped the attribution footer from objectui#9218's body on write; the seat declined to re-post it. Right call — the standing clause forbids looping the footer.

Clearing needs:contract-review on both carriers in one stroke, then ready → SQUASH.


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 12, 2026 01:53
@os-tesla
os-tesla added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit cc00c59 Sep 12, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-8626-detail-section-input-shape branch September 12, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants