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]>
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}/%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