Skip to content

fix(types): the flex bag's child list is typed as nodes on the TypeScript face (objectui#11564) - #11594

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11564-flex-bag-array-children
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11564-flex-bag-array-children

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11564

Clause-②: yes

On the TypeScript face, the flex bag's child LIST (properties.children: [ … ]) was unknown[]. That made it the one node slot the BaseSchema index-signature removal (objectui#8347) would have left unchecked. FlexBlockNode now types it as nodes. This is the card's option B, the seat's choice under the director's delegation (claim 5976113446). The zod arm does not move. objectui#8347 is not addressed here and remains open; it lands after this.

The premise, verified first (both zod faces)

Before any edit, the probe went through each face's published validator (dist/zod/index.zod.js): safeValidateSchema (tolerant) and StrictAnyComponentSchema (strict), at bdc9049ed.

probe tolerant strict
f02 list, { type: 'text', contnt: 'x' } accept (the tolerant face judges no undeclared key, here as anywhere) 1 issue: unrecognized_keys at properties.children.0
list, undeclared type 1 issue: invalid_union at properties.children.0.type same
list, element:text bag misspelling 2 issues, both at properties.children.0.properties… (missing content, unrecognized_keys for contnt) same
list, object with no type 1 issue at properties.children.0.type same
two levels down, undeclared type 1 issue at properties.children.0.properties.children.0.type same
CONTROL single child, misspelled key accept 1 issue: unrecognized_keys at properties.children
CONTROL single child, undeclared type 1 issue: invalid_union at properties.children (SchemaNodeSchema) same
lit, well-formed list accept accept
list of primitives (string, number, null, boolean) accept accept
list with a nested array entry accept accept

So the walk judges each list entry once, at its real path, on both faces, as the FLEX_BAG_CHILDREN docblock says. The single child is judged by SchemaNodeSchema. flex-properties-bag-11276.test.ts already pins this on both faces ("judged once, at its real path"), so the new pin does not repeat it.

