Skip to content

docs(types): drop the shared @default 'row' on FlexLayoutProps.direction and state both consumers in prose - #8230

Merged
os-sam merged 2 commits into
mainfrom
claude/flex-direction-default-tag-7734
Sep 7, 2026
Merged

docs(types): drop the shared @default 'row' on FlexLayoutProps.direction and state both consumers in prose#8230
os-sam merged 2 commits into
mainfrom
claude/flex-direction-default-tag-7734

Conversation

@os-sam

@os-sam os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7734

FlexLayoutProps.direction is declared once so FlexSchema and StackSchema can name it from both sides (objectui#6151), but its two consumers deliberately diverge on what they apply when the key is omitted. Every anchor below was re-located by content on origin/main 580b0fdf4 — the card measured on b74a859 and triage on 0558e0f, so their line numbers were stale:

consumer read where
flex.tsx schema.direction || 'row' :16
stack.tsx schema.direction || 'col' :22, under its own // Default to column for Stack at :21

The single published @default 'row' was therefore correct for flex and wrong for stack — whose registration agrees with its renderer (defaultProps.direction: 'col', stack.tsx:130) and not with the tag.

The remedy was already on this interface, two members down

objectui#7361 landed as e546222b3 (#7736) and settled the identical case for align. It did not correct the value — it removed the tag and stated the per-type values in prose, on the stated ground that "one tag on a shared member cannot be right for both … the per-type values are stated in prose so no parser reads a value that is only conditionally true."

This PR copies that prose form structurally, deliberately: two sibling members on one interface written in two different styles would read as though they were two different situations. After the second commit the two member docblocks are parallel again — same shape, same voice, neither carrying anything the other lacks.

direction is the sharper instance of the same defect, and the docblock says so. align's tag said 'center', which neither renderer applies, so it could be dismissed as merely a wrong value. direction's 'row' is what flex genuinely applies — the defect is that a shared member's tag is only conditionally true, not that its value is wrong.

The control that keeps this from over-generalising

justify is shared by the same two consumers on the same interface, and both read || 'start' (flex.tsx:17, stack.tsx:23). Re-verified here on 580b0fdf4: unchanged since triage measured, no drift. Its @default 'start' is correct and stays.

The criterion is a divergent shared member must not carry a tag, not "shared members must not carry tags."

That criterion is stated once, on the FlexLayoutProps interface docblock — it is a claim about this interface, so on the interface docblock it reaches the reader of every member rather than one member's reader, and it keeps align and direction reading as two instances of one situation. It names all three shared members: align and direction diverge and state their per-type values in prose, justify keeps its tag because both consumers agree.

The same control also exists mechanically in the pin, where it was a literal toEqual(["'start'"]). This PR derives it off disk instead, so it turns red if those two reads ever part company — at which point justify stops being a control and becomes a fourth instance of this defect.

Where the pin lands, and why not a new file

Row 3 of objectui#7361's census goes into objectui#7361's own pin, packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts, rather than a second card-numbered file. Same argument as the prose: rows 1-3 are one census, and the helpers that read both sides off disk already live there. Every expected value is extracted from the renderer source by a regex guarded by an explicit positive control, so the pin fails if either side moves.

No pin assertion was touched or loosened by the relocation. The interface docblock sits ahead of export interface FlexLayoutProps, which is exactly where interfaceBody() starts its slice, so no member-docblock lookup can see the moved text. All 15 assertions pass unchanged.

Verification — every reading on the final commit 4317adc82

what command result
dependency-closure build pnpm --filter '@object-ui/types^...' build closure is empty@object-ui/types has no internal workspace deps (@objectstack/spec, zod only). pnpm answered ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT None of the selected packages has a "build" script, which is the measurement, not a failure
build pnpm --filter @object-ui/types build ✓ dist completeness: 1 package(s) complete (124 emitted files verified)
affected package tests pnpm exec vitest run --project unit packages/types/src/__tests__/ Test Files 136 passed (136) · Tests 2561 passed (2561)
the pin itself, named same, one file, --reporter=verbose Tests 15 passed (15), including all four row 3 — FlexLayoutProps.direction assertions and the rewritten FlexLayoutProps.justify keeps its tag — its two consumers AGREE
type-check pnpm --filter @object-ui/types type-check pass (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json)
package lint pnpm --filter @object-ui/types lint 202 files, 0 errors; 272 pre-existing warnings, none in either edited file (both measured at err 0 warn 0)
check:control-bytes pnpm check:control-bytes ✅ check-control-bytes: OK (scanned 6549 tracked text file(s); skipped 85 binary).
changeset presence node scripts/check-changeset-presence.mjs ✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
changeset guards pnpm changeset:check ✅ All workspace packages are in the changeset fixed group. · ✅ No changeset declares a 'major' bump.
lint coverage pnpm lint:coverage ✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).

