Files
zeekay 65ded2d2d0 feat(figma): Hanzo AI for Figma plugin over @hanzo/ai
New @hanzo/figma package: a Figma + FigJam plugin that rewrites, translates,
content-fills, critiques (with a11y notes), renames layers, and generates copy
variants for the selected layers — built on the published @hanzo/ai headless
client against api.hanzo.ai.

Two-thread architecture per the Figma plugin model:
- code.ts (main): serializes the selection into a plain SelectionSnapshot
  (text nodes + structure + colors, node-budgeted) and applies write-backs
  (font-loaded setCharacters, rename, createText) — full figma.* access, no net.
- ui.ts (iframe): the only place the model is called; streams via design-core,
  parses per action, posts edits/inserts back to code.ts over a typed
  postMessage protocol (messaging.ts).
- design-core.ts (PURE): the design-action catalog (system+user prompt builders
  per action), selection->context assembly + budget truncation, response parsing
  (JSON id->value edit maps with id allow-listing, variant-list splitting), and
  the @hanzo/ai run funnels over an injectable client.

Key pasted in the UI, validated via /v1/models, stored in figma.clientStorage;
manifest networkAccess allowlists only api.hanzo.ai. esbuild build emits the
three Figma files (code.js IIFE, ui.html with JS+CSS inlined, manifest.json).

56 vitest tests green (design-core + messaging guards); tsc --noEmit clean.
2026-07-03 21:07:06 -07:00

22 lines
799 B
TypeScript

import { describe, it, expect } from 'vitest';
import { isUiToMain, isMainToUi } from '../src/messaging';
describe('messaging boundary guards', () => {
it('accepts well-formed messages with a string type', () => {
expect(isUiToMain({ type: 'ui-ready' })).toBe(true);
expect(isUiToMain({ type: 'apply-edits', edits: [] })).toBe(true);
expect(isMainToUi({ type: 'selection', snapshot: {} })).toBe(true);
});
it('rejects non-objects and missing/non-string type', () => {
for (const guard of [isUiToMain, isMainToUi]) {
expect(guard(null)).toBe(false);
expect(guard(undefined)).toBe(false);
expect(guard('str')).toBe(false);
expect(guard(42)).toBe(false);
expect(guard({})).toBe(false);
expect(guard({ type: 7 })).toBe(false);
}
});
});