Files
zooqueenandHanzo Dev f717dd89ad keys: the key a user pastes is sk-, not the retired hk-
28 apps told the user to paste an `hk-` key β€” input placeholders, settings
help text, READMEs, the error thrown when the field is empty β€” and five of
them validated that paste against /^hk-/ before sending it as a bearer.
Nothing mints that prefix any more: cloud accepts pk- and sk-, IAM mints
sk-. So the guard admitted a credential that authenticates nothing, and the
panel failed later at the gateway with nothing on screen to explain it.

The three leak detectors keep hk- and gain sk-. Salesforce's source scan
and the two Zendesk tests exist to catch a key committed to source or
echoed back through a request; retired keys are still in circulation and
leak exactly as badly, while a detector that only knew hk- would have gone
blind to every key minted from here on.

`sk-` used to be the example of the foreign key the guard screened out β€” an
OpenAI key. It cannot mean that now, and shape alone cannot tell one
vendor's sk- from another's, so those comments say what the check really
proves and point at the /v1/models call that proves the rest. The three
tests that pinned "sk- is rejected" now pin "hk- is rejected", which is the
invariant that survived.

Verified: 2029 tests pass across the 28 packages; typecheck clean on 25 of
them (hubspot, jupyter and teams fail identically at HEAD on JSX and ESM
type conflicts in files this does not touch); salesforce validate-source
green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 18:07:16 -07:00

6.0 KiB

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} / %s preserved).
  • 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:

  1. Open the Figma desktop app (browser Figma cannot load a local plugin).
  2. Menu β†’ Plugins β†’ Development β†’ Import plugin from manifest…
  3. Select packages/figma/dist/manifest.json.
  4. 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

  1. pnpm --filter @hanzo/figma build.
  2. In the Figma desktop app, right-click the plugin under Plugins β†’ Development β†’ Publish… (or Manage plugins in development).
  3. Fill in the name, description, icon, and cover art, then submit for review. The manifest.json already declares editorType: ["figma", "figjam"] and networkAccess.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 β†’ value map; 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