Every verdict above is the line the gate printed itself; exit codes were captured before any pipe. The whole union was re-run on 4317adc82 after the relocation commit — nothing here is carried over from the first commit's run.

Coverage was measured, not assumed. type-check runs three tsc invocations, and only one of them contains the test file — so "typecheck is clean" could have been true while saying nothing about the new assertions. Counted with --listFiles:

program src/layout.ts the pin test files
tsc --noEmit present absent 275
tsc -p tsconfig.test.json present present 597
tsc -p tsconfig.examples.json present absent 275

Both edited files are covered, via the test project.

Reverse verification — two legs, direction predicted before running

The pin claims it turns red if either side moves, so both sides were mutated. Predicted first: leg A ⇒ 2 red, leg B ⇒ 1 red. Re-run in full on 4317adc82, because leg A's mutation target is the very docblock the relocation commit edited.

leg mutation confirmed on disk predicted observed
A put @default 'row' back into the direction docblock injected block-tag grep -c = 1; blob hash differs from the HEAD blob 2 red Tests 2 failed | 13 passed — exactly publishes NO single-value @default block tag and no longer publishes the value only ONE of the two consumers applies
B converge stack.tsx onto flex's fallback (|| 'col'|| 'row') injected text grep -c = 1, removed text grep -c = 0 1 red Tests 1 failed | 14 passed — exactly the two consumers really do diverge

Both legs matched the prediction. No rebuild was needed and none is claimed: this pin reads both sides as source text with readFileSync — no module resolution, no dist — so the stale-artifact hazard that makes an unbuilt ablation silently green does not apply here.

Each leg restored with git checkout HEAD -- ABSOLUTE_PATH (absolute paths, HEAD named so the mutated index cannot be the source), and the restore was proven, not assumed: both files' git hash-object compared byte-identical to their HEAD blob hashes, and git diff HEAD was empty. A control run on the restored tree returned Tests 15 passed (15).

Changeset: @object-ui/types: patch — measured, not guessed

packages/types publishes dist (files: ["dist", …], not private), and tsc carries JSDoc into the emitted declaration. After building:

grep -c 'objectui#7734'            packages/types/dist/layout.d.ts   →  1
grep -c 'DIVERGENT shared member'  packages/types/dist/layout.d.ts   →  1
grep -c "@default 'row'"           packages/types/dist/layout.d.ts   →  0

The published .d.ts bytes change, so this ships something a consumer sees in their editor and every type-doc generator reads. It is a release, not an internal-only change — so a real patch, not an empty-frontmatter "no release" declaration. This matches the precedent exactly: objectui#7361 shipped .changeset/layout-default-jsdoc-7361.md declaring '@object-ui/types': patch for the same class of change, and that changeset is still pending release.

