Skip to content

feat(types)!: retire ObjectChartSchema xAxisField / yAxisFields / aggregation on the object-chart node (objectui#10608) - #10734

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-10608-object-chart-legacy-axis-keys
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-10608-object-chart-legacy-axis-keys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10608
Clause-②: yes

What changes

ObjectChartSchema.xAxisField, yAxisFields and aggregation are retired from the object-chart node on both published faces (ADR-0049). They are the LIST-VIEW chart block's vocabulary; the list-view relays translate that block before they compose an object-chart node, and nothing on the node's own render path reads them.

  • TS face (packages/types/src/objectql.ts): each member is now ?: never, with a docblock that names the spec spelling and a @deprecated tag.
  • zod mirror (packages/types/src/zod/objectql.zod.ts): each member is a retirementTombstone(), so one guidance string is both the parse-time issue message and the .describe() metadata. The refusal is invalid_type at the key's own path and names the remedy: xAxis: { field: 'status' }, yAxis: [{ field: 'amount' }], aggregate: { field, function, groupBy }.
  • Docblocks that taught the old spelling on this node are rewritten: the interface docblock's xAxisKey bullet (which said the author-facing spelling on this node is xAxisField), the xAxisKey member doc, the yAxis member's note about yAxisFields, and the mirror's xAxisKey .describe() text. The interface docblock gains a short section on the retirement.
  • Changeset: minor for @object-ui/types, with the breaking semantics stated. It is not major, because of the version-alignment rule.
  • Pin: packages/types/src/__tests__/object-chart-legacy-axis-keys-retired-10608.test.ts.

Precedent followed. This follows the package's ?: never + retirementTombstone() convention (tombstone.zod.ts). The nearest case is ChatbotSchema.userAvatar / height (objectui#7703, pinned in chatbot-dark-keys-retired-7703.test.ts): a dead spelling that sits beside a live one is refused, with the live one as the remedy. ObjectKanbanSchema.groupField (objectui#7322) is the other node-local case. Deleting the members was not the route. BaseSchema is .passthrough(), so an undeclared key is KEPT, not refused. Pin block (d) measures that on a misspelling.

Measured before retiring (the card's stop condition)

The stop condition was a real producer that writes these keys ON an object-chart node. None was found.

H1, declaration. Confirmed on origin/main 5c61e5241, the branch point. main had moved past the dispatch's f9c06ef6a, but this block is unchanged. The three members are on the TS interface and on the mirror, and the docblocks cited above teach the old spelling.

H2, no reader. Measured with a render probe, not a grep. The probe was a throwaway test file, deleted and not committed. It used a real SchemaRenderer with the package's own registrations and mocked AdvancedChartImpl to capture its props. The static rows were status: open, n: 3 and status: done, n: 5.

node reached the chart xAxisKey prop series prop refusal data-source calls
xAxisField: 'status', yAxisFields: ['n'], aggregation: 'sum', static data yes none none none none
CONTROL: xAxis: { field: 'status' }, yAxis: [{ field: 'n' }], static data yes status dataKey: 'n' none none
the same three legacy keys + objectName: 'task' no — — chart-missing-category-axis find once
CONTROL: aggregate: { field: 'n', function: 'sum', groupBy: 'status' } + objectName: 'task' yes status not passed none aggregate once

I also read normalizeChartSchema's axis and series sections in full. It resolves the category from xAxisKey, then xAxis.field, then a bare xAxis string, and it derives series from yAxis[].field. It names none of the three keys. In the non-test sources of plugin-charts, components, react and core, the three names appear only in comments.

H3, producers.

  • In-repo object-chart composers: app-shell ObjectView (both chart branches), plugin-view ObjectView, plugin-list ListView, DashboardRenderer and DashboardGridLayout. Each one translates the keys, and none spreads them onto the node it builds. The two dashboard sites do spread chartConfigPresentation(widget.chartConfig), but that function's whitelist (read in packages/core/src/utils/chart-presentation.ts) emits none of the three.
  • The object-chart registration's inputs advertise none of the three.
  • examples/**, content/**, apps/**, skills/**: there are zero hits for xAxisField or yAxisFields. The aggregation hits belong to the report schema's field aggregation and to the grid's aggregations, not to this node. skills/** teaches none of the three on object-chart, so no governed row is split out.
  • objectstack main 16c5a33: the three showcase pages that build an object-chart / ObjectChart write dataset / dimensions / values, yAxis: [{ field, stepSize }], and xAxis={{ field }} with aggregate. They write none of the three keys. The only xAxisField / yAxisFields hits are spec, lint and doc text about removing the keys from list charts and dashboard widgets.
  • hotcrm main 2f7b232 (shallow clone): zero hits for xAxisField, yAxisFields, object-chart or ObjectChart. The positive control, chartType, does hit.

H4. objectql.zod.ts#ObjectChartSchema is in SPEC_DERIVED_PAIRS (zod-mirror-parity.test.ts). This diff adds no spec symbol reference and removes no spec crossing.

H5. The door is safeValidateSchema. The pin asserts the refusal there by code + path, and checks that the message carries the remedy.

H6. No census row moved. object-chart-undeclared-keys-8885, widget-schema-anchors-7946 and zod-mirror-parity read the mirror's shape keys, and the tombstones stay declared. All three pass unchanged.

Verification, at 856ecc953

Reverse verification. The fix was committed first. I then checked out objectql.ts and objectql.zod.ts from the base 5c61e5241 and confirmed the change on disk: the tombstone text had 0 hits and xAxisField: z.string() had 1 hit in the mirror; xAxisField?: never had 0 hits and xAxisField?: string had 1 hit in the interface.

  • The pin went 12 failed / 11 passed. The failures were the 8 refusal cases, the 3 message-equals-describe rows and the vocabulary row. The declared-key rows, the lit controls, the passthrough control and the list-view control stayed green.
  • tsc -p tsconfig.test.json exited 2 with 6 errors: 3 TS2344 on the Equal assertions and 3 TS2578 for unused @ts-expect-error directives.
  • I restored with git checkout HEAD --. Both blob hashes equal HEAD's, and git diff HEAD is empty.

Cross-package check against the rebuilt .d.ts. After the dependency-closure build, a temporary file in plugin-charts typed xAxisField: 'status' on an ObjectChartSchema literal. It failed with TS2322 ("Type 'string' is not assignable to type 'undefined'"), and the xAxis: { field } control line compiled. The file was then removed and the tree is clean.

Consumers (all DOWNSTREAM of @object-ui/types). Their dependency closures were built first with turbo run build --concurrency=2.

command reading
pnpm exec vitest run packages/types/ 241 files / 5300 tests passed
pnpm --filter @object-ui/types type-check exit 0 (the pin is in the tsconfig.test.json program; counted with --listFiles)
pnpm --filter plugin-charts, plugin-view, plugin-dashboard, plugin-list run type-check exit 0, all four
pnpm exec vitest run packages/plugin-charts/ 81 files / 957 tests passed
pnpm exec vitest run packages/plugin-view/ 51 files / 485 tests passed
pnpm exec vitest run packages/plugin-dashboard/ 136 files / 1281 tests passed

Gates (each exit code captured before any pipe):

gate exit
node scripts/check-changeset-presence.mjs 0
node scripts/check-changeset-no-major.mjs 0
pnpm check:new-line-citations 0 (0 new citations)
pnpm check:control-bytes 0
pnpm check:spec-symbols 0
pnpm check:test-path-roots 0
node scripts/check-governed-queue-guard.mjs --test (the 4 paths) 0, NOT GOVERNED
pnpm check:component-surface-parity · check:designer-field-key-parity · check:doc-types · check:handler-key-reads · check:prompt-keys · check:installed-pin-claims · check:action-forward-parity · check:registry-bare-names · check:changeset-claims · check:pending-changeset-literals · check:unreferenced-sources 0 each
pnpm check:doc-examples · check:doc-snippets · check:skill-examples exit 2 (PRECONDITION NOT MET, unbuilt packages) on the first run; exit 0 each after the scoped build each one prints
ESLint --no-inline-config, the 3 touched .ts files exit 0; 0 errors; warnings 34 / 1 / 0, the same counts as the base versions of the two edited files

The ESLint run was narrowed. The population is the repository-root eslint.config.js; there is no package-level config under packages/types. The --format json output counted 3 files. The config has no projectService / parserOptions.project, so type-aware linting is off, and this diff cannot move the verdict on any untouched file.

NOT MEASURED: the Spec Main Shape Gate. Reason: running it locally needs @objectstack/spec built from objectstack main as a tarball, plus a virtual-store injection and a typecheck of the whole repo. The only objectstack checkout here is shared and has no built spec. This diff adds or moves no spec symbol reference, only three local members. The gate's workflow runs on every pull request without a paths filter, so CI takes the reading.

Acceptance notes

  • The list-view carriers keep these names. That is deliberate and out of scope. app-shell and plugin-view ObjectView and plugin-list ListView still read xAxisField / yAxisFields / aggregation from a list view's chart config and translate them. On the authoring door, the spec's chart block (dataset-only) already refuses them, and the legacy options.chart bag admits them. Pin block (e) holds that boundary as a control. Whether that legacy path should also be retired is a separate question. Carrier: none named.
  • One reading differs from the card. The card's reproduction said the objectName variant drew the refusal with 0 leaf calls. My probe saw the same refusal after one find call. That does not change the verdict (the legacy keys still aggregate nothing and bind no axis), and it is recorded here rather than chased.
  • The aggregation refusal lists the spec's function vocabulary in its message. Pin block (c) re-derives that list from ChartAggregateSchema on every run, so the prose cannot drift from the spec silently.
  • Overlaps. main had not moved from the branch point 5c61e5241 when this PR was opened. objectui#10710 edits the same two files at ObjectDataTableSchema and the import line, and this PR's hunks are the ObjectChartSchema block only. objectui#10584 is held serial behind this card on the same block.
  • The changeset states the producer census as a one-time reading recorded here. It is not a live count.

Generated by Claude Code

…regation on the object-chart node

The three are the list-view chart block's vocabulary; the list-view relays
translate that block before composing an object-chart node, and nothing on the
node's own render path reads them. Both published faces declared them anyway,
so a node written with them type-checked, parsed green, and drew no category
axis and no series.

Each is now a `?: never` tombstone on the TS face and a retirementTombstone()
on the zod mirror, refused by name with the spec spelling as its remedy
(`xAxis: { field }` / `yAxis: [{ field }]` / `aggregate: { field, function,
groupBy }`). The docblocks that taught the old spelling on this node are
rewritten. The list-view carriers are untouched.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

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

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

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

.changeset/5903-objectgantt-declared-keys.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

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

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/7113-chart-data-model.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7200-object-form-section-style-keys-undeclared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7322-object-kanban-group-by-limit.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

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

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

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

.changeset/7363-objectql-union-arms.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.changeset/7632-shared-record-source-config.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    That ladder is published contract on both faces — packages/types/src/objectql.ts and its zod mirror both ship .describe() strings naming getDataConfig's order (objectui#6939, maintainer ruling 2026-09-02), pinned by objectql-record-source-refinement-6939.test.ts — and it was hand-copied into five plugin components with no gate holding them together. A change to the ruled order had five edit sites and nothing that noticed a missed one; that is the AGENTS.md #0.1 drift class.

.changeset/7804-objectql-handler-key-arms.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The four plain objectql.ts node faces declare the nine handler keys their registered renderers read (objectui#7804, the objectql.ts slice): ObjectFormSchema.onCancel / .onError / .onOpenChange / .onStepChange / .onSuccess, ObjectGallerySchema.onCardClick / .onRowClick, ObjectGridSchema.onNavigate and ObjectViewSchema.onNavigate.

.changeset/7804-tree-view-handler-slot.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    'runtime-slot' and not 'retired', measured at this key's own channel. 'retired' publishes "no renderer reads this key, so nothing could ever run it" — true of the two siblings already tombstoned on this arm (onSelectChange, onExpandChange) and flatly false here, since the read is live and INVOKED. ⚠️ No in-repo host builds a tree-view node carrying the key: the channel is wired end to end and only the supplier is absent, which is the same shape as ObjectFormSchema.onStepChange in this card's objectql.ts slice and is not evidence of a dead read. The TypeScript declaration is unchanged and still callable, so a programmatic host supplies it exactly as before.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/7963-alert-dialog-footer-keys-retired.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Nothing else moves. These spellings are overloaded across the tree and every other owner is a live key on a different declaration — FormSchema.cancelLabel, objectql.ts's confirmLabel, plugin-designer's ConfirmDialog React props, plugin-grid's def.confirmLabel, and plugin-form's ModalForm / DrawerForm, which build a local cancelLabel from schema.cancelText. None is an AlertDialogSchema; none is touched, and a pin asserts it. No fixture, catalog schema, example app or doc fence authored any of the three on an alert-dialog node, so no shipped document is stranded.

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

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8767-object-grid-refuses-string-sort.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    Migration. Write the array: sort: [{ field: 'name', order: 'desc' }]. Both keys are required. SortConfig.order carries no ? in @object-ui/types (packages/types/src/objectql.ts) and no .optional() in its zod mirror, and the protocol's own reusable SortItemSchema requires order as well — measured: that schema refuses [{ field: 'name' }] with invalid_value at 0.order. Do not omit it: this block's array arm interpolates whatever is present, so an omitted order lowers to $orderby: 'name undefined' today. That is pre-existing behaviour on the arm this change does not touch, and it is filed as a successor card rather than widened into here.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.
  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8885-object-chart-drilldown-title-compareto.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9092-inline-locale-declared-face.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    • AppComponentSchema.label (app.ts) - ObjectGridSchema.label and .description (objectql.ts) - PageNodeSchema.aria.ariaLabel (layout.ts)

.changeset/9309-object-gallery-filter-destination-typed.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

.changeset/9511-record-id-is-a-string.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

.changeset/9549-tree-filter-declared.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9550-object-tree-root-barrel.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectQLComponentSchema declares the node types an ObjectQL block may be. Every one of its arms was a named export of this package's root barrel except ObjectTreeSchema, which was declared in objectql.ts, applied by the union, and re-exported by the ./zod barrel (objectui#7917) — while no TypeScript consumer could name it. There is no ./objectql subpath to reach around the barrel: the package's exports map is pinned by packages/types/src/__tests__/package-exports-manifest.test.ts, and the root barrel was the only route to this type.

.changeset/9606-object-kanban-card-title.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

.changeset/9628-kanban-column-collapsed-honoured.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The key was declared on both published faces of the object-kanban arm — the lane element of ObjectKanbanSchema (objectql.ts and its Zod mirror) and the runtime lane KanbanColumn (complex.ts and its mirror) — and read by KanbanEnhanced alone, a module no production source imports. An authored { "id": "todo", "title": "To Do", "collapsed": true } therefore parsed green on both faces and reached a board that did nothing with it: KanbanImpl's only collapse is the SWIMLANE row's, held in viewer state under objectui:kanban-collapsed:ANGLE-BRACKETS(swimlaneField) and never keyed to a lane's declared value. That is the ADR-0049 declared-but-unhonoured shape.

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

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

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

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 5c61e5241 (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1561 pending declaration(s) that publish a body (2148 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3062.4 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-BZdvz7Mv.js —
Status PASS —

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 547.82KB 131.15KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.29KB 65.64KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 81.82KB 22.51KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.05KB 35.61KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 155.41KB 39.70KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Red check that is not this PR's: Inert vi.mock Specifier Check. Read at 2026-09-27T02:30Z on head 856ecc953.

The same check is red on main 5c61e5241. Its one offender is apps/console/src/__tests__/filterContextTokensSweep-10666.test.tsx:51, which PR objectui#10729 added and this diff does not touch. No fix exists yet; the anchor card is objectui#10731. The check is not in the main ruleset's required set (Lint, Type Check, Build & E2E, Build Docs, Changeset Declaration, Test). It reproduces deterministically on the base, so it is not re-run here.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 856ecc953931956542bc3d3eefc053c847e95992

① Derived judgments

  • TS face: xAxisField / yAxisFields / aggregation on ObjectChartSchema moved from string / string[] / enum to ?: never. Evidence: git diff origin/main...856ecc953 hunk packages/types/src/objectql.ts @@ -4280,12 +4297,39 (three members, @deprecated docblocks). BaseSchema carries [key: string]: any (packages/types/src/base.ts:592), so a deleted member would read any; a declared ?: never member overrides the index signature, and the pin's synthetic control assertionEqualCanFail (Equal<ObjectChartSchema['xAxisFeld'], undefined> is false) measures exactly that. Enforced in CI: .github/workflows/ci.yml:659 run: pnpm type-check inside the type-check job (job header at :132) → turbo run type-check → packages/types/package.json:69 "type-check": "tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json", and tsconfig.test.json includes src/**/*.test.ts. CI Type Check on the head: success (completed 2026-09-27T02:37:13Z). Verdict: correct accept-set change, CI-enforced.
  • zod face: the three members are retirementTombstone() refusals. packages/types/src/zod/tombstone.zod.ts defines it as z.never({ error: guidance }).optional().describe(guidance): issue code invalid_type, path [key], message identical to .describe(). Door: safeValidateSchema(schema) is AnyComponentSchema.safeParse(schema) (packages/types/src/zod/index.zod.ts:541), and AnyComponentSchema is defineNodeComponentUnion(z.discriminatedUnion('type', [...])) (:465) with ObjectChartSchema listed at :322; defineNodeComponentUnion is an identity wrapper (base.zod.ts:140, the old superRefine is gone), so a document { type: 'object-chart', chartType: 'bar', xAxisField: 'status' } reaches the arm and is refused with the tombstone message. The CLI doors call it: packages/cli/src/commands/validate.ts:59 and check.ts:146. BaseSchema ends .passthrough() (base.zod.ts:486), so a deletion would have kept the key silently; pin block (d) pins that on xAxisFeld. Verdict: refused at the doors with a message naming the remedy.
  • Remedy text vs the spec's actual shapes. objectstack origin/main packages/spec/src/ui/chart.zod.ts: ChartAggregateSchema (:956) is strictObject({ field?: string (optional, needed unless count), function: ChartAggregateFunctionSchema, groupBy: ChartGroupBySchema }) with ChartAggregateFunctionSchema = z.enum(['count','sum','avg','min','max']) (:875–877); ChartAxisSchema (:187) is a strictObject with field: z.string() required; PR feat(types)!: declare the spec's axis config on ObjectChartSchema.xAxis / yAxis (objectui#10518) #10601 declared xAxis as ONE axis object and yAxis as an array on this node. The three messages' remedies (xAxis: { field: 'status' }, yAxis: [{ field: 'amount' }], aggregate: { field, function, groupBy } with function one of count, sum, avg, min, max) match. Pin block (c) re-derives the vocabulary from the installed spec (@objectstack/spec 17.4.0 per pnpm-lock.yaml:4216) through SpecChartAggregateSchema.shape.function.options; the spec's lazySchema Proxy forwards property reads (packages/spec/src/shared/lazy-schema.ts:126–137), and all eight CI test shards are green, so that read works. Nit (non-blocking): the old TS arm cardinality is refused but the message does not spell cardinality → count; an author infers it from the listed vocabulary. Verdict: remedy text correct.
  • Producers writing the three keys ON an object-chart node. In-repo composers, each read on the head tree: app-shell packages/app-shell/src/views/ObjectView.tsx:2572–2634 builds { type:'object-chart', objectName, chartType, aggregate:{field,function,groupBy}, xAxisKey, series, filter, className } from chartConfig.xAxisField / yAxisFields / aggregation (translated at :2606–2609, no spread) and the dataset branch writes dataset/dimensions/values/chartType/xAxisKey/series; plugin-view ObjectView.tsx:2030–2063 same two shapes (:2048–2050 translate); plugin-list ListView.tsx resolveListChartBinding (:205–227) then the nodes at :3623 / :3644 with zero ... spreads in :3633–3665; DashboardRenderer.tsx:671 and DashboardGridLayout.tsx:311 spread only chartConfigPresentation(widget.chartConfig), whose whitelist emits showLegend/showDataLabels/title/subtitle/description/height/annotations/interaction/colors/categoryColors (packages/core/src/utils/chart-presentation.ts:415–460). packages/plugin-dashboard/src/WidgetConfigPanel.tsx:601 lists the names only in LEGACY_ANALYTICS_KEYS, which sanitizeDraftForType DELETES (a scrubber, not a producer). CreateViewDialog.tsx:277–284 writes them into a list-view chart sub-config (carrier, not this node). grep -rn -E "xAxisField|yAxisFields" examples content apps skills → 0 hits; aggregation: hits there are only content/docs/core/report-schema.mdx / guide/schema-overview.md report-field aggregation. objectstack main: git grep -E "xAxisField|yAxisFields" origin/main hits only spec/lint/docs (packages/spec/src/ui/view.zod.ts:1742 says the legacy inline xAxisField + yAxisFields + aggregation was removed in the cutover; dashboard.zod.ts:175 refuses them as LEGACY_WIDGET_ANALYTICS_KEYS); the three showcase producers write dataset/dimensions/values/chartType/colors/yAxis:[{field,stepSize}] (examples/app-showcase/src/ui/pages/command-center.page.ts:92–95) and aggregate={{field,function,groupBy}} xAxis={{field}} yAxis=[{field,format}] (renewals-pipeline.page.ts:166). hotcrm: not reachable from this session (api.github.com/repos/objectstack-ai/hotcrm → 403), so that half of the census rests on the dev's reading. Verdict: no live producer would be refused.
  • List-view carriers untouched and green. The diff touches exactly 4 files (2 in packages/types/src, the pin, the changeset); the carrier reads at app-shell ObjectView.tsx:2606–2609, plugin-view ObjectView.tsx:2048–2050, plugin-list ListView.tsx:214–215 are unchanged. ListViewSchema.options is z.record(z.string(), z.any()) (objectql.zod.ts, in the ListViewSchema block from :1392), so pin block (e) parses. CI Test (shard 1/8 … 8/8), Test, Test (dist pins) all success. Verdict: untouched and green.
  • Docblocks tell the truth. xAxisKey bullet / member doc: normalizeChartSchema.ts:244–245 hoists xAxis.field to xAxisKey, so "the spec xAxis object is the author-facing spelling" holds. xAxisField doc "on the inline objectName path the category is aggregate.groupBy": resolveChartCategoryField (packages/plugin-charts/src/ObjectChart.tsx:123–135) reads aggregate.groupBy first, then the normalized xAxisKey. yAxisFields doc "each entry's field is a plotted column when no series is written": normalizeChartSchema.ts:653–672 derives series from the y-axes when series is absent. The mirror's xAxisKey .describe() no longer names xAxisField. No reader of the three exists on the render path: non-test grep over plugin-charts/components/react/core src hits only a comment at ObjectChart.tsx:83. Nit (non-blocking): the interface docblock names the vocabulary as "chart.xAxisField … on a chart list view"; on today's spec the chart block (ListChartConfigSchema, dataset required, bound by reference at objectql.zod.ts:1420) refuses that spelling and the surviving admit is objectui's options.chart bag (app-shell still reads viewDef.chart.xAxisField at :2606). Wording imprecision only.
  • The pin can fail, and the reverse reading is coherent with it. The file holds 23 tests: (a) 8 refusal cases (1 + 2 + 5 values) + 3 message-equals-describe + 3 stays-declared; (b) 4; (c) 1; (d) 1; (e) 1; (f) 2. On the base shape the 8 refusals pass parse → fail; the 3 message rows dereference issuesOf(r)[0] of a success → TypeError → fail; (c) indexOf is −1 → the regex match is null → .map throws → fail; that is the claimed 12 failed. The 3 declared rows, (b), (d), (e) and (f) (types erased under vitest) pass: 11. tsc -p tsconfig.test.json on the base shape: 3 × TS2344 (Expect<Equal<string | undefined, undefined>>) + 3 × TS2578 (unused @ts-expect-error) = the claimed 6. Forward failure modes: re-widening any arm, dropping the remedy substring, spec function enum drift. Verdict: live pin, coherent reverse verification.

② Semver level

minor for @object-ui/types. Policy: objectui AGENTS.md:271 — "changeset 里不要声明 major … objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)", mechanically enforced by scripts/check-changeset-no-major.mjs (:272); @object-ui/types sits in the single 40-package fixed group of .changeset/config.json. CI Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report: all success. Changeset sentences checked against the tree:

  • "Breaking, marked minor under this repo's version-alignment rule (a major in the fixed group would move all of it off the @objectstack major)" — true (AGENTS.md:268–271).
  • "An object-chart node that authors any of the three keys now FAILS to validate" — true (tombstone arm behind safeValidateSchema; pin (a)).
  • "a TypeScript literal typed as ObjectChartSchema that sets one no longer compiles" — true (?: never; pin (f) enforced by the CI Type Check job).
  • "The three are the LIST-VIEW chart block's vocabulary" — true (spec view.zod.ts:1742 names them as the removed legacy inline list-chart query; the three objectui relays read them from a list view's chart config).
  • "The list-view relays translate that block into aggregate / xAxisKey / series before they compose an object-chart node" — true (three composers cited above).
  • "nothing on the node's own render path reads them" — true (grep census; normalizeChartSchema reads xAxisKey / xAxis / yAxis / series / categories).
  • "a node written with xAxisField: 'status' and yAxisFields: ['amount'] over static rows reached the chart with no category key and no series" — consistent with the reader census (no normalizer read → no xAxisKey, no derived series); the render probe itself is the dev's one-time reading and was not re-run here.
  • "on the inline objectName path it drew the 'no category axis' refusal" — mechanism confirmed: ObjectChart.tsx:1344 drawsFetchedObjectRows && !resolveChartCategoryField(schema) renders data-testid="chart-missing-category-axis" (:1346).
  • "both published faces declared the keys, so the node type-checked and parsed green through objectui validate" — true (base objectql.ts:4283–4288, objectql.zod.ts:2465–2467; validate.ts:59).
  • "?: never tombstone … retirementTombstone() … (ADR-0049; BaseSchema is .passthrough(), so deleting the declarations would have KEPT an authored value in silence)" — true (base.zod.ts:486).
  • "The refusal is an invalid_type issue at the key's own path whose message names the key and the spelling to write instead" — true (z.never({ error }); each guidance string contains the backticked key and the remedy).
  • The three remedy bullets — true against ChartAxisSchema / ChartAggregateSchema / ChartConfigSchema.xAxis (object) and .yAxis (array).
  • "Not touched: the list-view chart carriers keep these names — they are a different node and they read them" — true.
  • "A one-time producer census, recorded on the card's pull request and not re-derived here, found no producer writing the three on an object-chart node — in this repository, in the objectstack showcase, or in hotcrm" — objectui and objectstack halves verified true; the hotcrm half is not verifiable from this session (403) and is stated as a recorded reading, not a live count. Non-blocking.
  • "Pinned in packages/types/src/__tests__/object-chart-legacy-axis-keys-retired-10608.test.ts" — true (file in the diff).

③ Boundary flags

  • (a) The dev did not run the Spec Main Shape Gate locally. CI on the head: Spec Main Shape Gate success, completed 2026-09-27T02:35:39Z (run 36288515091, job 108533873409). The NOT MEASURED line is closed by CI; the diff adds no spec symbol reference (check:spec-symbols and Doc Component Type Check also green).
  • (b) The list-view legacy options.chart path still reads these names. Correctly out of scope: the card body says "The list-view chart carriers that DO read these names are a different node and stay", triage's execution note and the claim's file surface both say "⛔ Not the list-view chart carriers", and pin block (e) holds the boundary as a control (ListViewSchema.options is z.record(z.string(), z.any())). On the spec face the chart block already refuses the spelling (ListChartConfigSchema, dataset-only); the remaining live admit is objectui's options.chart bag, and the PR names its retirement as a separate question with no carrier filed. That is a follow-up the PM may choose to file, not a defect here.
  • (c) Inert vi.mock Specifier Check is red on the head with the same signature as objectui main 5c61e5241: both runs of scripts/check-vi-mock-specifiers.mjs report exactly one finding, apps/console/src/__tests__/filterContextTokensSweep-10666.test.tsx:51 -- vi.mock("../../../../packages/plugin-map/node_modules/react-map-gl/dist/maplibre.js") (head job 108533873705, main job 108526869853). That file was added by f9c06ef6a (PR fix: resolve context tokens in every directly authored data node's own filter (objectui#10666) #10729) and git diff --stat origin/main...856ecc953 -- apps/console is empty. Pre-existing on main, not this PR's.
  • (c) Final CI state read on the head: 43 check runs — 39 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot), 1 failure (the vi.mock check above). Type Check, Lint, Build Docs, Test (shard 1/8 … 8/8), Test, Test (dist pins), Spec Main Shape Gate, all five changeset checks, Governed Surface Queue Guard: green.
  • (c) The spec's own ChartAggregateSchema treats aggregation as an alias of function INSIDE the aggregate (chart.zod.ts:977); this PR's node-level tombstone on the top-level aggregation key does not collide with it.
  • (c) The PR is a dispatched-dev draft (card carries a Claim:), so per AGENTS.md:336 ② it stays draft until the dispatch seat flips it; no governed surface is touched (Governed Surface Queue Guard green, check-governed-queue-guard --test NOT GOVERNED per the report).
  • (c) Non-blocking nits recorded above: the cardinality → count mapping is implicit in the aggregation message; the interface docblock's chart.xAxisField names the pre-cutover carrier spelling rather than the surviving options.chart bag; the hotcrm census sentence is not independently verifiable from this session.

Implemented-by: claude/issue-10608-object-chart-legacy-axis-keys
Reviewed-by: session_014fWVhLzhxR8qrFsJ5o8TYW

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 05:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 9327397 Sep 27, 2026
44 of 45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10608-object-chart-legacy-axis-keys branch September 27, 2026 05:20
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…e html tier's intrinsic elements, marked tier html — div stays out (objectui#10735) (objectstack-ai#10753)

Fixes objectstack-ai#10735
Clause-②: yes — the published manifest's declared vocabulary grows.

Executes the maintainer's ruling A on objectstack#20112 (batch objectstack-ai#225 item
1, 「其他同意」): the published `sdui.manifest.json` declares the html tier's
registered intrinsic set exactly as the registry declares it, with each
tag's `inputs` and child slot; `div` stays undeclared. Base
`origin/main` 5ad3b88; head 6417e8d (round 2: wording only, one commit
on top of 5bd6270).

## What changed

- `packages/core/src/registry/html-tier-intrinsics.ts` (new):
`HTML_TIER_INTRINSICS` — the 37 tags of `html-elements.tsx`'s `TAGS`,
plus `span`, `table`, `label` and the seven sectioning tags of
`semantic.tsx` (47). Exported from `@object-ui/core` beside
`PUBLIC_BLOCKS`.
- `packages/core/src/registry/Registry.ts`: `getPublicConfigs()` appends
the roster's registered tags LAST, each stamped `tier: 'html'` in the
projection (never written onto the registration);
`PublicComponentConfig.tier` admits `'html'`. `PUBLIC_BLOCKS` is
untouched.
- `packages/sdui-parser`: `ManifestComponent.tier?: 'html'`;
`manifestFromConfigs` writes exactly `'html'` or omits the key (every
curated entry serialises byte-identically); `generateBlockList` sections
the html tier under its own count; `RegistryConfigLike.tier` admits
`'html'`. `gen-manifest.ts` header updated, behaviour unchanged.
- `packages/components/src/renderers/layout/react-page.tsx`: the
`kind:'react'` scope builder skips `tier === 'html'` entries, so no `P`
/ `A` / `Img` wrapper is injected (file-surface amendment, forced by the
card's ⛔ on silent widening — see H2).
- Tests: new pins in core, sdui-parser, console;
`react-page-scope.test.tsx` and `container-declaration-census.test.tsx`
updated; `public-contract.test.ts` prose amended (two ledger passages
that said curating a sectioning tag would newly widen the manifest).
- Docs: `content/docs/guide/react-pages.md` (the html tier is not
injected on the react tier), `content/docs/guide/plugin-development.md`
(the component-level `tier: 'html'` marker).
- `.changeset/10735-html-tier-manifest.md`: `minor` for
`@object-ui/core`, `@object-ui/sdui-parser`, `@object-ui/components`.
- Round 2 (contract review, wording): the react-page skip line is quoted
as it now reads — `if (!tag || cfg.isContainer || cfg.tier === 'html')
continue;` — in `content/docs/guide/react-pages.md` ("skips every
container and every html-tier entry"; frontmatter lines 1–4 md5
unchanged, 9c5f94a7…), in both `public-contract.test.ts` passages, in
the `container-declaration-census` pin, and in
`layout-containers-declare-containment.test.tsx` (comment-only,
previously untouched). `git grep -F` of the old line at head: 2 hits,
both inside `.changeset/6740-flex-is-container.md` — its original
paragraph and the dated note that marks it. The 10735 changeset now says
"200 ledgered findings" (10 ledger rows whose counts sum to 200).
- `.changeset/6740-flex-is-container.md` (pre-existing,
`@object-ui/components: patch`): reconciled in the objectui#10533
dated-note form, scoped "at this change" — `⚠️ **Dated note, 2026-09-27
— the skip line quoted above has since gained a third arm —
objectui#10735.**` — frontmatter byte-identical (md5 ce888ebd… before
and after). `check-changeset-overwrite` REPORTS the modification
(report-only, exit 0) and did not refuse it.
- ⛔ Not touched: `html-elements.tsx`, `div.tsx`, `PUBLIC_BLOCKS`, the
registration-uniqueness test.

## H1 — the census (measured from `ComponentRegistry.getKnownTypes()`
after `@object-ui/components` + `apps/console/src/register-plugins.ts`
load, in the console vitest project)

Instrument for "is an HTML tag": the keys of `JSX.IntrinsicElements` in
`@types/react` (178 names, HTML and SVG; lit control: `div`, `p`, `svg`
in, `object-grid` out). Registry: 485 known keys, 208 bare.
Intersection: **64** bare registrations carry an element name.

- **(a) `html-elements.tsx` `TAGS` — 37, all registered:** h1 h2 h3 h4
h5 h6 p a blockquote pre strong em b i u small mark sub sup del ins abbr
ul ol li dl dt dd figure figcaption img hr br time address cite q.
- **(b) registered elsewhere, declared — 10:** `span` (basic/span.tsx;
inputs className, children), `table` (complex/table.tsx; caption,
footer, columns, data, className — no child slot), `label`
(form/label.tsx; text required, className — no child slot), and `aside
main header nav footer section article` (layout/semantic.tsx; className,
children; `isContainer`).
- **(c) excluded — 17:**
- `div` — deprecated on the JSON surface in favour of `box`
(objectui#3965, PR objectui#6878); kept out by the ruling.
- `code`, `summary`, `object` — bare namespace fallbacks of `field:code`
/ `field:summary` / `field:object` (inputs `[]`, no child slot); `view`
— `plugin-view`'s bare key (SVG name coincidence). ⚠️ The ruling's list
names `code` as "registered elsewhere"; measured, the only `code`
registration is the field widget (a code editor / `pre` block reading
`value`). Declaring it would teach a tag whose children the runtime
drops, so it is NOT declared — see Acceptance notes and the report's
open question.
- `kbd` — a `ui` component reading `keys` / `label`, not named by the
ruling; not declared (additive later on a named need; retracting is a
narrowing).
- `form input textarea select switch` — JSON-surface form components;
the tier excludes form controls by design (the exclusion list in
`html-elements.tsx`). `progress`, `dialog` — `ui` components under tag
names, not passthroughs, not named.
- `button text image html` — already curated in `PUBLIC_BLOCKS`; they
reach the manifest from there, unstamped.

The (c) list is not prose:
`apps/console/src/__tests__/html-tier-manifest.test.ts` holds every
HTML-named bare registration to one of `PUBLIC_BLOCKS`,
`HTML_TIER_INTRINSICS` or its exclusion ledger `EXCLUDED_HTML_NAMED` (13
entries, each with a reason, each pinned live), so a registration that
newly squats on a tag name lands there by absence.

## H2 — the fork, the consumer map, and the marker decision

**Premise measured true:** `getPublicConfigs()` through
`manifestFromConfigs` is the sole source of the published manifest —
objectui's `apps/console/dev/manifest-dump.tsx` /
`scripts/dump-public-manifest.mjs`, and objectstack's
`scripts/gen-sdui-manifest-node.mjs` (which imports the 16 registry
modules from objectui's built tree at `.objectui-sha` and calls exactly
`ComponentRegistry.getPublicConfigs()` then its lockstep copy's
`manifestFromConfigs`). The manifest is ONE flat `components` map with
no tier marker; `compile()` derives `allowedTags =
Object.keys(manifest.components)`. So listing the html tags in
`PUBLIC_BLOCKS` would have widened the curated JSON-surface vocabulary
silently — the fork the card names.

**Marker chosen: a per-entry `tier: 'html'` on `ManifestComponent`,
carried from a projection-only stamp in `getPublicConfigs()`.** Why not
a second registry read or a second manifest section: the objectstack
generator calls `getPublicConfigs()` and nothing else, and `compile()`
reads only `components` — either alternative needs an objectstack-side
change before the gate accepts a single html tag, and the second-section
shape would leave the gate refusing the tags until its lockstep copy was
ported. With the per-entry marker the objectstack gate accepts the tags
with NO reader change: it ignores the extra key.

Consumers, and what each does with the marker:

| reader | reads | change |
|---|---|---|
| objectstack `packages/lint` `validateJsxPages` → `compile()` | keys as
whitelist; `validateTree` reads `inputs` / `namespace` | none — accepts
the tags; ignores `tier` |
| objectstack `scripts/check-sdui-manifest.mjs` | `components[k].type
=== k`, count, sha | none |
| objectstack `scripts/gen-sdui-manifest-node.mjs` |
`getPublicConfigs()` + lockstep `manifestFromConfigs` | none to run; ⚠️
its lockstep copy of `packages/sdui-parser/src/{index,types,codegen}.ts`
needs this port for the marker to reach the tracked file — until then
the tags are declared there WITHOUT `tier`, which the gate treats
identically |
| objectui `react-page.tsx` `buildComponentScope` | `getPublicConfigs()`
| skips `tier === 'html'` (else `P` / `A` / `Img` wrappers would be
injected) |
| objectui `generateBlockList` | manifest | sections the html tier;
curated count unchanged (59) |
| objectui `generateDts` | manifest | emits the tags into
`JSX.IntrinsicElements` (the html-tier authoring surface) |
| objectui `page.tsx` html-tier compile | `getKnownTypes()`, not the
manifest | none (already accepted these tags) |
| console census tests | `getPublicConfigs()` |
`container-declaration-census` reads the curated contract as the
unstamped entries; `public-contract` coverage lists filter by
`PUBLIC_BLOCKS` and are unchanged |

Reverse verification that consumers read the REBUILT `.d.ts`: a scratch
console file assigning `tier: 'json'` to a `ManifestComponent` and to a
`PublicComponentConfig` — `tsc --noEmit` exit 2, `TS2322: Type '"json"'
is not assignable to type '"html"'` and `… to type '"html" | "public" |
"internal" | undefined'`; the `'html'` assignments compiled. Probe
removed; `git status` clean.

## H3 — the regenerated artefacts

Generated with the repo's own generator (`buildArtifacts` in
`packages/sdui-parser/scripts/gen-manifest.ts`) over the console
registration graph with the 16 plugin modules eager-imported in
`manifest-dump.tsx` order, once on the base tree and once on head:

| artefact | base | head |
|---|---|---|
| `sdui.manifest.json` components | 59 | **106** (+47 added, 0 removed,
0 curated entries changed) |
| `div` / `code` / `kbd` present | no | no |
| entries with `tier: 'html'` / without | 0 / 59 | 47 / 59 |
| `sdui-intrinsics.d.ts` intrinsic entries | 59 | 106 (+253 lines, −0) |
| `sdui-blocks.md` | 65 lines, one table | 119 lines: `# SDUI public
blocks (59)` unchanged + `## html tier intrinsic elements (47)` |

Sample head entries: `a` → inputs className, href, target, rel, title,
children (slot); `br` → className only (void, no slot); `label` → text
(required), className (no slot). The base run's manifest differs from
objectstack's tracked `sdui.manifest.json` only because the pin f8a9d0f
predates this base (`cmp` differs at byte 1183) — the regeneration at
the next pin bump is the `domain:cli` lane's step.

## Pins — red on base, green on head

Two mutation legs (implementation files checked out at base, tests at
head; trap-guarded restores to `HEAD`, proven by `git diff HEAD` empty
and blob-hash equality — "RESTORE PROVEN" printed twice):

- Leg 1 — `Registry.ts`, sdui-parser `index.ts` / `types.ts` /
`codegen.ts`, `react-page.tsx` at base: `Test Files 5 failed (5)`,
`Tests 17 failed | 61 passed (78)` — every behavioural pin red (the
roster-only pins stay green by design).
- Leg 2 — `react-page.tsx` alone at base: `Tests 1 failed | 8 passed
(9)` — `a PascalCased html-tier tag is an unknown identifier on this
tier` red (an `Img` wrapper was injected).
- Green on head 5bd6270 (round 1) after the restores: `Test Files 9
passed (9)`, `Tests 114 passed (114)` over the five pin files plus
`registration-uniqueness.test.tsx` (unchanged, green),
`public-tier.test.ts`, `tier.test.ts`, `public-contract.test.ts`.

Round 2 (head 6417e8d): the three re-quoted test files plus
`react-page-scope` and `html-tier-manifest` re-run — `public-contract`,
`container-declaration-census`, `layout-containers-declare-containment`,
`react-page-scope`, `html-tier-manifest` — `Test Files 5 passed (5)`,
`Tests 89 passed (89)`.

Broader affected suites at head 5bd6270: console `html-tier-manifest`,
`public-contract`, `public-block-binding-reach`,
`record-block-record-reach`; components `react-page-scope`,
`container-declaration-census`, `container-declaration-ratchet`,
`layout-containers-declare-containment`, `registration-uniqueness`;
plugin-tree `registration.publicTier-10064` — `Test Files 10 passed`,
`Tests 137 passed` (plus the scratch generator run).

## Gates (exit captured before any pipe; verdict lines quoted)

- Build: `turbo run build --filter="@object-ui/console^..."
--concurrency=2` under the verify lock — `Tasks: 34 successful, 34
total`, `VERDICT command-exit 0`.
- Type-check (`pnpm run type-check`, script echoed): `@object-ui/core`
exit 0, `@object-ui/sdui-parser` exit 0, `@object-ui/components` exit 0,
`@object-ui/console` exit 0. `--listFiles` counts the new / edited test
files inside their programs (1, 1, 2, 1).
- Lint, CI-parity: `turbo run lint` over the four packages — `Tasks: 5
successful, 5 total`, 0 errors; eslint `--format json` over the 14
touched files: 0 errors, 22 warnings, all pre-existing `no-explicit-any`
lines not introduced here.
- `check:control-bytes` — `✅ check-control-bytes: OK (scanned 8928
tracked text file(s))`.
- `check:new-line-citations` — `VERDICT new-cross-file-line-citations: 0
new citation(s)`.
- `check-changeset-presence` — round 2: `✅ 15 source file(s) of 4
released package(s) changed, and this change declares 1 changeset(s)`;
`changeset:check` — `✅ No changeset declares a major bump` and `✅ All
workspace packages are in the changeset fixed group`.
- Round 2 text gates on head 6417e8d: `check:control-bytes` `✅ OK
(scanned 8928 tracked text file(s))`; `check:new-line-citations`
`VERDICT new-cross-file-line-citations: 0 new citation(s)`;
`check-changeset-claims` exit 0 (2 bodies judged, no negation; the
re-read list is unchanged); `check-changeset-overwrite` exit 0
report-only — `1 changeset(s) added, 1 modified, 0 deleted`, the
modified one being `.changeset/6740-flex-is-container.md` (the dated
note above); `check-doc-links` — `Links are valid across 17 scan roots.`
- `check:unreferenced-sources` OK; `check:pending-changeset-literals` `✅
No test source names a pending changeset`; `check:test-path-roots` OK;
`check:doc-types` `✅ Every documented component type is registered`;
`check:doc-fences` OK; `check:registry-bare-names` `✓ no new bare-name
collision`; `check:esm-specifiers` (specifier leg) OK;
`check:component-surface-parity` report-only exit 0.
- `check:changeset-claims` (report-only): 13 pending changesets name a
file this change touches; each re-read — the "forwards exactly seven
keys per input" claims stay true (this adds a component-level key, no
input key), and "none of the 8 is in the curated public contract" stays
true (the seven now enter the manifest as html-tier entries, not
curated).
- NOT MEASURED locally, declared to CI: `check:readme-exports` (exit 1 =
8 self-imports unjudgeable because `packages/cli` and
`packages/plugin-ai` are outside the built closure; 538 judged, 0
fabricated), `check:doc-snippets` (exit 2 PRECONDITION NOT MET, same
unbuilt packages; the two edited guides gained prose only),
`check:eager-closure` and `check:sdui-registration-pins` (need a console
build), repo-wide `pnpm lint` (per-package eslint via turbo covered the
four touched packages exactly as CI runs them; `eslint.config.js`
declares no type-aware `project`, so this diff moves no untouched file's
verdict).

## Serial

Claim read: no open PR touches `gen-manifest.ts`, `public-blocks.ts` or
`html-elements.tsx`. Round 1: `origin/main` fetched into the private ref
`refs/issue-10735/main` (01700dc, four commits past base), `git
merge-tree --write-tree HEAD refs/issue-10735/main` exit 0 (tree
368f01d). Round 2, before the push of 6417e8d: main re-fetched at
704e05b (nine commits past base: objectstack-ai#10741, objectstack-ai#10574, objectstack-ai#10736, objectstack-ai#10745,
objectstack-ai#10749, objectstack-ai#10734, objectstack-ai#10733, objectstack-ai#10744, objectstack-ai#10752), `merge-tree --write-tree` exit
0 (tree 0d9bcb9); the set of files main moved since base is disjoint
from this branch's files (`comm -12` empty), so no merge commit was
needed.

## Acceptance notes

- **`div` on the html tier is now split across two faces.** The
published contract refuses it (`forbidden-tag`), while the renderer's
declaration still reads `deprecated: { surfaces: ['json'] }`
(objectui#4000) and the console's html-tier compile (`page.tsx`,
`getKnownTypes()`) still accepts and renders it. The ruling's item 3
parks the renderer-side retirement in these notes: the reconciliation is
a separate card (declare the `html` surface on `div`'s deprecation with
`box` as the replacement, and decide whether the console compile should
read the same roster).
- **"Refused with `box` named as the replacement" has no mechanism
today.** The gate's refusal is the parser's generic `forbidden-tag` text
(the tag is not an allowed component); nothing in the manifest or the
parser names `box` for an absent `div`. `Registry.deprecationFor('div',
'json').replacement` names it, but the gate never reaches the registry.
Not implemented here (a new contract shape); raised in the report's open
questions.
- **`code`:** the `html-elements.tsx` header and ADR-0081 §2 both say
`code` is "registered elsewhere"; the only registration is
`field:code`'s bare fallback. On an html page today `code` resolves to
that widget and drops its text — a reproducible runtime defect
independent of this card; reported as an out-of-scope finding.
- **`label` and `table` are declared as registered**, so the HTML-shaped
`label` with text children draws `missing-required-prop` (`text`) and
`not-a-container`, and a `table` authored with element children draws
`not-a-container`; the registered spellings (`text="…"`, `columns` /
`data`) are clean. Pinned.
- **objectstack side, for the `domain:cli` lane:** regenerate
`sdui.manifest.json` at the pin; port
`packages/sdui-parser/src/{index,types,codegen}.ts` to the lockstep copy
so the marker reaches the tracked file; the ledger's 200 `a` / `div`
rows go stale (the `a` rows) or stay refused (the `div` rows, to be
rewritten to `box` as the ruling says).
- `semantic.tsx`'s comment ("none of these seven tags is in the curated
`PUBLIC_BLOCKS` contract, so there is no injected identifier") remains
literally true and its conclusion holds on two grounds now; not edited
(no registration change).

## Pending changesets re-read (round 2, head 6417e8d)

The `changeset-claim-re-read` bot lists 12 pending changesets naming
files this diff touches (13 in round 1, `6740` now being one this change
modifies). Each re-read paragraph by paragraph against head:

- `5905-componentinput-inputtype-tombstone.md` — still true:
`packages/sdui-parser/src/index.ts` still forwards exactly seven keys
per input (`name`, `type`, `of`, `required`, `enum`, `binding`,
`description`); this change's `tier` write sits at component level,
beside `type` / `namespace` / `isContainer`, and adds no per-input key.
- `5905-componentinput-retire-constraint-keys.md` — still true, same
seven-keys-per-input claim, same reason.
- `7493-componentinput-retire-label-defaultvalue-advanced.md` — still
true, same seven-keys-per-input claim; `min` / `max` / `step` /
`placeholder` / `label` / `defaultValue` / `advanced` still reach no
manifest.
- `6067-component-meta-derive-from-canonical.md` — still true: it
describes the pre-6067 shape of `Registry.ts` in the past tense; at head
`ComponentMeta` is still `CanonicalComponentMeta &
RegistryComponentMetaExtras`, and `RegistryComponentMetaExtras.tier` is
unchanged (`'public' | 'internal'`). The `'html'` value lives only on
the `PublicComponentConfig` projection.
- `componentinput-reexport-4972.md` — still true: `Registry.ts` still
re-exports `ComponentInput` from `@object-ui/types` (`export type {
ComponentInput } from '@object-ui/types'`).
- `default-children-retired-5051.md` — still true: none of the three
`ComponentMeta` declarations offers `defaultChildren`; this change adds
no `ComponentMeta` key.
- `6764-container-declaration-census.md` — still true as written:
`react-page.tsx` drops containers from the react scope and reads
`getPublicConfigs()`, and none of the 8 is in the CURATED public
contract. The seven sectioning tags now ride that read as html-tier
entries, stamped `tier: 'html'` and skipped, which leaves the claim's
operative words ("curated public contract") intact; not corrected.
- `9280-record-highlights-entry-icon-retired.md` — still true:
`gen-manifest.ts` still serialises registry `inputs` into
`sdui.manifest.json` and `sdui-intrinsics.d.ts` (the header now also
names the html tier's stamped entries; behaviour unchanged).
- `components-react-page-published-dts-text-5666.md` — still true:
`react-pages.md`'s injected-scope note and §Styling still say plain HTML
with inline `style`, not Tailwind; the guide's edited sentence quotes
the skip line and adds "and every html-tier entry", nothing about
styling.
- `page-source-tailwind-framing-5461.md` — still true: `react-page.tsx`
keeps both styling comments (the header note and the
`buildComponentScope` note); this change adds a comment inside the loop
and removes none.
- `page-source-tailwind-prose-retraction-5469.md` — still true:
`react-pages.md` §Styling wording is unchanged.
- `palette-canonical-discussion-5495.md` — still true:
`public-blocks.ts` still records `record:chatter` as the same block
"under a Salesforce-familiar name"; this change adds a header paragraph
and touches no roster entry.
- `6740-flex-is-container.md` — CORRECTED (prose-only): its paragraph
quotes the react-page skip line as `if (!tag || cfg.isContainer)
continue;`, which this PR changed; a dated note in the objectui#10533
form (`⚠️ **Dated note, 2026-09-27 — the skip line quoted above has
since gained a third arm — objectui#10735.**`) records the current line
and keeps the entry as the reading of that change. Frontmatter
byte-identical, md5 ce888ebde4729564f67bca69a7928743 before and after.
`check-changeset-overwrite` reports the modification (report-only, exit
0).

---
_Generated by [Claude
Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants