Skip to content

docs(types): align two layout @default docblocks with the renderer fallbacks - #7736

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-7361-layout-default-jsdoc
Sep 5, 2026
Merged

docs(types): align two layout @default docblocks with the renderer fallbacks#7736
os-justin merged 2 commits into
mainfrom
claude/issue-7361-layout-default-jsdoc

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #7361

Two published @default docblocks in packages/types/src/layout.ts described a default that no renderer ever applies. The renderers are the authority — they are what runs — so only the docblocks moved. Changing the reads to match the tags would relayout every existing page that omits either key, which is a behaviour change and a separate ruling (the triage said so explicitly).

Row 1 — ContainerSchema.maxWidth

@default 'lg' became @default 'xl', with the read site named in prose: container.tsx applies schema.maxWidth ?? 'xl', so a container that omits the key renders max-w-xl.

Three other surfaces already agreed with the renderer and only the JSDoc dissented, which is why 'xl' is not a judgement call:

surface says
container.tsx read ?? 'xl'
registration inputs[].defaultValue 'xl'
registration defaultProps.maxWidth 'xl'
JSDoc tag (before this change) 'lg'

Row 2 — FlexLayoutProps.align, and why shape (a) rather than (b)

The dispatch allowed two shapes and preferred (b) — moving the tag onto FlexSchema and StackSchemaif those types carry a docblock where a tag can live without redeclaring the member. They do carry docblocks, but the condition is about where a tag can live as a tag for that member, and it fails:

@default is a member-level tag. Placed on an interface docblock it attaches to the interface reflection, not to align. TypeDoc would render it as a default value for FlexSchema itself, and editor hover over align on a flex node resolves to the declaring member on FlexLayoutProps and never sees the consuming interface's docblock. Shape (b) would therefore have replaced a wrong-value tag with a wrongly-placed one — a fresh instance of the same family the card is about, a declaration whose tooling behaviour does not match its apparent promise.

