perspective-dev/perspective · error · Error
Unknown chart tag
Error message
Unknown chart tag: ${tag} What it means
The worker renderer maps a chart tag string to a lazily-imported ChartImplementation factory in the CHART_IMPLS registry. When resolveChartImpl receives a tag that has no registered factory, it throws this Error instead of returning a constructor. This guards the worker against messages requesting chart types the build does not know about.
Solutions
- Log/print the exact ${tag} value from the message and compare it against the keys of CHART_IMPLS in renderer.worker.ts.
- Fix the tag spelling or use a supported chart tag from the registry.
- Rebuild and deploy the worker and main-thread bundles from the same package version so their chart tag sets match.
- If it's a custom chart, register its factory in CHART_IMPLS in the worker bundle.
- Validate chart tags at the API boundary on the main thread before posting the message, to fail fast with a clearer error.
Example fix
// before
worker.postMessage({ type: "render", tag: "barchart" });
// after (use the registered tag)
worker.postMessage({ type: "render", tag: "bar-chart" }); Defensive patterns
Strategy: validation
Validate before calling
const KNOWN_TAGS = new Set(Object.keys(CHART_IMPLS));
function isKnownChartTag(tag: string): boolean {
return KNOWN_TAGS.has(tag);
}
// call before posting the render message
if (!isKnownChartTag(cfg.tag)) throw new Error(`Unsupported chart tag: ${cfg.tag}`); Type guard
function isChartTag(tag: string): tag is keyof typeof CHART_IMPLS {
return tag in CHART_IMPLS;
} Try / catch
try {
await renderer.render(msg);
} catch (e) {
if (e instanceof Error && e.message.startsWith("Unknown chart tag")) {
console.warn(`Falling back: ${e.message}`);
await renderer.render({ ...msg, tag: "table" });
} else throw e;
} Prevention
- Use the exported chart tag union/enum type instead of raw strings in chart config.
- Version-lock the main-thread and worker bundles so their registries match.
- Validate chart tags at the main-thread API boundary before posting worker messages.
- Register any custom chart in the worker's CHART_IMPLS map, not just the main thread.
When it happens
Trigger: Sending a renderer message whose chart tag string is not a key of CHART_IMPLS — e.g. a typo ('barchart' vs 'bar-chart'), a chart type from a newer/older version of the package, or a tag constructed dynamically from user input.
Common situations: Host page and worker bundles are version-skewed (worker updated, main thread not, or vice versa) so the main thread sends tags the worker build lacks; misspelled tag in chart configuration; custom chart registered only on the main thread, never in the worker's CHART_IMPLS map.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Init error
- Missing perspective-client.wasm
- Missing perspective-client.wasm
- Missing perspective-server.wasm
- WebAssembly not supported.
AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09).
Data as JSON: /api/errors/00b7bf51e20f528e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/viewer-charts/src/ts/worker/renderer.worker.ts:76
/**
* Renderer state. One per host element. In worker mode it lives in
* the worker; in in-process mode (host loads this module via dynamic
* `import(workerURL)`) it lives on the main thread. The class itself
* doesn't care — both modes drive it through a `MessagePort` of
* `ControlMsg`s.
*/
/**
* Resolve a chart tag to its impl class via the lazy registry. Eager
* tags microtask-resolve; map tags trigger a dynamic `import()` that
* the bundler emits as a separately-fetched chunk.
*/
async function resolveChartImpl(
tag: string,
): Promise<new () => ChartImplementation> {
const factory = CHART_IMPLS[tag];
if (!factory) {
throw new Error(`Unknown chart tag: ${tag}`);
}
return await factory();
}
/**
* Renderer-scope shared context pool for pooled blit mode. Lazily
* created on first use so non-blit / non-pooled scopes never allocate
* one. One per renderer scope (the worker, or the main thread in
* in-process mode) — `WorkerRenderer`s in the same scope share its K
* contexts.
*/
let CONTEXT_POOL: ContextPool | null = null;
function getContextPool(precompile: boolean): ContextPool {
if (!CONTEXT_POOL) {
CONTEXT_POOL = new ContextPool(RENDER_CONTEXT_POOL_SIZE, {
precompile,View on GitHub (pinned to 11c8238c0c)