Repository navigation
feat(types)!: declare the spec's axis config on ObjectChartSchema.xAxis / yAxis (objectui#10518) - #10601
Conversation
….yAxis (objectui#10518) `ObjectChartSchema.yAxis` is `@objectstack/spec`'s `ChartAxisSchema[]` by reference on the Zod mirror and `ChartAxis[]` on the TS twin, with the objectui#8317 import boundary. A single axis object or a bare column name is refused, as on `ChartSchema.yAxis`. The two boundary ledgers record the new crossing; the pin is object-chart-axis-config-10518.test.ts. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
changeset-claim-re-read
|
✅ Console Performance Budget
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
Size Limits
|
…ma.xAxis (objectui#10518)
`ObjectChartSchema.xAxis` is ONE `@objectstack/spec` `ChartAxisSchema`
by reference on the Zod mirror and `ChartAxis` on the TS twin, with the
objectui#8317 import boundary. There is no string arm and no fold (seat
decision, option A): a bare column name or a list is refused with the
`xAxis: { field }` remedy, and a malformed object is refused at `xAxis`
with the spec's own diagnostic. The one typed renderer-tolerance fixture
that wrote a bare string takes an explicit cast, and the renderer is
unchanged. The pin, both ledger grounds and the changeset now cover
`xAxis`.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
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
Size Limits
|
Main moved in packages/types/src/objectql.ts (objectui#8932's kanban docblock edit, a distant range); merged rather than rebased. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ape draws today (objectui#10518) The lead-in said every now-refused document draws a chart today. A per-case render measurement shows that is false for three shapes: an xAxis object with no field, a list or other non-object xAxis, and a yAxis entry with no field on a chart whose series come from yAxis alone. Each can draw a refusal instead. The lead-in now says most of them draw a chart, and the affected bullets name the exception and what it draws. The zod comment that called a list xAxis a normalizer tolerance is corrected the same way. Prose only: the frontmatter is byte-identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsDelta verified. My own measurement. A 41-case probe rendered the real The four required cases hold: list The bullets' conditions are too broad in three published sentences, each with a measured counterexample:
Lesser: bullet 4 (single-object or bare-string The landing body's round-3 bullets (b) ("Otherwise they draw a refusal … The Observation (e) is real, loud, pre-existing and outside this card; a card is owed. Object-bound + ② Semver levelUnchanged and correct: ③ Boundary flags
Implemented-by: VERDICT: FAIL — three changeset sentences (bullet 1's "draws only when … otherwise a refusal", bullet 1's " Generated by Claude Code |
…ow-refused axis shapes (objectui#10518)
Review round 2 showed that the round-3 conditions were too broad. Whether
a no-category or no-field shape draws depends on the chart type and on the
rows: a `name` column, or the `value` fallback. The lead-in now says that
several of the now-refused documents draw a chart today, and that what the
rest draw depends on the chart type, the rows and the node's other
sources. The bullets state only equivalences the renderer guarantees: a
dropped key changes nothing; a bare string reads as `{ field }`; a list,
a non-object or a missing field names no category; a no-field entry adds
no series. Prose only; the frontmatter is byte-identical.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
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
Size Limits
|
… (objectui#10518) The lead-in credited the `minor` level to the refused documents that still draw a chart. The version policy's reason is the breaking semantics, which here is the narrowed accept set itself. The lead-in paragraph is replaced with the seat's exact text and rewrapped to the file's width. The bullets and the frontmatter are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Contract reviewServed-tier: ① Derived judgmentsDelta verified. Why the equivalences hold for every chart type and row set (source, read-only). Kept sentences:
"Shipped as Landing PR body r4 (changed sentences).
② Semver levelUnchanged and correct: ③ Boundary flags
Implemented-by: VERDICT: FAIL The one branch-side defect is the lead-in clause "which is why this ships as Generated by Claude Code |
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ies type and names chartType (objectui#10584) (objectstack-ai#10768) Fixes objectstack-ai#10584 Clause-②: no. The accept set does not move; this is a `.describe` and docblock wording fix. `yes` would be owed only if a named producer turned the card to widening, and then the dev stops and reports. ## What changed - `packages/types/src/zod/objectql.zod.ts`, the `series` member of `ObjectChartSchema`: the `.describe()` no longer calls the element "the arm ChartRendererProps declares". It now says the copy is that `{ dataKey }` arm minus the arm's per-series `type`, which this copy does not declare, and that this copy's per-series family override is `chartType` (`bar` | `line` | `area`). A short comment above the member points at the TS twin for the ground. - `packages/types/src/objectql.ts`, the `series` member docblock: "this copy has not taken up" (which read as a lag) now says the omission is deliberate. Taking `type` up widens a published accept set and waits for a named producer that writes it on an `object-chart` node. The docblock also states what a `type` written anyway meets on each face (measured below). - `.changeset/10584-object-chart-series-describe.md`: `@object-ui/types` `patch`, since the `.describe()` string is published text. Both edits stay inside the `ObjectChartSchema` block, which is the file surface the claim names. No member, type or schema node changes. ## Premise check on `origin/main` at `0896838deb` - **H1 holds.** The zod `.describe()` said "the arm ChartRendererProps declares", while both arms of `ChartRendererProps.schema.series` carry `type?: string` (objectui#8086). The TS docblock already named the omission ("every member of it except `type` ... this copy has not taken up"). It now says the omission is deliberate. - **H2 holds.** PR objectstack-ai#10734 (objectui#10608) and PR objectstack-ai#10601 (objectui#10518) are both on `main`, so this card's serial predecessors on the block have landed. The text was re-read on `main`; the card's line numbers were not used. - **H3: no pin.** `git grep` for the old describe text ("ChartRendererProps declares", "plotted series in the renderer", "has not taken up") matches only the two source files. No test, generated artifact or doc asserts the string, and no describe-verdict pin (`INTERNAL` / `AUTHORABLE` prefix) reads it. ## Stop condition: producer census for a per-series `type` on `object-chart` No producer found, so the work did not stop. Each reading has a control that hits: - **objectui (in-repo):** every `type: 'object-chart'` literal outside tests is in one of the relays: app-shell `ObjectView`, plugin-view `ObjectView`, plugin-list `ListView`, plugin-dashboard `DashboardRenderer` and `DashboardGridLayout`. Each writes `series` as `{ dataKey, label }`, and none writes `type`. app-shell forwards `viewDef.chart.series` verbatim, but the spec's `ListChartConfigSchema` is a `strictObject` with no `series` member, so no conforming stored view carries one. `chartConfigPresentation` emits no `series`. The only `{ dataKey, type }` literals are in `plugin-charts` tests of the `chart` renderer. - **objectstack showcase (at `16c5a33`):** `command-center.page.ts` writes `object-chart` nodes with no `series`. `renewals-pipeline.page.ts` writes `series={[{ name: 'total', label: 'Invoice value' }]}` on the ObjectChart react block: the `{ name }` arm, with no `type`. - **hotcrm (shallow clone at `2f7b232`):** zero `object-chart` / `ObjectChart` hits, and zero `series` in the 15 files under `src/**/dashboards` and `src/**/reports`. Control: 34 chart `type:` literals across those files (five dashboards and two reports). ## What each face does with a per-series `type` (measured, unchanged by this PR) Measured on the rebuilt `dist` at HEAD `5dc9a71e02`, with the same readings on the base build: - **zod mirror:** `ObjectChartSchema.safeParse` of `series: [{ dataKey: 'margin', type: 'line' }]` gives `success: true`, and the parsed series is `[{"dataKey":"margin"}]` (the plain `z.object` element strips `type`). `{ dataKey, chartType: 'line' }` keeps `chartType`. `{ label }` alone is still `invalid_type` at `series.0.dataKey`. The base build gives the same result for each. - **TS face:** `tsc --strict` on a literal typed `ObjectChartSchema` gives `TS2353` ("'type' does not exist in type ...") for `{ dataKey, type }` and compiles `{ dataKey, chartType }`. - **`dist` diff, base vs head:** the non-comment lines of `objectql.d.ts` are byte-identical. `zod/objectql.zod.d.ts` and `zod/index.zod.d.ts` differ only in union-member emission order on members this PR does not touch (`"json" | "csv" | "xlsx"` versus `"json" | "xlsx" | "csv"`, `position` and `operator` unions). `zod/objectql.zod.js` differs only in the comment and the describe string. ## Verification (all on HEAD `5dc9a71e02`, working tree clean) - `pnpm --filter @object-ui/types build`: `VERDICT command-exit 0`. The dependency closure `@object-ui/types^...` has no build script, so it is empty. - `pnpm --filter @object-ui/types type-check`: `VERDICT command-exit 0` (the script name is echoed as `type-check`). - `pnpm exec vitest run --maxWorkers=2 packages/types/`: `Test Files 245 passed (245)`, `Tests 5327 passed (5327)`, exit 0. - `pnpm exec vitest run --maxWorkers=2 packages/plugin-charts/`: `Test Files 82 passed (82)`, `Tests 964 passed (964)`, exit 0. Direction: the downstream consumer of `@object-ui/types` named in the dispatch, run to confirm no fixture reads the describe string. - Out-of-package tests that parse `objectql.ts` / `objectql.zod.ts` from disk (app-shell `relayRungCensus-7559`, `chartConfigForward-7891`; plugin-grid, plugin-kanban, plugin-tree and react census pins; and eight `scripts/__tests__` suites): `Test Files 15 passed (15)`, `Tests 602 passed (602)`, exit 0. - ESLint `--no-inline-config --format json` on the two touched sources: exit 0, 2 files linted, 0 errors, and 35 `no-explicit-any` warnings, none on an edited line. Type-aware linting is not enabled (`eslint.config.js` has no `parserOptions.project` / `projectService`), so this diff cannot move a verdict on any untouched file. - `node scripts/check-changeset-presence.mjs` 0 · `node scripts/check-changeset-no-major.mjs` 0 · `pnpm check:new-line-citations` 0 (`VERDICT new-cross-file-line-citations: 0 new citation(s)`) · `pnpm check:control-bytes` 0 · `pnpm check:spec-symbols` 0 · `pnpm check:test-path-roots` 0 · `pnpm check:changeset-claims` 0 (report-only). The pending objectui#8086 changeset's sentence "that copy of the internal arm does not carry `type`, and its type is unchanged" is still true. - `pnpm check:component-surface-parity` 0 · `check:designer-field-key-parity` 0 · `check:doc-types` 0 · `check:installed-pin-claims` 0 · `check:handler-key-reads` 0 · `check:action-forward-parity` 0. - `node scripts/check-governed-queue-guard.mjs --test` on the three paths: `NOT GOVERNED`. - **NOT MEASURED: `pnpm check:doc-examples`**, exit 2, prerequisite not met. It needs the dist of about 30 workspace packages, and that full build is CI's. This diff adds no `@example` block or fence (zero added lines match), so the gate's population is unchanged. - **NOT RUN: Spec Main Shape Gate.** The touched member is not spec-derived: the `series` element is a local copy of `ChartRendererProps`' arm and binds no `@objectstack/spec` symbol. The emitted `objectql.d.ts` is non-comment byte-identical, so compiling against any spec cannot move. ## Acceptance notes - **Kept verbatim: the `INTERNAL (relay-composed)` prefix.** It is objectui#7946's ruled verdict. The census above measured a channel it does not name: the ObjectChart react block. The spec's `react-blocks` entry has `schemaType: 'object-chart'` and `schema: ChartConfigSchema`, and it lists `series` in `dataProps`. `ChartSeriesSchema` is the `{ name }` arm and carries the per-series `type`. The showcase's `renewals-pipeline` page writes `series={[{ name, label }]}` through that block onto an `object-chart` node, and the published zod face refuses that node (`invalid_value` at `chartType`, `invalid_type` at `series.0.dataKey`). That belongs to the same family as objectui#10518 / objectui#10608 (the `object-chart` copy against the spec's ChartConfig contract), and it is reported to the seat rather than acted on here. It does not change this card's answer. On this node the spec's per-series `type` rides the `{ name }` arm, and the spec refuses `dataKey` by name, so a `{ dataKey, type }` entry is not spec-authorable on `object-chart` either. - The describe names `chartType` because it is this copy's declared per-series override. It is not a new authoring recommendation: the member stays `INTERNAL`. - No pin was added. The dispatch owed one only if the wording was already pinned, and it was not. Refs: objectui#8086 · objectui#7946 · PR objectstack-ai#10554 --- _Generated by [Claude Code](https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW)_ Co-authored-by: Claude <noreply@anthropic.com>
…i#10793, objectstack-ai#10802 and objectstack-ai#10821 made false (objectui#10877) (objectstack-ai#10891) Fixes objectstack-ai#10877 Clause-②: no. It is release-text hygiene, and no published type or behaviour moves. ## What this does Eight pending changesets each state a contract fact that a later PR in this same unreleased batch made false: PR objectstack-ai#10793 (objectui#7924, `densityMode` retired), PR objectstack-ai#10821 (objectui#7928, `ObjectViewSchema.listViews` mirrored by reference, a named view's `options` bag refused whole) and PR objectstack-ai#10802 (objectui#10770, `ObjectChartSchema.series` became two arms). A pending changeset publishes verbatim into the CHANGELOG at the next release, and the release PR objectui#5400 is still open. So each false sentence now has a dated supersession note beside it. The note form is copied from the landed precedents, PR objectstack-ai#10533 (the `6235-mergedsort-wrap.md` note) and PR objectstack-ai#10828 (objectui#10801): a bold headline naming the date and the card whose change supersedes the sentence, then what holds now, then the superseding entry by file and PR, then "the text above is kept as the reading of this change". - Zero lines deleted: `git diff --numstat` against the merge-base `9f0c84a448` is 168 insertions, 0 deletions, over 8 files. The first round added 10 notes in 6 files. Patch round 1 added 4 notes in 2 files (below). Patch round 2 edited the 7779 note and added none, so there are 14 notes. - The frontmatter is byte-identical in all nine entries the claim and its addendum `5862987983` name. The md5 of the frontmatter block is equal at the merge-base `9f0c84a448` and the head `50deccd71a` for each: 7924-retire `ecedb10c`, 8355 `8f322642`, 10321 `564c93d5`, 10584 `84f96a9a`, 8365 `379475b3`, 9242 `acef2a2c`, 8086 `e927372d`, 7779 `ecedb10c`, 7946 `2ffd8635`. - The seventh entry, `8086-chart-series-type-arm.md`, is untouched (whole-file md5 `7ea6b544` at base and head). Every sentence in it measured STILL-TRUE (see the lit control below). - No new changeset: the diff is `.changeset/` only, and `check-changeset-presence` says none is owed. | Entry | FALSE-NOW sentences, quoted | Superseding entry (PR) | Notes | | --- | --- | --- | --- | | `7924-named-list-view-retire.md` | "`NamedListView` (one entry of `ObjectViewSchema.listViews`)"; "Two bucket-③ members are NOT retired" and "They stay declared as they were." | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821); `7924-density-mode-through-fold.md` (objectstack-ai#10793) | 2 | | `8355-retire-calendar-date-aliases.md` | "it now names the key and the remedy on all five authoring surfaces", for "a named view's two nestings under `listViews`"; "plus a `.check()` on `ObjectViewSchema` for a named view's two nestings"; "`ObjectViewSchema.listViews` stays UNMIRRORED" and the rest of that paragraph; "`timeline.dateField` stays a live, accepted alias", on a named view; "the structural guard that `listViews` stays out of the object's `shape`" | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821) | 3 | | `10321-named-view-kanban-groupby-refused.md` | "`ObjectViewSchema.listViews` is unmirrored"; "and in its legacy `options.kanban` bag" with the issue path under `options.kanban`; "reports one issue per written key, naming the key and the replacement"; "Not changed, deliberately: `listViews` stays unmirrored" and the rest of that paragraph | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821) | 2 | | `10584-object-chart-series-describe.md` | "It now says the copy is that `{ dataKey }` arm minus its per-series `type`, and that the per-series family override on an `object-chart` node is `chartType`"; "taking `type` up would widen a published accept set" | `10770-object-chart-react-tier-node.md` (objectstack-ai#10802) | 1 | | `8365-stray-kanban-groupby-refused.md` | "objectui#10321 gives that route its own door, which refuses the key in either nesting with the same message" | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821) | 1 | | `9242-stray-kanban-groupby-lane-second-route.md` | "which refuses the key in either nesting with the same message"; "(a named view's `kanban` is typed as the `list-view`'s), but not the key inside the untyped `options.kanban` bag on either" | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821) | 1 | | `object-view-unmirrored-keys-7779.md` (patch round 1) | the table's "still unmirrored"; "`listViews` stays unmirrored, on the ruling's own fallback clause."; "The declaration's value is the local `NamedListView`"; the 21-read / 43-unread figures; "The key therefore stays in the parity ledger … until the maintainer decides its value type" | `7928-listviews-by-reference-fold.md` (objectstack-ai#10821) | 1 | | `objectchart-schema-anchor-7946.md` (patch round 1) | the `series` half of "value types taken from their READ sites (`ChartRendererProps` for `xAxisKey` / `series`)" and of "two are INTERNAL, relay-composed (`xAxisKey`, `series` …)"; "`series` entries must be `dataKey`-shaped."; "`aggregate` is the exception, and only because the spec's own object is strict." | `10770-object-chart-react-tier-node.md` (objectstack-ai#10802) | 3 | **Placement.** Each note sits directly after the paragraph that holds the false sentence. Where two false paragraphs are adjacent, one note covers both: the first 10321 note, the 10584 note and the last 8355 note. The 8355 note after the UNMIRRORED paragraph also covers the matching clause in the `@object-ui/types` bullet above it, because both describe the same named-view check. No note cites a line address; source facts are cited by symbol and test name, and `check:new-line-citations` reads 0 new citations. ## Measured, not inherited (one-time probes, not committed) The probes ran the worktree sources at base `b2683a2c0e`, with `@objectstack/spec` 17.4.0 resolved from `packages/types`. The zod face ran through `tsx`, and the TS face through `tsc --noEmit` against `packages/types/src/index.ts`. Each probe sits beside a control that differs only by the key. A named view is `listViews.v` with `columns` set. Zod face (`ObjectViewSchema.safeParse`; `safeValidateSchema` gives the same issues on the two `kanban` rows): | Probe | Result | | --- | --- | | control `kanban: { groupByField, columns }` | parses | | `kanban.groupBy` | two issues: `unrecognized_keys` at `listViews.v.kanban` naming `groupBy`, and `custom` at `listViews.v.kanban.groupBy` | | `options: { kanban: { groupBy } }` | one issue: `unrecognized_keys` at `listViews.v` naming `options`; nothing at the `options.kanban.groupBy` path | | `calendar.dateField` | `unrecognized_keys` at `listViews.v.calendar`, and `custom` at `listViews.v.calendar.dateField` | | `options: { calendar: { dateField } }` | one issue: `options` refused | | `timeline.dateField` | `unrecognized_keys` at `listViews.v.timeline`; control `timeline: { startDateField, titleField }` parses | | each of the 16 retired `NamedListView` spellings, and `densityMode` | `unrecognized_keys` at `listViews.v` naming the key (17 of 17); control `userActions: { search: false }` parses | | `listViews` in `ObjectViewSchema.shape` | present | | list-view route: `kanban.groupBy`, `options.kanban.groupBy`, `calendar.dateField`, `options.calendar.dateField`, `timeline.dateField` | `invalid_type`, `custom`, `invalid_type`, `custom`, parses: unchanged | TS face (`tsc`): | Probe | Result | | --- | --- | | named view `kanban.groupBy` in an `ObjectViewSchema` literal | TS2353, excess property on the spec's kanban block | | named view `options` | TS2353, `options` does not exist | | named view canonical control | compiles | | `list-view` `kanban.groupBy` | TS2322 (`never`): unchanged | | `list-view` `options.kanban.groupBy` | compiles: unchanged | | a `NamedListView` value as a `listViews` entry | TS2322, not assignable | | `NamedListView` with `densityMode`; with `allowExport` | TS2322; compiles | Chart series (`ObjectChartSchema.safeParse`): `{ name, type: 'line' }` parses with `type` kept; `{ dataKey, type: 'line' }` parses with `type` stripped; `{ name, chartType }` is refused (`invalid_union`). The series `.describe()` string now names both arms. The runtime fold was read, not probed: `normalizeListViewSchema` still folds `densityMode` and the eight `show*` flags, and `ListView` still merges `options.kanban` under `kanban`. ## Lit control: STILL-TRUE, left byte-identical - `8086-chart-series-type-arm.md`, the whole entry. PR objectstack-ai#10802 changed no `plugin-charts` source (its one file there is a test). `ChartRendererProps.schema.series` still declares `type?: string` on both arms. The `ObjectChartSchema.series` docblock still says the copy of the internal arm omits `type`, and that internal arm's type is the same literal as before PR objectstack-ai#10802. - 8365, "takes the identical message through a check on that untyped bag", and the distinguishing `options.kanban` fixture. Both are the `list-view` route, measured unchanged. - 9242, "a `list-view` document carrying the key, under `kanban` or under the legacy `options.kanban` bag, is refused by `safeValidateSchema`". Measured unchanged. - 8355, the `@object-ui/types` bullet's "on the legacy `options.calendar` nesting (a check, since an open record declares no member". This is the list-view surface, measured unchanged. - 7924-retire, "`densityMode` is folded onto `rowHeight` by `normalizeListViewSchema`" and "Only the authoring face changes". The fold still does both. - 10584, "Wording only. The accepted shape does not move on either face". This is a statement about that change's own delta, and it is still true of that change. - 7779 (patch round 1): `viewTabBar` retired; the eight keys enforced; "64 declared top-level members"; the spec record requires `columns` and refuses `options`, tuple filters and `default`; "not papered over with `z.any()`"; "absent stays valid on all nine"; the ledger move 14/96 → 14/87 (that change's own delta). Each measured true at `0ea55c3701`. - 7946 (patch round 1): the `xAxisKey` half of the INTERNAL sentence (its `.describe()` still says all five producers compute it); `type: 'object-chart'` required; `chartType` must be the declared union; `normalizeChartSchema` translates the `{ name }` arm; `aggregate` `function` / `groupBy` required with unknown members refused; a misspelled key on the node itself is not refused; `filter` keeps both arms. Each measured true at `0ea55c3701`. ## Gates (re-taken at head `50deccd71a`; hand-derived from `package.json` and `.github/workflows/`; each exit captured by redirect-then-status) | Command | Verdict line | Exit | | --- | --- | --- | | `pnpm check:pending-changeset-literals` | ✅ No test source names a pending changeset. | 0 | | `node scripts/check-changeset-claims.mjs` | ✅ No pending changeset names a file this change touches. (Self-contradiction reading: every declared package across the 8 bodies is not negated.) | 0 | | `node scripts/check-changeset-presence.mjs` | ✅ No source or published contract of a released package changed in this range, so no changeset is owed. | 0 | | `node scripts/check-changeset-no-major.mjs` | ✅ No changeset declares a `major` bump. | 0 | | `node scripts/check-changeset-overwrite.mjs` | report-only: this change touches 8 changesets it did not add. The declared packages are identical at base and head for all 8, which is the prose-correction case. | 0 | | `node scripts/check-changeset-fixed.mjs` | ✅ All workspace packages are in the changeset fixed group. | 0 | | `pnpm check:new-line-citations` | VERDICT new-cross-file-line-citations: 0 new citation(s) | 0 | | `pnpm check:control-bytes` | ✅ check-control-bytes: OK | 0 | | `pnpm check:spec-symbols` | ✅ spec member citations: nothing cites a key its spec symbol does not declare. | 0 | | `pnpm check:test-path-roots` | ✅ check-test-path-roots: OK | 0 | | `node scripts/check-installed-spec-pin-claims.mjs` | OK (it excludes `.changeset/`, so it was run for completeness only) | 0 | | `node scripts/check-governed-queue-guard.mjs --test` (the 8 paths) | ✅ NOT GOVERNED | 0 | Tests, from the repo root under the shared verify lock: every `scripts/__tests__` file that names `.changeset` (derived with `git grep -l`: 24 test files, plus one fixture that is not a test), and the five `@object-ui/types` pins that re-derive what the notes state (`calendar-date-alias-refusal-8355`, `named-view-kanban-stray-group-by-10321`, `object-view-listviews-by-reference-7928`, `object-chart-react-tier-node-10770`, `object-view-unmirrored-keys-7779`). Result at `f5e0123f4b`: **Test Files 29 passed (29), Tests 1193 passed (1193)**, lock `VERDICT command-exit 0`. Re-taken at `0ea55c3701` with the 24 `scripts/__tests__` files and the four pins the new notes rest on (`object-view-unmirrored-keys-7779`, `object-chart-react-tier-node-10770`, `object-view-listviews-by-reference-7928`, `zod-mirror-parity`): **Test Files 28 passed (28), Tests 1200 passed (1200)**, lock `VERDICT command-exit 0`. Re-taken at `50deccd71a` (patch round 2): the 24 `scripts/__tests__` files plus `object-view-unmirrored-keys-7779` gave **25 files / 1146 tests**, and the four other pins (`calendar-date-alias-refusal-8355`, `named-view-kanban-stray-group-by-10321`, `object-view-listviews-by-reference-7928`, `object-chart-react-tier-node-10770`) gave **4 files / 48 tests**. Both runs gave lock `VERDICT command-exit 0`. Not applicable, with the reason: - `@object-ui/types` type-check, package vitest runs and consumer sweeps: the diff touches no package source, only `.changeset/`. - The Spec Main Shape Gate: no touched type. - ESLint `--no-inline-config` on the touched files: `eslint.config.js` has no configuration that matches `.md`. All 8 files report "File ignored because no matching configuration was supplied", so it judged nothing (NOT MEASURED). ## Acceptance notes - **Residue in the same family: closed by patch round 1.** The first round stopped on two files outside the claimed surface, `object-view-unmirrored-keys-7779.md` and `objectchart-schema-anchor-7946.md`. Each is named by a superseding PR itself. The seat extended the file surface (addendum `5862987983`, option A), and patch round 1 (`80ee15eecb`) added one note to 7779 and three to 7946, in the same form and measured the same way. `Fixes objectstack-ai#10877` stays. - **For the at-tier review.** The last 7946 note also names two changes outside the three PRs: `drillDown` (objectui#8885, PR objectstack-ai#8895) and `xAxis` / `yAxis` (objectui#10518, PR objectstack-ai#10601). Both landed after the 7946 entry, and both also make its "`aggregate` is the exception" sentence false. That sentence of the note can be dropped if the review holds it out of scope; the rest of the note stands without it. - Not measured: the wider pending population that names `listViews` / `NamedListView` or `ObjectChartSchema`. Only the entries the superseding PRs name were read. - **Source prose. These are observations and were not filed; carrier: none.** - The `ObjectChartSchema.series` TS docblock (`packages/types/src/objectql.ts`) says "A `type` written on this arm anyway is an excess property on a literal typed by this interface." Since PR objectstack-ai#10802 that is false. `{ dataKey: 'amount', type: 'line' }` compiles in an `ObjectChartSchema` literal, because the union's `{ name }` arm declares `type` (tsc probe above). The zod mirror still strips it. - The zod docblock headed "WHERE THIS ARM IS INSTALLED — TWO ROUTES, TWO NESTINGS EACH, ONE STRING" (`packages/types/src/zod/objectql.zod.ts`) still says a named view's `listViews` "is unmirrored", and it still names a `custom` issue under `options.kanban` on that route. The docblocks of `checkNamedViewKanbanStrayGroupBy` and `namedViewKindRefusals` are current. - **A dispatch premise, corrected.** "Each one already states in its own body that it supersedes the older entry" holds for `7924-density-mode-through-fold.md` and `7928-listviews-by-reference-fold.md`. `10770-object-chart-react-tier-node.md` names no superseded entry. The supersession is stated in PR objectstack-ai#10802's body ("Superseded pending changesets"), which names the objectui#10584, objectui#7946-anchor and objectui#8086 entries. - **`main` merged, twice.** `origin/main` `4f38f39286` was merged as `0ea55c3701`: the two merged commits touch none of the probed sources, and the probes re-ran byte-identical. `origin/main` `9f0c84a448` was merged as `bade176fff` (patch round 2), which carries `35d68c4c` (PR objectstack-ai#10884, objectui#10758). That merge moved probed sources (`objectql.zod.ts`, `objectql.ts`, `ChartRenderer.tsx`); the probes re-ran at `50deccd71a`, byte-identical to round 1's head outputs. Neither merge used a rebase or a force-push. - **Patch round 2 (at-tier record `5864532186`, FAIL on one note).** The 7779 note said the renderer reads 20 members and leaves 44 unread. That was true at `4f38f39286`, but PR objectstack-ai#10884 re-took the census to 43 / 21 on `main`. Per the seat's decision (REWORK `5864543090`), the note now states no figure. It says the split is no longer the 21 / 43 the entry counted, names both changes that moved it (objectui#7928 and objectui#10758), and cites the census pin in `object-view-unmirrored-keys-7779.test.ts`, which re-derives the figures on every run (`43 read + 21 unread = 64` at the head). No other note moved. - **The headline card.** Each note's headline names the card whose change supersedes the sentence, as the PR objectstack-ai#10533 notes did. PR objectstack-ai#10828's notes named their own card instead, because the cause there was a spec release that has no pending entry. - **Commit trailers are model-free** (`Co-Authored-By: Claude`), per the dispatch. The harness offered a trailer that carries a model name, and it was not used. Implemented by a `domain:spec#1` dispatch (dev subagent). Session: `https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW`. --- _Generated by [Claude Code](https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10518
Clause-②: yes
ObjectChartSchema.xAxisandObjectChartSchema.yAxisnow declare@objectstack/spec's axis config by reference, on both faces.xAxisis ONEChartAxisSchemaobject andyAxisis a list of them. This follows ruling5809510046, branch 2 (declare), which triage5829725284reused for this card, and mirrors PR objectui#10511 onChartSchema. ForxAxisit follows the seat decision on the first round'sneeds_decision(comment5831464172, option A): the object only, with no string arm and no fold. No renderer changed.Implemented by the
os-devthat seatdomain:ui#2dispatched (claim5830527047, amended by that decision), sessionhttps://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN. There were five rounds:yAxis, thexAxispatch, then three changeset-prose rounds.1. Protocol first: the spec declares both keys on this node
Read from the installed
@objectstack/spec17.4.0, whichpnpm-lock.yamlpins:REACT_BLOCKSentry for theObjectChartreact block,schemaTypeis'object-chart',schemaisChartConfigSchemaitself (checked by identity), anddataPropsincludesxAxisandyAxis.ChartConfigSchema.xAxisis ONEChartAxisSchemawith no string arm; a bare string and a list are both refused atxAxis.ChartConfigSchema.yAxisis an ARRAY of them.ChartAxisSchemais a strict object with nine keys,fieldrequired, and a.default()onshowGridLinesand onlogarithmic.ComponentPropsMap['object-chart']entry. The spec skips that entry on purpose and says so incomponent.zod.ts.So a mirror that declares the spec's shapes is the protocol's own contract. It is not new vocabulary.
2. H1: reach path, measured with the real registry (one-time probe, not committed)
The probe rendered
SchemaRenderer, the real registry,ObjectChartBlock,ElementDataSourceGate,ObjectChart,ChartRendererandnormalizeChartSchema. OnlyAdvancedChartImplwas a double, and it recorded its props. Base wasa50600166.AdvancedChartImplreceivedpropertiesbag, dataset-bound)yAxes: [{ field: 'task_count', stepSize: 1 }]yAxisyAxesyAxisentryyAxes[0]xAxisxAxisrenewals-pipelinereact shape (objectName+aggregate+xAxis: { field }+yAxis: [{ field, format }])yAxes: [{ field: 'total', format: '$0,0' }],xAxisKey: 'status'Render leg, with no leaf double: the showcase node drew its y ticks as
0 1 2 3 4 5with itsyAxis, and as0 2 4 6 8without it. The node forwards the axis config, and the renderer acts on it.3. Per-key liveness read on
object-chartcarriers, with lit controlsThe scanner finds every
xAxis/yAxiswhose value is an object or a list. It reads the axis-level keys and attributes each hit to its carrier: an enclosing node'stype, or the enclosing JSX tag. The file population comes fromgit ls-treeof the same ref the reads use. Bare-string values were scanned separately.xAxisKey:object-chartcarrierobject-chartcarriera08e059command-centermetadata pageyAxis [{ field, stepSize }]; showcaserenewals-pipelinereact pagexAxis { field }andyAxis [{ field, format }]; the rest are docs (react-pages.mdx,validating-metadata.mdx), theobjectstack-uiskill and its eval, and lint testsa50600166xAxis { field }in the objectui#8168 render pin.examples/,content/,apps/andpackages/types/exampleshave 0, and theirchartTypecontrols light (2 / 11 / 3 / 1)4db37cdchartConfig, a different carrier)git ls-remoteasks for credentialsDesigner
inputsfor theobject-chartregistration:objectName,data,filter,aggregate,drillDown. There is no axis input; theaggregateinput is the control.object-chartnodefieldcommand-center(metadata,yAxis) andrenewals-pipeline(react,xAxisandyAxis)stepSizecommand-center(yAxis)formatrenewals-pipeline(yAxis)title,min,max,showGridLines,position,logarithmicThe six unwritten keys are declared as well. The spec declares all nine on this node's react block (section 1). The node's renderer reads all nine (probes P2 and P3). The dispatch fixes the binding as the spec schema by reference, with no hand-written subset. objectui#10511 closed the retire branch on the same grounds.
The non-spec shapes this PR now refuses on an
object-chartcarrier are a bare-string or listxAxisand a single-object or bare-stringyAxis. None appears in any of the three repos. The nearby bare-string hits are all report charts (ReportChartSchema). The one bare-stringxAxiswritten against this type is the objectui#8168 renderer-tolerance fixture (section 4).4. The change
packages/types/src/zod/objectql.zod.ts,xAxis:z.union([stripImportedDefaults(SpecChartAxisSchema), z.never()], { error }). Thez.never()arm admits nothing and adds nothing to the inferred type. It exists because zod runs a one-option union as its option, so a single-arm union's error hook never fires. Every refusal is oneinvalid_unionatxAxis, the shapeChartSchema.xAxisalready reports. A malformed object gets the axis arm's own issues, with their paths and in the spec's words, including its alias hint. A bare column name, a list or any other non-object gets the remedyxAxis: { field: 'status' }. ⛔ There is no string arm and no fold ontoxAxisKey: objectui#7113's alias stays scoped toChartSchema.yAxis:z.array(stripImportedDefaults(SpecChartAxisSchema)). A non-list gets a remedy message, and an issue inside an entry keeps its own path and the spec's wording.packages/types/src/objectql.ts: the TS twin,xAxis?: SpecChartAxisandyAxis?: SpecChartAxis[], typed with the spec'sChartAxis. ThexAxisKeymember doc now points atxAxis. The interface docblock records that neither census pin sees these two keys (see H4).packages/plugin-charts/src/ObjectChart.absentCategoryAxisRefusal-8168.test.tsx: ONE line, the surface extension the seat granted. The leg "a bare stringxAxis, the report surface spelling" pins the renderer's tolerance, andnormalizeChartSchemais unchanged. Its value now reads'stage' as unknown as ObjectChartSchema['xAxis'], the cast idiom that file'sUNAUTHORABLEhelper already applies toaggregate. The leg still pins the renderer, and the type no longer admits the shape. Reverse check: with the cast removed,tsc -p tsconfig.test.jsonin plugin-charts fails with TS2322 on that line.ChartAxisSchemais inIMPORTEDsince objectui#7690, andobjectql.zod.ts#ObjectChartSchemais inSPEC_DERIVED_PAIRS). Their ground comments now name objectui#10518 and both keys, and the pair's comment reads six spec crossings over five spec symbols. Membership is unchanged..changeset/10518-object-chart-y-axis-declared.md:@object-ui/typesminor, naming every document shape that now refuses on either key, and stating only what holds for every chart type; see section 6. The file name is kept from round one, and the frontmatter is byte-identical (md5ecedb10c5189b2c8841ea736b67c3cc6before and after the body edits).5. Pin, red on the base and green on the head
packages/types/src/__tests__/object-chart-axis-config-10518.test.tshas 39 tests:yAxis, blocks (a) to (e): the spec side read off the installed spec; the key in the mirror's own shape; every spec key accepted and kept; omitted defaults staying omitted (CONTROL: the raw spec schema injects them); the LIT CONTROL, the showcasecommand-centernode flattened the waySchemaRendererhoistsproperties; four entry refusals bycode+path, each message equal to the spec's own; the refusal atsafeValidateSchema, the doorobjectui validate/objectui checkrun; a single object or bare name refused withinvalid_type; and the TS twin through@ts-expect-errorrows plus anEqualtype pin.xAxis, blocks (f) to (j): the spec's one-object arm read off the spec, where a string and a list are refused atxAxis; the key in the mirror's own shape; every spec key accepted and kept; omitted defaults staying omitted; the object NOT folded, so noxAxisKeyis minted; the LIT CONTROL, the showcaserenewals-pipelineaxes (xAxis: { field },yAxis: [{ field, format }], with itsobjectName+aggregate), which parse on the mirror and throughsafeValidateSchema; four in-object refusals (min: 'zero',position: 'middle',grid, a missingfield), eachinvalid_unionatxAxiscarrying the spec's own message; the door; a bare name, a list and a number, each refused naming the{ field }remedy (a malformed object is NOT told the remedy); and the TS twin through four@ts-expect-errorrows plus anEqualtype pin.Every mutation committed the fix first, restored from
HEADby absolute path under a trap, and proved the restore by blob hash and an emptygit diff HEAD. The tests import the mirror from source, so nodist/is on the path.yAxisonly, at74cf402ed): both source faces reset to the base blobsefe2595f/2176166f. Observed 8 failed | 11 passed (19), plus TS2344 x1 and TS2578 x4, as predicted.yAxismember deleted. vitest stayed at 19 passed.tscgave the 5 errors above plus the parity ratchet's TS2322 namingobjectql.zod.ts#ObjectChartSchemaandyAxis.0b1b1c816): both faces reset to the base blobs; on disk, all four declaration counts went from 1 to 0. Predicted 19 failed / 20 passed, TS2344 x2 and TS2578 x8. Observed 19 failed | 20 passed (39). The 19 are the 8yAxisrefusals and shape pin, plus thexAxisshape pin, four in-object refusals, the door, three remedy refusals, the door for the bare string, and the CONTROL that a malformed object is refused.tsc -p tsconfig.test.jsonexited 2 with exactly TS2344 x2 and TS2578 x8. Restored: blobs51ad74bf/3dcbc78fmatchHEAD.xAxis?: SpecChartAxis;deleted. The first attempt was a no-op: a quoting slip left the anchor unmatched, the on-disk count stayed 1, and that reading was voided and rerun. The script now aborts when the mutation does not land. On the rerun the count went from 1 to 0 (blobd80951ba). vitest stayed at 39 passed.tscexited 2 with TS2344 x1 and TS2578 x4 in the pin, plus the parity ratchet's TS2322 namingobjectql.zod.ts#ObjectChartSchemaandxAxis. Restored and verified the same way.6. Gates, at
a6e5c6d77(the merge of main into the branch) unless notedpnpm exec vitest run packages/types/ examples/schema-catalog/test/ packages/plugin-charts/ packages/core/src/validation/__tests__/, plus the three cli validate/check suites andunfulfilled-chart-stubs-8760, from the repo root: 360 files / 8501 tests passed. At0b1b1c816, before the merge, the same set gave 359 / 8500.0b1b1c816:object-chart-undeclared-keys-8885,widget-schema-anchors-7946,zod-mirror-parity,imported-defaults-8317andObjectChart.absentCategoryAxisRefusal-8168gave 5 files / 208 tests passed, including "a bare stringxAxis, the report surface spelling".pnpm --filter @object-ui/types type-check(tsc, examples, tests): exit 0.@object-ui/plugin-charts, the package whose publishedObjectChartProps.schemais this type. Its dependency closure was built first (types, core, i18n, react, components, sdui-parser, data-objectstack, react-runtime). The rebuiltdist/objectql.d.tscarries both members. Thenpnpm --filter @object-ui/plugin-charts type-checkexited 0. The round-1 reverse check still stands: a scratch plugin-charts file typedyAxis: { field: 'n' }got TS2353. No other consumer writesxAxisoryAxison anObjectChart-typed value, per a grep of every file that namesObjectChartSchema,ObjectChartPropsor theObjectChartcomponent.node scripts/check-changeset-presence.mjs: 1 changeset for 6 source files in 2 released packages (the second is the plugin-charts test file).check-changeset-no-majorandcheck-changeset-overwrite: green;check-changeset-fixedwas green at0b1b1c816.check:changeset-claims, report-only, was run at0b1b1c816: the same 38 pending changesets as round 1, none naming the fixture file. The object-chart ones (objectui#8168, [finding]ObjectChartSchemastill leaves three keys undeclared thatObjectChart.tsxreads — andObjectChartBlock, the registry shell wrapping it, is still published as(props: any)#8885, finding(types,plugin-charts): ObjectChartSchema anchors nothing — ObjectChart is published asprops: any, and four keys its own producers write are declared on neither copy #7946) were read, and none is falsified.check:pending-changeset-literals: green.check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).check:control-bytesandcheck-type-check-coverage: green. At0b1b1c816:check:spec-symbols,check:phantom-deps,check:test-path-rootsandcheck:unreferenced-sourcesgreen, andcheck-governed-queue-guard --testover the diff NOT GOVERNED.--no-inline-config --format json, at0b1b1c816): 0 errors. Per rule, base against head on the five edited files is unchanged:no-explicit-anyis 34 to 34 inobjectql.ts, 1 to 1 inobjectql.zod.ts, 2 to 2 inzod-mirror-parity.test.ts, 0 to 0 inimported-defaults-8317.test.ts, and 3 to 3 in the 8168 test. The new pin has 1 warning, from theEqual OF anycan-fail control that it copies fromobject-chart-undeclared-keys-8885.test.ts; warnings are not gated. Each file is in ESLint's own population.eslint.config.jssets noparserOptions.project/projectService, so this diff cannot move another file's verdict.objectql.ts(objectui#8932's kanban docblock, a distant range, landed as PR objectui#10588), so main was merged in as a merge commit, with no rebase and no force-push. After the merge,git merge-tree --write-treeagainst the newestorigin/main(adeecd666) and against PR objectui#10595's branch (882c5e13e) is clean both times. At that point, main's later commits (throughadeecd666) touched none of this diff's files.0e710207d) measured each listed shape through the realobject-chartpath, whose runtime is byte-identical to basef169a030a. That round also corrected oneobjectql.zod.tscomment: a listxAxisis not a normalizer tolerance. Contract-review round 2 (record5832703331) then showed that whether a no-category or no-field shape draws depends on the chart type and on the rows' columns (anamecolumn, thevaluefallback), extending round 1's finding (record5832280197) that not every refused shape draws a chart. The changeset therefore names only what holds everywhere.5367bc102. The lead-in now says that several of the now-refused documents draw a chart today, and that what the rest draw depends on the chart type, on the rows and on the node's other category and series sources. The bullets keep only these equivalences:min/position, or an undeclaredgrid/logScale, changes nothing that is drawn;xAxisdraws what{ field }draws;fieldnames no category;yAxisentry or a singleyAxisobject with nofieldadds no series.They were measured on the published
normalizeChartSchemaand by rendering 11 chart types against 2 row sets, one of which carries anamecolumn. The frontmatter md5 is stillecedb10c5189b2c8841ea736b67c3cc6. Gates at5367bc102:check-changeset-presence✅,check-changeset-no-major✅,check:new-line-citationsVERDICT … 0 new citation(s),check:control-bytes✅, andcheck-changeset-claimsreport-only exit 0.git merge-tree --write-treeagainstorigin/maine0f820246is clean.b2936fc66, prose only. Contract-review round 3 found one clause false: the lead-in credited theminorlevel to what the refused documents draw. The lead-in now gives the reason the version policy reads, which is the narrowed accept set: breaking semantics, which this repo ships asminor. Nothing else changed, and the frontmatter md5 is stillecedb10c5189b2c8841ea736b67c3cc6.7. H4 and H6
object-chart-undeclared-keys-8885andwidget-schema-anchors-7946countschema.KEYreads inObjectChart.tsx. The census regex returns 16 reads for that file, and neitherxAxisnoryAxisis among them: the component forwards both by spreading the node. Neither key sat in either ledger, so nothing moved. Both pins pass at the head.content/docs/api/schema-reference.mdhas noObjectChartSchemarow set, andplugin-charts.mdxhas noobject-chartsection. PR objectui#10571 is on main, but this PR needs neither page, and neither was touched.Acceptance notes
xAxisField/yAxisFields/aggregationare declared on this node and not read there (a one-time probe; seat filing). A static-dataobject-chartwith onlyxAxisField/yAxisFieldsreachedAdvancedChartImplwith noxAxisKeyand no series. AnobjectNamenode withxAxisFieldand noaggregatedrew the objectui#8168 refusal (chart-missing-category-axis,role="alert"), andAdvancedChartImplwas never called. The seat filed it as objectui#10608, for its own ruling. Neither key was retired or changed here (H5).schema.KEYonly, and AGENTS.md's "a source grep's zero cannot answer this" rule applies. These keys needed the render probe. That is an observation; no card is proposed.propertiesbag is not aBaseSchemamember, sosafeValidateSchemaon the unhoisted envelope does not reachproperties.xAxis/properties.yAxis. The pins judge the hoisted shape and say so.renewals-pipelinereact node as a whole is not this mirror's vocabulary: the react wrapper parks itstypeasspecType, and itsseriesuses the spec's{ name }arm. The lit control therefore judges that producer's axis props and binding on a node withchartType. That is outside this card.maintombstonesxAxis/yAxison the dataset-bound dashboard WIDGET'schartConfigfor the next major. Those tombstones are scoped to that carrier, and they say the baseChartConfigSchemathat theObjectChartreact block publishes keeps both keys.Liveness reads, probes and ablations are one-time proofs. Nothing in this PR re-derives them except the pins above.
Generated by Claude Code