Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit 5262f7d

Browse files
feat(types)!: an authored object-chart takes its props in the properties bag; the flat spelling is refused by name (objectui#11276) (#11328)
Part of #11276 Clause-②: yes (narrowing) `Clause-②: yes (narrowing)`, copied from the PM claim `5924110462`: `@object-ui/types`' authoring faces widen (a bag `object-chart` node that is refused today parses) and narrow (a flat `object-chart` node that parses today is refused). This is objectui's narrowing-as-`minor` rule, with a **BREAKING (authoring)** banner in the changeset. The `object-chart` batch of objectui#11276. It executes triage's routing call A (`5921136804`) as released by the retriage answer (`5922517855`, Q2: "the next claim's scope is `object-chart` only"), with the construct of objectui#10859 batches 4 and 5 (PRs objectui#11291 and objectui#11309). The card stays open: `object-grid` is the next batch, and `flex` is with the maintainer as objectui#11300. ## What changed - `packages/types/src/zod/objectql.zod.ts`: new `ObjectChartBlockSchema`, the batch 4/5 construct: `BaseSchema` + the `object-chart` literal + `NODE_ENVELOPE` + `properties` through the shared `propsBag` helper, beside: - one `aliasKeyRefusal` per bag member, keyed by the bag's own key set (read off the bag, not transcribed), so each prop written flat is refused on both faces with `Did you mean KEY → properties.KEY?` and one prescription that shows the whole bag document; - the three list-view spellings objectui#10608 retired (`xAxisField`, `yAxisFields`, `aggregation`) keep the flat mirror's own retirement tombstones when written flat (the same objects), so their remedy stays the spec spelling rather than a pointer at a bag member that is itself retired; - `body` / `children`: the flat mirror's objectui#9256 refusals, by reference; - the chart-family floor (objectui#10770), read in the bag: `requireObjectChartFamilyInBag` refuses a node naming neither `properties.chartType` nor `properties.specType`, at `properties.chartType`. - **⛔ No spec row, and none invented.** Installed `@objectstack/spec` 17.5.0's `ComponentPropsMap` has no `object-chart` row, so the spec's page component takes ANY bag on this type. The bag (`ObjectChartPropsBag`) is therefore the flat `ObjectChartSchema` mirror's OWN members by reference: its shape less the node-level keys (`BaseSchema`'s and `NODE_ENVELOPE`'s, both read off their declarations), each the same schema object the mirror holds. It is built from the mirror's shape, not with `.omit()` on the mirror, because zod refuses `.pick()` / `.omit()` on an object carrying a refinement. The bag keeps the mirror's posture (`.passthrough()`), so an undeclared bag key is judged exactly as it was on the flat node: unjudged by the tolerant face, refused by name by the strict face. The code docblock and the bag's `.describe()` say this. - `packages/types/src/zod/public-blocks.zod.ts`: `propsBag` gains an optional `description` argument for the one bag that is not a spec row, so the arm does not describe itself as `ComponentPropsMap['object-chart']`. Every other caller omits it and its description is byte-identical. - The arm joins `ObjectQLPublicBlockComponentSchema` (`object-chart` is in `PUBLIC_BLOCKS`). `ObjectChartSchema` (the flat mirror) leaves `ObjectQLComponentSchema` (10 to 9 arms), stays exported, and stays paired with its TypeScript twin in the parity census. - `packages/types/src/objectql.ts`: the TypeScript `ObjectChartSchema` is **re-declared, not retired**. Members unchanged; its docblock now says it is the node as `ObjectChart` reads it after `SchemaRenderer` hoists `properties`, and as code composes it. The TypeScript union's docblock names the zod union's new count. - `packages/types/src/zod/index.zod.ts` (one barrel export) and `packages/types/src/zod/README.md` (the ObjectQL public-block group lists the arm, 6). - New pin `packages/types/src/__tests__/object-chart-properties-bag-11276.test.ts` (58 rows plus a type-level block). - One changeset, `.changeset/11276-object-chart-properties-bag.md`: `@object-ui/types` minor, BREAKING (authoring) banner. No other released package's source changes, so there is no second changeset. **Files beyond the claimed surface, each forced:** - census rows the new export forces: `zod-mirror-parity.test.ts` (one `EXCLUSIONS` row; `EXPECTED_MIRROR_PAIRS` untouched), `objectql-union-arms-7363.test.ts` (9 arms, `object-chart` not among them), `registered-type-arms-10859-b2.test.ts` (the exact public-block literal list gains `object-chart`), `public-block-responsive-styles-10872.test.ts` (`object-chart` gets the smallest family bag in `RECORD_SOURCE`, because the arm keeps the floor), `flat-arm-responsive-styles-10872.test.ts` (the batch-9 flat population is `flex` and `object-grid`; one row pins that the flat mirror keeps the shared member as the post-hoist reading); - fixture triage where a face now judges the bag (mirror rows keep the post-hoist spelling; only door rows move): `content-channel-e3-residual-9256.test.ts` (`AUTHORED_REQUIRED` gains the chart), `object-chart-axis-config-10518.test.ts` (5 door rows and the showcase lit control write the bag), `object-chart-legacy-axis-keys-retired-10608.test.ts` (the door refuses the retired key in the bag AND written flat; the remedy rows write the bag), `object-chart-react-tier-node-10770.test.ts` (the door row reads the wrapper's props in the bag), `strict-face-read-keys-11070.test.ts` (the `dataSource` row writes its chart props in the bag; the row's verdict is unchanged). ## Zone 1, measured before building 1. **The reason holds for this type.** Installed `@objectstack/spec` 17.5.0: `ComponentPropsMap` has no own `object-chart` property (controls: `object-map` and `object-grid` have one). `PageComponentSchema` refuses the flat node `{ type: 'object-chart', chartType, objectName, aggregate }` with `unrecognized_keys` naming the three ("Unrecognized key(s) on this page component … ADR-0089 D3a") and accepts the same props in `properties`. Control: a flat `action:button` `actionType` is refused the same way. 2. **Census, with the stop line.** At base `0c6f9bbd7`, `git grep -l object-chart` gives 57 non-test files: 16 `CHANGELOG.md` and 16 pending changesets (records, not producers) and 25 others. Seat 1's census (`5922247963`) re-verified on this base: | class | files | action | |:--|:--|:--| | authored page-node producers (any spelling) | **none** | nothing to rewrite | | runtime composers (build a flat node, render through `SchemaRenderer`, never meet a zod face) | app-shell `views/ObjectView.tsx` (2 sites), plugin-view `ObjectView.tsx` (2), plugin-list `ListView.tsx` (2), plugin-dashboard `DashboardGridLayout.tsx` (1) and `DashboardRenderer.tsx` (1); the react-page wrapper (`react-page.tsx`, a comment hit; it composes by tag) | kept flat (they read the TypeScript twin) | | the declarations | `objectql.ts`, `objectql.zod.ts`, `public-blocks.zod.ts` (comment + `propsBag`) | this change | | names the type, no node shape | `register-plugins.ts`, `known-schema-types.ts`, core `public-blocks.ts`, `chart-category-key.ts`, plugin-charts `ObjectChart.tsx` / `index.tsx` / `normalizeChartSchema.ts`, plugin-dashboard `ObjectMetricWidget.tsx` / `index.tsx`, sdui-parser `parse.ts`, types `data-display.ts`, `scripts/regenerate-known-schema-types.mjs`, `ROADMAP.md`, `content/docs/guide/data-source.md` (a table row) | none | | governed `skills/**` | `skills/objectui/guides/data-integration.md`, `skills/objectui/guides/schema-expressions.md`: each names `object-chart` in a prose list of `useDataScope` callers, no node | listed, not rewritten (no producer) | **Stop line: not tripped.** The zod `ObjectChartSchema` object has one reader in objectui source, the `ObjectQLComponentSchema` union (plus the barrel re-export); no schema is built from it (unlike `object-view`'s `table` slot from `ObjectGridSchema`, the shape the `object-grid` batch meets). Every non-page context that keeps the flat spelling depends on the TypeScript twin (the composers, `ObjectChartProps.schema`, `ObjectMetricWidget`'s aggregate type), which is unchanged. 3. **The arm**, as above. The TypeScript twin is re-declared, not retired; nothing had to retire, so the `needs_decision` stop did not fire. 4. **Producers:** zero objectui-authored flat `object-chart` page-node producers to rewrite; zero governed producers (two governed prose hits, listed above). ## Zone 2, re-measured 1. Held, by symbol: `ObjectChartSchema` is in `objectql.zod.ts`, its twin in `objectql.ts`, `ObjectMapBlockSchema` (batch 5) in `objectql.zod.ts`. The new arm sits right after the flat mirror it reads, which also keeps it out of PR objectui#11312's hunks. 2. Held: 25 non-test files outside the CHANGELOG and changeset records. 3. PR objectui#11312 (objectui#11117) and PR objectui#11320 merged into `main` at 2026-10-01T04:02Z, after this branch's commits and before this PR opened. Together with `be5211522`, `main` has moved by three commits since the base, and the last two touch `objectql.zod.ts`, `objectql.ts`, `public-blocks.zod.ts` and `zod-mirror-parity.test.ts`. Their hunks are disjoint from this diff's: GitHub reports the PR mergeable, and the merge queue re-tests the merged tree. ## Tests (heavy runs through `os-verify-lock.sh`, slot `issue-11276`) - **Red first** at the arm commit `a13229954` (before triage): `pnpm exec vitest run packages/types/src/` gave `Tests 29 failed | 7663 passed (7692)` in 10 files: exactly the census rows and door fixtures listed above. - **Green, union at the final head `03b6e0d98`:** `pnpm exec vitest run packages/types/ packages/cli/` gave `Test Files 330 passed (330)`, `Tests 8079 passed (8079)`, VERDICT command-exit 0. - **Consumer sweep at `f1cd6ab70`** (the head before the README + changeset commit): 234 tracked test files outside `packages/types` and `packages/cli` that name `object-chart`, `plugin-charts`, `ObjectChartSchema`, `ObjectChartBlockSchema`, `AnyComponentSchema`, `StrictAnyComponentSchema`, `safeValidateSchema`, `ObjectQLComponentSchema`, `ObjectQLPublicBlockComponentSchema`, `PUBLIC_BLOCKS`, `getPublicConfigs`, `ComponentPropsMap`, `KNOWN_SCHEMA_TYPES`, `nodeOracles` or the parity / strict-face / handler-key ledgers, in three chunks: `79 passed / 2499 tests`, `73 passed / 787 tests`, `82 passed / 1576 tests`, each VERDICT 0. The later commit changes only `README.md` and the changeset; the tests that read the zod README are in `packages/types` and `scripts/__tests__`, both re-run after it. - **`scripts/__tests__/`** (a markdown file changed) at `03b6e0d98`: `Test Files 177 passed | 2 skipped (179)`, `Tests 5371 passed | 2 skipped (5373)`, VERDICT 0. `node scripts/markdown-test-inputs.mjs --audit`: "107 candidate test files, all adjudicated; 78 declared entries, all present". - **Type-check:** `pnpm --filter @object-ui/types type-check` (`tsc --noEmit`, examples, `tsconfig.test.json`, which includes `src/**/*.test.ts`), VERDICT 0. `@object-ui/types` has no workspace dependency to build first. - **Runtime ablation** (objectstack `scripts/ablation-replace.mjs`, two nested WRAP legs plus an EXIT/INT/TERM trap, absolute paths): the flat arm restored, i.e. `ObjectChartSchema` back into `ObjectQLComponentSchema` (anchor x1 to x0, blob `ec019f61` to `0bc58a72`) and `ObjectChartBlockSchema` out of the public-block union (anchor x1 to x0, blob to `fb03dd31`); on disk, `ObjectChartSchema,` x1 in the ObjectQL union and `ObjectChartBlockSchema,` x0 in the public one. Over 14 files (the new pin, the 10 triaged and census files, and three untouched chart and bag pins as controls): `Tests 66 failed | 651 passed (717)`. Red: 43 rows of the new pin and 23 triaged or census rows (10608 12, 10518 3, 9256 2, 10872-batch-9 2, and one each in 10770, 7363, b2 and 11070), which is exactly the set this diff added or changed. Green: the untouched pins (`object-chart-undeclared-keys-8885`, `widget-schema-anchors-7946`, `object-map-properties-bag-10859-b5`), the census files whose subject is the export itself, and the pin's 15 rows that do not depend on which arm the union selects (the spec lit controls, the bag's key set and member identity, its description, and the refusals the flat mirror shares). Direction red, as expected. Restored: blob == HEAD `ec019f61`, `git diff HEAD` empty. Vitest imports the zod sources relatively, so no build sat between the mutation and the run. - **Type-level reverse validation** (same tool): the bag's `.omit(OBJECT_CHART_NODE_LEVEL_KEYS)` widened to also drop `dataset` made `tsc -p tsconfig.test.json` exit 2 with exactly one error, `TS2344: Type 'false' does not satisfy the constraint 'true'` on the pin's `keyof Bag` row. Restored, blob == HEAD. - **Render identity** (a one-time probe through the real `SchemaRenderer` and registry, not committed): the bag node `{ type: 'object-chart', properties: { objectName, chartType, aggregate, filter, title } }` makes the same data-source calls as the flat node with the same props and draws an identical DOM; control: a bag naming another object makes different calls. `Tests 1 passed (1)`. ## Gates (exit codes captured before any pipe) - exit 0: `node scripts/check-changeset-presence.mjs` ("16 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"), `changeset:check` ("No changeset declares a `major` bump."), `check:control-bytes` ("OK (scanned 9793 tracked text file(s); skipped 85 binary)"), `check:new-line-citations` ("VERDICT new-cross-file-line-citations: 0 new citation(s)"), `check:handler-key-reads` ("OK 140 arm(s), 212 registration(s) (138 keyed onto an arm), 56 reachable handler read(s), 56 judged"), `check:spec-symbols`, `check:element-data-source-declaration`, `check:component-surface-parity`, `check:doc-types`, `check:prompt-keys`, `check:test-path-roots`, `check:installed-pin-claims`, `check:vi-mock-specifiers`, `check:pending-changeset-literals`, `check:changeset-claims` (report-only), `docs:check-links`, `check:governed-queue-guard`, and `check-governed-queue-guard.mjs --test` over the 17 changed paths ("NOT GOVERNED"). - `pnpm exec eslint` on the 15 changed source and test files: 0 errors, 37 warnings, all `no-explicit-any` on lines this diff does not touch. - **NOT MEASURED** (PREREQUISITE NOT MET: the packages they import are not built in this worktree, so each refused to run, exit 2 or a collapsed population): `check:readme-exports`, `check:doc-snippets`, `check:doc-examples`, `check:skill-examples`, `check:sdui-registration-pins`. This diff changes no fenced block in any README, doc or skill. Left to CI, which builds first. - Not run locally: the repo-wide `pnpm lint` and the full `pnpm test` farm (CI's). ## Acceptance notes - **`data` stays a node-level `BaseSchema` key on this arm**, as on every arm. `ObjectChart` reads `schema.data` (inline rows) and the registration's `inputs` list `data`, but neither the mirror nor the TypeScript twin declares it as a chart member, so it is not in the bag: a flat `data` passes both objectui faces (the spec's page component refuses it at node level), and `properties.data` passes the tolerant face unjudged and is refused by the strict face. Whether `data` is a chart member is a declaration decision this card did not take. Noted, not filed; no carrier. - **`dataSource` stays undeclared on `object-chart`**, on the bag arm as on the flat mirror, as objectui#11070 left it ("refused by name until it is declared"). The registration is gate-wrapped and reads the binding. - **The mirror's INTERNAL members ride into the bag with it** (`xAxisKey`, the `{ dataKey }` series arm, `specType`): the bag is the mirror's members, so they are judged in the bag exactly as they were judged flat. No member's verdict changed; only its layer. - **The same release's pending changesets** `10518`, `10608`, `10770` and `10872-flat-arm-responsive-styles` describe validator readings on a flat `object-chart`; this PR's changeset says those readings now happen in the bag on an authored node and still hold on the flat mirror (the batch-5 precedent). They are not edited. - **No render pin is committed**: the renderer half is `SchemaRenderer`'s generic hoist, already pinned for the earlier bag arms; the one-time probe above measured it for this type. ## Overlap PR objectui#11312 (merged first) edited other regions of `objectql.zod.ts`, `objectql.ts`, `public-blocks.zod.ts` and `zod-mirror-parity.test.ts`. The hunks are disjoint, and this PR lands through the merge queue, which tests the merged tree. Session: `https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec` (the dispatching seat's id; this run is its subagent on branch `claude/issue-11276-object-chart-properties-bag`). --- _Generated by [Claude Code](https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 95bd23c commit 5262f7d

17 files changed

Lines changed: 781 additions & 50 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
feat(types): an authored `object-chart` takes its props in the `properties` bag; the flat spelling is refused by name (objectui#11276)
6+
7+
**BREAKING (authoring):** an `object-chart` node that writes its props on the node itself is now refused by `safeValidateSchema`, `validateSchema` and the strict authoring face, and so by `objectui validate`. Move each prop into the node's `properties` bag:
8+
9+
```json
10+
{ "type": "object-chart", "properties": { "chartType": "bar", "dataset": "task_metrics", "dimensions": ["status"], "values": ["task_count"] } }
11+
```
12+
13+
Nothing changes at render time: `SchemaRenderer` hoists every `properties` key onto the node before `ObjectChart` runs, so the bag node draws what the flat one drew, and a node built in code (`ObjectView`, `ListView`, the dashboard renderers and the react-page wrapper all build a flat `object-chart` node) keeps working with its flat keys.
14+
15+
**Clause-②: yes (narrowing)** — the accept set narrows (a flat `object-chart` node that parsed is refused) and widens (a bag node that was refused parses). Released as `minor` under the objectui narrowing rule, with this banner.
16+
17+
**Why.** `@objectstack/spec`'s strict `PageComponentSchema` refuses a prop written on any page component itself as mis-layered (ADR-0089 D3a): `properties` is the only home of a component's props. The arm here was the flat mirror of the TypeScript `ObjectChartSchema`, so the two validators disagreed both ways: the objectstack showcase's command-center charts, written in the bag, were refused (the flat mirror found no chart family on the node), and the flat node `os validate` refuses was accepted.
18+
19+
**What changed, in observable terms.**
20+
21+
- `@object-ui/types/zod` exports `ObjectChartBlockSchema`, built the way `ObjectFormBlockSchema` and `ObjectMapBlockSchema` are: `BaseSchema`, the `object-chart` literal, the node-level `responsiveStyles`, and `properties`. One difference: `@objectstack/spec` has no `ComponentPropsMap['object-chart']` row, so the spec's page component takes any bag on this type, and no row is invented here. The bag's members are the members `ObjectChartSchema` already declares (beyond the node base), the same schema objects, so their value checks and spec bindings (`aggregate`, `series`, `drillDown`, `xAxis`, `yAxis`) judge the bag exactly as they judged the flat node.
22+
- Each of those members written flat on the node is refused on both faces, with a message that names its bag member (`Did you mean \`chartType\` → \`properties.chartType\`?`). The refusal set is read off the bag. The three list-view spellings retired by objectui#10608 (`xAxisField`, `yAxisFields`, `aggregation`) keep their retirement message, on the node and in the bag.
23+
- The chart-family floor stays, read in the bag: the node needs `properties.chartType` (or `properties.specType`). A node with neither is refused at `properties.chartType`, as the flat node was refused at `chartType`. The spec has no such rule.
24+
- A key the chart does not declare stays as before, one layer down: unjudged by the tolerant face, refused by name by the strict face. `BaseSchema`'s keys (`className`, `hidden`, …) stay on the node. `dataSource` stays undeclared on this node, as objectui#11070 left it. `body` / `children` stay refused (objectui#9256).
25+
- `ObjectChartBlockSchema` reaches `AnyComponentSchema` through `ObjectQLPublicBlockComponentSchema`. `ObjectChartSchema` leaves `ObjectQLComponentSchema`.
26+
27+
**The same release's `.changeset/10518-object-chart-y-axis-declared.md`, `.changeset/10608-object-chart-legacy-axis-keys-retired.md`, `.changeset/10770-object-chart-react-tier-node.md` and `.changeset/10872-flat-arm-responsive-styles.md`** describe what the validators say about an `object-chart` written flat. On an authored node those readings now happen in the bag: a malformed `xAxis` is refused at `properties.xAxis`, a retired key at `properties.KEY`, a missing family at `properties.chartType`; `responsiveStyles` stays node-level and is judged there as they say. The flat mirror they name still behaves as they say.
28+
29+
**What did not move.** The TypeScript `ObjectChartSchema` and its zod mirror `ObjectChartSchema` stay published and unchanged in shape. They are the node as `ObjectChart` reads it after the hoist, and as code composes it or hands it to `<ObjectChart schema={…}>` directly.

‎packages/types/src/__tests__/content-channel-e3-residual-9256.test.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ const ROWS: ReadonlyArray<readonly [type: string, mirror: Mirror, required: Reco
126126
['object-view', ObjectViewMirror as unknown as Mirror, { objectName: 'account' }],
127127
['object-gantt', ObjectGanttMirror as unknown as Mirror, { objectName: 'account' }],
128128
['object-calendar', ObjectCalendarMirror as unknown as Mirror, { objectName: 'account' }],
129+
// As `object-form` above: the authored node's spelling is `AUTHORED_REQUIRED`
130+
// below (objectui#11276).
129131
['object-chart', ObjectChartMirror as unknown as Mirror, { objectName: 'account', chartType: 'bar' }],
130132
['detail-view', DetailViewMirror as unknown as Mirror, { objectName: 'account' }],
131133
['email', InputShorthandMirror as unknown as Mirror, {}],
@@ -134,14 +136,15 @@ const ROWS: ReadonlyArray<readonly [type: string, mirror: Mirror, required: Reco
134136

135137
/**
136138
* The authored spelling of a row's required members, where it differs from the
137-
* mirror's: an authored `object-form` or `object-map` takes its props in the
138-
* spec's `properties` bag, and `AnyComponentSchema` refuses them flat
139-
* (objectui#10859 batches 4 and 5). The mirror rows above keep the post-hoist
140-
* spelling.
139+
* mirror's: an authored `object-form`, `object-map` or `object-chart` takes its
140+
* props in the `properties` bag, and `AnyComponentSchema` refuses them flat
141+
* (objectui#10859 batches 4 and 5; objectui#11276). The mirror rows above keep
142+
* the post-hoist spelling.
141143
*/
142144
const AUTHORED_REQUIRED: Readonly<Record<string, Record<string, unknown>>> = {
143145
'object-form': { properties: { objectName: 'account', mode: 'create' } },
144146
'object-map': { properties: { objectName: 'account' } },
147+
'object-chart': { properties: { objectName: 'account', chartType: 'bar' } },
145148
};
146149

147150
const CHANNELS = ['body', 'children'] as const;

‎packages/types/src/__tests__/flat-arm-responsive-styles-10872.test.ts‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
*
3232
* The three arms spread the ONE shared fragment the public-block arms spread,
3333
* `PAGE_COMPONENT_ENVELOPE`, so the member is the same object on all 33 arms.
34+
*
35+
* ⚠️ objectui#11276 moved `object-chart`'s AUTHORED arm into the public-block
36+
* set (`ObjectChartBlockSchema`, its props in the `properties` bag), so the
37+
* population below is the two arms the node union still selects a flat mirror
38+
* for. The flat `ObjectChartSchema` keeps the shared member as the post-hoist
39+
* reading, and the authored chart arm is pinned with the other public blocks in
40+
* `./public-block-responsive-styles-10872.test.ts`.
3441
* The key is NOT declared on `BaseSchema` and NOT on any arm without a measured
3542
* producer: `grid`, `stack` and `container` below are the control, and the
3643
* whole arm list is read at run time so an arm that gains the member silently
@@ -122,14 +129,16 @@ function armsByLiteral(): Map<string, Arm> {
122129
}
123130

124131
/**
125-
* The three arms this batch widens: the arms outside the public-block set that
126-
* a producer writes `responsiveStyles` on (objectui#10872's batch-9 pull). NAMED,
127-
* not derived, because the population is a measurement of producers.
132+
* The arms this batch widens that the node union still selects: the arms
133+
* outside the public-block set that a producer writes `responsiveStyles` on
134+
* (objectui#10872's batch-9 pull). NAMED, not derived, because the population
135+
* is a measurement of producers. `object-chart` was the third until
136+
* objectui#11276 moved its authored arm into the public-block set; its flat
137+
* mirror keeps the member (the post-hoist row below).
128138
*/
129139
const FLAT_ARMS = [
130140
['flex', FlexSchema],
131141
['object-grid', ObjectGridSchema],
132-
['object-chart', ObjectChartSchema],
133142
] as const;
134143

135144
/** Arms with no producer: the strict face still refuses the key on them. */
@@ -161,15 +170,6 @@ const VALID_NODES = {
161170
columns: ['title', 'status'],
162171
responsiveStyles: { large: { minWidth: '0', display: 'block' } },
163172
},
164-
'object-chart': {
165-
type: 'object-chart',
166-
id: 'cc_status_c',
167-
dataset: 'showcase_task_metrics',
168-
dimensions: ['status'],
169-
values: ['task_count'],
170-
chartType: 'bar',
171-
responsiveStyles: { large: { width: '100%', minWidth: '0' }, xsmall: { display: 'none' } },
172-
},
173173
} as const;
174174

175175
/** Values the spec refuses: a breakpoint it does not have, a non-object, a non-CSS value. */
@@ -220,21 +220,28 @@ describe('the population (objectui#10872 batch 9)', () => {
220220
});
221221
});
222222

223-
describe('`responsiveStyles` is declared on the three arms, from the public blocks\' fragment (objectui#10872 batch 9)', () => {
223+
describe('`responsiveStyles` is declared on the batch-9 arms, from the public blocks\' fragment (objectui#10872 batch 9)', () => {
224224
it.each(FLAT_ARMS)('%s carries the member, and it is the SAME object the public-block arms carry', (_type, arm) => {
225225
const shared = PageSectionBlockSchema.shape.responsiveStyles;
226226
expect(shared).toBeDefined();
227227
expect(arm.shape.responsiveStyles).toBe(shared);
228228
});
229229

230+
it('the flat `ObjectChartSchema` mirror keeps the SAME member as the post-hoist reading (objectui#11276)', () => {
231+
// Its authored arm left the node union for the public-block set; the
232+
// mirror is the node as `ObjectChart` reads it, and it still declares the key.
233+
expect(ObjectChartSchema.shape.responsiveStyles).toBe(PageSectionBlockSchema.shape.responsiveStyles);
234+
expect(armsByLiteral().get('object-chart')).not.toBe(ObjectChartSchema);
235+
});
236+
230237
it('the member is the spec\'s own map: the same breakpoints, each the spec\'s own value schema', () => {
231238
const member = (FlexSchema.shape.responsiveStyles as z.ZodOptional).unwrap() as z.ZodObject;
232239
const spec = SpecResponsiveStylesSchema as unknown as z.ZodObject;
233240
expect(Object.keys(member.shape).sort()).toEqual(Object.keys(spec.shape).sort());
234241
for (const key of Object.keys(spec.shape)) expect(member.shape[key]).toBe(spec.shape[key]);
235242
});
236243

237-
it('exactly the public-block arms and these three declare it: no other arm, and not `BaseSchema`', () => {
244+
it('exactly the public-block arms and these flat arms declare it: no other arm, and not `BaseSchema`', () => {
238245
expect(Object.keys(BaseSchema.shape)).not.toContain('responsiveStyles');
239246
const publicBlocks = [
240247
...optionsOf(PublicBlockComponentSchema),
@@ -282,7 +289,7 @@ describe('an invalid `responsiveStyles` is refused on BOTH faces, with the spec\
282289
});
283290
});
284291

285-
describe('the other five envelope keys stay undeclared on the three arms (objectui#10872 batch 9)', () => {
292+
describe('the other five envelope keys stay undeclared on the batch-9 arms (objectui#10872 batch 9)', () => {
286293
it('no arm gained any of them: `dataSource` only where objectui#11070 declared it', () => {
287294
const declared = FLAT_ARMS.flatMap(([type, arm]) =>
288295
UNDECLARED_ENVELOPE.filter((key) => key in arm.shape).map((key) => `${type}#${key}`),

‎packages/types/src/__tests__/object-chart-axis-config-10518.test.ts‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ import { safeValidateSchema } from '../zod/index.zod';
7373

7474
const CHART = { type: 'object-chart', chartType: 'bar' } as const;
7575
const chart = (extra: Record<string, unknown>) => ({ ...CHART, ...extra });
76+
/**
77+
* The AUTHORED spelling of `chart(extra)` (objectui#11276): an authored
78+
* `object-chart` takes its props in the `properties` bag, and the public door
79+
* judges that arm, so every `safeValidateSchema` row below hands it this. The
80+
* mirror rows keep `chart(extra)`, the node as `ObjectChart` reads it.
81+
*/
82+
const authored = (extra: Record<string, unknown>) => ({ type: 'object-chart', properties: { chartType: 'bar', ...extra } });
7683

7784
const parsed = (doc: unknown) => {
7885
const r = ObjectChartMirror.safeParse(doc);
@@ -124,6 +131,12 @@ const SHOWCASE_NODE = {
124131
yAxis: [{ field: 'task_count', stepSize: 1 }],
125132
} as const;
126133

134+
/** The same producer node as the producer WRITES it — its props in the bag (objectui#11276). */
135+
const SHOWCASE_AUTHORED = (() => {
136+
const { id, type, responsiveStyles, ...props } = SHOWCASE_NODE;
137+
return { id, type, responsiveStyles, properties: props };
138+
})();
139+
127140
type Shape = Record<string, unknown>;
128141
const mirrorShape = (ObjectChartMirror as unknown as { shape: Shape }).shape;
129142

@@ -200,7 +213,7 @@ describe('objectui#10518 (b) — LIT CONTROL: the showcase producer\'s node pars
200213
});
201214

202215
it('through `safeValidateSchema`, the door `objectui validate` / `objectui check` run', () => {
203-
expect(safeValidateSchema(SHOWCASE_NODE).success).toBe(true);
216+
expect(safeValidateSchema(SHOWCASE_AUTHORED).success).toBe(true);
204217
});
205218
});
206219

@@ -220,14 +233,14 @@ describe('objectui#10518 (c) — a malformed `yAxis` entry is refused at its own
220233
});
221234

222235
it('the refusal reaches the public door — `safeValidateSchema` refuses the node', () => {
223-
expect(safeValidateSchema(chart({ yAxis: [{ field: 'task_count', stepSize: 'big' }] })).success).toBe(false);
236+
expect(safeValidateSchema(authored({ yAxis: [{ field: 'task_count', stepSize: 'big' }] })).success).toBe(false);
224237
});
225238

226239
/* CONTROL for every refusal above: the same documents minus the defect parse. */
227240
it('CONTROL — the same entries, minus the defect, are accepted', () => {
228241
expect(() => parsed(chart({ yAxis: [{ field: 'task_count', stepSize: 2 }] }))).not.toThrow();
229242
expect(() => parsed(chart({ yAxis: [{ field: 'task_count', position: 'right', logarithmic: true }] }))).not.toThrow();
230-
expect(safeValidateSchema(chart({ yAxis: [{ field: 'task_count', stepSize: 2 }] })).success).toBe(true);
243+
expect(safeValidateSchema(authored({ yAxis: [{ field: 'task_count', stepSize: 2 }] })).success).toBe(true);
231244
});
232245
});
233246

@@ -343,7 +356,7 @@ describe('objectui#10518 (g) — LIT CONTROL: the showcase `renewals-pipeline` a
343356
});
344357

345358
it('through `safeValidateSchema`, the door `objectui validate` / `objectui check` run', () => {
346-
expect(safeValidateSchema(chart(RENEWALS_AXES)).success).toBe(true);
359+
expect(safeValidateSchema(authored(RENEWALS_AXES)).success).toBe(true);
347360
});
348361
});
349362

@@ -363,7 +376,7 @@ describe('objectui#10518 (h) — a malformed `xAxis` object is refused at `xAxis
363376
});
364377

365378
it('the refusal reaches the public door — `safeValidateSchema` refuses the node', () => {
366-
expect(safeValidateSchema(chart({ xAxis: { field: 'status', min: 'zero' } })).success).toBe(false);
379+
expect(safeValidateSchema(authored({ xAxis: { field: 'status', min: 'zero' } })).success).toBe(false);
367380
});
368381

369382
/* CONTROL for every refusal above: the same objects, minus the defect, parse. */
@@ -388,7 +401,7 @@ describe('objectui#10518 (i) — `xAxis` is the spec object, and only that', ()
388401
});
389402

390403
it('the bare string is refused at the public door too', () => {
391-
expect(safeValidateSchema(chart({ xAxis: 'status' })).success).toBe(false);
404+
expect(safeValidateSchema(authored({ xAxis: 'status' })).success).toBe(false);
392405
});
393406

394407
it('CONTROL — the same column as the spec object parses, and a malformed OBJECT is not told the remedy', () => {

‎packages/types/src/__tests__/object-chart-legacy-axis-keys-retired-10608.test.ts‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ import { ListViewSchema, safeValidateSchema } from '../zod/index.zod';
6565

6666
const CHART = { type: 'object-chart', chartType: 'bar' } as const;
6767
const chart = (extra: Record<string, unknown>) => ({ ...CHART, ...extra });
68+
/**
69+
* The AUTHORED node (objectui#11276): its props in the `properties` bag, the
70+
* arm the public door judges. `authoredFlat` writes `extra` on that node
71+
* itself. The mirror rows keep `chart(extra)`, the node as `ObjectChart` reads it.
72+
*/
73+
const authored = (extra: Record<string, unknown>) => ({ type: 'object-chart', properties: { chartType: 'bar', ...extra } });
74+
const authoredFlat = (extra: Record<string, unknown>) => ({ type: 'object-chart', properties: { chartType: 'bar' }, ...extra });
6875

6976
/**
7077
* The retired keys, each with the values an author would plausibly have written
@@ -102,10 +109,17 @@ describe.each(RETIRED_KEYS)('objectui#10608 (a) — `%s` is RETIRED on `object-c
102109
it.each(values.map((v) => [JSON.stringify(v), v] as const))(
103110
'refuses `%s` at the public door and on the mirror: ONE `invalid_type` issue at the key, naming the remedy',
104111
(_label, value) => {
105-
for (const r of [safeValidateSchema(chart({ [key]: value })), ObjectChartMirror.safeParse(chart({ [key]: value }))]) {
112+
// objectui#11276: at the door the key is retired in the authored node's
113+
// bag AND written flat on it (the flat mirror's own tombstone, by
114+
// reference, not a pointer at a bag member that is itself retired).
115+
for (const [r, path] of [
116+
[safeValidateSchema(authored({ [key]: value })), ['properties', key]],
117+
[safeValidateSchema(authoredFlat({ [key]: value })), [key]],
118+
[ObjectChartMirror.safeParse(chart({ [key]: value })), [key]],
119+
] as const) {
106120
expect(r.success, `an authored \`${key}: ${JSON.stringify(value)}\` was ACCEPTED`).toBe(false);
107121
const issues = issuesOf(r);
108-
expect(issues.map(({ code, path }) => ({ code, path }))).toEqual([{ code: 'invalid_type', path: [key] }]);
122+
expect(issues.map(({ code, path }) => ({ code, path }))).toEqual([{ code: 'invalid_type', path }]);
109123
expect(issues[0]!.message).toContain(`\`${key}\``);
110124
expect(issues[0]!.message).toContain(remedy);
111125
}
@@ -130,12 +144,12 @@ describe('objectui#10608 (b) — LIT CONTROL: each remedy parses on the same nod
130144
['`yAxis: [{ field }]` for `yAxisFields`', { yAxis: [{ field: 'amount' }] }],
131145
['`aggregate: { field, function, groupBy }` for `aggregation`', { objectName: 'task', aggregate: { field: 'amount', function: 'sum', groupBy: 'status' } }],
132146
])('%s', (_label, extra) => {
133-
expect(issuesOf(safeValidateSchema(chart(extra)))).toEqual([]);
147+
expect(issuesOf(safeValidateSchema(authored(extra)))).toEqual([]);
134148
expect(issuesOf(ObjectChartMirror.safeParse(chart(extra)))).toEqual([]);
135149
});
136150

137151
it('all three remedies together, on one node', () => {
138-
const node = chart({
152+
const node = authored({
139153
objectName: 'task',
140154
aggregate: { field: 'amount', function: 'sum', groupBy: 'status' },
141155
xAxis: { field: 'status' },

0 commit comments

Comments
 (0)