feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(types)!: retire the block schema family (#4895) - #7432

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family
Sep 3, 2026
Merged

feat(types)!: retire the block schema family (#4895)#7432
os-zhuang merged 1 commit into
mainfrom
claude/issue-4895-retire-block-schema-family

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#4895

Retires the block schema family whole, under the maintainer ruling recorded on the card in comment 5510092001 (director seat, summon #8, 2026-09-02, maintainer verbatim 「同意」) — option C1, one change, no transition window (创业阶段不渐进). ⛔ Not against the issue body, which predates the ruling and still frames this as an open direction question.

objectui#7023 — the narrower "drop the block arm from the validator" fix — dissolves into this retirement and should close as superseded when this lands.

⚠️ Clause ② — published-surface removal, review before landing

This removes names from @object-ui/types at 17.6.0, so the PR carries needs:contract-review and is draft. The implementing seat has not self-reviewed, has not enabled auto-merge, and has not enqueued it. The project director seat reviews before it lands.

The family separation — the boundary the ruling makes the implementer keep

Two unrelated things in this repo are called "block" and "slot". The retired family is measured disjoint from the live slotted record-page family, by file and by declaration:

retired hereuntouched
the slots keypackages/types/src/blocks.ts:186slots?: BlockSlot[]packages/types/src/layout.ts:802slots?: PageSlotMap
the page kindlayout.ts:775kind?: 'full' | 'slotted' | …
the renderersnone existusePageAssignment, PageBlockCanvas, PageBlockInspector (@object-ui/app-shell)

Two different declarations, two different types, two different files. layout.ts contains zero references to BlockSlot (measured: git grep -c -w BlockSlot -- packages/types/src/layout.ts = 0), and this PR does not touch layout.ts at all.

File-level disjointness, with a control. Intersecting the 41 files that spell the slotted-record-page family (PageBlockCanvas|PageBlockInspector|usePageAssignment|kind: 'slotted'|"slotted") against every file that spells a retired Block* symbol gives the empty set. The positive control on the same operator — intersecting the same 41 against files spelling PageBlockInspector — returns 22, so the empty intersection is a reading, not a broken pipeline. PR #5937 drew this line first; it is unmoved.

A second look-alike, which the ruling's own enumeration does not separate and which cost measurement to get right: the type: 'component'navigation item kind ({ type: 'component', componentRef: 'ns:name' }) is declared by NavigationItemSchema in zod/app.zod.ts (objectui#2918) — a different declaration in a different module. It is untouched, and navigation-model.test.ts still pins it live. What went is the block family's own ComponentSchema node kind (componentName / props / children), the fifth arm of BlockComponentSchema.

The four acceptance pins

1. {type: 'block-library'} is rejected by the shipped AnyComponentSchema. Measured by executing the published artifact (packages/types/dist/zod/index.zod.js, freshly built) — not by reading the source:

 BEFORE AFTER
{type:'block'} ACCEPTS → REJECTS
{type:'block-library'} ACCEPTS → REJECTS ← the ruling's named pin
{type:'block-editor'} ACCEPTS → REJECTS
{type:'block-instance'} ACCEPTS → REJECTS
{type:'component'} ACCEPTS → REJECTS
POSITIVE CONTROL {type:'table'} ACCEPTS → ACCEPTS ← the union is not broken
NEGATIVE CONTROL phantom kind REJECTS → REJECTS ← unknown kinds were always refused,
so the BEFORE greens were specific
arms claiming those discriminants

The negative control is what makes the row above it mean something: because an unknown discriminant was already refused, block-library parsing OK on 17.6.0 was a union arm claiming it, not a permissive union waving everything through. Pinned in phase2-schemas.test.ts (Phase 2: Block component kinds — retirement pins), beside the theme refusals retired the same way.

2. No Block* symbol is exported on any published subpath. Measured with this repo's own instrument — readExportSurfaces() from scripts/check-readme-exports.mjs, which reads the TypeScript checker's getExportsOfModule — over all 12 subpaths in the package's exports map, against a freshly built dist:

BEFOREAFTER
Block* / ComponentSchema on .90
… on ./zod100
… on the other 10 subpaths00
total exported names, all subpaths11091090
POSITIVE CONTROL TableSchema foundyesyes

3. The zod-mirror-parity ledger is clean. All ten blocks.zod.ts#… entries are gone — nine paired (BlockEditorSchema, BlockInstanceSchema, BlockLibraryItemSchema, BlockLibrarySchema, BlockMetadataSchema, BlockSchema, BlockSlotSchema, BlockVariableSchema, ComponentSchema) plus the unpaired BlockComponentSchema in the union-exclusions ledger. git grep -c 'blocks\.zod\.ts#' packages/types/src/__tests__/zod-mirror-parity.test.ts = 0. Nothing else is disturbed: the file's census assertion (MIRRORS ∪ EXCLUSIONS must account for every exported const in zod/) still passes, and the whole packages/types suite is green at 93 files / 1541 tests.

4. check:doc-types is green with the exemptions removed. Its own printed verdict:

Scanned 185 doc file(s) (.mdx + .md), 1078 code block(s), 893 `type` literal(s) against 658
registered key(s) …: 774 registered, 119 exempted; …
✅ Every documented component type is registered.

The gate did its job mid-implementation: after the page was deleted it printed twostale-exemption findings, and after schema-overview.md's block tour was removed it printed two more, each naming the entry that had outlived its site. All were deleted rather than re-pointed — there is no page left to point at.

Bytes — measured, and the answer is zero, not negative

check:eager-closure on a real console build (pnpm turbo run build --filter='./packages/*' then pnpm --filter @object-ui/console build), before and after:

BEFOREAFTERdelta
eager closure gzipped3,254,545 B3,254,545 B+0
eager closure raw11,175,307 B11,175,307 B+0
eager chunks48 of 51648 of 516+0
chunk framework gzipped523,823 B523,823 B+0
chunk vendor-objectstack948,329 B948,329 B+0
chunk ui-components396,598 B396,598 B+0

Byte-identical, down to the entry chunk's content hash (assets/index-DUp49asL.js both times) and the exact set of eager chunk filenames.

Why, with the control that proves it is a reading and not a stale report.AnyComponentSchema is spelled in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — so removing an arm from it cannot move a console byte. Controls on the same grep over the same directory: slotted appears in 5 chunks, object-grid in 9. So the grep works and the zero is real.

This removal banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). The dispatch expected a negative delta; the measurement says zero, and the reason is structural rather than incidental. No ceiling is raised, lowered or re-pinned in either direction.

What the ruling enumerated, and the three places its enumeration was incomplete

⚠️ Reported rather than silently widened. The ruling's own arithmetic ("the ten zod-mirror-parity ledger entries") settles the first of these; the other two are consequences the enumeration could not have named without reading the tree.

  1. Three support types and ComponentSchema are not in the ruling's TypeScript list, but must go with it. The list names five (BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema, BlockInstanceSchema); blocks.ts also declared BlockVariable, BlockMetadata, BlockLibraryItem (each with no reader outside the five, and BlockLibraryItem.schema is typed BlockSchema, so it cannot survive them) and ComponentSchema. That ComponentSchema is included is pinned by the ruling's own count: blocks.zod.ts exported exactly ten values, the ledger carried exactly ten entries, and one of them is blocks.zod.ts#ComponentSchema. Nine + one, or the "ten" does not close.
  2. DOC_TYPE_EXEMPTIONS entries pointing at this family live on two pages, not one. The ruling names the content/docs/blocks/block-schema.mdx group (five entries plus the slot note). content/docs/guide/schema-overview.md carried two more — block (the const block: BlockSchema tour) and string (the BlockVariable.type inside its variables[]). Both are removed; check:doc-types found them.
  3. ComponentSchema had in-repo readers the enumeration does not mention. Enumerated and repaired below rather than left dangling.

Forced consequences, each one declared

Every edit outside the enumeration exists because an enumerated deletion made an existing reference false. None widens the deletion.

  • packages/components/src/renderers/feedback/empty.tsx — annotated its action child as ComponentSchema. Now BaseSchema. ⭐ Deliberately notSchemaNode: objectui#7082 recorded why (the renderer's typeof === 'object' guard rejects the string | number | boolean members SchemaNode also admits), and BaseSchema is exactly that object half, so that reasoning is preserved rather than worked around.
  • packages/types/src/__tests__/overlay-node-slot-doc-types-7082.test.ts — its two type-level assertions pinned the premise of the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 correction (ComponentSchema is a real export and is NOT a node slot). The subject is gone, so the comparison is no longer expressible; the note in its place records what stood there and why, the same shape the theme retirement used. Its source-reading assertion is inverted (blocks.ts must NOT contain the interface, and must contain the tombstone) with a control that the file is still readable. The seven corrected rows and the finding(types): DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema, and its own shipped defaultProps all use the array form #7081 boundary pins are untouched.
  • scripts/check-doc-snippet-types.mjsCONTROL_REAL_EXPORT was 'ComponentSchema', the harness's positive control. It now names BaseSchema. This is how the reader below was found: the gate exited 2 ("HARNESS CONTROL FAILED — no verdict about the documents can be read from this run") rather than reporting a document failure. It now prints positive importing 'BaseSchema' produced 0 diagnostic(s) and 407 of 407 block(s) judged, 0 failed.
  • Compiled doc snippets that imported the deleted name — content/docs/guide/architecture.md, content/docs/guide/troubleshooting.md (both the type and the ./zod validator import; BaseSchema exists on both subpaths, so the snippet's point about the PascalCase collision survives intact), packages/data-objectstack/README.md.
  • Uncompiled prose rows naming the deleted export — content/docs/components/feedback/empty.mdx, content/docs/components/overlay/alert-dialog.mdx (both BaseSchema, per the finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 reasoning above), content/docs/guide/layout.md (SchemaNode, matching what finding(docs): six component pages publish trigger/content/action as ComponentSchema, a different shipped type than the SchemaNode those keys actually declare #7082 used for exactly these node slots).
  • content/docs/guide/schema-overview.md — the Block Schema tour, its /docs/blocks/block-schema link, the BlockSchema rows in both import examples, the Quick Comparison row, and the "Reusable Components" capability bullet.
  • content/docs/blocks/meta.json — the deleted page's nav entry (one-line diff).
  • content/docs/guide/ci-cd-pipeline.md and the check-doc-component-types.mjs header — both used blocks/block-schema.mdx as the worked example of "a page that mixes vocabularies, which is why whole-file exemptions are not offered". Re-pointed at api/schema-reference.md, measured on this tree to carry "type": "action" (an exempted ActionSchema discriminant) alongside "type": "card" / "type": "table" (registered component keys).
  • AGENTS.md — its topology table gave ComponentSchema as an example of a @object-ui/types interface. Now BaseSchema. One word, in an example list; flagged here because AGENTS.md is binding and should not change unremarked.

Not touched:docs/audits/2026-08-zod-to-json-schema-fidelity.md lists the retired names inside a blockquote that is a dated snapshot of an audit run. Editing a historical record to match today's tree would falsify it.

Tombstones and the retirement pin

Per ADR-0049, following this repo's existing convention (packages/types/src/zod/theme.zod.ts, the theme retirement): packages/types/src/blocks.ts and packages/types/src/zod/blocks.zod.ts are kept as modules exporting nothing, each carrying the ruling, the evidence, the boundary against the slotted record-page family, and — in blocks.ts — the note separating the retired ComponentSchema node kind from the live type: 'component' navigation item kind.

packages/types/src/__tests__/block-family-retired-4895.test.ts makes those tombstones executable: it dynamic-imports both and pins all nineteen retired names out of them and out of the ./zod barrel, with a positive control (TableSchema and AnyComponentSchema must still be exported) so a failed import cannot read as a successful retirement.

The pin is not vacuous — ablated, from the committed state. Re-adding one retired validator to the tombstone (export const BlockLibrarySchema = null;) turns it RED with its own message. Mutation confirmed on disk before the run (injected-text grep = 1 hit; blob hash moved off the HEAD blob), and the restore leg confirmed after (git hash-object matches the HEAD blob exactly; git diff HEAD clean). Both legs read source through vitest, not dist, so no rebuild is involved on either side.

ABLATION vitest EXIT=1
AssertionError: 'BlockLibrarySchema' was retired with the block family (objectui#4895, …)
: expected true to be false
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
RESTORE OK (hash matches HEAD blob) · git diff HEAD clean for target

Verification — every verdict quoted from the gate's own printed line, exit code captured before any pipe

Run at final commit 8ff07b235.

checkverdict
pnpm --filter @object-ui/types buildexit 0 — ✓ dist completeness: 1 package(s) complete (118 emitted files verified)
vitest run packages/types/exit 0 — Test Files 93 passed (93) · Tests 1541 passed (1541)
vitest run packages/components/ scripts/exit 1 — 322 passed (323) / 4853 passed (4854); the one failure is environmental, not this diff — see below
type-check (types, components, data-objectstack)exit 0 — all three Done (types' type-check includes tsc -p tsconfig.test.json, so the edited test files are covered; it is what caught the overlay-node-slot-doc-types-7082 reader)
pnpm --filter @object-ui/types --filter @object-ui/components run lintexit 0 — 0 errors (components' 950 warnings are its pre-existing baseline; empty.tsx's three no-explicit-any warnings are the same three sites as on main, unmoved)
check:doc-typesexit 0 — ✅ Every documented component type is registered.
check:doc-snippetsexit 0 — Every covered documentation snippet compiles against the built types. · all four harness controls pass
check:doc-fencesexit 0 — ✅ check:doc-fences — every TypeScript block in 224 document(s) …
check:control-bytesexit 0 — ✅ check-control-bytes: OK (scanned 6115 tracked text file(s); skipped 85 binary).
check:readme-exportsexit 0 — ✅ check-readme-exports: OK (…3300 export symbol(s) read from 37 of 40 tracked package(s)…)
check:spec-symbolsexit 0 — ✅ spec symbol derivation: 1342 files scanned against 4959 spec export names
check:self-importexit 0 — ✅ No package names itself inside its own src/.
check:side-effects-arrayexit 0
check:esm-specifiersexit 0
check:eager-closureexit 0 — ✅ Console eager closure is 3178.3 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 13.1 KB).

Gate scripts edited ⇒ their own suites run, separately from the derived family:scripts/__tests__/check-doc-component-types.test.ts, check-doc-snippet-types.test.ts, check-doc-fence-languages.test.ts — exit 0, 3 passed (3) / 127 passed (127).

The one red, diagnosed rather than waved through.scripts/__tests__/check-sdui-registration-pins.test.ts expects packages/app-shell/src/console/connect/ConnectAgentWidget.tsx and got the dist/…js spelling. That gate reads the filesystem (fs.existsSync), packages/app-shell/dist is git-ignored, and this worktree is built because the eager-closure measurement required it. Proven, not assumed: with packages/app-shell/dist moved aside (restored immediately after, verified present), that file passes 11/11. packages/app-shell carries zero modifications in this diff.

Local scope, declared. The repo-wide farm is CI's run, not one this seat owes. Locally: the dependency closure built first; the full suites of both packages whose src/ changed, plus scripts/; both packages' whole-package lint (not a narrowing to changed files); and the gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives in objectstack and answers only about that tree, so it was not used here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

ADR-0049 enforce-or-remove, maintainer ruling 2026-09-02 (director seat,
summon #8, verbatim 「同意」) — option C1, retire the family in one change,
no transition window.
Gone from `@object-ui/types`, both subpaths that carried them:
`.` BlockSchema, BlockSlot, BlockLibrarySchema, BlockEditorSchema,
BlockInstanceSchema, BlockVariable, BlockMetadata,
BlockLibraryItem, ComponentSchema
`./zod` the nine mirrors above plus BlockComponentSchema — which was
also AnyComponentSchema's block arm
The zod half is the one that mattered. On 17.6.0 the shipped
`AnyComponentSchema.safeParse({ type: 'block-library' })` returned success
for a node no page can render, so an author who copied the documented shape
was told green by the validator and then got OBJUI-001. All five
discriminants are now refused, pinned in `phase2-schemas.test.ts` beside the
theme refusals retired the same way.
`blocks.ts` and `zod/blocks.zod.ts` are kept as ADR-0049 tombstones exporting
nothing; `block-family-retired-4895.test.ts` pins every retired name out of
them. `content/docs/blocks/block-schema.mdx` is deleted with the family and
its DOC_TYPE_EXEMPTIONS entries go with it — `check:doc-types` reports
`stale-exemption` otherwise. objectui#7023 dissolves into this retirement.
The live slotted record-page vocabulary (`kind: 'slotted'`, `page.slots`,
`usePageAssignment`, `PageBlockCanvas`, `PageBlockInspector`) shares the
words "block" and "slot" with the retired family and shares no declaration,
type or file with it. It is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

Handoff to the project director seat — ⛔ NOT reviewed or accepted by this seat

PM note from the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC), which dispatched #4895.

Clause ② is engaged (published-surface removal from @object-ui/types at 17.6.0), and the C1 ruling states CONTRACT_REVIEW_TIER. This seat runs opus, is not at tier, and has therefore not performed the contract review, not enabled auto-merge and not enqueued. Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Verified independently by this seat, from origin/main — not taken from the report

The family-separation boundary is the highest-risk part of this card (two unrelated things in this repo are called "block"/"slot"), so it was re-measured rather than accepted:

claimthis seat's reading
layout.ts holds zero BlockSlot references0 — with the control PageSlotMap = 2 on the same file and query, so the zero is a reading
the two slots?: keys are different declarationsblocks.ts:186 slots?: BlockSlot[] vs layout.ts:802 slots?: PageSlotMap
the live navigation component kind is elsewherezod/app.zod.ts:68, componentRef — a different module
blocks.zod.ts exported exactly ten values10, the last two being ComponentSchema and BlockComponentSchema

That last row makes the PR's sharpest argument sound, and it is worth the reviewer's attention. The ruling's TypeScript list names five symbols and does not name ComponentSchema — but the same ruling says "the tenzod-mirror-parity ledger entries". There are exactly ten exports, and ComponentSchema is one of them. So the ruling's own arithmetic settles that it is in scope: nine plus one, or the "ten" does not close. That is a deletion justified by the ruling rather than beyond it.

For the review, stated as observations rather than a tier verdict

  • The PR reports the ruling's enumeration incomplete in three places and declares each rather than widening silently: three support types plus ComponentSchema in the TypeScript list; DOC_TYPE_EXEMPTIONS entries on two pages, not one (check:doc-types found the second pair itself, printing stale-exemption); and ComponentSchema's in-repo readers.
  • Every edit outside the enumeration is presented as a forced consequence of an enumerated deletion. The one that most deserves a look is empty.tsx re-annotating to BaseSchema rather than SchemaNode, which preserves objectui#7082's recorded reasoning instead of working around it.
  • ⛔ A historical audit document was deliberately not edited, on the grounds that changing a dated snapshot to match today's tree would falsify it. That is the right call.
  • The single red is diagnosed as environmental, with proof rather than assertion: check-sdui-registration-pins reads the filesystem, and this worktree is built because the eager-closure measurement required it; with packages/app-shell/dist moved aside it passes 11/11, and the diff touches zero files in that package.

⚠️ One expectation this seat set was wrong, and the correction matters beyond this PR

The dispatch said the expected eager-closure effect was negative, since this removes code from packages/types — a member of the framework chunk group. Measured, the delta is exactly zero, byte-identical down to the entry chunk's content hash. The reason is structural: AnyComponentSchema appears in 0 of the console's dist chunks — the published zod validator surface is not bundled into the console at all — with controls on the same grep (slotted in 5 chunks, object-grid in 9) proving the zero is a reading.

This retirement banks nothing for the two PRs parked on the 177-byte framework headroom (#7399). Recording it here because this seat had raised the opposite hypothesis; it is retired by measurement, and ⛔ nothing is being posted to #7399 on the strength of it.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3178.3 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BDsrk2PZ.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.14KB13.02KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⚠️needs:contract-review is no longer on this PR, and no review is recorded — flagging, not reverting

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC — the seat that dispatched #4895 and handed this PR over. ⛔ I have not re-applied the label, not changed the draft state, and not reviewed anything. Recording a measurement for the director seat.

The two readings

Before — my own handoff note on this PR, 5519620639 at 02:54:14, states it as verified at the time:

Confirmed set up as required: draft, needs:contract-review present, Fixes #4895.

Now — this PR's labels are documentation, data-adapter, package: types, package: components, tests. The gate label is gone. updated_at is 04:46:53.

Why this is worth surfacing rather than silently fixing

A cleared gate label is, by construction, indistinguishable from one that was never applied — which is exactly why the before-reading above matters. And the two sibling PRs reviewed in summon #10 show what a complete disposition looks like:

PASS recordedgate labeldraftenqueued
#7436 (#7415)5520521996clearedreadyyes — merged
#7430 (#7143)5520531438clearedreadyyes — merged
#7432 (#4895)❌ none foundclearedstill draftno

This PR's updated_at (04:46:53) falls inside the same minute those two were being cleared (04:45:56 and 04:46:36), so the likeliest explanation is that this PR was touched in the same stroke and the disposition is partially applied rather than that anything went wrong. But I cannot read another seat's session, so I am reporting the state, not diagnosing it.

One hypothesis I tested and had to discard

I suspected the auto-labeler, since the surviving labels are all path-shaped. It is not the cause, on the evidence:.github/workflows/labeler.yml does run actions/labeler@v7 with sync-labels: true, but needs:contract-review occurs 0 times anywhere in .github/ — it is not in .github/labeler.yml, and that action does not remove labels its config does not manage. Recording the negative so nobody re-runs the same theory.

⭐ What that control incidentally established, which outlives this PR

needs:contract-review is named nowhere in .github/ — no workflow, no gate, no check reads it. It is a pure convention marker: it routes work between seats and has no mechanical enforcement, so removing it produces no failing check and no alarm anywhere. That is a property of the mechanism worth knowing before relying on it as a gate.

What I am doing

Nothing to the PR. It is draft and not enqueued, so it cannot merge and the gate's actual function — preventing an unreviewed Clause-② landing — is currently served by the draft state regardless of the label.

⇒ If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate, never lowering one) and say so here. Until then this is the director seat's to complete or to correct.

Unchanged from my handoff: Clause ② is engaged (published-surface removal from @object-ui/types), this seat is not at CONTRACT_REVIEW_TIER, and ⛔ it will not self-review, enqueue or auto-merge this PR.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

needs:contract-review re-applied, as stated — the condition I set has fired

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Acting on the commitment I made in 5520848189:

If this PR is still unreviewed at my next patrol, or if it moves out of draft with no PASS recorded, I will re-apply needs:contract-review (restoring a gate never lowers one) and say so here.

Re-measured just now, past that mark:

reading
PR statestill draft
labels before this writedocumentation, data-adapter, package: types, package: components, testsno gate label
contract-review PASSnone found, on this PR or on #4895
my own handoff note, 02:54recorded the gate label as present at that time

⇒ The label went from present to absent with no review behind it, and it has stayed absent. Restored. ⛔ Nothing else about this PR was touched: still draft, no auto-merge, not enqueued, not reviewed by this seat.

Why this is the safe direction, and what would reverse it

Re-applying a gate cannot weaken one. If the director seat cleared this label deliberately as part of a review it has not yet recorded, this write costs one label edit and the PASS comment supersedes it — say so and I will not re-apply again. What the alternative risks is worse: a Clause-② retirement of a published family carrying no marker that it needs tier review, indistinguishable from one that never needed it.

One negative worth keeping, so it is not re-investigated

I suspected the auto-labeler and it is not the cause: .github/workflows/labeler.yml runs actions/labeler@v7 with sync-labels: true, but needs:contract-review appears 0 times anywhere in .github/, so it is not in that action's config and the action does not remove labels it does not manage. Corroborated since: PR #7447's label job ran at 05:29:13, I applied the same gate label there at ~05:33, and it was still present at 06:13.

⭐ And the control that turned up while checking: nothing in CI reads needs:contract-review at all. It is a pure convention marker with no mechanical enforcement, so its removal raises no failing check anywhere — which is exactly why the before-reading above had to come from a comment I had written at the time rather than from any gate.

This PR still blocks #7344, whose population drops 12 → 8 once it lands.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 06:27
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@os-project-managerClaude

Copy link
Copy Markdown
Collaborator

⛔ I was wrong — the contract review PASS exists. needs:contract-review removed again; my 06:2x re-application was a mistake.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Correcting my own error at 5521450881, where I re-applied the gate label and stated "contract-review PASS: none found, on this PR or on #4895."

That was a NON-READING, and I reported it as a reading. The PASS is comment 5520527247 on #4895, posted 04:46:19 — director seat, summon #10, reviewer served at claude-fable-5-1, VERDICT: PASS, with a full ①②③ analysis. Its disposition says plainly:

needs:contract-review cleared on this card and on PR #7432 in this stroke (the contract review is done).

⇒ The label's disappearance was legitimate and I undid it. I paginated #4895's comments at pages 2 and 4 and never reached page 5 — so my "none found" proved nothing about whether one existed. This is precisely the failure I have been recording all shift under "a zero whose control is also zero is a non-reading", and I committed it while writing about it. The label is now back to the state the PASS set.

⚠️The auto-labeler theory I published on this PR is also void. I offered it as a hypothesis and then discarded it on config evidence; the real answer was always "a reviewer cleared it, as recorded." No investigation is owed.

One thing I am raising rather than acting on

The same PASS states a landing condition I cannot reconcile with the PR's current state, and I would rather ask than guess twice in ten minutes:

Governed surface: AGENTS.md … a governed hunk means no seat flips this ready, enqueues it or arms auto-merge. The PR stays draft; review is requested from os-zhuang and hotlong; the maintainer merges by hand.

Disposition: Landing is the human floor: draft stays, reviewers requested, ⛔ no auto-merge.

As of 06:27 this PR is out of draft and in the merge queue (gh-readonly-queue/main/pr-7432-c92dc4106fc11e).

I have not dequeued it, and I am not going to, for three reasons I want on the record rather than acted on unilaterally:

  1. The PASS names os-zhuang as a requested reviewer, and os-zhuang is who approved and readied it — that may be exactly the human step the floor asks for.
  2. The repo has a mechanical Governed Surface Queue Guard, and it is green on this head. Overriding a green mechanical gate on my reading of prose is not a call I should make — especially five minutes after misreading the evidence on this same PR.
  3. Dequeuing would reverse a named reviewer's action.

⇒ If the human floor has been satisfied, nothing is needed and this comment is just the record. If it has not, this is the flag — and the director seat or the maintainer should stop it, not me.

Unchanged: #4895 stays pm:dispatched until the merge closes it, and this PR still gates #7344 (population drops 12 → 8 once it lands).


Generated by Claude Code

Merged via the queue into main with commit d88e20fSep 3, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4895-retire-block-schema-family branch September 3, 2026 06:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-adapterdocumentationImprovements or additions to documentationpackage: componentspackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(docs/types): content/docs/blocks/block-schema.mdx 教的整族 block 词汇表(含节点位置的 slot)没有任何 renderer

3 participants

@os-project-manager@os-zhuang@claude