Skip to content

feat(spec,lint): the ui vocabularies admit what the renderers implement (objectui#2945) - #4070

Merged
os-zhuang merged 5 commits into
mainfrom
claude/spec-vocab-2945-additions
Jul 30, 2026
Merged

feat(spec,lint): the ui vocabularies admit what the renderers implement (objectui#2945)#4070
os-zhuang merged 5 commits into
mainfrom
claude/spec-vocab-2945-additions

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Additions-only follow-up to the vocabulary audit (objectstack-ai/objectui#2901, objectstack-ai/objectui#2945). Companion to #4061, which deletes the orphans; this one closes the gaps in the opposite direction.

Nothing here narrows a vocabulary. No accepted value stops being accepted, so no already-stored metadata and no in-flight payload changes meaning. That is why this is safe while the actual consolidation stays blocked on #3948.

ChartTypeSchema admits combo

The taxonomy could not name the one chart family the rest of chart.zod.ts is written for:

  • ChartSeriesSchema.type overrides a series' chart type — its own doc comment says "combo charts";
  • ChartSeriesSchema.yAxis binds a series to the left or right axis, which is only meaningful for mixed marks.

objectui's renderer draws it distinctly — mixed bar/line/area, dual axes, per-series type resolution (AdvancedChartImpl.tsx:819) — and had to carry combo in a local fork of this list, in a set whose comment claims to be "every value the spec's ChartTypeSchema admits".

This satisfies the taxonomy's own admission rule (in the NOTE below the enum): only families the default renderer draws distinctly. combo does; it is not a variant that falls back to a base family.

WidgetActionTypeSchema is ActionType

The two lists had drifted apart by one member, form, and the disagreement was backwards.

A dashboard header or widget action button dispatches through the same ActionRunner that implements form. objectui's DashboardRenderer routes everything except a raw url into it, deliberately — the code comment records that the previous modal/script allow-list meant a flow header action fell through to a warn and never dispatched (#3528).

So the narrower enum rejected at validation exactly what the shared dispatcher then executes at runtime. Derived rather than restated: the next type the runner implements needs one edit, not two.

dashboard.zod.tsaction.zod.ts is a new import edge with no cycle (action.zod.ts imports neither dashboard.zod.ts nor anything that reaches it).

ListChartConfigSchema.chartType is ChartTypeSchema.extract([...])

Same five members as before — a de-duplication, not a widening. A member renamed in the chart taxonomy now fails at build time instead of leaving a second list quietly disagreeing with the first. (.extract() resolves correctly through the lazySchema proxy — verified by execution, and by the guard.)

view.zod.tschart.zod.ts is likewise cycle-free (chart.zod.ts imports only i18n.zod and lazy-schema).

@objectstack/lint's chart-family set derives from the taxonomy

validate-widget-bindings decides which widgets need a chartConfig measure mapping from a hand-written list of families. Its omissions fail in the worst direction: an unlisted family reads as "not a chart", so a widget missing its measure mapping passes validation instead of being reported.

combo was exactly that case. Proven rather than asserted — pinning the old hand-written list back:

AssertionError: expected a warning for chart type 'combo': expected [] to have a length of 1 but got +0

The set is now the taxonomy minus an explicit MEASURE_EXEMPT_CHART_TYPES (single-value: gauge/solid-gauge/metric/kpi/bullet; tabular: table/pivot), so a family added to the spec is covered without editing the rule.

Guards

packages/spec/src/ui/vocabulary-derivation.test.ts asserts both derivations still hold — a restated list fails silently, since it keeps validating, just not what the other list says:

  • the list-chart subset admits exactly its five members, all of them taxonomy members, still rejects donut and combo, accepts each of the five, and defaults to bar;
  • WidgetActionTypeSchema's options equal ActionType's exactly, form parses, an unknown type still throws.

The lint suite now walks every multi-series family in the taxonomy rather than a list of its own.

One item from the audit deliberately not done

The audit also proposed promoting navigation into ActionType. I did not, and the reason is the audit's own thesis:

  • ActionRunner.executeNavigation is a strictly weaker executeUrl — no ${param.X} / ${ctx.X} interpolation, no apiBase promotion for /api/... paths, no openIn — differing only by a replace option;
  • its one live producer is the SDUI element:button action prop, and ElementButtonPropsSchema does not model an action prop at all (page block properties are z.record(z.string(), z.unknown()), so it is neither validated nor stripped today).

Promoting the name would add a second spelling of navigate to a closed authorable vocabulary — members cannot be removed later, per the audit's own rule — without closing the gap that actually exists, which is the undeclared element:button.action. Worth its own issue; not worth a one-way door here.

Verification

  • @objectstack/spec: 6944 tests across 267 files, green. tsc --noEmit clean.
  • @objectstack/lint: 544 tests across 37 files, green. tsc --noEmit clean for the touched file (two remaining diagnostics in this worktree are @objectstack/formula having no local dist types, in files this PR does not touch).
  • The lint guard was confirmed to fail before the fix, as quoted above.

Refs objectstack-ai/objectui#2945, objectstack-ai/objectui#2901, #3528, #4061

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 30, 2026 8:51am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/m and removed documentation Improvements or additions to documentation tests protocol:ui tooling labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec.

105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

os-zhuang and others added 4 commits July 30, 2026 16:36
…nt (objectui#2945)

Additions-only follow-up to the vocabulary audit. Nothing narrows, so no
already-stored metadata changes meaning.

- ChartTypeSchema admits `combo`. The taxonomy could not name the one
  family the rest of chart.zod.ts is written for: ChartSeriesSchema.type
  exists to override a series' type ("combo charts", per its own doc
  comment) and ChartSeriesSchema.yAxis binds left/right, which only means
  something for mixed marks. objectui draws it distinctly and carried
  `combo` in a local fork whose comment claimed to mirror this list.

- WidgetActionTypeSchema is ActionType. They disagreed by `form`, and
  backwards: a widget/header action dispatches through the same
  ActionRunner that implements `form` (DashboardRenderer routes everything
  but a raw `url` into it, deliberately — #3528), so the narrower enum
  rejected at validation what the dispatcher executes.

- ListChartConfigSchema.chartType is ChartTypeSchema.extract([...]) —
  same five members, a de-duplication rather than a widening.

- @objectstack/lint's chart-family set derives from the taxonomy. Its
  omissions failed in the worst direction: an unlisted family read as
  "not a chart", so a widget with no chartConfig measure mapping PASSED
  validation. Pinning the old hand-written list back reproduces exactly
  that for `combo` — zero findings — which is the new test's teeth.

ActionType deliberately does NOT gain `navigation`: executeNavigation is a
strictly weaker executeUrl (no ${param} interpolation, no apiBase
promotion, no openIn) differing only by `replace`, and its one live
producer is the SDUI element:button `action` prop that
ElementButtonPropsSchema does not model at all. Adding the name would put
a second spelling of "navigate" into a closed vocabulary without closing
the real gap.

spec 6944 tests / 267 files and lint 544 / 37 green; tsc clean on both.

Refs objectstack-ai/objectui#2945, objectstack-ai/objectui#2901, #3528

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ce docs regenerate

Two gates fired on the taxonomy addition, both correctly.

- app-showcase's coverage test requires a gallery widget for every
  distinctly-renderable ChartType, so admitting `combo` without
  demonstrating it failed. The Chart Gallery now shows one: task count as
  bars on the left axis, avg progress as a line on the right — the
  configuration series[].type / series[].yAxis exist for. Rows below it
  shift down by 4 to keep the 12-column layout non-overlapping.

- content/docs/references/ui/{chart,dashboard,report}.mdx regenerated for
  the new chart-type member and the two derived enums.

check:docs, check:api-surface and check:authorable-surface pass; the
showcase coverage suite is 13/13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`view.zod.ts` now imports `chart.zod.ts` (for the extracted list-chart
subset), so the taxonomy joins that skill's transitive reference set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rt type

Fifth generated artefact tracking the spec surface: the react-blocks
contract and its reference table inline the ChartType union.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xuyushun441-sys
xuyushun441-sys force-pushed the claude/spec-vocab-2945-additions branch from bd8ffb8 to f93da17 Compare July 30, 2026 08:36
check-i18n-coverage freezes each example's untranslated-declared-string
count and fails on growth; the `combo` widget's title took app-showcase
from 456 to 457. Translated in both locales the example claims to support,
which is the remedy the bundle's own `globalActions` comment prescribes.

The gallery's other 20 widget titles predate that ratchet and stay part of
the frozen baseline — this does not ratchet the number up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@os-zhuang
os-zhuang merged commit 1bd2795 into main Jul 30, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/spec-vocab-2945-additions branch July 30, 2026 09:04
xuyushun441-sys pushed a commit that referenced this pull request Jul 30, 2026
…tionSchema (objectui#2997)

element:button's renderer reads `properties.action` and dispatches it
through the ActionRunner — it is the only thing making a standalone-page
button interactive — and ElementButtonPropsSchema did not declare it.

It is authored anyway: cloud's service-tenant pages carry five sites across
the billing and pricing funnel, each `{type:'navigation', to: …}`, each cast
`as any`.

An undeclared prop is STRIPPED, not ignored — safeParse returned success
with no `action` in `data`. Harmless only because page block `properties`
are still z.record(z.string(), z.unknown()); the moment that is tightened
to validate against ComponentPropsMap, every one of those buttons loses its
action at save with a green parse.

InlineActionSchema is derived, not a new dialect. ActionSchema is
z.object(…).refine(…).refine(…), so `.pick()` is unavailable on the export;
the object half is now a factory both build from, preserving lazySchema's
deferral. The inline schema picks the twelve fields element:button actually
forwards, sharing their describe() text, the ActionType vocabulary and the
target-required refinement.

`name`/`label` are optional — a registry entry needs an identity and a menu
label, an inline action has neither (the button supplies its own label).
Registry-only concerns are excluded, as are icon/variant (the button has its
own) and body (a page button running a sandboxed script is a separate
decision).

`navigation` and `to` become normalizing aliases folding to `url`/`target`,
the VIEW_FILTER_OPERATOR_ALIASES pattern: cloud's pages keep validating
while output is always canonical, and the aliases can be tombstoned once
producers migrate. `url` is also the better target — its runner path has
${param} interpolation, apiBase promotion and popup-safe openIn; the
navigation path has none of those.

NOT done: promoting `navigation` into ActionType bare (declined in #4070) —
it names the type while leaving the prop undeclared and `to` homeless, and
adds a permanent synonym to a vocabulary whose members cannot be removed.

Nothing narrowed; all generated surfaces gain entries only. spec suite
7038/271 green, tsc clean, all twelve check:* gates pass.

Refs objectstack-ai/objectui#2997, objectstack-ai/objectui#2945, #4070

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xuyushun441-sys pushed a commit that referenced this pull request Jul 30, 2026
…tionSchema (objectui#2997)

element:button's renderer reads `properties.action` and dispatches it
through the ActionRunner — it is the only thing making a standalone-page
button interactive — and ElementButtonPropsSchema did not declare it.

It is authored anyway: cloud's service-tenant pages carry five sites across
the billing and pricing funnel, each `{type:'navigation', to: …}`, each cast
`as any`.

An undeclared prop is STRIPPED, not ignored — safeParse returned success
with no `action` in `data`. Harmless only because page block `properties`
are still z.record(z.string(), z.unknown()); the moment that is tightened
to validate against ComponentPropsMap, every one of those buttons loses its
action at save with a green parse.

InlineActionSchema is derived, not a new dialect. ActionSchema is
z.object(…).refine(…).refine(…), so `.pick()` is unavailable on the export;
the object half is now a factory both build from, preserving lazySchema's
deferral. The inline schema picks the twelve fields element:button actually
forwards, sharing their describe() text, the ActionType vocabulary and the
target-required refinement.

`name`/`label` are optional — a registry entry needs an identity and a menu
label, an inline action has neither (the button supplies its own label).
Registry-only concerns are excluded, as are icon/variant (the button has its
own) and body (a page button running a sandboxed script is a separate
decision).

`navigation` and `to` become normalizing aliases folding to `url`/`target`,
the VIEW_FILTER_OPERATOR_ALIASES pattern: cloud's pages keep validating
while output is always canonical, and the aliases can be tombstoned once
producers migrate. `url` is also the better target — its runner path has
${param} interpolation, apiBase promotion and popup-safe openIn; the
navigation path has none of those.

NOT done: promoting `navigation` into ActionType bare (declined in #4070) —
it names the type while leaving the prop undeclared and `to` homeless, and
adds a permanent synonym to a vocabulary whose members cannot be removed.

Nothing narrowed; all generated surfaces gain entries only. spec suite
7038/271 green, tsc clean, all twelve check:* gates pass.

Refs objectstack-ai/objectui#2997, objectstack-ai/objectui#2945, #4070

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…tionSchema (objectui#2997) (#4135)

element:button's renderer reads `properties.action` and dispatches it
through the ActionRunner — it is the only thing making a standalone-page
button interactive — and ElementButtonPropsSchema did not declare it.

It is authored anyway: cloud's service-tenant pages carry five sites across
the billing and pricing funnel, each `{type:'navigation', to: …}`, each cast
`as any`.

An undeclared prop is STRIPPED, not ignored — safeParse returned success
with no `action` in `data`. Harmless only because page block `properties`
are still z.record(z.string(), z.unknown()); the moment that is tightened
to validate against ComponentPropsMap, every one of those buttons loses its
action at save with a green parse.

InlineActionSchema is derived, not a new dialect. ActionSchema is
z.object(…).refine(…).refine(…), so `.pick()` is unavailable on the export;
the object half is now a factory both build from, preserving lazySchema's
deferral. The inline schema picks the twelve fields element:button actually
forwards, sharing their describe() text, the ActionType vocabulary and the
target-required refinement.

`name`/`label` are optional — a registry entry needs an identity and a menu
label, an inline action has neither (the button supplies its own label).
Registry-only concerns are excluded, as are icon/variant (the button has its
own) and body (a page button running a sandboxed script is a separate
decision).

`navigation` and `to` become normalizing aliases folding to `url`/`target`,
the VIEW_FILTER_OPERATOR_ALIASES pattern: cloud's pages keep validating
while output is always canonical, and the aliases can be tombstoned once
producers migrate. `url` is also the better target — its runner path has
${param} interpolation, apiBase promotion and popup-safe openIn; the
navigation path has none of those.

NOT done: promoting `navigation` into ActionType bare (declined in #4070) —
it names the type while leaving the prop undeclared and `to` homeless, and
adds a permanent synonym to a vocabulary whose members cannot be removed.

Nothing narrowed; all generated surfaces gain entries only. spec suite
7038/271 green, tsc clean, all twelve check:* gates pass.

Refs objectstack-ai/objectui#2997, objectstack-ai/objectui#2945, #4070

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant