…e charts plugin never fulfils
`apps/console` registered ten chart variants as `registerLazy` stubs pointing at
`@object-ui/plugin-charts`. That package registers eight keys, and three of the
ten -- `line-chart`, `area-chart`, `advanced-chart` -- were not among them.
An unfulfilled stub does not fail, it succeeds at being useless. At render,
`SchemaRenderer`'s lazy branch re-checks `hasLazy(type)` on every pass and
`Registry.register()` deletes a lazy entry only for the keys the loaded module
actually registers -- so an unfulfilled key keeps its entry and every pass
returns the placeholder. Measured through the real chain: `line-chart` painted
`Loading line-chart...` permanently, with no alert and no error, rather than the
OBJUI-001 panel. At authoring, a stub is enough to enter `getKnownTypes()`, so
`check:doc-types` and the CLI's `KNOWN_SCHEMA_TYPES` snapshot blessed all three
and `content/docs/plugins/plugin-dashboard.mdx` taught one of them.
Removal rather than implementation: `line-chart` / `area-chart` duplicate
families the plugin already draws as `chart` + `chartType`, `advanced-chart`
named an internal module, and an authored-usage sweep over both repositories
found exactly one occurrence -- the doc snippet corrected here -- against lit
controls in the same commands.
Both console stub loops move, because the doc gate's key universe is their
union; the dashboard snippet is corrected to `chart` + `chartType: "line"`; the
generated CLI snapshot is regenerated; and the `line-chart` leg of
`node-slot-registered-arms-8499.test.ts` is re-pointed, reading the stub list
rather than the file so the retirement comment cannot satisfy it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
Fixes #8760
Ten chart variants were registered as
registerLazystubs pointing at@object-ui/plugin-charts. That package registers eight keys, and three of the ten —line-chart,area-chart,advanced-chart— were not among them. This PR retires those three, corrects the doc that taught one of them, and pins the outcome so the shape cannot come back.1. What a registered-but-unfulfilled key actually does (measured, and the card's premise needed correcting)
The card says the key "paints OBJUI-001". It does not. Measured on
b775500afthrough the real chain (console registrations, realloadLazy, realSchemaRenderer):data-lazy-loadingpie-chart(fulfilled control)line-chartstatusline-chartLoading line-chart…area-chartstatusarea-chartLoading area-chart…advanced-chartstatusadvanced-chartLoading advanced-chart…totally-unknown-zzz(unknown-key control)alertUnknown component type: … (OBJUI-001)The mechanism:
SchemaRenderer's lazy branch re-checkshasLazy(type)on every render pass, andRegistry.register()deletes a lazy entry only for the keys the loaded module actually registers. An unfulfilled key therefore keeps its entry after the load and every later pass returns the placeholder again. So the observed failure was an endless skeleton, not a red panel — no alert, no error, no console warning. A spinner that never resolves reads to a user as a slow network, which is why this outlived the card that first noticed it.That makes the grading argument stronger, not weaker: the unknown-key control on the same row is refused loudly, immediately.
2. M1 — fulfilment per variant, measured as the CALL and not the NAME
Driven through the console's own registrations, then
await ComponentRegistry.loadLazy(key), then a registry re-check — which is whatloadLazy's own docblock instructs callers to do, because it resolves "whether or not the loaded module actually registered the expected type".object-chartObjectChartBlockbar-chartChartBarRendererpie-chartChartRendererdonut-chartChartRendererradar-chartChartRendererscatter-chartChartRendererchart:barChartRendererline-chartundefinedarea-chartundefinedadvanced-chartundefinedSeven lit controls in the same command, so the probe is looking rather than broken.
getKnownTypes()contained all ten — which is the authoring-side half of the defect.3. M2 — the authored-usage sweep, and the stop condition it clears
R3 makes a positive finding a STOP. Swept both repositories for authored NODES of the three types (not filenames, not prose), with lit controls in the same command:
typeline-chartarea-chartadvanced-chartbar-chart(lit control)chart(lit control)object-chart(lit control)The single hit is
content/docs/plugins/plugin-dashboard.mdx:210— the doc the card itself puts in the deliverable. No example app, fixture, seed document, preview sample, e2e spec or deployment metadata authors any of the three. ⇒ no STOP.Two near-misses worth naming because they look like usage and are not:
examples/schema-catalog/src/schemas/plugin-charts/area-chart.jsonandadvanced-line-chart.jsonare FILENAMES. Both author{ "type": "chart", "chartType": "area" | "line" }, which is the spelling that already draws.content/docs/guide/component-registry.md:351showsComponentRegistry.register('line-chart', LineChart)as a generic example of the register API in a reader's own plugin, not as an ObjectUI key. It is untouched.Controls discarded rather than kept for padding:
pie-chartandobject-chartas authored-node controls in objectui both read 0 on both sides, so neither is a control here —object-chartearns its place only in the objectstack column, where it reads 1.4. The repair chosen, and the argument against the other
Removal (R2's default). The argument against fulfilment is not just "no demand":
line-chartandarea-chartwould duplicate, under a second node key, families the plugin already draws as{ "type": "chart", "chartType": "line" | "area" }— resolved byCHART_TYPE_KEYWORD_FAMILIES. Two spellings for one capability is the dialect problem AGENTS.md commandment 0.1 exists to refuse.advanced-chartwas never a family at all. It namesAdvancedChartImpl, an internal module — registering it would publish an implementation name as an authorable key.packages/types/src/__tests__/node-slot-registered-arms-8499.test.tsrecords objectui#8499's decision to leaveline-chartunarmed because that card's triage admits arms only for renderers that already draw correctly at runtime — and it carriesarea-chartin itsUNREGISTEREDfiring-control array, i.e. the repo already asserts that nothing registers it. Fulfilment would red an existing, deliberate control.Both stub loops move, and that is load-bearing rather than scope creep.
apps/console/src/preview-gallery.tsxis a secondregisterLazysite for the same plugin carryingline-chartandarea-chart, andcheck:doc-typesderives its key universe from the UNION of every such loop. Retiring the keys fromregister-plugins.tsalone would have left them blessed from the gallery, so the doc gate would have stayed green and the whole retirement would have changed nothing an author can observe. Leg 4 of the ablation below measures exactly that world.5. M3 — the doc
content/docs/plugins/plugin-dashboard.mdxtaught{ "type": "line-chart", "data": [], "height": 300 }inside acardbody. It now teaches the spelling that draws —chartpluschartType: "line", with rows,xAxisKeyandseriesso the snippet is a complete, valid document — plus a short paragraph naming how chart families are actually selected and a Callout recording why the old key is gone.6. Ablation matrix — every leg mutated, proved on disk, ran, restored, proved restored
Each leg compares the on-disk blob hash against the HEAD blob hash before running (an equal pair aborts as a no-op, an empty hash aborts as a failure), restores with
git checkout HEAD -- PATH, and proves restoration by an emptygit diff HEAD— not by an exit code.check:doc-typesline-chartcheck:doc-typesplugin-dashboard.mdx:221 [unregistered-doc-type] type 'line-chart'line-chart— the pre-repair worldcheck:doc-typesLeg 3 against leg 4 is the whole card in two readings: the same document, the same gate, green while the unfulfilled stub existed and red once it is gone. That is "refused loudly at authoring time" demonstrated on the real gate rather than asserted.
The 4 tests that stayed GREEN in legs 1 and 2 are exactly the control blocks — the fulfilled variants are unmoved by the mutation in both directions.
7. Pins added
apps/console/src/__tests__/unfulfilled-chart-stubs-8760.test.tsx— reads the stub list from the app's own SOURCE (never a copy), drives every key through the REAL loader, and fails on the first one that resolves to nothing. Then the retirement as a RENDER outcome: an authored retired node paints one alert and zero placeholders and names OBJUI-001. Controls:pie-chartandbar-chartresolve to their own distinct renderers, stay ingetKnownTypes(), and still draw with no alert and no placeholder — each asserted individually, each non-zero on both sides.packages/cli/src/__tests__/unfulfilled-chart-stubs-retired-8760.test.ts— the authoring-time refusal on the PUBLISHED surface:objectui checkover real temporary files now reportsUnknown schema type "line-chart"where it was silent. Controls:bar-chart/pie-chartstay silent, the spelling the corrected doc now teaches stays silent, and a firing control proves the instrument can still hear a warning at all.packages/types/src/__tests__/node-slot-registered-arms-8499.test.ts— re-pointed. Itsline-chartleg assertedtoContain("'line-chart'")over the console source and FIRED on this change with its own message, "the console stub moved — re-derive the premise". It now pins the retirement from both sources, and reads the STUB LIST rather than the whole file, because the file still names all three in the ⛔ comment left behind and a substring search would have been satisfied by a re-registration. 26 tests → 28.8. Verification
pnpm exec vitest run apps/console/ packages/cli/ packages/types/turbo run type-checkfor the three packages (+ dependency-closure build)pnpm lint(turbo run lint, the CI task graph)check:doc-typescheck:doc-snippets(after its scoped 35-package build)check:doc-examples·check:doc-fences·check:doc-example-ids·check:doc-example-readerscheck:sdui-registration-pins(after the console build)check:prompt-keys·check:handler-key-reads·check:element-data-source-declarationcheck:readme-exports·check:unreferenced-sources·check:test-path-roots·check:new-line-citationscheck:changeset-presence·check:changeset-no-major·check:changeset-claimscheck:control-bytesnode scripts/regenerate-known-schema-types.mjs --checknode scripts/markdown-test-inputs.mjs --auditADJUDICATEDrow is owedEvery reading above was taken at
c517c72fb, which is this branch's only commit. Exit codes were captured before any pipe.Acceptance notes
Found and deliberately NOT fixed here:
content/docs/guide/component-registry.md:351usesline-chartas the example key in a genericComponentRegistry.registerillustration. It teaches the API, not an ObjectUI key, andcheck:doc-typesexempts it by file; changing it would be a style edit on an unrelated document. Carrier if anyone does touch it: the next PR editing that guide.apps/console/src/register-plugins.tsandapps/console/src/preview-gallery.tsxkeep two hand-maintained copies of the chart stub list that nothing compares to each other — this card's own repair had to edit both. A parity pin between the two would be the general fix; today the new console pin covers onlyregister-plugins.ts. Carrier: the next PR that adds a plugin to either list.check:doc-typesblesses any key aregisterLazystub declares, whether or not the target module fulfils it. This card repairs the instance; the class stays open for the other lazy loops (dashboard, map, tree, report, calendar, kanban, timeline, gantt, markdown), which were not measured here. Carrier: no single PR — this is the generalisation of objectui#8760 and would want its own card if the maintainer wants the sweep.⛔ Not touched, per the card's own scope ruling: objectui#8499's
AnyComponentSchemaarms. That card is about renderers that already draw; this is the opposite case, andAnyComponentSchemarefused all three spellings before this change and refuses them after — a reading this PR did not move and therefore does not claim.Draft on purpose: not enqueued, no auto-merge, not flipped to ready.
Generated by Claude Code