What changes

  • packages/types/src/authoring-nodes.ts: FlexBlockNode reads a new FlexBlockShape. That is FlexBlockSchema's closed shape with ONE member replaced: the bag's children is the flat FlexSchema mirror's own children member, by reference (SchemaNode | SchemaNode[], which is also what FlexLayoutProps declares). Every other member of the node and of the bag is still the arm's. It is still an interface (the objectui#11466 cycle), and it declares nothing of its own. If the arm's properties stops being an optional zod object, the shape becomes never and every flex literal stops compiling; it does not silently lose the bag.
  • packages/types/src/__tests__/flex-bag-children-list-11564.test.ts (new, permanent):
    • Equal rows: the bag's children equals SchemaNode | SchemaNode[] | undefined, the mirror's member, and FlexLayoutProps['children'].
    • A control that the arm's own list is still unknown[], plus rows showing it is the ONLY member that differs, on the node and in the bag.
    • @ts-expect-error rows: the f02 shape with a closed node (element:text bag misspelling) refused inside the list (with the arm-typed reading as a lit control), an undeclared type, a nested array, and an untyped object.
    • A lit well-formed list (declared nodes, primitives, a nested flex).
    • A runtime leg that re-derives the one TS/zod divergence (nested array) and the agreements on both zod faces.
  • The mirror ledger: zod-mirror-parity.test.ts keys every divergence ledger (KnownDrift, WiderThanDeclared, WIDER_ARMS, …) by REGISTERED pair, and FlexBlockSchema is not one: it sits in EXCLUSIONS, because its TS face is derived. The divergence is therefore recorded in that EXCLUSIONS row's reason, with its comment pointing at the pin that re-derives it. (EXCLUSIONS reasons are only checked for presence and length.)
  • .changeset/11564-flex-bag-children-list.md: @object-ui/types minor, the narrowing, the migration, and what does not move.
  • Outside the listed file surface, two comments this change made false (prose only, no code change): packages/types/examples/dashboard.ts and the packages/types/README.md composition example both said the list is unknown[]. Each keeps its satisfies FlexBlockNode, for the reason measured as probe A below.

The dispatch's hypotheses, measured

  • H1, which spelling is "a node": SchemaNode for both arms. That is the slot type objectui#11466 gave every node slot (BaseSchema.children, the zod NodeSlotZodType), and the single-child arm already read it (the input of SchemaNodeSchema). DeclaredNode | string is the PROP's root type and is not used: a child slot admits primitives on both zod faces.
  • H2, B is not inert today for non-object entries: falsified as stated. SchemaNode admits string | number | boolean | null | undefined, and so do both zod faces. What B refuses today is narrower, and only where FlexBlockNode itself is the target type: an undeclared or absent type, a nested array, a closed node's misspelling (element:text and the other spec-derived nodes), and a value statically wider than SchemaNode. At a SchemaNode / DeclaredNode slot B is inert today, measured with tsc. A nested-flex bag misspelling, an undeclared type in the list, and a nested array all compile there (probes A, B, C), because the post-hoist FlexSchema arm's index signature absorbs properties. Census, by TypeScript AST over the tracked files:
population count
tracked .ts / .tsx files 6165
files naming flex 73
type: 'flex' object literals 95
literals with properties.children 31 (8 of them in the new pin file)
typed : FlexBlockNode, both green 2 (authoring-nodes-bag-carriers-11468.test.ts, examples/dashboard.ts)
runtime only, untyped (safeValidateSchema(unknown), toEqual, zod fixtures) the rest
  • The 71 examples/schema-catalog JSON files with type: 'flex' are typed as an Example whose schema is unknown (the type argument is spelled in words here because GitHub strips angle-bracket shapes), so no node type checks them; they are judged at runtime.
  • No markdown fence names FlexBlockNode, and no skill fence changes.
  • Type-check census: 8 programs (types, components, core, react, sdui-parser, cli, app-shell, example-schema-catalog), 41/41 turbo tasks, 0 cached, 0 error TS, at c5140c1. Zero sites turned red, so no consumer fix was needed. No site puts a non-node in the list on purpose: renderChildren hands a nested array to SchemaRenderer whole.
  • H3, the ledger: see "What changes" above. No ledger keys derived authoring faces, so the row went into the EXCLUSIONS reason.
  • H4, headroom: no zod type is touched (the new types are type-level only), so the objectui#11573 headroom pin was not re-read for a before and after. It ran green inside the full types suite.

The card's acceptance pin: f02 under the P1 mutation (one-off, not committed)

For P1, BaseSchema's [key: string]: any; was removed through ablation-replace.mjs (WRAP mode, anchor hit 1 to 0, blob c0d9ad447f8d to 86201e15c7b2). Each mutated tree was restored and proved: blob back to the HEAD blob, git diff HEAD empty, status clean. The probe is a scratch file with one verdict per line, read off tsc.

probe line HEAD c5140c1, signature present HEAD + P1 BASE bdc9049ed + P1
f02 (list, text + contnt) compiles refused (TS2322) compiles (the card's gap)
single-child control compiles refused (TS2353) refused (TS2353)
f02 at DeclaredNode compiles refused (TS2322) compiles
lit well-formed list compiles compiles compiles
element:text bag misspelling in the list refused (TS2322) refused compiles

The first mutated run was a no-op. Its replacement text was a substring of the anchor, so the tool's count check refused before running the probe, and it restored and proved both files. The rerun used a marker replacement (0 to 1) and is the run cited above.

Reverse verification

The new pin file was compiled against BASE's authoring-nodes.ts, in a detached comparison worktree, so this tree was never mutated. It went red with 7 errors: the three Equal rows (TS2344) and four now-unused @ts-expect-error (TS2578, on the f02 closed-node row, undeclared type, nested array and untyped rows). Against HEAD it gives exit 0. The direction is red, as expected.

Gates

gate head exit verdict line
pnpm --filter @object-ui/types type-check 9107906 0 three tsc legs, no errors
pnpm --filter @object-ui/types test 9107906 0 352 files, 9428 tests passed
turbo run type-check (TURBO_FORCE, 8 programs above) c5140c1 0 41 successful, 0 cached
core schema-builder.test.ts + 3 schema-catalog flex test files c5140c1 0 4 files, 87 tests passed
check:doc-snippets 9107906 0 "Every covered documentation snippet compiles against the built types." (777 of 777 judged)
check:doc-examples 9107906 0 "Every covered @example compiles, or fails exactly as its ledger row declares."
check:doc-types 9107906 0 "Every documented component type is registered."
check:skill-examples 9107906 0 18 of 18 ts fences judged, 0 failed
check:readme-exports 9107906 0 563 self-imports judged, 0 wrong-path, 0 fabricated
check:spec-symbols 9107906 0 nothing cites a key its spec symbol does not declare
check:changeset-claims (report-only) 9107906 0 16 pending bodies name a touched file; each was re-read and none is falsified
check:pending-changeset-literals 9107906 0 "No test source names a pending changeset."
check:control-bytes 9107906 0 OK
changeset:check 9107906 0 no major
check-changeset-presence 9107906 0 1 changeset declared
type-check:coverage 9107906 0 43/43 packages compile their tests
check:new-line-citations 9107906 0 0 new citations
eslint on the 4 changed TS files (objectui's per-package eslint . form) 9107906 0 4 files, 0 errors, 0 warnings

Between c5140c1 and 9107906 only packages/types/README.md changed (git diff --stat), and no tsconfig program reads it.

On the eslint row, the narrowing is a measurement: the 4 files are the diff's TS files, read from --format json, and eslint.config.js declares no parserOptions.project or projectService, so linting is not type-aware and cannot move a verdict in an untouched file. Repo-wide lint is CI's.

Acceptance notes

  • A pending entry, .changeset/11468-bag-carrier-node-types.md, calls FlexBlockNode "the FlexBlockSchema arm's input". After this change one bag member is read off the flat mirror instead. This release's 11564 entry says so, so the CHANGELOG reads coherently. Left unedited; the alternative is a dated note in the style of that entry's objectui#11440 note.
  • Both zod faces admit a nested-array entry in a bag child list (flex, and the page: containers through the same walk), and the renderer cannot draw it as a list. No producer of that shape was measured, and the render outcome is NOT MEASURED. Observation only; carrier: none.
  • ablation-replace.mjs refuses a replacement that occurs inside its own anchor (the replacement count cannot rise). That failure is loud and safe, and a marker replacement avoids it. Observation; carrier: none.

The draft is the dev's terminal deliverable. Flipping it to ready and queueing it are the seat's actions.


Generated by Claude Code

claude added 3 commits October 4, 2026 03:32
…ript face (objectui#11564)

`FlexBlockNode` read its bag's `children` off `FLEX_BAG_CHILDREN`, whose
list arm is `z.array(z.unknown())`, so the list, the form a `flex` node is
authored in, took any entry on the TypeScript face while a single child was
judged as a node. The zod faces need the `unknown`: the spec page walk
judges each list entry there, once, at its real path, on both faces.

`FlexBlockNode` now reads the bag's `children` off the flat `FlexSchema`
mirror's own member, by reference (`SchemaNode | SchemaNode[]`, the slot
every node slot takes); every other member is still the arm's. The zod arm
does not move. The divergence is recorded in the mirror-parity ledger's
`EXCLUSIONS` row for `FlexBlockSchema` and pinned in
`flex-bag-children-list-11564.test.ts`.

Claude-Session: https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ
Co-authored-by: Claude <noreply@anthropic.com>
… is typed as nodes (objectui#11564)

The comment said the bag's child list is `unknown[]`, which is no longer
true. The nested `flex` node keeps its `satisfies FlexBlockNode`: as a list
entry it also matches the post-hoist `FlexSchema`, whose index signature
admits any `properties` until objectui#8347.

Claude-Session: https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ
Co-authored-by: Claude <noreply@anthropic.com>
…t is checked as nodes (objectui#11564)

The comment said the bag's child list is `unknown[]`, which is no longer
true. The code is unchanged: the nested `flex` keeps `satisfies
FlexBlockNode`, because as a list entry it also matches the post-hoist
`FlexSchema`, whose index signature admits any `properties` until that
signature is removed.

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

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3312.1 KB 3330.4 KB
Main entry chunk (gzip) 151.2 KB 350 KB
Entry file index-DfkkaE6-.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.69KB 64.78KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 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) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9107906411daa671f19528d65b8a1055cd624584
Local-runs: none

PR #11594 (card #11564), head 9107906 on claude/issue-11564-flex-bag-array-children, net diff against main at that head: 6 files, +262/-15. Inputs: the card body and its four comments (the director's unlock 5974978503, the serial hold 5975387146, the claim 5976113446, the dev report 5976542048), the PR body, its file list, the diff, and the check-runs on the head. Read-only throughout: nothing built, run or re-run; the sibling checkout was read at origin/... refs only.

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged:

  1. FlexBlockNode['properties']['children'] narrows from unknown[] | SchemaNode | undefined to SchemaNode | SchemaNode[] | undefined — RIGHT. It is the card's option B, delegated to the claiming seat by the director's unlock and ruled in claim 5976113446. The new member is the flat zod FlexSchema mirror's own children (a union of SchemaNodeSchema and an array of it, optional), by reference, which is also what FlexLayoutProps.children declares (SchemaNode | SchemaNode[], read at the head). SchemaNode[] is a subset of unknown[], so this is a pure narrowing: nothing an author could not write before becomes writable.
  2. Every other member of the node and of the bag stays the FlexBlockSchema arm's — RIGHT, and pinned by the two Omit-based Equal rows in the new test (node minus properties, bag minus children), plus a control that the arm's own list is still unknown[]. FlexBlockNode stays an interface (the objectui#11466 cycle) and declares no member of its own; a properties member that is not an optional zod object collapses the shape to never rather than silently dropping the bag.
  3. The zod faces do not move — RIGHT. FLEX_BAG_CHILDREN in layout.zod.ts keeps z.array(z.unknown()); its docblock, read at the head, is the reason: the spec page walk judges each list entry once, at its real path, on both faces, and a typed member there was measured to report a refused child twice. The dev verified that premise through both published validators before editing (the ten-row table in the PR body); the claim made the verification the fork condition, and it held. Runtime accept set: unchanged.
  4. The union consequences — AuthoringNode, DeclaredNode, SchemaNode (V1, objectui#11466) and SchemaRenderer's schema prop all carry the flex arm — narrow correspondingly, and correctly. One reading to keep in view: at a SchemaNode or DeclaredNode slot the narrowing is inert TODAY, because the flat TypeScript FlexSchema (layout.ts, which extends BaseSchema) also matches a flex literal and its index signature absorbs the properties bag; it bites today only where FlexBlockNode is the target type, and bites everywhere once objectui#8347 removes the signature. The dev measured this (probes A to D) and says so in the PR body, the README and the dashboard example; the changeset scopes its claims to FlexBlockNode. Consistent with the card: the gap "bites the day objectui#8347 removes it", and this card lands before that removal.
  5. Public surface: no new export. FlexBlockArmShape and FlexBlockShape are module-private type aliases; FlexBlockNode was already on the . entry (src/index.ts re-exports it from authoring-nodes.ts; the exports map's . is dist/index.d.ts). Nothing in the diff widens. Test (dist pins) and the build's dist-completeness check are green on the head.
  6. The card's acceptance pin — f02 refused under the P1 mutation, the single child as the control — is reported met: HEAD plus P1 refuses f02 (TS2322), BASE plus P1 compiles it, the single-child control is refused on both (TS2353). That run is one-off and not in the diff, as the card allows. The committed, permanent pin covers the shape every node has after objectui#8347: a closed spec-derived node (element:text with contnt) refused inside the list today, the arm-typed reading of the same document as the lit control, plus an undeclared type, a nested array and an untyped object refused, and primitives admitted. The pin is compiled by a real tsc program: tsconfig.test.json includes src/**/*.test.ts and the package's type-check script runs it as its third leg, so the @ts-expect-error rows are not phantom checks; the dev's reverse verification (7 errors against BASE, 0 against HEAD) shows the same from the other side.
  7. The mirror ledger: the divergence is recorded in the EXCLUSIONS reason for layout.zod.ts#FlexBlockSchema in zod-mirror-parity.test.ts — RIGHT as the carrier. Every keyed divergence ledger there asserts registered mirror pairs, and FlexBlockSchema is an exclusion because its TS face is derived; an EXCLUSIONS reason is checked for presence and a length of at least 20 characters only, so the mechanical half of the record is the new pin's runtime leg (both zod faces admit a nested array; the flat FlexSchema and the TS face refuse it).
  8. Prose corrections in packages/types/README.md and packages/types/examples/dashboard.ts: comments that said the list is unknown[] were made false by the change and corrected; no code moved, each keeps satisfies FlexBlockNode, and the stated reason (the post-hoist FlexSchema arm absorbs the bag until objectui#8347) is the same fact as item 4. Doc Snippet Type Check is green on the head. Outside the claim's file surface, declared in the report and the PR body — accepted.
  9. The new docblock's claim about renderChildren (@object-ui/components) is true at the head: an array is mapped entry by entry into SchemaRenderer, so a nested-array entry is handed to it whole.

Gates on the head, as read at 2026-10-04T04:36Z: 31 check-runs success, 3 skipped (dependabot and the two coverage lanes), 8 in_progress — Test (shard 1/8), 2/8, 4/8, 5/8, 6/8, 7/8, 8/8 and Spec Main Shape Gate. Green among the 31: Type Check, Lint, Build & E2E, Test (dist pins), Test (shard 3/8), Governed Surface Queue Guard, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, Doc Snippet Type Check, Doc Example Id Check, Doc Component Type Check, README Export Check, Skill Example Check, Line Citation Gate, Control Byte Scan, Build Docs, Bundle Analysis. An in-progress gate is not a pass: this record judges the contract; the landing waits for the eight to conclude green. No governed path in the file list (the queue guard agrees), the head repo is the base repo, 277 changed lines.

② Semver level

  • Changeset .changeset/11564-flex-bag-children-list.md, @object-ui/types: minor — RIGHT. The only released package whose source moved is @object-ui/types (Changeset Declaration green: 3 source files, 1 changeset); the fixed group moves together. The direction is a narrowing of a published type, which is breaking; objectui's version-number policy (AGENTS.md, enforced by check-changeset-no-major.mjs, green here as Changeset Bump Policy) ships the repo's own breaking changes as minor with the break stated in the body. The body states it: the FROM type (unknown[] | SchemaNode) and the TO type (SchemaNode | SchemaNode[]), the four accept-set consequences, what does not move (every zod face, every runtime path), and a one-line migration. skip-changeset would have been wrong for a published type change; major is forbidden in this repo.
  • Clause-②: line. The PR body carries Clause-②: yes, the claim's value copied verbatim, as the executor must; the changeset carries Clause-②: yes (narrowing). By the fleet's one reader (clause2-line.mjs) the value answers whether the card widens the accept set or grows the public surface — and this diff widens nothing (item 5 above), so the byte-exact declaration for it is no (narrowing). The yes is the dispatching seat's own ruling in claim 5976113446, chosen to route the card to this tier; it misleads no gate (no objectui gate reads the line, and the widening-tells gate never blocks a yes), and both readings require the same bump (yes takes at least minor; a narrowing is breaking, which is minor in this repo). Judged acceptable as declared, with the exact reading recorded here for the seat's ledger; the changeset's arm is the truthful direction and is the text that ships.

③ Boundary flags

Dev report 5976542048: nine deviations and one open question, each answered.

  1. Two prose-only files outside the claim's file surface (the README and the dashboard example) — accepted, see ① item 8.
  2. H2's suggested pin (a non-object entry refused today) not built, because SchemaNode admits primitives on every face — accepted; the pin records the measured accept set instead (primitives compile; a nested array and an untyped object are the refused non-node entries), which is what a pin is for.
  3. H3: no per-pair ledger keys a derived face, so the row is the EXCLUSIONS reason — accepted, see ① item 7.
  4. The first P1 mutation attempt was a no-op (the tool refused a replacement that sits inside its own anchor) and is declared; the marker rerun is the cited one — accepted; the refusal was loud and both trees were proved restored.
  5. The consumer type-check union cited at c5140c1, not 9107906 — accepted: the stat of 9107906 is packages/types/README.md alone, and no tsconfig program reads it.
  6. Doc gates first exited 2 on unbuilt dists, counted as NOT MEASURED, then rerun green after the builds — accepted; CI's Doc Snippet Type Check, Doc Example Id Check and Doc Component Type Check are green on the head independently.
  7. The run stopped at a usage wall and was resumed by the seat; the P1 legs were rerun from the committed state — accepted.
  8. Commit trailers carry the model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named trailer — RIGHT under both repos' rule; verified on all three commits.
  9. .changeset/11468-bag-carrier-node-types.md left unedited — the open question, below.

Open question (the 11468 entry's sentence "FlexBlockNode: the FlexBlockSchema arm's input"; A leave it, B append a dated note): A, and B is no longer available. main moved past the branch's base by exactly one commit, b493919 chore: release packages (#5400), which consumed that changeset into packages/types/CHANGELOG.md under 17.7.0 (@object-ui/types is 17.7.0 on main, 17.6.0 on the branch). A released CHANGELOG entry is release-owned: it is amended only for a factual error, in a dedicated docs-only PR — and the sentence was true at 17.7.0. The 11564 entry ships in the next version and states the one member that now differs. Consequence for the PR body: its first acceptance note calls the 11468 entry "pending", which stopped being true at that merge — prose staleness in the body, no diff change owed. Not escalated.

Out-of-scope findings (two, carrier none), each answered:

  • Both zod faces admit a nested-array entry in a bag child list and renderChildren hands it to SchemaRenderer whole, render outcome NOT MEASURED — the acceptance note is the right carrier; it becomes a card the day someone measures the render outcome, which is the repro a card needs. Not a contract violation: the TS/zod difference is declared in the ledger and pinned.
  • ablation-replace.mjs (objectstack) refuses a replacement that occurs inside its own anchor — a tooling observation about the sibling repo, loud and safe; the note is enough.

Nothing escalated to the maintainer. Ordering holds: objectui#8347 stays pm:blocked behind this card, and the report's re-price (the removal has nothing flex-specific left to carry) is consistent with the P1 table.

Implemented-by: claude/issue-11564-flex-bag-array-children
Reviewed-by: session_01CPvhwGcirXqBGEdPSb72TZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 04:38
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 04:38
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit b10c68e Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11564-flex-bag-array-children branch October 4, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types tests

Projects

None yet

2 participants