# CLAUDE.md: Figma plugin

## Commands
- `npm run build` (bundles `code.ts` and the UI) · `npm run watch` · `npm run typecheck` · `npm run lint` if present.
- Test by importing `manifest.json` in the Figma desktop app (Plugins → Development). You can't run Figma here; say what needs a manual check.

## Architecture
- Two contexts: the main thread (`code.ts`) has the `figma` API but no DOM or browser APIs; the UI (`ui.html`/React in an iframe) has the DOM but no `figma` API. They talk only through `figma.ui.postMessage(msg)` ↔ `window.onmessage` and `parent.postMessage({ pluginMessage: msg }, '*')` ↔ `figma.ui.onmessage`.
- Define every message as a typed discriminated union in `src/messages.ts`, shared by both sides. Validate incoming messages; ignore unknown types.
- Keep `@figma/plugin-typings` current and typecheck both bundles.

## Figma API rules
- The manifest uses `"documentAccess": "dynamic-page"`: use async APIs (`figma.getNodeByIdAsync`, `page.loadAsync()`, `figma.loadAllPagesAsync()` only when truly needed). Don't touch other pages' nodes without loading them.
- Load fonts before changing text: `await figma.loadFontAsync(node.fontName as FontName)`; handle `figma.mixed` font names by loading each segment's font.
- Batch edits and keep them scoped to the selection or a known node; never iterate the whole document with `findAll` when `findAllWithCriteria` or the selection works.
- Wrap work in try/catch and finish with `figma.notify(...)` for success or failure and `figma.closePlugin()` for one-shot commands.
- Use `figma.clientStorage` for user preferences; use `setPluginData`/`setSharedPluginData` (with a namespace) for data attached to nodes. Never store secrets in plugin data.

## Manifest
Declare `networkAccess.allowedDomains` precisely (no `*` unless justified in `reasoning`), the `editorType` actually supported (`figma`, `figjam`, `dev`), and `capabilities` only when used. Don't change the plugin `id`.
