Skip to content

fix(spec-parity): the Tier-3 spec values render instead of red-boxing (#2943) - #3011

Merged
os-zhuang merged 1 commit into
mainfrom
claude/tier3-loud-failures
Jul 30, 2026
Merged

fix(spec-parity): the Tier-3 spec values render instead of red-boxing (#2943)#3011
os-zhuang merged 1 commit into
mainfrom
claude/tier3-loud-failures

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2943 (Tier 3 of the #2901 audit — spec values that fail loudly; audit doc). Follows #2993 (Tier 1) and #3008 (Tier 2).

This tier renders SchemaRenderer's red role="alert" panel naming the missing type and dumping the schema JSON. Cheaper to triage precisely because it cannot hide.

Dashboard chart dispatch

Three surfaces restated the chart vocabulary independently and disagreed — DatasetWidget covered all 19 ChartTypeSchema values, DashboardRenderer 15, DashboardGridLayout 8. A type a surface didn't name fell through to { ...widget }, whose type no component registers.

The sharpest part was already half-done, exactly as the issue observed: METRIC_LIKE_TYPES existed with a docstring saying gauge/solid-gauge/kpi/bullet "render as a metric card rather than a chart" — and was consulted only to pick a grid span. The tile was sized correctly and the widget was never routed.

New widgetDispatch.ts is the single classification (series / metric / table / pivot / custom / unsupported / passthrough); both renderers consume it. Net effect:

Before Now
gauge / solid-gauge / kpi / bullet red box (renderer), red box (grid layout) metric card, object-bound or static
radar / treemap / sankey drawn (renderer), red box (grid layout) drawn in both
heatmap / candlestick / … placeholder (renderer), red box (grid layout) labelled placeholder in both
list its own near-duplicate branch folded into table, lighter chrome preserved

#3008 made this cheaper than filed: AdvancedChartImpl already renders single-value families as a number card, so this is routing, not a new renderer.

The trap: ai:chat_window

Offered by the Studio palette with a config panel (block-types.ts, block-config.ts) while placeholders.tsx documents a deliberate exclusion so it produces a loud unknown-type error. The reasoning was sound; the palette was never told. An author dragged a block Studio advertised and got a red box.

Pruned from both palette artifacts per the audit's recommendation — the floating chat overlay (plugin-chatbot) stays canonical.

The soft gaps turned out to be one defect, not seven

The audit lists 7 types as "dashed placeholder, but only in hosts that call registerPlaceholders()". Tracing it: registration is entirely opt-in and only apps/console calls it, so four blocks the palette does offer — nav:menu, nav:breadcrumb, global:search, ai:suggestion — red-boxed in every other host purely because that host skipped an optional bootstrap.

Those four now register eagerly through the ./renderers barrel. The rest of the protocol vocabulary stays opt-in on purpose: auto-registering everything would mask genuinely missing renderers, which is the same reasoning that keeps ai:chat_window loud. The three shell singletons (app:launcher, global:notifications, user:profile) are not page blocks at all — a page referencing one is a misconfiguration, so the red box is correct and they're recorded as palette exclusions instead.

The guard is inverted, as the issue asked

block-config.test.ts hand-asserted a few palette exclusions, which locks drift in — a new spec block type could land and never reach the palette with nothing failing. It now derives coverage from PageComponentType (34 values) and asserts:

  • every value is offered or listed in the new PALETTE_EXCLUSIONS with a reason;
  • no value is both offered and excluded;
  • no exclusion names a non-spec type (no stale entries);
  • a block with a config panel is a block the palette offers — the seam ai:chat_window hid in.

Inverting it immediately caught four wrong entries in my own first exclusion list (page:sidebar, record:activity, record:chatter, record:reference_rail are genuinely offered), which is the point.

Verification

  • Suites over the 4 touched packages: 3755 tests green (24 skipped), including 17 new tests across two guards.
  • pnpm build 43/43; tsc --noEmit clean on all 4; eslint 0 errors on every touched file.

Refs #2901. With this, all three renderer-facing tiers are done. Remaining: #2944 (forks — already in progress elsewhere) and #2945 (vocabularies; Track C still blocked on objectstack#3948).

🤖 Generated with Claude Code

…#2943)

This tier fails LOUDLY — `SchemaRenderer` renders a red `role="alert"` panel
naming the missing type and dumping the schema JSON. Three rows:

**Dashboard chart dispatch.** Three surfaces restated the chart vocabulary
independently and disagreed: `DatasetWidget` covered all 19 `ChartTypeSchema`
values, `DashboardRenderer` 15, `DashboardGridLayout` 8. A type a surface
didn't name fell through to `{ ...widget }`, whose `type` no component
registers. The sharpest part was already half-done: `METRIC_LIKE_TYPES`
existed with a docstring saying gauge/solid-gauge/kpi/bullet "render as a
metric card rather than a chart" — and was consulted only to pick a grid span,
so the tile was sized correctly and the widget was never routed.

One shared `widgetDispatch` module now classifies a widget type into its family
(series / metric / table / pivot / custom / unsupported / passthrough) and both
renderers consume it. The single-value families reach the metric card, the
dropped families get a labelled placeholder in the grid layout too, and the
`list` branch folds into `table` while keeping its lighter chrome.

**`ai:chat_window` was offered by Studio with a config panel and has no
renderer.** `placeholders.tsx` documents a deliberate decision to exclude it so
it fails loudly; the palette was never told, so an author dragged a block
Studio advertised and got the red box. Pruned from `block-types.ts` and
`block-config.ts` per the audit's recommendation — the floating chat overlay
(plugin-chatbot) stays canonical.

**Placeholder coverage was host-dependent.** `registerPlaceholders()` is
opt-in and only `apps/console` calls it, so four blocks the palette DOES offer
(`nav:menu`, `nav:breadcrumb`, `global:search`, `ai:suggestion`) red-boxed in
every other host. Those four now register eagerly through the `./renderers`
barrel; the rest of the protocol vocabulary stays opt-in on purpose, because a
genuinely missing renderer must keep failing loudly.

**The guard is inverted, as the issue asked.** `block-config.test.ts` used to
hand-assert a few palette EXCLUSIONS, which locks drift in — a new spec block
type could land and never reach the palette with nothing failing. It now
derives coverage from `PageComponentType` (34 values): every value must be
offered or listed in the new `PALETTE_EXCLUSIONS` with a reason, no value can
be both, no exclusion may name a non-spec type, and a block with a config
panel must be authorable (the seam `ai:chat_window` hid in). Inverting it
immediately caught four wrong entries in my own first exclusion list.

Refs #2943, #2901

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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)
objectui Ignored Ignored Jul 30, 2026 10:03am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-Cv6AbVWH.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.25KB 1.01KB
auth (org-roles.js) 6.72KB 2.85KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 456.16KB 99.60KB
core (index.js) 2.16KB 0.78KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 134.67KB 34.24KB
fields (index.js) 222.07KB 54.35KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 60.52KB 17.11KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 111.59KB 28.74KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 216.52KB 53.02KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 104.48KB 25.29KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 180.86KB 47.45KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 102.39KB 24.18KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 40.32KB 10.53KB
plugin-timeline (index.js) 25.75KB 7.32KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.88KB 20.99KB
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.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 3.47KB 1.54KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 2.00KB 0.96KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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 (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 1.08KB 0.64KB
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

@os-zhuang
os-zhuang merged commit 8997889 into main Jul 30, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/tier3-loud-failures branch July 30, 2026 10:07
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…e spec adopting one (#2945) (#3016)

The parity guard added in #3011 asserted `dialect === ['combo']` — an exact
match against the one name objectui draws that the resolved spec does not
define. That is the wrong shape for a drift guard: framework#4070 has already
promoted `combo` into `ChartTypeSchema`, so the moment objectui bumps to a spec
version carrying it, `dialect` becomes `[]` and the test fails with "expected
[] to deeply equal ['combo']" — a cryptic red for the good outcome, landing on
whoever does the bump rather than on whoever adds dialect.

Now the tracked set is an upper bound: only UNTRACKED dialect fails, so the
list shrinks silently as the spec adopts a name, and an entry the spec has
since defined is reported via console.info so it gets pruned instead of
accumulating as a stale exception.

No renderer change; the coverage assertions are untouched.

Refs #2945, #2943

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tier 3 (#2901): unregistered PageComponentType values, and the ai:chat_window palette trap

1 participant