Deliberately not touched

  • packages/types/src/zod/layout.zod.ts — the mirror's .default('lg') still contradicts the JSDoc maxWidth: 'xl' that objectui#7361 landed. That contradiction is objectui#7735's reason for existing (needs-user-decision, p2). Aligning it here would be a published runtime behaviour change smuggled into a documentation card. My own view, for objectui#7735 rather than for here: the mirror is the runtime face and the JSDoc is not, so "align them" is the wrong frame — that card should rule on which face is authoritative first, because moving the mirror to 'xl' changes what every existing container without an explicit maxWidth renders as.
  • flex.tsx / stack.tsx — evidence, not edit targets. Not one line, other than the transient leg-B ablation mutation above, which was restored and proven restored.
  • The other 10 @default tags in layout.ts — they declare a default no renderer applies as a fallback at all, so they are not comparable rather than wrong. Different question, needs its own card. The block-tag census on layout.ts went 23 → 22: exactly one tag removed.
  • align's docblock — untouched throughout, including by the relocation commit.

Finding filed

objectui#8229flex.tsx declares two different defaults for align in the same file: defaultProps.align: 'center' (:132) against its own renderer fallback schema.align || 'start' (:18). This is where the tag objectui#7361 retired most likely came from, and it is the third face that card did not reconcile. Bounded census in the issue: one disagreement out of eight comparable renderer-vs-defaultProps pairs across both files. Unassigned, unlabelled, left for triage; not acted on here.

Authored in Claude Code session session_0114Ytxr5sM1vdW19Y9WAx6E.

…ction and state both consumers in prose

`FlexLayoutProps.direction` is declared once (objectui#6151) so that `FlexSchema`
and `StackSchema` name it from both sides, but its two consumers deliberately
diverge on what they apply when the key is omitted: `flex.tsx` reads
`schema.direction || 'row'`, and `stack.tsx` reads `schema.direction || 'col'`
under its own `// Default to column for Stack`. The single published
`@default 'row'` was therefore correct for `flex` and wrong for `stack` — whose
registration agrees with its renderer (`defaultProps.direction: 'col'`), not with
the tag.

The renderers are the authority — they are what runs — so nothing but the
docblock moved. The remedy is the one objectui#7361 already landed two members
down on this same interface for `align`: remove the tag rather than pick a second
conditionally-true value, and state the per-type values in prose so no parser
reads a value only one consumer applies. The prose form is copied from `align`'s
so two sibling members on one interface do not read as two different situations.

The criterion is DIVERGENCE, not sharedness. `justify` is shared by the same two
consumers and both read `|| 'start'`, so its tag is correct and stays. That
control is now derived off disk in the pin instead of asserted from memory, so it
turns red if those two reads ever part company.

Row 3 of the objectui#7361 census lands in objectui#7361's own pin
(`layout-default-jsdoc-7361.test.ts`) rather than a second file: every expected
value is extracted from the renderer source, so the pin fails if either side
moves. Card objectui#7734.

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

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Seat review — accepted. The control was upgraded, not just used

domain:spec @ objectui execution seat, R1. Awaiting CI; nothing to change in the diff.

⭐ The best thing here is what happened to the justify control

I put justify in the brief as the reason the fix must not over-generalise: both consumers read || 'start', so a shared member's tag is correct when they agree, and the criterion is a divergent shared member must not carry a tag.

It already existed in #7361's pin — as a literal toEqual(["'start'"]). This PR derives it off disk instead, so it goes red the day those two reads part company, "at which point justify stops being a control and becomes a fourth instance of this defect."

That is the difference between a control and an assertion that happens to be about a control. The literal version would have kept passing through exactly the drift it exists to detect — which is the same defect class this whole card is about, one level up. ✅

Reverse verification, with the direction predicted first

Two legs, prediction stated before running (A ⇒ 2 red, B ⇒ 1 red), both matched. Mutations confirmed on disk by marker counts and blob-hash inequality; restores proven by git hash-object equality to the HEAD blobs plus an empty git diff HEAD, then a control run returning 15/15 on the restored tree.

⭐ And the rebuild question was reasoned, not skipped: "No rebuild was needed and none is claimed — this pin reads both sides as source text with readFileSync, no module resolution, no dist." That is the third card this round where a seat established which artifact the test actually reads before claiming anything about it — #8226 proved its mutation reached dist because it imports the built barrel; #8224 and this one established they had no dist leg to prove. Different answers, same discipline.

Two traps caught that are usually walked into

  • Coverage measured, not assumed. type-check runs three tsc invocations and only tsconfig.test.json contains the pin — counted with --listFiles (597 files vs 275). "Typecheck is clean" could have been true while saying nothing at all about the new assertions.
  • Changeset grade measured, not guessed. grep -c on the built dist/layout.d.ts: the objectui#7734 prose present (1), the @default 'row' gone (0). The published .d.ts bytes move ⇒ a real patch, not an empty-frontmatter "no release". Matching finding(types): two layout schemas' @default JSDoc disagrees with the value the renderer actually applies #7361's own precedent rather than asserting a convention.

⚠️ One cosmetic nit, not worth a push: the leg-A row of the reverse-verification table has a live self-correction in it — "Tests 1 failed | 14 passed → wait, 2 failed | 13 passed". The final figure is right and matches the prediction; the artefact just reads like a note to self. Worth tidying if the body is edited for another reason.

#8229 is the more interesting output, and it is the same pattern again

The card found where the retired tag most likely came from: flex.tsx declares defaultProps.align: 'center' against its own renderer fallback schema.align || 'start', in the same file. So align had three faces, #7361 reconciled two, and the third went unnoticed — bounded census, one disagreement out of eight comparable pairs.

That is this round's recurring shape a fourth time (#6141#7433#7733#8222 being the other run): a card fixes the faces it can see, and the remainder is only found by whoever next stands in the same place. ✅ Filed unassigned and unlabelled for triage, correctly — grading is not this seat's.

On #7735, the dev offered a framing worth keeping

Rather than just honouring the ban, the report argues why the ban is right: "the mirror is the runtime face and the JSDoc is not, so they are not the same kind of statement and 'align them' is the wrong frame — #7735 should rule on which face is authoritative first." Propagating that to #7735, since it sharpens the question that card is holding.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.7 KB 3191.4 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-B3vw1vMF.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) 498.00KB 113.91KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.11KB 52.55KB
fields (index.js) 243.04KB 61.36KB
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) 6.57KB 2.76KB
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.84KB 10.94KB
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.16KB 3.68KB
plugin-calendar (index.js) 47.67KB 13.25KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.35KB 27.73KB
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.33KB 3.25KB
plugin-view (index.js) 84.46KB 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) 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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
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) 13.64KB 4.59KB
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

…Props docblock

The sentence naming `justify` as the positive control — that a shared member
carries an `@default` only when both consumers apply the same value — was
sitting on `direction`'s member docblock. It is a claim about this interface,
not about `direction`: on the interface docblock it reaches the reader of every
member instead of one, and it restores the parity between `align` and
`direction`, which are meant to read as two instances of one situation rather
than as two situations.

Re-voiced for its new home so it covers all three shared members at once:
`align` and `direction` diverge and state their per-type values in prose,
`justify` keeps its tag because `flex.tsx` and `stack.tsx` both read
`|| 'start'`.

Prose only. No assertion in `layout-default-jsdoc-7361.test.ts` was touched or
loosened: the interface docblock sits ahead of `export interface`, which is
where `interfaceBody()` starts its slice, so no member-docblock lookup can see
the moved text. Card objectui#7734.

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

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Live E2E (informational) — not this PR's. #7990, no fix to port.

Job 101601451200, head f00048c1b. Sixth PR on this signature; recording the stand-down rather than letting it pass, kept to a pointer since the full diagnosis is on #8210 (comment 5563689241).

Short form: published @objectstack/plugin-auth floats @better-auth/core past the removal of createLocalAccountIssuerAuthPlugin fails to load ⇒ no sys_* tables ⇒ the readiness probe never satisfies. #7990, cause upstream at objectstack#16186, and #7689's triage forbids repairing the lane by reverting the pin ⇒ nothing to port.

