Skip to content

fix(types,plugin-dashboard): retire drillDown on the bare pivot node; object-pivot is where a pivot drills (objectui#10932) - #10972

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10932-retire-pivot-drilldown
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10932-retire-pivot-drilldown

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10932
Clause-②: no — a retirement narrows the accept set; it widens nothing and adds no public surface.

drillDown on the bare pivot node validated and did nothing. This retires it on both published faces, with a tombstone that names object-pivot as the remedy. That is option (b) of the card, as graded in triage comment 5868092364. object-pivot's own drillDown (ObjectPivotDrillDownConfig) is untouched and still drills.

Implemented by the domain:spec dispatched dev, session https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm, on the PM claim 5868869683. Base 95a7c8d38.

Premise gate (measured before any edit)

The grade's premise was: no shipped or example document authors drillDown on a pivot node (as opposed to object-pivot), and a static-data drill has no meaning. It holds.

  • objectui, tree at base 95a7c8d38 (clean worktree; enumeration git ls-files, reads from the same checkout). A structural classifier looks up the type of the object that encloses every drillDown key, and of the parent widget when that object is an options bag. It also lists every type: 'pivot' / 'object-pivot' object. It scanned 9,125 files (json/ts/tsx/js/md/mdx/yaml) and found 88 drillDown key sites.
    • Enclosing type pivot: 2 sites, both test fixtures. drillRefusal-10789.test.tsx renders ObjectPivotTable, so it is an object-pivot drill. registered-type-arms-10859-b2.test.ts is the objectui#10859 accept pin, which this PR updates. These are the matcher's positive control.
    • Non-test type: 'pivot' objects: the DashboardGridLayout static-data branch, plugin-dashboard/SKILL.md, a CHANGELOG entry, a code comment, and the declaration itself. None carries drillDown.
    • examples/** (504 files, 473 of them in examples/schema-catalog) has zero pivot or drillDown mentions. The control is that the same query finds "type" there.
    • apps/**, content/docs/**, every package README and skills/** hold no pivot node document at all. The only hits are type-name tables and prose.
  • objectstack, at origin/main dbddf02c1, fetched into a named ref. Enumeration and reads both come from that ref (git grep / git show). The local checkout was 15 commits behind, so it was not read.
    • 7 pivot-typed objects: 6 pivot and 1 object-pivot (in sdui.manifest.json). None carries drillDown.
    • The one shipped pivot widget, pivot_tasks in the app-showcase chart-gallery dashboard, is dataset-bound. It renders through DatasetWidget, not the pivot node.
    • 9 drillDown key sites, none on a pivot. All are spec pins: bar, summary, and the dashboard-widget refusal ("Drill-through on a dashboard is AUTOMATIC").
  • DashboardGridLayout's static-data pivot emits { type: 'pivot', ...options, data }. It never writes a drillDown of its own; only its table branch defaults one. A pivot widget's options.drillDown would pass through, and none is authored anywhere above.
  • Static-data drill meaning. The only drill implementation is ObjectPivotTable's DrillDownDrawer, and its renderDrillDrawer returns nothing without schema.objectName. A pivot node declares no object, so a drill has nothing to list.
  • Runtime. onDrillDown has exactly one producer, ObjectPivotTable. The pivot registration is bare PivotTable, and console's lazy stub loads that same registration. The inertness was also read at runtime, through the real SchemaRenderer and registry (the new plugin-dashboard probe): a pivot node carrying drillDown: { enabled: true } renders the cross-tab with zero role=button elements.

What changed

  • @object-ui/types
    • PivotTableSchema.drillDown is a ?: never tombstone. Its docblock follows the DataTableSchema.toolbar convention and points at object-pivot / ObjectPivotDrillDownConfig.
    • The zod pivot arm's drillDown is retirementTombstone(PIVOT_DRILL_DOWN_RETIRED). The one string feeds both the parse-time message and .describe(), and it names object-pivot.
    • PIVOT_NEITHER_CHANNEL, the body / children refusal, no longer lists drillDown among what a pivot renders.
    • Three docblocks that described the old accept are updated: DrillDownConfigSchema, the zod PivotTableSchema, and ObjectPivotDrillDownConfig.
  • @object-ui/plugin-dashboard
    • PivotTable reads nothing off schema for its drill. The host's onDrillDown is now the only switch.
    • ObjectPivotTable types its schema as PivotTableSchema minus drillDown (key remapping, not Omit: BaseSchema's index signature makes Omit drop every declared member), intersected with its own drillDown?: ObjectPivotDrillDownConfig. It strips that key before handing the node to PivotTable.
  • .changeset/10932-pivot-drilldown-retired.md: @object-ui/types and @object-ui/plugin-dashboard, both minor, with the break stated. The changeset names the three pending entries whose sentences this supersedes (see Acceptance notes).

Decision the dispatch asked for: the component's drill switch

PivotTable had no drillDown React prop. It read schema.drillDown and gated it with isDrillEnabled. With the node key tombstoned, it had to stop reading it, and there were two routes:

  • (i) Move the config to a new drillDown React prop. Not taken. SchemaRenderer spreads a node's keys as React props, so a prop of that name would hand an authored drillDown on a pivot node straight back to the component. That reopens the channel the tombstone closes.
  • (ii) Make the host's handler the only switch. Taken. The only thing PivotTable took from the config was the on/off bit, and its one drilling host already decides that before it passes onDrillDown. Behaviour of object-pivot is unchanged.

For a React caller the published behaviour changes: a handler now turns the drill on by itself, and a schema.drillDown no longer gates it (the type refuses that key anyway). The changeset states this.

Claimed file surface: exceeded, on purpose

The claim listed packages/types/src/data-display.ts, packages/types/src/zod/data-display.zod.ts, pins under packages/types/src/__tests__/ and one changeset. This PR also edits packages/plugin-dashboard/src/PivotTable.tsx, packages/plugin-dashboard/src/ObjectPivotTable.tsx and two plugin-dashboard tests.

That is forced by the ruled TS tombstone: reverse verification 5 below shows ObjectPivotTable stops compiling without the change. The dispatch's mechanism assumption 3 also asked for this decision.

Tests

All runs are from head fd2befe42.

  • pnpm --filter @object-ui/types type-check (all three legs): exit 0.
  • pnpm exec vitest run packages/types/: 269 files, 5,963 tests passed.
  • pnpm --filter @object-ui/plugin-dashboard type-check (both legs): exit 0. tsc -p tsconfig.test.json --listFiles includes PivotTable.drill.test.tsx, pivotNode.drillDownRetired-10932.test.tsx and ObjectPivotTable.drillDownRefusal-10685.test.tsx.
  • pnpm exec vitest run packages/plugin-dashboard/: 149 files; 1,346 passed, 6 skipped.
  • apps/console/src/__tests__/registry-inputs-spec-parity.test.ts + packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts: 2 files, 213 passed.
  • Build: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-dashboard^...' build, exit 0. It includes @object-ui/types, whose build runs check-dist-completeness. The rebuilt dist/data-display.d.ts carries drillDown?: never.

New and updated pins

  • packages/types/src/__tests__/pivot-drilldown-retired-10932.test.ts (new)
    • Refusal: code invalid_type, expected never, path ['drillDown'], on the arm, safeValidateSchema and StrictAnyComponentSchema.
    • Message: names `object-pivot`, equals .describe(), and is not zod's generic text.
    • The key stays declared: a tombstone, not a deletion.
    • Control: the same node without drillDown parses on all three.
    • TS: a @ts-expect-error line, plus Equal rows (drillDown reads undefined; title stays string | undefined).
  • registered-type-arms-10859-b2.test.ts: the "fully populated" fixture drops drillDown. The member-level pin now asserts the whole-key refusal at ['drillDown'].
  • PivotTable.drill.test.tsx: the node's drillDown can neither turn the drill on (no handler) nor turn it off (enabled: false with a handler). The fixtures no longer author the retired key.
  • pivotNode.drillDownRetired-10932.test.tsx (new): the SchemaRenderer + real-registry probe above, with a lit control (the same query finds the affordance when a host passes onDrillDown).
  • Not a zod control: object-pivot with drillDown has no zod arm, so a safeParse of it is refused at type either way. The object-pivot accept controls are the existing live case in ObjectPivotTable.drillDownRefusal-10685.test.tsx (TS, green) and the CONTROL case in drillRefusal-10789.test.tsx (the drawer opens, green).

Reverse verifications

Each was run from committed state through ablation-replace.mjs, which checks the anchor hit and restores on a trap. Every restore was proven: the blob equals HEAD and git diff HEAD is empty.

  1. Zod arm back to DrillDownConfigSchema.optional(). Five tests go red: 4 in the new pin and the rewritten objectui#10859 path pin. Exit 1.
  2. TS member back to drillDown?: DrillDownConfig. tsc -p tsconfig.test.json in packages/types reports 4 errors: the pin's Equal row, an unused @ts-expect-error, the objectui#10859 MismatchedKeys row, and the zod-mirror-parity ledger. Exit 2.
  3. PivotTable's switch re-reads the node key (... && Boolean(schema.drillDown?.enabled)). 11 tests go red: 6 in PivotTable.drill.test.tsx and 5 in drillRefusal-10789.test.tsx, since ObjectPivotTable no longer hands the key down. Exit 1.
  4. Non-vacuity of the SchemaRenderer probe, which is green on base by design because it records the premise: with the switch forced to true, the probe goes red. Exit 1.
  5. Cross-package, which also proves the rebuilt .d.ts is read: ObjectPivotTable's schema intersected with the full PivotTableSchema again gives 4 TS2339 errors on drillDown?.target / columns / maxRows / report (property of never). Exit 2.

Gates

These were hand-derived from objectui's package.json and .github/workflows/, since objectui has no dispatch-gates.

  • Exit 0 at head: check-control-bytes, check-new-cross-file-line-citations (0 new citations), check-spec-symbol-derivation, check-changeset-presence, check-changeset-no-major, check-changeset-overwrite (no pre-existing changeset modified), check-changeset-fixed, check-changeset-claims, check-pending-changeset-literals, check-handler-key-read-sites, check-component-surface-parity, check-test-path-roots, check-unreferenced-sources.
  • Governed-surface guard --test on the 10 paths: NOT GOVERNED.
  • A control-byte grep of the changed files found no hits.
  • Lint, narrowed, measured per file rather than run repo-wide. The repo-wide pnpm lint belongs to CI. The evidence has three parts:
    1. The population is the 9 changed TS files, linted under the root eslint.config.js. No package has its own config.
    2. --format json reports 9 files, 0 errors.
    3. The config enables no type-aware linting (no parserOptions.project / projectService), and no rule in eslint-rules/ reads another file, so this diff cannot move a verdict on an untouched file.
    • Warning counts are identical base vs head for each of the 7 pre-existing files. The 2 new test files have 0 warnings.
  • NOT MEASURED: check-sdui-registration-pins. Its prerequisite is a console bundle (exit 2, prerequisite not met), and no registration changed.
  • NOT MEASURED: e2e, and the full pnpm test / pnpm lint. Those runs belong to CI.

Acceptance notes

  • Release text. Three pending changesets say something this change makes false for the release. They are not edited here: check-changeset-overwrite guards pre-existing entries, and the repo's precedent is a separate dated-note PR. This PR's own changeset names all three.
    • .changeset/10859-pivot-object-block-zod-arms.md: "drillDown is the shared DrillDownConfigSchema".
    • The dated note in .changeset/7352-drill-down-config-mirror.md: "its drillDown is this entry's DrillDownConfigSchema, so the opening paragraph's two referencing declarations are three for the release".
    • The dated note in .changeset/10685-drilldown-per-block.md: "a validator does read the members of a pivot node's drillDown, and accepts mode there".
  • Stale prose, not touched. The dataProvider tombstone docblock in ObjectPivotTable.tsx and the header of widgetDataProviderRetired-7353.test.tsx still say PivotTableSchema has no zod mirror. That has been false since objectui#10859, and it concerns a different member.
  • main moved 3 commits past the base (328abeb55), and none touches these files. PR fix(types): content-channel refusals name the parser tier's not-a-container warning (objectui#10928) #10956 (objectui#10928), the serial neighbour, was already in the base.

Generated by Claude Code

…(objectui#10932)

`drillDown` on a `pivot` node validated and did nothing: `PivotTable`
drilled only for a host that passed `onDrillDown`, and the `pivot`
registration passes none. Retire it on both faces, naming `object-pivot`:

- `PivotTableSchema.drillDown` is a `?: never` tombstone.
- The zod `pivot` arm refuses it via `retirementTombstone()`.
- `PivotTable` reads nothing off the node for its drill: the host's
  `onDrillDown` is the only switch.
- `ObjectPivotTable` keeps its own `drillDown` (`ObjectPivotDrillDownConfig`),
  intersected with the pivot schema minus the tombstone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…oth faces (objectui#10932)

- types: the zod arm refuses `drillDown` at its path (`invalid_type`,
  message naming `object-pivot`, one string on both channels), on the arm,
  the rendering face and the strict face; a `@ts-expect-error` and an
  `Equal` row pin the `?: never` tombstone. Controls: the node without it
  parses on all three.
- The objectui#10859 pins that asserted the old accept now assert the
  refusal.
- plugin-dashboard: the node's `drillDown` can neither turn `PivotTable`'s
  drill on nor off (the host handler is the switch), and a `pivot` node
  rendered through `SchemaRenderer` draws no drill affordance.
- Changeset: `@object-ui/types` and `@object-ui/plugin-dashboard` minor,
  breaking behaviour stated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…l three superseded entries (objectui#10932)

- The SchemaRenderer probe types its fixtures instead of casting to `any`:
  the document carries the retired key, the host-handler control renders
  the plain pivot.
- The changeset names every pending entry whose sentence about the `pivot`
  arm's `drillDown` this change supersedes (objectui#10859, and the dated
  notes on the objectui#7352 and objectui#10685 entries). Those entries
  are not edited here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 14 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5926-empty-action-visible-when.md

  • names data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    emptyAction was the one authored-node exception in the tree. The empty-state CTA slot resolved the registry directly — ComponentRegistry.get(node.type) — and mounted the result itself, so the node never passed through SchemaRenderer and its visibleWhen was never evaluated. @objectstack/spec accepts the key (SchemaNodeSchema carries visibleWhen, and data-display.zod.ts types emptyAction as a SchemaNode), so an author wrote a gate, the platform took it, and nothing enforced it — declared-not-enforced, the same class c86185eb5 closed for record:alert, one level down.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

.changeset/6881-retire-data-table-toolbar.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    The hand-written zod mirror in zod/data-display.zod.ts declared rowActions: z.array(z.any()).optional(). Every other face of the same key says boolean: the TS declaration it mirrors (rowActions?: boolean), the renderer's destructuring default (rowActions = false), its two truthiness gates and two colSpan arithmetic sites, the registered authoring input ({ type: 'boolean', label: 'Show Row Actions' }), defaultProps: { rowActions: true }, and the renderer's own docblock example, which authors "rowActions": true. The mirror was the single outlier — and the published one, so safeValidateSchema refused the exact spelling the component's documentation, defaults and authoring UI all teach. Two shipped examples/schema-catalog entries (user-table.json, full-featured-table.json) failed validation for this and no other reason; both now validate unchanged.

.changeset/6951-tree-view-data-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

  • names data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

.changeset/6972-markdown-inert-keys-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured, on this branch's base. sanitize was declared ?: boolean with @default true on both published faces — data-display.ts and the Zod mirror — documented, and read by NOTHING. Worse than an ordinary inert key, it implied a switch that does not exist: sanitization is unconditional. rehypePlugins in plugin-markdown/src/MarkdownImpl.tsx is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema], handed to ReactMarkdown as-is — no ternary, no if, no runtime assembly. MarkdownRenderer forwards exactly content and className, and MarkdownImplProps accepts only those two. A repo-wide grep for schema.sanitize over packages/ and apps/ returns nothing, against a control of 20 .tsx files reading schema.content in the same query shape, so the zero is a reading, not a blind query. An author writing sanitize: false believed they turned XSS filtering off; one writing sanitize: true believed they turned it on. Neither was true.

.changeset/7125-dashboard-empty-state-keys-retired.md

  • names PivotTable.tsx → packages/plugin-dashboard/src/PivotTable.tsx — edited by this change

    Not touched: table.noRows ('No rows to display') and engine.form.noRows (packages/app-shell/src/views/metadata-admin/i18n.ts, read at widgets.tsx) — two different, same-named keys in different namespaces. Nor the comments in WidgetEmptyState.tsx, DatasetWidget.tsx, ObjectDataTable.tsx and PivotTable.tsx that record WHY three widgets with three strings became one shared empty state; the packs' own comment keeps that rationale and now names the retirement instead of a row that is gone.

.changeset/7352-drill-down-config-mirror.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7694-chart-series-chart-type-alias-refusal.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    • Before: series: [{ name: 'revenue', chartType: 'line' }] validated green through @object-ui/types/zod (safeValidateSchema, objectui check / objectui validate, any pipeline that keeps parse()'s output) — and the key was gone from the output, so a consumer of the parse result drew that series in the chart's own family, precisely what the author was overriding. On the TypeScript face the key was merely an excess property on a fresh literal; a widened object carrying it assigned structurally. - After: the same document REFUSES at series[i].chartType (issue code invalid_type) with one message on both channels — the parse-time issue and the .describe() metadata: Unrecognized key(s) on this chart series: \chartType`. Did you mean `chartType` → `type`? …followed by the reason and the remedy. Writetype: 'bar' | 'line' | 'area'. On the TypeScript face ChartDataSeries.chartTypeis a?: nevertombstone, so both the fresh literal and the widened assignment aretsc errors. - **Both written** ({ type: 'bar', chartType: 'line' }) is refused at chartTypealone — the key is not folded ontotypeand no precedence is minted between the two spellings. - **Which documents to scan.** The narrowing does not stop atChartDataSeriesSchema; it reaches every document through the parents that embed it — ChartSchema.series (zod/data-display.zod.ts, z.array(ChartDataSeriesSchema)) and, one level further out, ReportSectionSchema.chart (zod/reports.zod.ts, ChartSchema.optional()). Authors meet it through safeValidateSchema() (zod/index.zod.ts, which parses AnyComponentSchema) and through the CLI's objectui validate command (packages/cli/src/cli.ts). In practice: every chartnode'sseries[], and every report section whose chart` carries one.

.changeset/7722-wrapper-class-five-more.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/8331-data-table-empty-action-primitive-node.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

  • names packages/types/src/zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

.changeset/8478-describe-line-addresses.md

.changeset/9256-list-timeline-content-channels.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    These two were held out of the previous family-D slice for a SERIAL constraint on packages/types/src/data-display.ts and never for a verdict. Readership was re-derived for both rather than inherited: a TypeScript compiler-API sweep files every .body / .children read under the declared type of its receiver and answers zero for ListSchema and TimelineSchema while its live controls fire. timeline's bare-key owner is any-typed, so it was attributed directly as well — packages/plugin-timeline contains no channel read of any kind.

.changeset/table-renderer-declared-column-contract-5350.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    renderers/complex/table.tsx resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name]. Neither label nor name is declared on TableColumn, which declares header and accessorKey — both required (packages/types/src/data-display.ts). This was the fourth site of the column-alias family, after data-table, ObjectDataTable and ObjectGrid (objectui#5350).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 328abeb55 (merge-base with origin/main): 9 file(s) changed outside .changeset/, read against 1690 pending declaration(s) that publish a body (2290 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.3 KB 3104.5 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-CdJ3p3sm.js —
Status PASS —

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.56KB 133.87KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.08KB 46.94KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.15KB 22.35KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fd2befe42c1874095b47954e3a9bb2581b356b75
Local-runs: none

Inputs: card objectui#10932 (body, triage 5868092364, claim 5868869683, os-dev-report 5869510338, claim addendum 5869553261), PR #10972 (body, 10-file list, net diff of claude/issue-10932-retire-pivot-drilldown against main at 328abeb55), the head's check-runs, and read-only git show / git grep on the head to check the dev's claims against code. Nothing built, run or re-run.

① Derived judgments

Accept-set changes, the bare pivot node on both published faces:

  1. Zod pivot arm: drillDown goes from DrillDownConfigSchema.optional() to retirementTombstone(PIVOT_DRILL_DOWN_RETIRED). Verified on the head: retirementTombstone(guidance) is z.never({ error: guidance }).optional().describe(guidance), so the refusal is invalid_type against never at path ['drillDown'], whole-key, and the parse message is the same string as the .describe() text. The key stays declared, which is load-bearing because BaseSchema is .passthrough() and a deleted key would ride through unjudged. Same shape as the body / children tombstones already on this arm, and the guidance string follows the RETIRED (objectui#NNNN, ADR-0049) spelling the other zod modules use. Premise verified on the head: the pivot registration is bare PivotTable, console's lazy stubs load that same registration, and onDrillDown has exactly one producer, ObjectPivotTable. On the objectstack side the spec declares no pivot-side drillDown (the only drillDown in component.zod.ts is on object-metric), sdui.manifest.json carries drillDown on object-metric and object-chart only, the showcase chart-gallery pivot is dataset-bound, and WIDGET_DRILL_NEAR_KEYS already refuses drillDown as a dashboard widget key. RIGHT — ADR-0049 remove arm, option (b) as graded in 5868092364.
  2. TypeScript PivotTableSchema.drillDown goes from DrillDownConfig to ?: never. This package's tombstone convention (DataTableSchema.toolbar?: never, verified on the head), not a deletion, since the index signature would readmit the key as any. RIGHT.
  3. PIVOT_NEITHER_CHANNEL no longer names drillDown among what a pivot renders. Prose of an existing refusal; the body / children accept set does not move. RIGHT, and pinned with a lit control (columnColors still named).
  4. object-pivot accept set: unchanged. ObjectPivotTableProps.schema keeps drillDown?: ObjectPivotDrillDownConfig; its base is now PivotTableSchema with drillDown remapped away. Key remapping over Omit is correct: under BaseSchema's string index signature keyof PivotTableSchema is string | number, so Omit would keep the index signature and drop every declared member, and intersecting the tombstone with the block's own member would collapse it to never. object-pivot has no zod arm (no z.literal('object-pivot') in the mirrors), so no zod accept set moves there. RIGHT.

Public-surface changes:

  1. PivotTable, exported from @object-ui/plugin-dashboard: drillEnabled goes from isDrillEnabled(schema.drillDown) && typeof onDrillDown === 'function' to typeof onDrillDown === 'function'. Props type unchanged; no new prop, no new export. Published behaviour changes for one existing input combination: a React caller passing a handler without an enabled schema.drillDown used to get no drill and now gets one; the changeset states it. Route (ii), the host's handler as the only switch, is RIGHT over a drillDown React prop: SchemaRenderer hands a node's keys to the component as props (its own comments record a node reaching createElement carrying props it never declared), so a prop of that name would reopen the channel the tombstone closes. Effect on object-pivot: none. Verified on the head, ObjectPivotTable computes handleDrillDown = isDrillEnabled(drillDown) ? fn : undefined, passes it as onDrillDown, and now strips its own drillDown before building finalSchema. Effect on DashboardGridLayout's static-data pivot: none. It emits { type: 'pivot', ...options, data } with no handler, so it never drilled and still never drills; the options.drillDown ?? { enabled: true, mode: 'record' } defaults in both dashboard renderers sit on the object-data-table and chart branches, not the pivot branch. RIGHT.
  2. @object-ui/core: PivotTable no longer imports isDrillEnabled; the export stays and is still read by ObjectPivotTable, ObjectChart, ObjectDataTable and ObjectMetricWidget. No surface change. RIGHT.
  3. Registry and manifest: the pivot registration's inputs never advertised drillDown, so check-component-surface-parity and objectstack's sdui.manifest.json have nothing to move, and no .objectui-sha bump is owed by this change. RIGHT.

Pins: adequate. The types pin covers the arm, safeValidateSchema and StrictAnyComponentSchema, the message naming object-pivot and equalling .describe(), declared-not-deleted, an accept control, and the content-channel prose; the objectui#10859 batch-2 fixture and path pin move to the whole-key refusal at ['drillDown']; PivotTable.drill.test.tsx pins both directions (the node key can neither turn the drill on nor turn it off); the new SchemaRenderer probe records the runtime premise through the real registry with a lit control, green on base by design and shown non-vacuous by the dev's forced-switch ablation. Docblocks updated where they described the old accept: DrillDownConfigSchema, the zod PivotTableSchema, the TS ObjectPivotDrillDownConfig. Note, not a finding: the TS DrillDownConfig docblock still says "shared by pivot tables and charts" and lists a pivot event payload; that stays true of object-pivot, which extends it.

② Semver level

.changeset/10932-pivot-drilldown-retired.md declares @object-ui/types: minor and @object-ui/plugin-dashboard: minor. Both packages have src/ edits, so check-changeset-presence owes a declaration and one exists; both are in the fixed group. The change is breaking for authored metadata (a key an author could write is now refused) and changes a published React component's behaviour; objectui's version policy (AGENTS.md section 9) grades a breaking change minor, never major, and Changeset Bump Policy is green. minor on both is RIGHT: patch would understate the PivotTable behaviour change, and declaring types alone would leave the plugin's published behaviour change out of its CHANGELOG. The migration is stated FROM to TO in the body: delete the key, or author an object-pivot (objectName plus the same rowField / columnField / valueField) and put drillDown there, which is the sentence an upgrading agent greps after the tombstone. The body names the three pending entries it supersedes; their dated-note repair is routed separately (③).

Clause-②: no (PR body opening line, claim 5868869683) is RIGHT: the diff widens no accept set and adds no public surface. The zod arm and the TS type narrow, ObjectPivotTableProps keeps its key set, and PivotTableProps is unchanged. The line carries no (narrowing) arm; a bare no is a well-formed spelling of the closed pair, and the changeset's first line says "Breaking", so the two declarations agree.

③ Boundary flags

open_questions: none declared by the dev; none found.

Deviations (os-dev-report 5869510338), each answered:

  • (1) File surface exceeded by PivotTable.tsx, ObjectPivotTable.tsx and two plugin-dashboard tests. Forced by the ruled TS tombstone: ObjectPivotTable typed its schema as PivotTableSchema and reads drillDown?.target, columns, maxRows and report, which cannot compile against never, and PivotTable read schema.drillDown and had to stop. The seat's addendum 5869553261 amends the claim to this surface. Accepted.
  • (2) Second changeset package @object-ui/plugin-dashboard: minor. Answered in ②: correct.
  • (3) The dispatch's suggested zod control ("object-pivot with drillDown still parses") does not apply because object-pivot has no zod arm. Verified. The stand-ins the dev names, the TS live case in ObjectPivotTable.drillDownRefusal-10685.test.tsx and the CONTROL case in drillRefusal-10789.test.tsx, exist on the head. Accepted.

out_of_scope_findings, routing judged:

  • (a) Three pending changesets (10859-pivot-object-block-zod-arms, the dated notes on 7352-drill-down-config-mirror and 10685-drilldown-per-block) say the pivot arm's drillDown is the shared DrillDownConfigSchema, which is false for the release once this lands. The seat filed it as objectui#10974 (verified: open, finding, ungraded, serial after this PR and before the release PR). Routing RIGHT: check-changeset-overwrite guards pre-existing entries, the repo's route is a dated-note PR, and this PR's own changeset names the supersession. Not a blocker for this PR. The card itself escalates its one open question, which seat holds the pending-changeset append write, to the maintainer; nothing for this record to add.
  • (b) Stale prose in ObjectPivotTable.tsx's dataProvider tombstone docblock and the widgetDataProviderRetired-7353.test.tsx header ("neither object-pivot nor PivotTableSchema has a zod mirror", false since objectui#10859). Acceptance-note route RIGHT under Prime Directive [WIP] Enhance every detail of the designer #10: not a defect, a contract violation or an authoring trap, and a different member. Nit only: the sentence sits in a file this PR edits and could have gone in the same edit.

Gates: 43 check-runs on the head, 40 success, 3 skipped (the two coverage matrix rows and dependabot, skipped by design), 0 failures, 0 in progress, including Type Check, Lint, Test (shard 1/8) through Test (shard 8/8), Test (dist pins), Build & E2E, Changeset Bump Policy, Changeset Declaration, Changeset Overwrite Report, Governed Surface Queue Guard and Spec Main Shape Gate. None of the 10 paths is a governed surface. Same-repo branch, draft, no auto-merge armed; main is three commits past the fork point and none touches these files. Check-runs re-read at 2026-09-28T12:22Z, before this record was posted.

Implemented-by: claude/issue-10932-retire-pivot-drilldown
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 12:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit cc4e476 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10932-retire-pivot-drilldown branch September 28, 2026 12:41
huangyiirene pushed a commit that referenced this pull request Sep 29, 2026
…drillDown that PR #10972 made false (objectui#10974)

PR objectui#10972 (objectui#10932) retired `drillDown` on the bare `pivot`
node on both faces in this same release: `PivotTableSchema.drillDown` is a
`?: never` tombstone and the zod `pivot` arm declares it as a
`retirementTombstone()`. Three pending entries still read it as the shared
`DrillDownConfigSchema`, so each gets a dated 2026-09-29 supersession note,
appended at the end of the file:

- 10859-pivot-object-block-zod-arms.md
- 7352-drill-down-config-mirror.md (its 2026-09-28 note)
- 10685-drilldown-per-block.md (its 2026-09-28 note)

Append only: frontmatter byte-identical, no existing line edited or
deleted, per the maintainer's 2026-09-29 ruling recorded on the card.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants