linshenkx/prompt-optimizer · error · Error

Canvas is not available

Error message

Canvas is not available

What it means

Thrown by ensureCanvas in packages/ui/src/utils/favorite-share-export.ts when neither the provided canvasFactory nor document.createElement('canvas') yields an object with a getContext method. This is an environment-capability guard: in normal browsers a canvas always has getContext, so hitting it means canvas support is absent or the factory returned something invalid. It fires before any 2D rendering begins.

Solutions

  1. If in tests, pass a canvasFactory returning a real or adequately mocked canvas (with getContext and a 2D context stub)
  2. Guard the export call: only invoke PNG share export when typeof document !== 'undefined' and document.createElement('canvas').getContext exists
  3. In SSR, run the export only after mount in the browser, or use a node-canvas-backed factory
  4. Fix a canvasFactory that returns a non-canvas object (e.g. a context instead of the canvas)

Example fix

// before
const canvas = ensureCanvas(() => fakeCanvasStub) // stub lacks getContext -> throws Canvas is not available

// after
const canvas = ensureCanvas(() => realCanvas) // or omit factory in a real browser DOM
const context = canvas.getContext('2d')
if (!context) throw new Error('Canvas 2D context is not available')
Defensive patterns

Strategy: validation

Validate before calling

const canUseCanvas = (): boolean =>
  typeof document !== 'undefined' &&
  typeof document.createElement === 'function' &&
  typeof document.createElement('canvas').getContext === 'function'

if (!canUseCanvas()) {
  // hide PNG share button or fall back to text sharing
}

Type guard

const isCanvasLike = (el: unknown): el is HTMLCanvasElement =>
  typeof el === 'object' && el !== null && typeof (el as HTMLCanvasElement).getContext === 'function'

Try / catch

try {
  const blob = await exportFavoriteSharePng(options)
} catch (e) {
  if (e instanceof Error && e.message === 'Canvas is not available') {
    // fall back to text-only share, or disable the feature in this environment
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Running the PNG share-image export in an environment without DOM/canvas (SSR, Node, jsdom, Web Workers without DOM access); passing a canvasFactory that returns null, undefined, a plain object, or a detached mock lacking getContext; or an environment where HTMLCanvasElement is not implemented.

Common situations: Unit tests in jsdom that trigger image export; server-side rendering paths that accidentally call share-image generation; mocking document.createElement without providing getContext; embedded webviews with canvas disabled. Note the factory result is used as-is, so a falsy factory result silently falls back to document.createElement — the error only fires when both paths fail.


AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27). Data as JSON: /api/errors/903cbb432a989893. Report an issue: GitHub.

Appendix: source

Thrown at packages/ui/src/utils/favorite-share-export.ts:932

        } else {
          fail();
        }
      });
    }());
  </script>
</body>
</html>`

  return {
    blob: new Blob([html], { type: 'text/html;charset=utf-8' }),
    result,
  }
}

const ensureCanvas = (canvasFactory?: () => HTMLCanvasElement): HTMLCanvasElement => {
  const canvas = canvasFactory?.() || document.createElement('canvas')
  if (!canvas.getContext) {
    throw new Error('Canvas is not available')
  }
  return canvas
}

const wrapCanvasText = (
  context: CanvasRenderingContext2D,
  text: string,
  maxWidth: number,
): string[] => {
  const lines: string[] = []
  for (const rawLine of text.split(/\r?\n/)) {
    const words = rawLine.split(/(\s+)/).filter(Boolean)
    let line = ''
    for (const word of words) {
      if (context.measureText(word).width > maxWidth) {
        for (const char of Array.from(word)) {
          const next = `${line}${char}`
          if (line && context.measureText(next).width > maxWidth) {

View on GitHub (pinned to 3e677b1d9f)