@hanzo/canva
v1.0.0
Published
Hanzo AI for Canva — a side-panel Apps SDK app: generate marketing copy, rewrite the selected text, translate, brainstorm content ideas, and ask about the design. An @canva/app-ui-kit React panel that reads the selection/page via @canva/design and adds/re
Readme
Hanzo AI for Canva
A Canva Apps SDK side-panel app that puts Hanzo's models next to your design. Non-designers, marketers, and SMBs — Canva's core audience — get copywriting help without leaving the canvas:
- Generate copy — headlines, captions, and marketing copy from a short brief.
- Rewrite selected text — tighten, restyle, or restate the text you selected, in place.
- Translate — the selected text into any language, tone preserved.
- Content ideas — a numbered list of angles for a topic; click one to add it.
- Ask — a question about the design or its copy.
It reads the current selection and page via @canva/design, runs the model gateway
through the published @hanzo/ai headless client over api.hanzo.ai, and adds or
replaces text elements with the result. You paste your own Hanzo hk- key; the app
holds no secret and calls no /api/ path — only api.hanzo.ai/v1/....
Architecture — one way to do everything
| Module | Responsibility | Depends on |
| --- | --- | --- |
| src/design-core.ts | PURE. The action catalog, prompt builders, design-context assembly + truncation, and response parsing. No SDK, no DOM. Fully unit-tested. | — |
| src/hanzo.ts | Thin wrapper over @hanzo/ai — the only place the model gateway is called. | @hanzo/ai |
| src/canva.ts | Thin adapter over @canva/design — read the selection, replace it, insert a new text element. | @canva/design |
| src/config.ts | Gateway base URL, default model, context budget, hk- key guard. | — |
| src/app.tsx | The App UI Kit panel — binds the three above to widgets; holds only UI state. | @canva/app-ui-kit |
| src/index.tsx | Entry — mounts App under AppUiProvider. | react-dom, @canva/app-ui-kit |
The design-core mirrors @hanzo/figma's design-core so the two design
apps expose the same five actions with the same context/prompt/parse contracts.
The read-selection → insert-text flow
- On mount,
canva.tsonSelectionChangesubscribes toselection.registerOnChange({ scope: 'plaintext' }). Each change yields aSelectionSnapshot(count, joinedtext); the panel gates Rewrite / Translate oncount > 0and shows a preview of the selected text. - You pick an action, add a brief/question/language as needed, and press Run.
design-core.buildActionMessages(id, inputs, { selection })resolves the action's task, fences the observed design as data (with an honest truncation note if it was cut), and returns the OpenAI-shaped message list.hanzo.runChat(messages, { token, model })calls@hanzo/aichat.completions.create(non-streaming) and returns the assistant text.design-core.parseCopy/parseIdeasstrips code fences, wrapping quotes, and list markers into paste-ready text.- You press Replace selection (Rewrite →
canva.replaceSelectedText, which reads a fresh draft, overwritesdraft.contents[].text, anddraft.save()s) or Add to design (everything else →canva.insertText→addElementAtCursorwith a newTextElement). Ideas insert one line per click.
Develop locally
Prerequisites: Node 18+, pnpm, and a Canva account.
1. Create the app in the Canva Developer Portal
- Go to https://www.canva.com/developers/apps and Create an app.
- Under App details, copy the App ID.
- Under Add features → Editing, enable the app to run in the editor side panel (this app needs the Design editing scope: read the selection, add/replace elements).
- Under Configure your app, set the Development URL to the CLI/preview origin (default
http://localhost:8080).
2. Configure
cd packages/canva
cp .env.template .env
# put your App ID into CANVA_APP_ID3. Install, test, build
pnpm install # from the repo root (workspace)
pnpm --filter @hanzo/canva test # vitest — the pure design-core + hanzo shaping
pnpm --filter @hanzo/canva typecheck # tsc --noEmit, strict
pnpm --filter @hanzo/canva build # esbuild → dist/{index.html,app.js,app.css}4. Preview in Canva
Serve the bundle and point the Developer Portal's Development URL at it. With the Canva CLI:
npx @canva/cli@latest login
pnpm --filter @hanzo/canva watch # rebuilds dist/ on change
npx @canva/cli@latest apps preview # opens the app in the Canva editor, hot-reloadingOpen any design, launch Hanzo AI from the app side panel, paste your hk- key,
pick a model, and run an action. Select text on the canvas to enable Rewrite and
Translate.
Getting a Hanzo hk- key
Sign in at https://hanzo.id and mint an API key (prefixed hk-). The panel
validates the prefix before use and sends it as the bearer to api.hanzo.ai. The key
lives only in the panel's session state — it is never persisted or bundled.
Submit to the Canva Apps Marketplace
- Finish and test the app in preview; ensure
pnpm buildis clean anddist/loads. - Host
dist/on your production origin (behindhanzoai/ingress+ thehanzoai/staticplugin — never nginx/caddy) and set the app's production App source URL to it. - In the Developer Portal, complete App listing (name, icon, description, screenshots) and Submit for review.
- Canva reviews for the requested scopes (Design editing here) and UX. Address feedback and resubmit; on approval the app is published to the Apps Marketplace.
Tests
test/design-core.test.ts covers the pure core: whitespace collapse, context
assembly + budget truncation, message fencing (data vs. task, the truncation note,
the no-context path), the five prompt builders (brief/tweak/language/count/question
embedding and the count clamp), the id → messages path, and the response parsers
(fence/quote stripping, idempotence, list-marker stripping). test/hanzo.test.ts
asserts the @hanzo/ai request shaping against a mock client (model, stream:false,
temperature, messages, abort signal, empty-content error, model listing).
test/config.test.ts covers the /v1 endpoint builders and the hk- key guard.
Test Files 3 passed (3)
Tests 40 passed (40)