`pnpm typecheck` failed in canvas-lms, figma, imanage, procore and
workday with TS2550: Property 'at' does not exist on type
'ChatCompletionMessage[]'. Their tsconfigs load lib ES2020, which has no
Array.prototype.at. @types/node 20 declared it, and 09997dba moved these
packages to @types/node 26, which does not.
Each failing line asked for `.at(1)!`, the second message, which the test
a few lines up in the same file reads as `msgs[1]`. They read it the same
way now. The source these packages ship was already ES2020, so the lib
stays.
Co-authored-by: Hanzo Dev <[email protected]>
Hanzo AI for Figma
A Figma & FigJam plugin that puts Hanzo AI next to your canvas. Select layers or a frame and:
- Improve copy β tighten and clarify the selected text layers.
- Translate β localize every text layer in the selection to a target
language (placeholders like
{name}/%spreserved). - Fill content β replace lorem/placeholder text with realistic, on-brand copy.
- Rename layers β give the selected layers sensible, consistent names.
- Critique + a11y β a plain-language design critique plus an accessibility review (color-contrast risks from the actual layer colors, WCAG AA notes).
- Variants β generate options, e.g. "3 headline options" or "dark-mode copy", and insert any of them onto the canvas.
The AI runs on the api.hanzo.ai gateway through the
published @hanzo/ai headless client.
Your Hanzo API key (sk-β¦) is pasted once, validated against /v1/models, and
stored only inside the plugin (figma.clientStorage). Nothing is sent anywhere
but api.hanzo.ai.
How it works (two threads)
A Figma plugin is a main thread (code.js β full figma.* document access,
no DOM, no network) plus an iframe UI (ui.html β DOM and network, no
document access). They talk over postMessage.
selection ββββββββββββββ main thread (code.ts) ββββββββββββββ
change ββββΆβ serialize selection β SelectionSnapshot (data) β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ
β postMessage: { selection }
βΌ
ββββββββββββββ iframe UI (ui.ts) ββββββββββββββββββ
chip ββββββΆβ design-core.stream(action, snapshot, note) β
click β β @hanzo/ai β live tokens β
β design-core.parseResult(action, snapshot, text) β
βββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββββββ
β apply-edits β insert-text (variants)
βΌ βΌ
ββββββββββββββ main thread (code.ts) ββββββββββββββ
β load fonts β setCharacters / rename / createText β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
The AI call happens only in the iframe (the main thread has no fetch). The
main thread only reads the selection and writes results back onto nodes. All the
pure logic β the action catalog, prompt building, selectionβcontext assembly and
truncation, and response parsing β lives in design-core.ts and is unit-tested.
Develop locally
Prerequisites: Node β₯ 18, pnpm, and the Figma desktop app.
pnpm install # from the repo root
pnpm --filter @hanzo/figma build # β packages/figma/dist/{code.js, ui.html, manifest.json}
# or, from packages/figma:
pnpm build # one-shot
pnpm watch # rebuild on change
pnpm test # vitest (design-core + messaging)
pnpm typecheck # tsc --noEmit
Import into Figma:
- Open the Figma desktop app (browser Figma cannot load a local plugin).
- Menu β Plugins β Development β Import plugin from manifestβ¦
- Select
packages/figma/dist/manifest.json. - Run it from Plugins β Development β Hanzo AI on any file (or a FigJam
board). Paste your
sk-key on first run.
With pnpm watch running, re-run the plugin after a change to pick up the new
build (Figma reloads the manifest's code.js/ui.html each launch).
Publish to the Figma Community
pnpm --filter @hanzo/figma build.- In the Figma desktop app, right-click the plugin under Plugins β Development β Publishβ¦ (or Manage plugins in development).
- Fill in the name, description, icon, and cover art, then submit for review.
The
manifest.jsonalready declareseditorType: ["figma", "figjam"]andnetworkAccess.allowedDomains: ["https://api.hanzo.ai"]with a reason, which the Community review requires.
Layout
packages/figma/
βββ manifest.json Figma plugin manifest (main β code.js, ui β ui.html)
βββ build.js esbuild: code.js (IIFE) + ui.html (JS+CSS inlined)
βββ src/
β βββ code.ts MAIN thread: serialize selection, write back nodes
β βββ ui.ts IFRAME: the panel; the only place @hanzo/ai is called
β βββ ui.html / ui.css the panel shell (single inlined file at build time)
β βββ design-core.ts PURE: action catalog, prompts, context, parsing, run
β βββ messaging.ts typed postMessage protocol (main β iframe)
β βββ config.ts gateway base, default model, storage keys, budget
βββ tests/ vitest over design-core + messaging
Notes
- Context budget. A big frame won't fit a model window, so the selection is serialized (text layers first, then structure, with ids and colors) and truncated to a character budget β with an honest note in the prompt and the panel when anything was dropped.
- Write-back safety. Rewrite/translate/fill/rename ask the model for a strict
JSON
id β valuemap; only ids that are actually in the current selection are applied, so the model cannot mutate nodes it wasn't shown. - Fonts. Setting text requires the node's fonts to be loaded first; the main thread loads every font range before writing.
- Theme. The panel uses Figma's injected theme variables, so it matches the editor's light/dark mode with no toggle.
MIT Β© Hanzo AI