Skip to content

Commit cc4e476

Browse files
fix(types,plugin-dashboard): retire drillDown on the bare pivot node; object-pivot is where a pivot drills (objectui#10932) (#10972)
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 #10956 (objectui#10928), the serial neighbour, was already in the base. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fe56394 commit cc4e476

10 files changed

Lines changed: 382 additions & 47 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
'@object-ui/types': minor
3+
'@object-ui/plugin-dashboard': minor
4+
---
5+
6+
`drillDown` is retired on the bare `pivot` node: author `object-pivot` to drill (objectui#10932)
7+
8+
**Breaking for authored metadata, graded `minor` by this repo's convention (AGENTS.md: a
9+
breaking change here is `minor`, never `major`):** a `pivot` node no longer accepts
10+
`drillDown` on either published face. The TypeScript `PivotTableSchema.drillDown` is a
11+
`?: never` tombstone, and the `pivot` arm of the zod mirror, the door `objectui validate` /
12+
`safeValidateSchema` run, refuses the key by name with `invalid_type` at `drillDown`. The
13+
refusal names the remedy. Migration: delete the key, or, for a pivot whose cells open the
14+
records behind a value, author an `object-pivot` (`objectName` plus the same `rowField` /
15+
`columnField` / `valueField`) and put the `drillDown` there. Every other `pivot` member is
16+
accepted exactly as before.
17+
18+
Nothing ever honoured the key on this node. `PivotTable` fired a drill only when its host
19+
passed an `onDrillDown` handler. The one host that does is `ObjectPivotTable`, which renders
20+
`object-pivot`, and the `pivot` registration passes none. So an authored `drillDown`
21+
validated and clicking a cell did nothing, with no signal. A pivot over inline `data` also
22+
names no object to list the drilled records from. No shipped or example document authors
23+
the key on a `pivot` node.
24+
25+
This supersedes what three pending entries say about the `pivot` arm's `drillDown`: the
26+
objectui#10859 (batch 2) entry ("`drillDown` is the shared `DrillDownConfigSchema`"), and the
27+
dated notes on the objectui#7352 and objectui#10685 entries, which read that key as the shared
28+
mirror (a third referencing declaration, and `mode` accepted there). As of this change the
29+
arm refuses the key.
30+
31+
- `@object-ui/types`: `PivotTableSchema.drillDown` is `?: never`, and the zod `pivot` arm's
32+
`drillDown` is a `retirementTombstone()`. The content-channel refusal on `body` / `children`
33+
no longer lists `drillDown` among what a `pivot` renders.
34+
- `@object-ui/plugin-dashboard`: `PivotTable` reads nothing off `schema` for its drill.
35+
**Behaviour change for React callers:** its `onDrillDown` prop is now the only switch. A
36+
caller that passes a handler gets interactive cells, headers and totals, and a
37+
`schema.drillDown` no longer gates them (the type refuses that key anyway). A caller that
38+
wants no drill passes no handler. `ObjectPivotTable` already passed its handler only when
39+
its own `drillDown` was enabled, so `object-pivot` behaves exactly as before, and its
40+
`schema.drillDown` keeps the `ObjectPivotDrillDownConfig` type.

‎packages/plugin-dashboard/src/ObjectPivotTable.tsx‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,23 @@ import type { ObjectPivotDrillDownConfig, PivotTableSchema } from '@object-ui/ty
3838
*/
3939
const EMPTY_ROWS = Object.freeze([]) as unknown as any[];
4040

41+
/**
42+
* `PivotTableSchema` minus `drillDown`, which is a retirement tombstone on the
43+
* `pivot` node (objectui#10932). `object-pivot` declares its own `drillDown`
44+
* below, and intersecting it with the tombstone would collapse that member to
45+
* `never`: the one pivot that drills could not be authored with a drill.
46+
*
47+
* Key remapping, not `Omit`: `PivotTableSchema` extends `BaseSchema`, whose
48+
* `[key: string]: any` widens `keyof` to all of `string`, so
49+
* `Omit<PivotTableSchema, 'drillDown'>` would keep the index signature and drop
50+
* every declared member.
51+
*/
52+
type PivotTableSchemaWithoutDrillDown = {
53+
[K in keyof PivotTableSchema as K extends 'drillDown' ? never : K]: PivotTableSchema[K];
54+
};
55+
4156
export interface ObjectPivotTableProps {
42-
schema: PivotTableSchema & {
57+
schema: PivotTableSchemaWithoutDrillDown & {
4358
objectName?: string;
4459
/**
4560
* RETIRED (objectui#7353, ADR-0049 remove arm, ruling 5809008870) — write
@@ -96,11 +111,12 @@ export interface ObjectPivotTableProps {
96111
// sink, so the drill is correct for both arms whatever this key is typed.
97112
filter?: any;
98113
/**
99-
* This block's drill-down shape, not the shared `DrillDownConfig` that
100-
* `PivotTableSchema` carries: `mode` is refused by name
101-
* (`ObjectPivotDrillDownConfig`, objectui#10685, applying objectui#9002's
102-
* ruling B). Every click point on a pivot is an aggregated bucket, so it
103-
* always drills through; there is no row for `mode` to open as a record.
114+
* This block's drill-down shape, `ObjectPivotDrillDownConfig`: `mode` is
115+
* refused by name (objectui#10685, applying objectui#9002's ruling B).
116+
* Every click point on a pivot is an aggregated bucket, so it always drills
117+
* through; there is no row for `mode` to open as a record. `PivotTableSchema`
118+
* carries no drill at all: on a plain `pivot` node the key is a retirement
119+
* tombstone (objectui#10932), and this block is where a pivot drill lives.
104120
*/
105121
drillDown?: ObjectPivotDrillDownConfig;
106122
};
@@ -354,9 +370,12 @@ export const ObjectPivotTable: React.FC<ObjectPivotTableProps> = ({ schema, data
354370
);
355371
}
356372

357-
// Delegate to PivotTable with resolved data
373+
// Delegate to PivotTable with resolved data. `drillDown` is this block's and
374+
// stays behind: `PivotTable` drills through `onDrillDown` below, and on the
375+
// `pivot` node it renders the key is a retirement tombstone (objectui#10932).
376+
const { drillDown: _objectPivotDrillDown, ...pivotSchema } = schema;
358377
const finalSchema: PivotTableSchema = {
359-
...schema,
378+
...pivotSchema,
360379
data: finalData,
361380
};
362381

‎packages/plugin-dashboard/src/PivotTable.tsx‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import React, { useMemo } from 'react';
1010
import type { PivotTableSchema, PivotAggregation } from '@object-ui/types';
1111
import { cn } from '@object-ui/components';
12-
import { isDrillEnabled, type DrillEvent } from '@object-ui/core';
12+
import type { DrillEvent } from '@object-ui/core';
1313
import { useSafeTranslate } from '@object-ui/i18n';
1414
import { WidgetEmptyState } from './WidgetEmptyState';
1515

@@ -41,9 +41,17 @@ export interface PivotTableProps {
4141
*/
4242
sourceLabel?: string;
4343
/**
44-
* Drill-down click handler. When provided **and** `schema.drillDown` is
45-
* enabled, cells / row & column headers / totals become interactive.
46-
* Receives the click context which is forwarded to the drill-down engine.
44+
* Drill-down click handler, and the ONLY drill switch. When provided, cells /
45+
* row & column headers / totals become interactive and each click hands its
46+
* context here; the host decides what a drill opens. `ObjectPivotTable`
47+
* passes it exactly when its `object-pivot` `drillDown` is enabled.
48+
*
49+
* Nothing is read off `schema` for this: `drillDown` on a `pivot` node is a
50+
* retirement tombstone (objectui#10932), since this component used to drill
51+
* only for a host that passed this handler and the `pivot` registration
52+
* passes none. Not a `drillDown` prop either: `SchemaRenderer` spreads a
53+
* node's keys as props, so a prop of that name would hand an authored
54+
* `drillDown` straight back to this component.
4755
*/
4856
onDrillDown?: (event: DrillEvent) => void;
4957
}
@@ -179,11 +187,11 @@ export const PivotTable: React.FC<PivotTableProps> = ({ schema, className, rowLa
179187
showColumnTotals = false,
180188
format,
181189
columnColors,
182-
drillDown,
183190
} = schema;
184191
const totalLabel = useTotalLabel();
185192

186-
const drillEnabled = isDrillEnabled(drillDown) && typeof onDrillDown === 'function';
193+
// The host's handler is the switch (see `onDrillDown`); nothing on the node is.
194+
const drillEnabled = typeof onDrillDown === 'function';
187195
const fireDrill = (ev: DrillEvent) => {
188196
if (!drillEnabled) return;
189197
onDrillDown!({

‎packages/plugin-dashboard/src/__tests__/PivotTable.drill.test.tsx‎

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,34 @@ const baseSchema: any = {
2626
};
2727

2828
describe('PivotTable drill-down', () => {
29-
it('does not render interactive cells when drillDown is omitted', () => {
29+
it('does not render interactive cells without a host `onDrillDown`', () => {
3030
render(<PivotTable schema={baseSchema} />);
3131
// No element with role="button"
3232
expect(screen.queryAllByRole('button').length).toBe(0);
3333
});
3434

35-
it('does not call onDrillDown when drillDown.enabled !== true', () => {
36-
const onDrillDown = vi.fn();
37-
render(<PivotTable schema={baseSchema} onDrillDown={onDrillDown} />);
35+
// objectui#10932: `drillDown` on a `pivot` node is a retirement tombstone,
36+
// and `PivotTable` reads nothing off the node for its drill. The host's
37+
// `onDrillDown` is the only switch (`ObjectPivotTable` passes it exactly when
38+
// its `object-pivot` drill is enabled). Both directions are pinned: the node
39+
// key can neither turn the drill on nor turn it off.
40+
it('the node\'s `drillDown` cannot turn the drill on: no handler, no interactive cells (objectui#10932)', () => {
41+
render(<PivotTable schema={{ ...baseSchema, drillDown: { enabled: true } }} />);
3842
expect(screen.queryAllByRole('button').length).toBe(0);
39-
expect(onDrillDown).not.toHaveBeenCalled();
43+
});
44+
45+
it('the node\'s `drillDown` cannot turn the drill off: the host handler is the switch (objectui#10932)', () => {
46+
const onDrillDown = vi.fn();
47+
render(<PivotTable schema={{ ...baseSchema, drillDown: { enabled: false } }} onDrillDown={onDrillDown} />);
48+
fireEvent.click(screen.getByLabelText('Drill into stage=won, source=web'));
49+
expect(onDrillDown).toHaveBeenCalledTimes(1);
4050
});
4151

4252
it('emits cell payload with rowKey/colKey/value/scope on click', () => {
4353
const onDrillDown = vi.fn();
4454
render(
4555
<PivotTable
46-
schema={{ ...baseSchema, drillDown: { enabled: true } }}
56+
schema={baseSchema}
4757
onDrillDown={onDrillDown}
4858
/>,
4959
);
@@ -62,7 +72,7 @@ describe('PivotTable drill-down', () => {
6272
const onDrillDown = vi.fn();
6373
render(
6474
<PivotTable
65-
schema={{ ...baseSchema, drillDown: { enabled: true } }}
75+
schema={baseSchema}
6676
onDrillDown={onDrillDown}
6777
/>,
6878
);
@@ -74,7 +84,7 @@ describe('PivotTable drill-down', () => {
7484
const onDrillDown = vi.fn();
7585
render(
7686
<PivotTable
77-
schema={{ ...baseSchema, drillDown: { enabled: true } }}
87+
schema={baseSchema}
7888
onDrillDown={onDrillDown}
7989
/>,
8090
);
@@ -86,7 +96,7 @@ describe('PivotTable drill-down', () => {
8696
const onDrillDown = vi.fn();
8797
render(
8898
<PivotTable
89-
schema={{ ...baseSchema, drillDown: { enabled: true } }}
99+
schema={baseSchema}
90100
onDrillDown={onDrillDown}
91101
/>,
92102
);
@@ -100,7 +110,7 @@ describe('PivotTable drill-down', () => {
100110
const onDrillDown = vi.fn();
101111
render(
102112
<PivotTable
103-
schema={{ ...baseSchema, drillDown: { enabled: true } }}
113+
schema={baseSchema}
104114
rowLabels={{ won: 'Won', lost: 'Lost' }}
105115
columnLabels={{ web: 'Web', event: 'Event' }}
106116
onDrillDown={onDrillDown}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* A `pivot` NODE drills nowhere, whatever `drillDown` it carries
11+
* (objectui#10932): the runtime half of the retirement, read through the real
12+
* `SchemaRenderer` and the real `pivot` registration rather than by calling
13+
* `PivotTable` directly.
14+
*
15+
* The route matters. `SchemaRenderer` spreads a node's keys as React props, so
16+
* a component can consume a key it never reads off `schema`. This render is
17+
* what an author's document goes through, so it sees both channels: `schema`
18+
* and the spread. `drillDown` on a `pivot` node is refused at validation (the
19+
* `@object-ui/types` pin is `pivot-drilldown-retired-10932.test.ts`); this
20+
* file pins that a document which reaches the renderer anyway draws a plain
21+
* cross-tab with no drill affordance, rather than a clickable cell that does
22+
* nothing.
23+
*/
24+
25+
import React from 'react';
26+
import { afterEach, describe, expect, it, vi } from 'vitest';
27+
import { cleanup, render, screen } from '@testing-library/react';
28+
import { SchemaRenderer } from '@object-ui/react';
29+
// Registers `pivot` → `PivotTable`, the registration every `pivot` path uses.
30+
import '../index';
31+
import { PivotTable } from '../PivotTable';
32+
33+
afterEach(cleanup);
34+
35+
const PIVOT = {
36+
type: 'pivot' as const,
37+
rowField: 'stage',
38+
columnField: 'source',
39+
valueField: 'amount',
40+
data: [
41+
{ stage: 'won', source: 'web', amount: 100 },
42+
{ stage: 'lost', source: 'event', amount: 25 },
43+
],
44+
};
45+
46+
/** What an author's document carries: the retired key, reaching the renderer anyway. */
47+
const NODE = { ...PIVOT, drillDown: { enabled: true } };
48+
49+
describe('a `pivot` node authored with `drillDown` draws no drill affordance (objectui#10932)', () => {
50+
it('renders the cross-tab through SchemaRenderer, with no interactive cell or header', () => {
51+
render(<SchemaRenderer schema={NODE} />);
52+
// Lit control: the pivot itself rendered, cells and headers included.
53+
expect(screen.getByText('won')).toBeTruthy();
54+
expect(screen.getByText('100')).toBeTruthy();
55+
expect(screen.queryAllByRole('button')).toHaveLength(0);
56+
expect(screen.queryByLabelText('Drill into stage=won, source=web')).toBeNull();
57+
});
58+
59+
it('CONTROL — the query can see a drill affordance when a host passes `onDrillDown`', () => {
60+
render(<PivotTable schema={PIVOT} onDrillDown={vi.fn()} />);
61+
expect(screen.getByLabelText('Drill into stage=won, source=web')).toBeTruthy();
62+
expect(screen.queryAllByRole('button').length).toBeGreaterThan(0);
63+
});
64+
});

‎packages/types/src/__tests__/drill-down-config-mirror-7352.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
* ledger side (both `UnmirroredDeclared` rows gone, the new pair registered)
2525
* is pinned by the parity file's own ratchet.
2626
*
27-
* ⚠️ `PivotTableSchema.drillDown` is NOT covered here: that declaration has no
28-
* zod mirror at all, so it sits in no ledger. The mirror minted here is the home
29-
* that key will use whenever the pivot pair is mirrored (a separate card).
27+
* ⚠️ `PivotTableSchema.drillDown` is NOT covered here. The pivot pair gained a
28+
* zod mirror in objectui#10859 (batch 2), which judged that key by the mirror
29+
* minted here, and objectui#10932 then retired it on the `pivot` node, which
30+
* nothing drills: `pivot-drilldown-retired-10932.test.ts` pins the refusal.
3031
*
3132
* ⚠️ Not the spec's `ChartDrillDownSchema`, deliberately: `@objectstack/spec/ui`
3233
* models the CHART-ONLY subset (`enabled` / `filter` / `title` / `target` /

0 commit comments

Comments
 (0)