Skip to content

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) by os-sam · Pull Request #7066 · objectstack-ai/objectui · GitHub
Skip to content

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) by os-sam · Pull Request #7066 · objectstack-ai/objectui · GitHub
Skip to content

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

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

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

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

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) by os-sam · Pull Request #7066 · objectstack-ai/objectui · GitHub
Skip to content

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) by os-sam · Pull Request #7066 · objectstack-ai/objectui · GitHub
Skip to content

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

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

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881) - #7066

Open
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar
Open

feat(types)!: retire DataTableSchema.toolbar — declared everywhere, read nowhere (objectui#6881)#7066
os-sam wants to merge 1 commit into
mainfrom
claude/issue-6881-retire-datatable-toolbar

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#6881

Executes the maintainer ruling of 2026-08-31 (director batch 5, item 2), quoted verbatim from the card:

裁定:退役 DataTableSchema.toolbar —— 两个发布面(TS data-display.ts:674 + zod 镜像)同笔墓碑化,⛔ 不接线。 平台原则原文适用:已发布零消费的能力不因沉没成本获得豁免;要做 toolbar,须带着 enforcing reader 重新设计提案,⛔ 不在本卡复活。

CONTRACT REVIEW: this PR narrows a published validator's accept-set (ruling clause 4: "条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review 走复审链;changeset 带 BREAKING 记号"). The label is applied at creation per that clause.

Premises re-verified on this PR's base (85b4957), per the executor prerequisites

Line numbers re-located with git grep, not quoted from the card; the read-point census re-run with the card's positive control, not cited from it.

ClaimMeasuredHolds
TS face declares the keypackages/types/src/data-display.ts:675toolbar?: SchemaNode[], inside DataTableSchema (interface opens at line 650)yes
Zod mirror declares it widerpackages/types/src/zod/data-display.zod.ts:244 — union of one node or an array of nodes, .optional()yes
Renderer reads it nowheredata-table.tsx contains the word at lines 986 and 1211 only — both prose comments; no schema.toolbar read repo-wide (remaining hits are i18n keys such as gantt.toolbar.refresh, ARIA roles, and same-named React props: PreviewShelltoolbar, toolbarAddon, toolbarActions)yes
Positive control finds a real reademptyAction — same interface, same SchemaNode shape — hits data-table.tsx:2118-2119, mounted through SchemaRenderer; the zero above is a reading, not a blind queryyes
No parity ledger watches itzod-mirror-parity.test.ts — zero occurrences of the word across all four ledgersyes
The key validates todayMeasured before this change: array spelling ACCEPTED, single-node spelling ACCEPTED (see accept-set delta below)yes

What changed — both faces, one stroke

  • TS face (data-display.ts): the live member toolbar?: SchemaNode[] becomes the tombstone toolbar?: never, with the ADR-0049 docblock carrying the measurement and the ruling.
  • Zod face (data-display.zod.ts): the union becomes retirementTombstone(...), so the refusal carries its remediation in the parse message AND the .describe() metadata (one string, both channels — the objectui#6931 mechanism).
  • Pin test (packages/types/src/__tests__/data-table-toolbar-retired.test.ts): envelope-asserted refusal on both spellings, remediation text asserted derived-equal with the describe text, boundary legs on both sides, route guard against softening the tombstone into a deletion, and the compile-time half via ts-expect-error directives verified present in the type-checked program (tsc listFiles count = 1 for the new file).

On the ruling's "TS 成员删除" wording: the live TS member is deleted and the house tombstone stands in its place, rather than the line being removed outright. Removing the line entirely would fall through to BaseSchema's string index signature and be admitted as any again — silently, even at fresh literals (measured and pinned by dataTableSchemaSlot-6459.test.ts: a bogus key on a bare DataTableSchema literal compiles). The ruling's own headline verb is 「同笔墓碑化」 and its pin ("authored toolbar flips to an authoring-time loud rejection") is only achievable through the tombstone; the same reasoning is already house law in timeline-timescale-retired.test.ts and the StaticTableColumn retirement.

Accept-set delta — exactly what an author could write before and cannot after

Could write before (validated green, rendered nothing, no signal anywhere):

{ "type": "data-table",
"columns": [{ "header": "Name", "accessorKey": "name" }],
"data": [],
"toolbar": [{ "type": "button", "label": "Refresh" }] }

Also could write before — the spelling only the Zod mirror accepted (the TS face always refused it; this was the card's secondary observation, the mirror-wider-than-declared drift):

{ "toolbar": { "type": "button", "label": "Refresh" } }

After this PR, both fail validation with the same envelope — code invalid_type, path toolbar, expected never, message:

RETIRED (objectui#6881) — never mounted by the data-table renderer; use the built-in toolbar chrome (searchable / exportable), or compose nodes beside the table

Nothing else stops validating. That is the whole delta: one key, both spellings.

Boundary proven on both sides (a change that refuses too much would pass a refusal-only test):

  • Just outside: the two documents above — REFUSED, measured, exact envelope asserted in the pin test.
  • Just inside: the same document with the key absent — ACCEPTED; the same document with the sibling SchemaNode slot emptyAction (the census's positive control, actually mounted) — ACCEPTED. Both pinned.

The rider settles by construction (PM triage clause): retiring both faces in the same stroke makes them agree by refusing both spellings, so the TS-vs-mirror shape divergence dies with the key — it was deliberately not "fixed first". The pin test keeps the single-node leg so the agreement is measured, not assumed.

Documents in this repo that would newly fail: NONE (enumerated, not assumed)

Swept fixtures, examples, catalog, docs and apps for authored toolbar on a data-table:

  • All five examples/schema-catalog/src/schemas/components-complex-data-table/*.json — recursive key scan: zero toolbar keys at any depth.
  • content/docs/components/complex/data-table.mdx — zero occurrences of the word.
  • content/docs/api/schema-reference.md — two occurrences, both prose about ObjectGrid's own runtime chrome, not authored keys.
  • Repo-wide sweep of toolbar: authoring spellings — every hit is an i18n locale key, an ARIA role in tests, or an unrelated React prop (PreviewShell, toolbarAddon, toolbarActions); the catalog-meta.json "toolbar buttons" description refers to a card that composes buttons by hand, not the retired key.
  • packages/types examples and tests, the objectui#6882 declared-keys pin, and the objectui#6459 slot pin — zero references to the key.

Changeset — BREAKING marker, graded per repo law

.changeset/6881-retire-data-table-toolbar.md bumps @object-ui/typesminor and leads with "Breaking for authored metadata:", enumerating the delta and migration. It is deliberately NOT a literal major: AGENTS.md 版本号策略 reserves the fixed group's major for tracking @objectstack across a major, spells out "objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", and scripts/check-changeset-no-major.mjs (changeset-guard workflow) exits non-zero on any major changeset — a literal major cannot land. This matches the landed precedent for the closest cousin narrowing (.changeset/5120-retire-data-table-name-alias.md). The dispatch order's "(major)" gloss is thereby implemented as the ruling's actual requirement — a BREAKING-marked changeset — under the repo's mechanically-enforced grading; flagged here so the contract-review chain can veto.

Verification (all on head 7511392 unless noted)

  • Before-reading (base 85b4957, pristine tree): both toolbar spellings ACCEPTED, controls ACCEPTED — the flip is measured, not asserted.
  • pnpm exec vitest run --maxWorkers=2 packages/types (root-relative form): 80 files, 977 tests passed — includes the new pin file and the untouched parity ledgers staying green.
  • pnpm run type-check in packages/types (all three tsconfigs): green, with the two ts-expect-error directives consumed — the compile-time refusal direction is proven by their being used, and tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked program.
  • Cross-package reverse verification against rebuilt dist: @object-ui/types rebuilt (toolbar?: never confirmed present in dist/data-display.d.ts:654); a consumer-side probe in packages/plugin-grid importing the package by name goes RED on the toolbar write (TS2322 at the property) and GREEN with the key removed; probe deleted, tree clean.
  • Gates: check:control-bytes green (5858 files); check:doc-types green (185 docs, 1068 blocks); changeset guard (check-changeset-no-major.mjs) green; targeted eslint (plain form) on the three changed source files: 0 errors.
  • check:doc-snippets: exit 2 = PRECONDITION NOT MET locally (needs 20 packages built — the farm build CI owns). Declared narrowing in its place: the census above measured zero doc snippets authoring the key, and CI runs the full gate on this PR.

Generated by Claude Code


Generated by Claude Code

…ead nowhere (objectui#6881)
Maintainer ruling 2026-08-31 (option A: retire, do not wire): the key was
declared on both published faces, documented, mirrored — and no renderer ever
mounted it. An authored toolbar got a green document and a blank result.
Both faces tombstoned in the same stroke, per the house ADR-0049 convention:
- TS: `toolbar?: never` (deletion would fall through to BaseSchema's
`[key: string]: any` and be silently admitted again)
- Zod: `retirementTombstone()` — refusal carries the remediation (use the
built-in chrome: searchable / exportable)
The mirror-wider-than-declared drift (single-node spelling only the mirror
accepted) dies with the key: both faces now agree by refusing both spellings.
Pinned in data-table-toolbar-retired.test.ts: envelope-asserted refusal on
both spellings, remediation text derived-equal with .describe(), absent stays
valid, sibling emptyAction still accepted (counter-probe), key stays declared
(route guard), plus the compile-time half via @ts-expect-error in the
type-checked test program.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-CIIlTzAV.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)14.51KB5.35KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
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.95KB10.97KB
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)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
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)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.79KB21.10KB
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.72KB2.24KB
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)3.40KB1.71KB
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-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

This PR is green, mergeable, and deliberately NOT armed for auto-merge. It needs a human, and the domain:ui dispatch seat is standing down.

Stating it once so the PR does not read as forgotten.

#6881's ruling is explicit about the gate, quoted from the card:

条款②:yes(收窄已发布接受集)——PR 建立时挂 needs:contract-review走复审链;changeset 带 BREAKING 记号

The label is applied, the changeset carries the BREAKING declaration, CI is green and there is no conflict. Arming auto-merge would merge this the moment the queue reached it, which would bypass the review chain the ruling prescribed — so the hold is the ruling being followed, not caution.

⚠️Note the contrast with its sibling, which I initially got wrong. PR #7065 (#5934, ActionRunner.onSuccess) is also a published-surface narrowing, and I held it for the same chain — incorrectly. #5934's ruling reads 「已发布类型成员的 breaking 删除属人工地板,本裁定即该地板动作的记录」: the human floor is discharged by that ruling itself, and it prescribes no review chain. #7065 is now merged/queued. This card is different: its ruling names the chain explicitly. A reviewer should not take #7065's release as precedent for releasing this one.

What is needed: a contract review per the ruling. When it passes, arm auto-merge (squash) — the head has not moved since CI went green, so nothing else is owed.

This seat's shift has ended; no further check-ins are scheduled against this PR. Handover for the next domain:ui PM is #7089.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): DataTableSchema.toolbar is declared on both published faces and read by nothing — no renderer ever mounts it

2 participants

@os-sam@claude