Normalize every packages/* package for public npm publish: - version: pre-1.0 (0.x) → 1.0.0; already-≥1.0 kept (mcp 2.4.1, cli-tools 1.8.0, dxt 1.8.1, browser/hanzo-ide/office/outlook 1.9.31, gworkspace 1.9.30, aci/auth 1.0.0) - remove "private" from all 22 gated packages/* (apps/site + repo root stay private — site is a deployed website, not an npm library) - add publishConfig.access=public everywhere (scoped @hanzo/* need it) - add files[] + valid main pointing at built output so no empty tarballs; no-build packages ship src+README CI npm job now publishes the whole workspace: - build @hanzo/cards first (slack/teams import its dist), then pnpm -r --if-present run build || true - pnpm -r publish --access public --no-git-checks --ignore-scripts --ignore-scripts prevents raycast/vscode `publish` lifecycle scripts (Raycast Store / VS Marketplace) from hijacking and aborting the run. workspace:* (auth, cards) is rewritten to the real version at publish.
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_ID
3. 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-reloading
Open 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)