So shape (a): the member keeps its single declaration (objectui#6151 — StackSchema cannot derive it through an Omit without erasing every named member), and the docblock now carries no @default tag at all, stating both consumers in prose instead:

  • flex.tsx reads schema.align || 'start'
  • stack.tsx reads schema.align || 'stretch' ("Stack items usually stretch")

Dropping the tag rather than writing a tag whose text is not a value expression is the point: one shared member serving two deliberately divergent types cannot carry a single correct machine-readable default, and publishing one that is only conditionally true is what this card exists to stop. The prose still renders in hover and in the generated API reference.

Triage boundary 3 — does the tag reach anything downstream? Measured

The card's stated route is that sdui-parser serializes the tag into sdui.manifest.json and sdui-intrinsics.d.ts. That is not the actual route, and the correction matters for how much this change is worth:

  • scripts/dump-public-manifest.mjs builds the manifest from runtime registrations (config.inputs, via manifestFromConfigs, read out of a real browser). Its own header says it records what each registration declares. It never opens a TypeScript source file.
  • packages/sdui-parser/src/codegen.ts generates the intrinsics declaration from that manifest, not from JSDoc.
  • Neither artefact is checked in (git ls-files returns nothing for either; the manifest is written into packages/console/dist at build time), so there was nothing to regenerate here.
  • Repo-wide there is no JSDoc-tag parser at all: no getJSDocTags, no jsDocTags, no @microsoft/tsdoc. The only matches for those names are prose mentions of "TSDoc" inside comments.

What the tag does reach is real, just different: TypeDoc is wired up (typedoc.json, the docs:api script, and the devDependency), and packages/types is its first entry point — its output directory is gitignored, so it is regenerated on demand. And the docblock ships: the emitted packages/types/dist/layout.d.ts carries both corrected blocks verbatim, which is what editor hover reads for every consumer of the published package.

So: no generator consumes the tag, and the card's premise that it "reaches downstream" holds for the shipped declaration file, the API reference and IDE hover — not for the serialized SDUI artefacts.

Triage boundary 4 — the census, with its denominator

layout.ts declares 24 @default tags. 14 are cleanly comparable against a fallback the matching renderer actually applies; the other 10 declare a default no renderer applies as a fallback at all, so they are not comparable rather than wrong. Of the 14, 3 disagreed:

member tag renderer applies disposition
ContainerSchema.maxWidth 'lg' container.tsx: xl fixed here
FlexLayoutProps.align 'center' flex.tsx: start, stack.tsx: stretch fixed here
FlexLayoutProps.direction 'row' flex.tsx: row, stack.tsx: col out of scope, filed

The remaining 11 agree. The census was matched per component type rather than by member name — several names (variant, size, orientation, gap) recur across unrelated schemas, and a name-only sweep produces false pairs.

Out-of-scope findings filed (not touched here)

  • objectui#7734FlexLayoutProps.direction carries a shared @default 'row' that is right for flex and wrong for stack, whose renderer reads || 'col' under the comment "Default to column for Stack". Structurally identical to row 2 and on the same interface; left alone because the dispatch drew the file surface at exactly two docblocks.
  • objectui#7735 — the bigger one, and runtime rather than documentation: packages/types/src/zod/layout.zod.ts carries .optional().default(VALUE) on these same members, and .default() substitutes at parse time. Measured through the built safeValidateSchema: a bare container node parses to maxWidth: "lg" and a bare flex node parses to align: "center" — values the renderers never apply. A node that has been through the mirror therefore renders differently from the same node authored as-is. Deliberately untouched here: the dispatch ruled out moving any default VALUE, and both candidate remedies change published behaviour, so it needs a ruling rather than a patch.

Both were deduplicated against open and closed issues before filing, with a known-hit control query to prove the search channel was live in this session.

Verification

All runs foreground; heavy runs through the container's shared verify lock; exit codes captured before any pipe. Everything below was re-run on the merged head 243236ea unless noted.

check result
pnpm exec vitest run --maxWorkers=2 packages/types/ 116 files, 1977 tests passed
pnpm --filter @object-ui/types build exit 0 (dist completeness: 120 emitted files)
pnpm --filter @object-ui/types type-check exit 0
pnpm lint (whole repo) exit 0 — 0 errors
node scripts/check-changeset-presence.mjs exit 0, declares @object-ui/types: patch
check-changeset-fixed / -no-major / -overwrite exit 0
check-control-bytes exit 0 (6302 files)
check-spec-symbol-derivation exit 0
governed-surface predicate on the final file list NOT governed (0 of 3 paths)

The new pin is inside tsconfig.test.json's program, proven with --listFilesOnly rather than assumed.

The pin, and its ablation

packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts derives every expected value by reading the renderer sources off disk with narrow regexes and comparing them against the docblock text extracted from layout.ts. Nothing is written from memory, so it turns red if either side moves — a renderer changing its fallback without the docblock following is the same defect in the other direction. The regexes are guarded by explicit positive controls, because a regex that quietly matches nothing would make every assertion vacuous.

Ablation, with the fix committed first, trap-guarded, absolute paths, and the mutation proven to have reached the disk before anything was read:

mutation : layout.ts reverted to b74a859
           '@default lg' 1, '@default center' 1, '@default xl' 0
           blob 266e700e differs from HEAD blob 92225fa2  (not a no-op)
result   : vitest exit 1 — 5 failed | 6 passed (11)
           red: the 2 row-1 legs and the 3 row-2 legs
           green: both positive controls, the divergence assertion,
                  and all 3 negative controls (centered / justify / gap)
restore  : blob back to 92225fa2, identical to HEAD; git diff HEAD empty

The pin reads source off disk rather than a built artefact, so no rebuild is needed for the mutation to take effect and the dist preflight that guards dist-resolved ablations does not apply.

Notes

  • Renderers untouched. No member, type, union or default VALUE moved — docblock text only.
  • layout.ts:113 (align on TextSchema, text alignment) is a different member and was left alone; it carries no @default tag at all.
  • This PR is left in draft for the PM seat to route.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s


Generated by Claude Code

…fallbacks

`ContainerSchema.maxWidth` documented `@default 'lg'` while `container.tsx`
applies `schema.maxWidth ?? 'xl'`, and the shared `FlexLayoutProps.align`
documented `@default 'center'` while `flex.tsx` applies `schema.align ||
'start'` and `stack.tsx` applies `schema.align || 'stretch'`.

The renderers are the authority — they are what runs — so only the docblocks
moved; changing the reads would relayout every existing page that omits either
key, which is a behaviour change and a separate ruling.

`align` is the structural half: the member is declared once on
`FlexLayoutProps` (objectui#6151) but its two consumers deliberately diverge,
so no single `@default` value can be correct. Its tag is replaced by prose
naming both consumers rather than by a second wrong single value.

Pinned by `layout-default-jsdoc-7361.test.ts`, which derives each expected
value by reading the renderer source off disk, so the pin turns red if either
side moves. Card objectui#7361.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.4 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CefIat3_.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) 15.67KB 5.75KB
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) 5.13KB 2.35KB
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) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
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) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
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.98KB 10.98KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.37KB 46.41KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
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.74KB 1.41KB
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 (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

@os-justin
os-justin marked this pull request as ready for review September 5, 2026 15:24
@os-justin
os-justin added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit e546222 Sep 5, 2026
34 of 35 checks passed
@os-justin
os-justin deleted the claude/issue-7361-layout-default-jsdoc branch September 5, 2026 15:39
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.

finding(types): two layout schemas' @default JSDoc disagrees with the value the renderer actually applies

2 participants