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

  1. Log/print the exact ${tag} value from the message and compare it against the keys of CHART_IMPLS in renderer.worker.ts.
  2. Fix the tag spelling or use a supported chart tag from the registry.
  3. Rebuild and deploy the worker and main-thread bundles from the same package version so their chart tag sets match.
  4. If it's a custom chart, register its factory in CHART_IMPLS in the worker bundle.
  5. 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

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


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)