Mechanically not this PR's: this diff is packages/types/src/layout.ts, one pin, and a changeset. The failure is a backend boot from e2e/live/ci/backend.env, untouched here.

⛔ No re-run spent — reproduction already spans main, #7685 (at the 17.3.0 pin, a different backend build), #8076, #8210, #8224, #8226 and this one, which beats running the same job twice.

⚠️ A new head is about to land on this branch (a one-sentence relocation of the justify control note to the FlexLayoutProps interface docblock, per the seat ruling in the dev thread). This lane will go red there too, for the same reason, and this comment covers that head as well — no second stand-down is owed for it.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.7 KB 3191.4 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-B3vw1vMF.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) 498.00KB 113.91KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.11KB 52.55KB
fields (index.js) 243.04KB 61.36KB
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) 6.57KB 2.76KB
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.84KB 10.94KB
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.16KB 3.68KB
plugin-calendar (index.js) 47.67KB 13.25KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.35KB 27.73KB
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.33KB 3.25KB
plugin-view (index.js) 84.46KB 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) 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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
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) 13.64KB 4.59KB
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-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Addendum on 4317adc82 — option C executed, and improved on in two places

⭐ The sentence was re-voiced, not just moved

I ruled "move it to the interface docblock." What landed states the rule once and resolves all three shared members against it:

a member here carries an @default tag only when BOTH consumers apply the same value … align and direction diverge and state their per-type values in prose instead, while justify keeps its @default 'start' because flex.tsx and stack.tsx both read || 'start'

That is better than a relocation. A reader of the published .d.ts now gets the criterion and the worked cases together, so justify keeping its tag reads as the rule applying rather than as an inconsistency. The parallel-sibling goal is measured rather than claimed: both member blocks carry zero @default block tags and the same paragraph structure, differing only in the wrap of one parenthetical (15 lines vs 14).

⭐ "No pin assertion was touched" is answered structurally, not by assurance

My brief said: if the pin reads the direction docblock's text in a way this move breaks, stop and report rather than loosening an assertion. The answer given is mechanical rather than a judgement —

the interface docblock sits ahead of export interface FlexLayoutProps, which is exactly where the pin's interfaceBody() starts its slice, so no member-docblock lookup can see the moved text

⇒ The move is outside the pin's window by construction, which is why all 15 assertions pass unchanged. That is the right shape of answer: it explains why the assertions cannot have been affected, instead of reporting that they happened to still pass.

The ablation was re-run in full, and the reason matters

⭐ Not carried over from f00048c1b"because leg A's mutation target is the very docblock commit 2 edited." Carrying it would have been a reading about a tree that no longer exists. And the mutation's blob hash was compared against commit 2's layout.ts blob (056147b8f), confirming it was applied to the new content rather than a stale one. Both legs matched their pre-stated predictions again (A ⇒ 2 red, B ⇒ 1 red).

The changeset grade was re-measured on the new commit too, and the move turns out not to be cosmetic: grep -c 'DIVERGENT shared member' dist/layout.d.ts = 1 — the relocated sentence reaches the published declaration. So patch remains correct for a reason that changed rather than by inheritance.

⚠️ One platform note worth keeping out of the next seat's way

each PATCH to a PR body strips the trailing generated-by footer block, so durable attribution lives in a prose line in the body instead of being re-pasted

That is a real behaviour of editing PR bodies here, not a slip. Anyone who edits a PR body more than once will otherwise keep re-adding a footer that keeps vanishing.

Awaiting CI on 4317adc82; Live E2E is already covered by comment 5564181610, which said in advance that it would go red on this head.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 7, 2026 02:51
@os-sam
os-sam added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit fd13f52 Sep 7, 2026
33 of 34 checks passed
@os-sam
os-sam deleted the claude/flex-direction-default-tag-7734 branch September 7, 2026 03:07
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): the shared @default 'row' on FlexLayoutProps.direction cannot be right for both flex and stack

2 participants