@imgly/codesign-mcp 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/docs/handbook/SKILL.md +6 -0
- package/dist/assets/inline.generated.js +1 -1
- package/dist/assets/inline.generated.js.map +1 -1
- package/dist/cli/http.d.ts.map +1 -1
- package/dist/cli/http.js +4 -0
- package/dist/cli/http.js.map +1 -1
- package/dist/cli/stdio.d.ts.map +1 -1
- package/dist/cli/stdio.js +55 -18
- package/dist/cli/stdio.js.map +1 -1
- package/dist/engine/cesdk-adapter.d.ts +7 -1
- package/dist/engine/cesdk-adapter.d.ts.map +1 -1
- package/dist/engine/cesdk-adapter.js +7 -1
- package/dist/engine/cesdk-adapter.js.map +1 -1
- package/dist/engine/unsplash-source.d.ts +8 -0
- package/dist/engine/unsplash-source.d.ts.map +1 -0
- package/dist/engine/unsplash-source.js +77 -0
- package/dist/engine/unsplash-source.js.map +1 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +0 -1
- package/dist/http/server.js.map +1 -1
- package/dist/server.d.ts +0 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +1 -1
- package/dist/test/test/cli/redirect-console.test.d.ts +2 -0
- package/dist/test/test/cli/redirect-console.test.d.ts.map +1 -0
- package/dist/test/test/cli/redirect-console.test.js +47 -0
- package/dist/test/test/cli/redirect-console.test.js.map +1 -0
- package/dist/test/test/cli/resolve-license.test.d.ts +2 -0
- package/dist/test/test/cli/resolve-license.test.d.ts.map +1 -0
- package/dist/test/test/cli/resolve-license.test.js +20 -0
- package/dist/test/test/cli/resolve-license.test.js.map +1 -0
- package/dist/test/test/cli/stdio-license.test.d.ts +2 -0
- package/dist/test/test/cli/stdio-license.test.d.ts.map +1 -0
- package/dist/test/test/cli/stdio-license.test.js +19 -0
- package/dist/test/test/cli/stdio-license.test.js.map +1 -0
- package/dist/test/test/config.test.d.ts +2 -0
- package/dist/test/test/config.test.d.ts.map +1 -0
- package/dist/test/test/config.test.js +91 -0
- package/dist/test/test/config.test.js.map +1 -0
- package/dist/test/test/engine/asset-sources.test.d.ts +2 -0
- package/dist/test/test/engine/asset-sources.test.d.ts.map +1 -0
- package/dist/test/test/engine/asset-sources.test.js +100 -0
- package/dist/test/test/engine/asset-sources.test.js.map +1 -0
- package/dist/test/test/engine/mutex.test.d.ts +2 -0
- package/dist/test/test/engine/mutex.test.d.ts.map +1 -0
- package/dist/test/test/engine/mutex.test.js +63 -0
- package/dist/test/test/engine/mutex.test.js.map +1 -0
- package/dist/test/test/engine/session.test.js +15 -0
- package/dist/test/test/engine/session.test.js.map +1 -1
- package/dist/test/test/engine/unsplash-source.test.d.ts +2 -0
- package/dist/test/test/engine/unsplash-source.test.d.ts.map +1 -0
- package/dist/test/test/engine/unsplash-source.test.js +129 -0
- package/dist/test/test/engine/unsplash-source.test.js.map +1 -0
- package/dist/test/test/handbook-contract.test.js +8 -4
- package/dist/test/test/handbook-contract.test.js.map +1 -1
- package/dist/test/test/http/server.test.js +129 -0
- package/dist/test/test/http/server.test.js.map +1 -1
- package/dist/test/test/http/session-id-header.test.d.ts +2 -0
- package/dist/test/test/http/session-id-header.test.d.ts.map +1 -0
- package/dist/test/test/http/session-id-header.test.js +76 -0
- package/dist/test/test/http/session-id-header.test.js.map +1 -0
- package/dist/test/test/http/session-manager.test.js +14 -0
- package/dist/test/test/http/session-manager.test.js.map +1 -1
- package/dist/test/test/http/workspace-auth.test.d.ts +2 -0
- package/dist/test/test/http/workspace-auth.test.d.ts.map +1 -0
- package/dist/test/test/http/workspace-auth.test.js +41 -0
- package/dist/test/test/http/workspace-auth.test.js.map +1 -0
- package/dist/test/test/metrics/mount.test.d.ts +2 -0
- package/dist/test/test/metrics/mount.test.d.ts.map +1 -0
- package/dist/test/test/metrics/mount.test.js +59 -0
- package/dist/test/test/metrics/mount.test.js.map +1 -0
- package/dist/test/test/metrics/recorder.test.d.ts +2 -0
- package/dist/test/test/metrics/recorder.test.d.ts.map +1 -0
- package/dist/test/test/metrics/recorder.test.js +118 -0
- package/dist/test/test/metrics/recorder.test.js.map +1 -0
- package/dist/test/test/revisions/store.test.js +81 -11
- package/dist/test/test/revisions/store.test.js.map +1 -1
- package/dist/test/test/server.metrics.test.d.ts +2 -0
- package/dist/test/test/server.metrics.test.d.ts.map +1 -0
- package/dist/test/test/server.metrics.test.js +87 -0
- package/dist/test/test/server.metrics.test.js.map +1 -0
- package/dist/test/test/server.test.js +33 -4
- package/dist/test/test/server.test.js.map +1 -1
- package/dist/test/test/tools/asset-search.test.d.ts +2 -0
- package/dist/test/test/tools/asset-search.test.d.ts.map +1 -0
- package/dist/test/test/tools/asset-search.test.js +121 -0
- package/dist/test/test/tools/asset-search.test.js.map +1 -0
- package/dist/test/test/tools/edit.test.js +71 -9
- package/dist/test/test/tools/edit.test.js.map +1 -1
- package/dist/test/test/tools/export.test.js +60 -30
- package/dist/test/test/tools/export.test.js.map +1 -1
- package/dist/test/test/tools/index.test.d.ts +2 -0
- package/dist/test/test/tools/index.test.d.ts.map +1 -0
- package/dist/test/test/tools/index.test.js +55 -0
- package/dist/test/test/tools/index.test.js.map +1 -0
- package/dist/test/test/tools/metrics.test.d.ts +2 -0
- package/dist/test/test/tools/metrics.test.d.ts.map +1 -0
- package/dist/test/test/tools/metrics.test.js +29 -0
- package/dist/test/test/tools/metrics.test.js.map +1 -0
- package/dist/test/test/tools/preview.test.js +6 -4
- package/dist/test/test/tools/preview.test.js.map +1 -1
- package/dist/test/test/tools/rename.test.js +1 -2
- package/dist/test/test/tools/rename.test.js.map +1 -1
- package/dist/test/test/tools/save.test.d.ts +2 -0
- package/dist/test/test/tools/save.test.d.ts.map +1 -0
- package/dist/test/test/tools/save.test.js +137 -0
- package/dist/test/test/tools/save.test.js.map +1 -0
- package/dist/test/test/viewer/router.test.js +56 -0
- package/dist/test/test/viewer/router.test.js.map +1 -1
- package/dist/test/test/viewer/session-mount.test.d.ts +2 -0
- package/dist/test/test/viewer/session-mount.test.d.ts.map +1 -0
- package/dist/test/test/viewer/session-mount.test.js +52 -0
- package/dist/test/test/viewer/session-mount.test.js.map +1 -0
- package/dist/test/test/viewer/url-builder.test.js +76 -9
- package/dist/test/test/viewer/url-builder.test.js.map +1 -1
- package/dist/test/test/workspace/s3.test.d.ts +2 -0
- package/dist/test/test/workspace/s3.test.d.ts.map +1 -0
- package/dist/test/test/workspace/s3.test.js +153 -0
- package/dist/test/test/workspace/s3.test.js.map +1 -0
- package/dist/test/test/workspace/scoped.test.d.ts +2 -0
- package/dist/test/test/workspace/scoped.test.d.ts.map +1 -0
- package/dist/test/test/workspace/scoped.test.js +59 -0
- package/dist/test/test/workspace/scoped.test.js.map +1 -0
- package/dist/test/tsconfig.test.tsbuildinfo +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +14 -4
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -111,9 +111,15 @@ Returns a URL the human can open to view the design in CE.SDK.
|
|
|
111
111
|
|
|
112
112
|
The viewer is **read-only** — the human's edits there are local and discarded on your next `edit`. They look, you write.
|
|
113
113
|
|
|
114
|
+
<!-- BETA: asset generation is not available in this beta, so its handbook entry
|
|
115
|
+
is hidden to avoid advertising a tool that is not registered. Do not reference
|
|
116
|
+
it. The section below is preserved verbatim for re-enablement — restore it when
|
|
117
|
+
ASSET_GENERATE_ENABLED (packages/codesign-mcp/src/tools/index.ts) is flipped on.
|
|
118
|
+
|
|
114
119
|
### `asset_generate({ kind: "image", prompt, width?, height? })`
|
|
115
120
|
|
|
116
121
|
Generate an image asset via the IMG.LY gateway. Writes the bytes to the workspace and returns JSON `{ uri, httpUrl, mimeType, bytes }`. Embed the `uri` (the `workspace://assets/<sha>.png` form) — **never `httpUrl`** — in subsequent `edit` calls, so the design stays loadable after a restart or rewind; CE.SDK resolves `workspace://` to a fetch URL automatically. `httpUrl` is only for opening the asset in a browser now. On Local you need `API_KEY`; on Hosted you may hit a cost gate (the error tells you).
|
|
122
|
+
-->
|
|
117
123
|
|
|
118
124
|
### `asset_search({ sourceId?, query?, page?, perPage? })`
|
|
119
125
|
|
|
@@ -395,7 +395,7 @@ export const INLINE_ASSETS = {
|
|
|
395
395
|
"docs/guide/user-interface/ui-extensions/quick-actions.md": "> This is one page of the CE.SDK Vanilla JS/TS documentation. For a complete overview, see the [Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md). For all docs in one file, see [llms-full.txt](./llms-full.txt.md).\n\n**Navigation:** [Guides](./guides.md) > [User Interface](./user-interface.md) > [UI Extensions](./user-interface/ui-extensions.md) > [Quick Actions](./user-interface/ui-extensions/quick-actions.md)\n\n---\n\nExtend CE.SDK with one-click editing actions using official plugins for background removal, vectorization, QR codes, and cutouts.\n\n\n\n> **Reading time:** 8 minutes\n>\n> **Resources:**\n>\n> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)\n> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-user-interface-ui-extensions-quick-actions-browser)\n> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-user-interface-ui-extensions-quick-actions-browser)\n> - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-ui-extensions-quick-actions-browser/)\n\nQuick actions are single-click operations that appear in the canvas menu when users select a block. CE.SDK provides official plugins that add image processing capabilities like background removal, vectorization, and QR code generation. These plugins integrate directly with the editor UI and execute their operations immediately when clicked.\n\n```typescript file=@cesdk_web_examples/guides-user-interface-ui-extensions-quick-actions-browser/browser.ts reference-only\nimport type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';\n\nimport {\n BlurAssetSource,\n ColorPaletteAssetSource,\n CropPresetsAssetSource,\n DemoAssetSources,\n EffectsAssetSource,\n FiltersAssetSource,\n PagePresetsAssetSource,\n StickerAssetSource,\n TextAssetSource,\n TextComponentAssetSource,\n TypefaceAssetSource,\n UploadAssetSources,\n VectorShapeAssetSource\n} from '@cesdk/cesdk-js/plugins';\nimport { DesignEditorConfig } from './design-editor/plugin';\nimport BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web';\nimport CutoutLibraryPlugin from '@imgly/plugin-cutout-library-web';\nimport QRCodePlugin from '@imgly/plugin-qr-code-web';\nimport VectorizerPlugin from '@imgly/plugin-vectorizer-web';\n\nexport default class QuickActionsExample implements EditorPlugin {\n name = 'QuickActionsExample';\n version = '1.0.0';\n\n async initialize({ cesdk }: EditorPluginContext) {\n if (!cesdk) {\n throw new Error('CE.SDK instance is required for this plugin');\n }\n\n const engine = cesdk.engine;\n await cesdk.addPlugin(new DesignEditorConfig());\n\n // Add asset source plugins\n await cesdk.addPlugin(new BlurAssetSource());\n await cesdk.addPlugin(new ColorPaletteAssetSource());\n await cesdk.addPlugin(new CropPresetsAssetSource());\n await cesdk.addPlugin(\n new UploadAssetSources({ include: ['ly.img.image.upload'] })\n );\n await cesdk.addPlugin(\n new DemoAssetSources({\n include: [\n 'ly.img.templates.blank.*',\n 'ly.img.templates.presentation.*',\n 'ly.img.templates.print.*',\n 'ly.img.templates.social.*',\n 'ly.img.image.*'\n ]\n })\n );\n await cesdk.addPlugin(new EffectsAssetSource());\n await cesdk.addPlugin(new FiltersAssetSource());\n await cesdk.addPlugin(new PagePresetsAssetSource());\n await cesdk.addPlugin(new StickerAssetSource());\n await cesdk.addPlugin(new TextAssetSource());\n await cesdk.addPlugin(new TextComponentAssetSource());\n await cesdk.addPlugin(new TypefaceAssetSource());\n await cesdk.addPlugin(new VectorShapeAssetSource());\n\n // Add background removal plugin with canvas menu button\n await cesdk.addPlugin(\n BackgroundRemovalPlugin({\n ui: {\n locations: ['canvasMenu']\n }\n })\n );\n\n // Add vectorizer plugin with canvas menu button\n await cesdk.addPlugin(\n VectorizerPlugin({\n ui: {\n locations: 'canvasMenu'\n }\n })\n );\n\n // Add cutout library plugin for print workflows (dock only, no canvas menu)\n await cesdk.addPlugin(CutoutLibraryPlugin());\n\n // Add cutout library to the dock for easy access\n const cutoutAssetEntry = cesdk.ui.getAssetLibraryEntry(\n 'ly.img.cutout.entry'\n );\n cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [\n ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),\n {\n id: 'ly.img.assetLibrary.dock',\n label: 'Cutout',\n key: 'ly.img.assetLibrary.dock',\n icon: cutoutAssetEntry?.icon,\n entries: ['ly.img.cutout.entry']\n }\n ]);\n\n // Add QR code plugin (adds canvas menu button automatically)\n await cesdk.addPlugin(QRCodePlugin());\n\n // Add QR code generator to the dock\n cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [\n ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),\n 'ly.img.spacer',\n 'ly.img.generate-qr.dock'\n ]);\n\n // Create scene with gradient background and text\n await cesdk.actions.run('scene.create', {\n page: { width: 800, height: 600, unit: 'Pixel' }\n });\n\n const page = engine.block.findByType('page')[0];\n const pageWidth = engine.block.getWidth(page);\n const pageHeight = engine.block.getHeight(page);\n\n // Add gradient background to the page\n const pageFill = engine.block.createFill('gradient/linear');\n engine.block.setGradientColorStops(pageFill, 'fill/gradient/colors', [\n { stop: 0, color: { r: 0.18, g: 0.1, b: 0.4, a: 1 } },\n { stop: 1, color: { r: 0.55, g: 0.25, b: 0.6, a: 1 } }\n ]);\n engine.block.setFloat(pageFill, 'fill/gradient/linear/startPointX', 0);\n engine.block.setFloat(pageFill, 'fill/gradient/linear/startPointY', 0);\n engine.block.setFloat(pageFill, 'fill/gradient/linear/endPointX', 1);\n engine.block.setFloat(pageFill, 'fill/gradient/linear/endPointY', 1);\n engine.block.setFill(page, pageFill);\n\n // Add main title text with auto height\n const titleBlock = engine.block.create('text');\n engine.block.setString(titleBlock, 'text/text', 'Explore Quick Actions');\n engine.block.setFloat(titleBlock, 'text/fontSize', 100);\n engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center');\n engine.block.setWidth(titleBlock, pageWidth);\n engine.block.setHeightMode(titleBlock, 'Auto');\n engine.block.appendChild(page, titleBlock);\n\n // Set title text color to white\n engine.block.setTextColor(titleBlock, { r: 1, g: 1, b: 1, a: 1 });\n\n // Add subtitle text with auto height\n const subtitleBlock = engine.block.create('text');\n engine.block.setString(subtitleBlock, 'text/text', 'IMG.LY');\n engine.block.setFloat(subtitleBlock, 'text/fontSize', 64);\n engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center');\n engine.block.setWidth(subtitleBlock, pageWidth);\n engine.block.setHeightMode(subtitleBlock, 'Auto');\n engine.block.appendChild(page, subtitleBlock);\n\n // Set subtitle text color to white\n engine.block.setTextColor(subtitleBlock, { r: 1, g: 1, b: 1, a: 1 });\n\n // Add a sample image to demonstrate quick actions\n const imageBlock = engine.block.create('graphic');\n\n // Set shape for the graphic block\n const rectShape = engine.block.createShape('rect');\n engine.block.setShape(imageBlock, rectShape);\n\n // Set image fill\n const imageFill = engine.block.createFill('image');\n engine.block.setString(\n imageFill,\n 'fill/image/imageFileURI',\n 'https://img.ly/static/ubq_samples/sample_1.jpg'\n );\n engine.block.setFill(imageBlock, imageFill);\n\n const imageSize = 250;\n engine.block.setWidth(imageBlock, imageSize);\n engine.block.setHeight(imageBlock, imageSize);\n engine.block.appendChild(page, imageBlock);\n\n // Position all elements - text at top, image below\n const titleHeight = engine.block.getFrameHeight(titleBlock);\n const subtitleHeight = engine.block.getFrameHeight(subtitleBlock);\n const textSpacing = 10;\n const imageGap = 80;\n\n // Position content vertically centered with offset\n const totalHeight =\n titleHeight + textSpacing + subtitleHeight + imageGap + imageSize;\n const startY = (pageHeight - totalHeight) / 2 + 40;\n\n engine.block.setPositionX(titleBlock, 0);\n engine.block.setPositionY(titleBlock, startY);\n engine.block.setPositionX(subtitleBlock, 0);\n engine.block.setPositionY(\n subtitleBlock,\n startY + titleHeight + textSpacing\n );\n engine.block.setPositionX(imageBlock, (pageWidth - imageSize) / 2);\n engine.block.setPositionY(\n imageBlock,\n startY + titleHeight + textSpacing + subtitleHeight + imageGap\n );\n\n // Select the image to show the canvas menu with quick actions\n engine.block.select(imageBlock);\n\n // Open the cutout library panel\n cesdk.ui.openPanel('//ly.img.panel/assetLibrary', {\n payload: {\n entries: ['ly.img.cutout.entry'],\n title: 'Cutout'\n }\n });\n }\n}\n```\n\nThis guide demonstrates how to install and configure quick action plugins, add asset libraries to the dock, and optimize plugin loading for production use.\n\n## Plugin Overview\n\nThis guide covers four official plugins that extend CE.SDK with quick actions:\n\n| Plugin | Use Case |\n| ------------------ | ----------------------------------------- |\n| Background Removal | Remove backgrounds from product photos |\n| Vectorizer | Convert logos to scalable vectors |\n| QR Code | Generate trackable QR codes for marketing |\n| Cutout Library | Add die-cut shapes for print production |\n\n## Adding Quick Action Plugins\n\nEach plugin adds a button to the canvas menu that appears when users select compatible blocks. Install the plugin package, then call `cesdk.addPlugin()` to register it with the editor.\n\n### Installing the Plugins\n\nThe background removal plugin requires `onnxruntime-web` for its machine learning model. The vectorizer and QR code plugins have no additional dependencies.\n\n<Tabs>\n <TabItem label=\"npm\">\n ```sh\n npm install @imgly/plugin-background-removal-web@$UBQ_VERSION$ @imgly/plugin-vectorizer-web@$UBQ_VERSION$ @imgly/plugin-qr-code-web@$UBQ_VERSION$ onnxruntime-web@1.21.0\n ```\n </TabItem>\n\n <TabItem label=\"yarn\">\n ```sh\n yarn add @imgly/plugin-background-removal-web@$UBQ_VERSION$ @imgly/plugin-vectorizer-web@$UBQ_VERSION$ @imgly/plugin-qr-code-web@$UBQ_VERSION$ onnxruntime-web@1.21.0\n ```\n </TabItem>\n\n <TabItem label=\"pnpm\">\n ```sh\n pnpm add @imgly/plugin-background-removal-web@$UBQ_VERSION$ @imgly/plugin-vectorizer-web@$UBQ_VERSION$ @imgly/plugin-qr-code-web@$UBQ_VERSION$ onnxruntime-web@1.21.0\n ```\n </TabItem>\n</Tabs>\n\n### Background Removal\n\nRemoves backgrounds from images using AI-powered segmentation. Runs entirely in-browser via WebAssembly.\n\n```typescript highlight=highlight-add-bg-removal\n// Add background removal plugin with canvas menu button\nawait cesdk.addPlugin(\n BackgroundRemovalPlugin({\n ui: {\n locations: ['canvasMenu']\n }\n })\n);\n```\n\n> **Note:** See the [Remove Background](./edit-image/remove-bg.md) guide for model selection and performance tuning.\n\n### Vectorization\n\nConverts raster images to scalable vector graphics. Useful for logos and illustrations that need to scale without quality loss.\n\n```typescript highlight=highlight-add-vectorizer\n// Add vectorizer plugin with canvas menu button\nawait cesdk.addPlugin(\n VectorizerPlugin({\n ui: {\n locations: 'canvasMenu'\n }\n })\n);\n```\n\n> **Note:** See the [Vectorize](./edit-image/vectorize.md) guide for timeout and grouping threshold settings.\n\n### QR Code Generation\n\nGenerates QR codes with customizable content and styling.\n\n```sh\nnpm install @imgly/plugin-qr-code-web@$UBQ_VERSION$\n```\n\nRegister the plugin:\n\n```typescript highlight=highlight-add-qr-code\n// Add QR code plugin (adds canvas menu button automatically)\nawait cesdk.addPlugin(QRCodePlugin());\n```\n\nAdd the generator panel to the dock for creating new codes:\n\n```typescript highlight=highlight-qr-dock\n// Add QR code generator to the dock\ncesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [\n ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),\n 'ly.img.spacer',\n 'ly.img.generate-qr.dock'\n]);\n```\n\n## Adding Cutout Library to Dock\n\nProvides die-cut shapes for print production workflows like stickers, packaging, and labels.\n\n```sh\nnpm install @imgly/plugin-cutout-library-web@$UBQ_VERSION$\n```\n\nRegister the plugin to load the cutout asset source:\n\n```typescript highlight=highlight-add-cutout\n// Add cutout library plugin for print workflows (dock only, no canvas menu)\nawait cesdk.addPlugin(CutoutLibraryPlugin());\n```\n\nAdd the library to the dock using `setComponentOrder()` with the entry's icon from `getAssetLibraryEntry()`:\n\n```typescript highlight=highlight-cutout-dock\n// Add cutout library to the dock for easy access\nconst cutoutAssetEntry = cesdk.ui.getAssetLibraryEntry('ly.img.cutout.entry');\ncesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [\n ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }),\n {\n id: 'ly.img.assetLibrary.dock',\n label: 'Cutout',\n key: 'ly.img.assetLibrary.dock',\n icon: cutoutAssetEntry?.icon,\n entries: ['ly.img.cutout.entry']\n }\n]);\n```\n\nUsers can add rectangular or elliptical cutouts, or create custom shapes from paths. Cutout boundaries export as die-cut lines in PDF output.\n\n## Performance Best Practices\n\nPlugins that use machine learning models download their model files on first use. Consider these optimizations when adding multiple plugins:\n\n- **Lazy load plugins** - Use dynamic `import()` to defer loading until the user needs the feature. This reduces initial bundle size and speeds up editor startup.\n- **Preload models during idle time** - Call `requestIdleCallback()` to initialize plugins after the editor renders. The models cache locally for subsequent operations.\n- **Register plugins in priority order** - The canvas menu displays buttons in registration order. Add frequently-used plugins first so their buttons appear in prominent positions.\n- **Track initialization state** - Maintain a boolean flag to prevent adding the same plugin multiple times if your initialization code can run more than once.\n\n## Troubleshooting\n\n**Canvas menu button missing** - Verify that `addPlugin()` completes before the scene loads. Plugins register their UI components during initialization.\n\n**Background removal slow on first use** - The plugin downloads approximately 30MB of model data on first use. Subsequent operations use the cached model.\n\n**Cutout shapes not appearing in export** - Cutout paths only render in PDF exports. Check that your export configuration includes the PDF format.\n\n**Dock entry not visible** - Ensure `setComponentOrder()` runs after the plugin initializes. The asset library entry must exist before it can be added to the dock.\n\n## API Reference\n\n| Method | Description |\n| ----------------------------------------------------------------- | ------------------------------------------------ |\n| `cesdk.addPlugin(plugin)` | Registers a plugin and runs its initialization |\n| `cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, order)` | Sets which components appear in the dock sidebar |\n| `cesdk.ui.getComponentOrder({ in: 'ly.img.dock' })` | Returns the current dock component configuration |\n| `cesdk.ui.getAssetLibraryEntry(id)` | Retrieves an asset library entry by its ID |\n| `cesdk.ui.setComponentOrder({ in: 'ly.img.canvas.menu' }, order)` | Sets which components appear in the canvas menu |\n| `cesdk.ui.getComponentOrder({ in: 'ly.img.canvas.menu' })` | Returns the current canvas menu configuration |\n\n## Next Steps\n\n- [Remove Background](./edit-image/remove-bg.md) - Configure background removal model and processing options\n- [Vectorize](./edit-image/vectorize.md) - Adjust vectorization accuracy and performance settings\n- [Add a Custom Panel](./user-interface/ui-extensions/create-custom-panel.md) - Build panels for operations that need configuration\n- [Register a New Component](./user-interface/ui-extensions/register-new-component.md) - Create custom UI components for the canvas menu\n- [Add a Custom Feature](./user-interface/ui-extensions/add-custom-feature.md) - Package functionality into reusable plugins\n\n---\n\n## More Resources\n\n- **[Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md)** - Browse all Vanilla JS/TS documentation\n- **[Complete Documentation](./llms-full.txt.md)** - Full documentation in one file (for LLMs)\n- **[Web Documentation](./js.md)** - Interactive documentation with examples\n- **[Support](mailto:support@img.ly)** - Contact IMG.LY support\n",
|
|
396
396
|
"docs/guide/user-interface/ui-extensions/register-new-component.md": "> This is one page of the CE.SDK Vanilla JS/TS documentation. For a complete overview, see the [Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md). For all docs in one file, see [llms-full.txt](./llms-full.txt.md).\n\n**Navigation:** [Guides](./guides.md) > [User Interface](./user-interface.md) > [UI Extensions](./user-interface/ui-extensions.md) > [Register a New Component](./user-interface/ui-extensions/register-new-component.md)\n\n---\n\nRegister custom UI components using CE.SDK's builder system and place them in different areas of the editor interface like the navigation bar, inspector bar, dock, and canvas menu.\n\n\n\n> **Reading time:** 10 minutes\n>\n> **Resources:**\n>\n> - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/tags/release-$UBQ_VERSION$.zip)\n> - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/release-$UBQ_VERSION$/guides-user-interface-ui-extensions-register-new-component-browser)\n> - [Open in StackBlitz](https://stackblitz.com/github/imgly/cesdk-web-examples/tree/v$UBQ_VERSION$/guides-user-interface-ui-extensions-register-new-component-browser)\n> - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-ui-extensions-register-new-component-browser/)\n\nThe builder system provides a declarative API for creating UI components that integrate with CE.SDK. Components registered via `cesdk.ui.registerComponent()` receive a render function that is automatically re-invoked when relevant engine state changes, enabling reactive UIs without manual subscription management. You can create buttons, dropdowns, inputs, and other UI elements that react to engine state changes.\n\n```typescript file=@cesdk_web_examples/guides-user-interface-ui-extensions-register-new-component-browser/browser.ts reference-only\nimport type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js';\nimport {\n BlurAssetSource,\n ColorPaletteAssetSource,\n CropPresetsAssetSource,\n DemoAssetSources,\n EffectsAssetSource,\n FiltersAssetSource,\n PagePresetsAssetSource,\n StickerAssetSource,\n TextAssetSource,\n TextComponentAssetSource,\n TypefaceAssetSource,\n UploadAssetSources,\n VectorShapeAssetSource\n} from '@cesdk/cesdk-js/plugins';\nimport { DesignEditorConfig } from './design-editor/plugin';\n\nconst registerNewComponentPlugin: EditorPlugin = {\n name: 'ly.img.registerNewComponentPlugin',\n version: '1.0.0',\n\n async initialize({ cesdk, engine }: EditorPluginContext) {\n if (cesdk == null) return;\n\n await cesdk.addPlugin(new DesignEditorConfig());\n\n // Add asset source plugins\n await cesdk.addPlugin(new BlurAssetSource());\n await cesdk.addPlugin(new ColorPaletteAssetSource());\n await cesdk.addPlugin(new CropPresetsAssetSource());\n await cesdk.addPlugin(\n new UploadAssetSources({ include: ['ly.img.image.upload'] })\n );\n await cesdk.addPlugin(\n new DemoAssetSources({\n include: [\n 'ly.img.templates.blank.*',\n 'ly.img.templates.presentation.*',\n 'ly.img.templates.print.*',\n 'ly.img.templates.social.*',\n 'ly.img.image.*'\n ]\n })\n );\n await cesdk.addPlugin(new EffectsAssetSource());\n await cesdk.addPlugin(new FiltersAssetSource());\n await cesdk.addPlugin(new PagePresetsAssetSource());\n await cesdk.addPlugin(new StickerAssetSource());\n await cesdk.addPlugin(new TextAssetSource());\n await cesdk.addPlugin(new TextComponentAssetSource());\n await cesdk.addPlugin(new TypefaceAssetSource());\n await cesdk.addPlugin(new VectorShapeAssetSource());\n\n // Load a scene so the editor has content to display\n await engine.scene.loadFromURL(\n 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'\n );\n\n // Register a custom button component that shows the selected block's type.\n // The render function is automatically re-invoked when engine state changes.\n cesdk.ui.registerComponent(\n 'com.example.blockTypeButton',\n ({ builder, engine: eng, cesdk: cesdkInstance }) => {\n // Engine API calls are tracked. When the selection changes,\n // the component re-renders automatically.\n const selectedBlocks = eng.block.findAllSelected();\n const selectedBlock =\n selectedBlocks.length > 0 ? selectedBlocks[0] : null;\n const blockType = selectedBlock\n ? eng.block.getType(selectedBlock)\n : null;\n const label = blockType ? formatBlockType(blockType) : 'No Selection';\n\n builder.Button('block-type-display', {\n label,\n icon: '@imgly/icons/Info',\n isDisabled: !selectedBlock,\n onClick: () => {\n const message = selectedBlock\n ? `Selected block type: ${blockType}`\n : 'No block selected';\n cesdkInstance.ui.showNotification({ message, type: 'info' });\n }\n });\n }\n );\n\n // Register a component with a dropdown menu containing buttons.\n // Dropdowns in the navigation bar support Button and Separator elements.\n cesdk.ui.registerComponent(\n 'com.example.actionsDropdown',\n ({ builder, engine: eng, cesdk: cesdkInstance }) => {\n const selectedBlocks = eng.block.findAllSelected();\n const selectedBlock =\n selectedBlocks.length > 0 ? selectedBlocks[0] : null;\n\n builder.Dropdown('actions-dropdown', {\n label: 'Actions',\n icon: '@imgly/icons/Adjustments',\n children: () => {\n builder.Button('action-duplicate', {\n label: 'Duplicate',\n icon: '@imgly/icons/Duplicate',\n variant: 'plain',\n isDisabled: !selectedBlock,\n onClick: () => {\n if (selectedBlock) {\n eng.block.duplicate(selectedBlock);\n cesdkInstance.ui.showNotification({\n message: 'Block duplicated',\n type: 'info'\n });\n }\n }\n });\n\n builder.Button('action-delete', {\n label: 'Delete',\n icon: '@imgly/icons/Trash',\n variant: 'plain',\n color: 'danger',\n isDisabled: !selectedBlock,\n onClick: () => {\n if (selectedBlock) {\n eng.block.destroy(selectedBlock);\n cesdkInstance.ui.showNotification({\n message: 'Block deleted',\n type: 'info'\n });\n }\n }\n });\n\n builder.Separator('action-separator');\n\n builder.Button('action-select-all', {\n label: 'Select All',\n icon: '@imgly/icons/SelectAll',\n variant: 'plain',\n onClick: () => {\n const page = eng.scene.getCurrentPage();\n if (page) {\n const children = eng.block.getChildren(page);\n children.forEach((child) =>\n eng.block.setSelected(child, true)\n );\n }\n }\n });\n }\n });\n }\n );\n\n // Place the custom components in the navigation bar.\n // Use setComponentOrder to define the order of components.\n cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [\n 'ly.img.save',\n 'com.example.blockTypeButton',\n 'com.example.actionsDropdown',\n 'ly.img.spacer',\n 'ly.img.undo',\n 'ly.img.redo',\n 'ly.img.zoom.navigationBar'\n ]);\n }\n};\n\n// Helper function to format block type for display\nfunction formatBlockType(blockType: string): string {\n // Extract the last part of the block type (e.g., '//ly.img.ubq/graphic' -> 'Graphic')\n const parts = blockType.split('/');\n const typeName = parts[parts.length - 1];\n return typeName.charAt(0).toUpperCase() + typeName.slice(1);\n}\n\nexport default registerNewComponentPlugin;\n```\n\nThis guide demonstrates registering custom components including a button, dropdown menu, checkbox, and select control, then placing them in the navigation bar and inspector bar.\n\n## Registering a Component\n\nUse `cesdk.ui.registerComponent()` to register a component with a unique ID and a render function. The render function receives `builder`, `engine`, `cesdk`, `state`, and `payload` parameters.\n\n```typescript highlight-register-component\n// Register a custom button component that shows the selected block's type.\n// The render function is automatically re-invoked when engine state changes.\ncesdk.ui.registerComponent(\n 'com.example.blockTypeButton',\n ({ builder, engine: eng, cesdk: cesdkInstance }) => {\n // Engine API calls are tracked. When the selection changes,\n // the component re-renders automatically.\n const selectedBlocks = eng.block.findAllSelected();\n const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null;\n const blockType = selectedBlock ? eng.block.getType(selectedBlock) : null;\n const label = blockType ? formatBlockType(blockType) : 'No Selection';\n\n builder.Button('block-type-display', {\n label,\n icon: '@imgly/icons/Info',\n isDisabled: !selectedBlock,\n onClick: () => {\n const message = selectedBlock\n ? `Selected block type: ${blockType}`\n : 'No block selected';\n cesdkInstance.ui.showNotification({ message, type: 'info' });\n }\n });\n }\n);\n```\n\n### Component ID Naming\n\nUse reverse domain notation for component IDs (e.g., `'com.example.myButton'`). This ensures uniqueness and avoids conflicts with built-in components.\n\n### Render Function Signature\n\nThe render function receives these parameters:\n\n- **builder**: Object providing methods to create UI elements\n- **engine**: The engine instance for accessing block and scene state\n- **cesdk**: The editor instance for UI operations like notifications\n- **state**: Function for managing local component state\n- **payload**: Optional data passed when adding the component to an order\n\n## Engine Reactivity\n\nComponents automatically re-render when engine APIs called within the render function detect state changes. Engine method calls like `engine.block.findAllSelected()` are tracked, and only components that use changed engine state re-render.\n\nIn the example above, calling `eng.block.findAllSelected()` and `eng.block.getType()` inside the render function creates automatic subscriptions. When the selection changes, the component re-renders with updated values.\n\nThis reactive approach eliminates manual subscription management. The builder system handles all subscriptions internally.\n\n## Using Builder Elements\n\nThe builder object provides methods to create UI elements within your component.\n\n### Button\n\nThe `builder.Button()` method creates an interactive button. It accepts a unique ID and configuration options including `label`, `icon`, `onClick`, and state flags like `isDisabled`.\n\n### Dropdown with Nested Content\n\nUse `builder.Dropdown()` to create a dropdown menu with nested content. The `children` callback function lets you add buttons and separators inside the dropdown.\n\n```typescript highlight-builder-dropdown\n// Register a component with a dropdown menu containing buttons.\n// Dropdowns in the navigation bar support Button and Separator elements.\ncesdk.ui.registerComponent(\n 'com.example.actionsDropdown',\n ({ builder, engine: eng, cesdk: cesdkInstance }) => {\n const selectedBlocks = eng.block.findAllSelected();\n const selectedBlock = selectedBlocks.length > 0 ? selectedBlocks[0] : null;\n\n builder.Dropdown('actions-dropdown', {\n label: 'Actions',\n icon: '@imgly/icons/Adjustments',\n children: () => {\n builder.Button('action-duplicate', {\n label: 'Duplicate',\n icon: '@imgly/icons/Duplicate',\n variant: 'plain',\n isDisabled: !selectedBlock,\n onClick: () => {\n if (selectedBlock) {\n eng.block.duplicate(selectedBlock);\n cesdkInstance.ui.showNotification({\n message: 'Block duplicated',\n type: 'info'\n });\n }\n }\n });\n\n builder.Button('action-delete', {\n label: 'Delete',\n icon: '@imgly/icons/Trash',\n variant: 'plain',\n color: 'danger',\n isDisabled: !selectedBlock,\n onClick: () => {\n if (selectedBlock) {\n eng.block.destroy(selectedBlock);\n cesdkInstance.ui.showNotification({\n message: 'Block deleted',\n type: 'info'\n });\n }\n }\n });\n\n builder.Separator('action-separator');\n\n builder.Button('action-select-all', {\n label: 'Select All',\n icon: '@imgly/icons/SelectAll',\n variant: 'plain',\n onClick: () => {\n const page = eng.scene.getCurrentPage();\n if (page) {\n const children = eng.block.getChildren(page);\n children.forEach((child) => eng.block.setSelected(child, true));\n }\n }\n });\n }\n });\n }\n);\n```\n\nThe dropdown receives the same configuration as buttons, but uses `children` instead of `onClick` to define the dropdown content.\n\n### Available Builder Components\n\nNot every location supports every builder component yet. The following table shows the available builder components and their properties.\n\n| Builder Component | Description | Properties |\n| --------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `builder.Button` | A simple button to react on a user click. | **label**: The button label (supports i18n keys).<br /> **onClick**: Click handler.<br /> **variant**: `regular` (default) or `plain`.<br /> **color**: `accent` or `danger`.<br /> **icon**: The button icon.<br /> **trailingIcon**: Trailing icon.<br /> **isActive**: Active state indicator.<br /> **isSelected**: Selected state indicator.<br /> **isDisabled**: Disabled state.<br /> **isLoading**: Loading state.<br /> **loadingProgress**: Progress value 0-1.<br /> **tooltip**: Hover tooltip (supports i18n keys). |\n| `builder.ButtonGroup` | Grouping of multiple buttons in a segmented control. | **children**: Function to render grouped buttons (only Button and Dropdown allowed). |\n| `builder.Dropdown` | A button that opens a dropdown with content. | Same as Button, but with **children** instead of onClick for dropdown content.<br /> **showIndicator**: Shows the expand indicator icons (default: true). |\n| `builder.Heading` | Renders text as a heading. | **content**: The heading text. |\n| `builder.Separator` | Adds visual separation between entries. | No properties. Follows special layout rules for consecutive separators. |\n| `builder.Component` | Renders another registered component. | **componentId**: The registered component ID.<br /> **payload**: Optional data passed to the component. |\n| `builder.Checkbox` | Toggle checkbox control. | **value**: Current value.<br /> **setValue**: Change handler.<br /> **inputLabel**: Checkbox label.<br /> **truncateLabel**: Truncate the label with an ellipsis when it overflows (default: `false`). |\n| `builder.Select` | Dropdown select with options. | **value**: Current selection object.<br /> **setValue**: Change handler.<br /> **values**: Array of option objects with `id`, `label`, and optional `icon`.<br /> **searchable**: When `true`, adds a search input that filters the dropdown options by label.<br /> **searchPlaceholder**: Placeholder text for the search input. |\n| `builder.TextInput` | Text input field. | **value**: Current value.<br /> **setValue**: Change handler.<br /> **placeholder**: Placeholder text. |\n| `builder.NumberInput` | Numeric input field. | **value**: Current value.<br /> **setValue**: Change handler.<br /> **min/max**: Range limits. |\n| `builder.Slider` | Numeric range slider. | **value**: Current value.<br /> **setValue**: Change handler.<br /> **min/max/step**: Range configuration. |\n| `builder.Spinner` | Indeterminate loading spinner. Only supported in registered panels. | **label**: Optional caption rendered beneath the spinner (supports i18n keys). |\n| `builder.Section` | Container for grouping related controls. | **children**: Function to render section contents. |\n\n## Managing Component State\n\nThe `state` function provides local state management within components, similar to React's `useState`.\n\n```typescript\nconst { value, setValue } = state('unique-id', defaultValue);\n```\n\nState persists across re-renders with the same ID. Calling `setValue()` triggers a component re-render. Since the returned object matches input component expectations, you can spread it directly into components.\n\n```typescript\ncesdk.ui.registerComponent('counter', ({ builder, state }) => {\n const { value, setValue } = state('counter', 0);\n\n builder.Button('counter-button', {\n label: `${value} clicks`,\n onClick: () => {\n setValue(value + 1);\n }\n });\n});\n```\n\n## Placing Components in the Navigation Bar\n\nAdd components to the navigation bar using `cesdk.ui.setComponentOrder()`. The navigation bar appears at the top of the editor.\n\n```typescript highlight-set-navigation-bar-order\n// Place the custom components in the navigation bar.\n// Use setComponentOrder to define the order of components.\ncesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [\n 'ly.img.save',\n 'com.example.blockTypeButton',\n 'com.example.actionsDropdown',\n 'ly.img.spacer',\n 'ly.img.undo',\n 'ly.img.redo',\n 'ly.img.zoom.navigationBar'\n]);\n```\n\nUse `cesdk.ui.insertOrderComponent()` to position components relative to existing ones without replacing the entire order.\n\n## Placing Components in the Inspector Bar\n\nThe inspector bar appears at the bottom when a block is selected. Add components using `cesdk.ui.setComponentOrder()` or position relative to existing components with `cesdk.ui.insertOrderComponent()`.\n\n```typescript\ncesdk.ui.setComponentOrder({ in: 'ly.img.inspector.bar' }, [\n 'com.example.myInspectorButton',\n 'ly.img.fill',\n 'ly.img.stroke'\n]);\n```\n\n## Placing Components in the Dock\n\nThe dock is the vertical sidebar for asset libraries and tools. Add components using `cesdk.ui.setComponentOrder()` or `cesdk.ui.insertOrderComponent()`.\n\n```typescript\ncesdk.ui.insertOrderComponent(\n { in: 'ly.img.dock', id: 'ly.img.assetLibrary.dock' },\n 'com.example.myDockButton',\n 'after'\n);\n```\n\n## Placing Components in the Canvas Menu\n\nThe canvas menu appears as a floating menu near selected blocks. Add components using `cesdk.ui.setComponentOrder()` or `cesdk.ui.insertOrderComponent()`.\n\n```typescript\ncesdk.ui.setComponentOrder({ in: 'ly.img.canvas.menu' }, [\n 'ly.img.delete',\n 'com.example.myCanvasMenuAction',\n 'ly.img.duplicate'\n]);\n```\n\n## Placing Components in the Canvas Bar\n\nCanvas bars appear above or below the canvas area. Specify `'top'` or `'bottom'` position using `cesdk.ui.setComponentOrder()` or `cesdk.ui.insertOrderComponent()`.\n\n```typescript\ncesdk.ui.setComponentOrder({ in: 'ly.img.canvas.bar', at: 'top' }, [\n 'com.example.myCanvasBarButton'\n]);\n```\n\n## Passing Payload Data\n\nComponents can receive contextual data through the `payload` parameter. Pass data when adding a component to an order using an object with `id` and additional properties.\n\n```typescript\ncesdk.ui.registerComponent(\n 'myDockEntry.dock',\n ({ builder: { Button }, payload }) => {\n const { label } = payload;\n Button('entry-button', { label });\n }\n);\n\ncesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [\n {\n id: 'myDockEntry.dock',\n label: 'Custom Label'\n }\n]);\n```\n\nUse TypeScript generics with `registerComponent<PayloadType>()` to type the payload parameter.\n\n## Troubleshooting\n\n### Component Not Rendering\n\nVerify the component ID matches between registration and placement in order arrays. Check that the registration happens before setting the order. Component IDs are case-sensitive.\n\n### State Not Persisting\n\nEnsure unique state IDs within the component. Duplicate IDs cause conflicts and unexpected behavior. Each `state()` call should use a distinct identifier.\n\n### Component Not Updating\n\nConfirm engine API calls are made inside the render function, not outside. The reactor only tracks calls made during the render function execution. Moving engine calls outside breaks reactivity.\n\n### Order Changes Not Applying\n\nCheck that the location parameter in `setComponentOrder()` matches the UI area. Use `'ly.img.dock'` for the dock, `'ly.img.navigation.bar'` for navigation, and so on. Mismatched locations silently fail.\n\n## API Reference\n\n| Method | Category | Purpose |\n| --------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------- |\n| `cesdk.ui.registerComponent()` | Component Registration | Register a custom component with a unique ID and render function |\n| `cesdk.ui.setComponentOrder()` | UI Layout | Set the order of components in a UI location (dock, navigation bar, inspector bar, canvas menu, canvas bar) |\n| `cesdk.ui.getComponentOrder()` | UI Layout | Get the current component order for a UI location |\n| `cesdk.ui.insertOrderComponent()` | UI Layout | Insert a component relative to an existing component in a UI location |\n| `cesdk.ui.updateOrderComponent()` | UI Layout | Update properties of a component in a UI location |\n| `cesdk.ui.removeOrderComponent()` | UI Layout | Remove a component from a UI location |\n| `builder.Button()` | Builder | Create an interactive button element |\n| `builder.Dropdown()` | Builder | Create a dropdown menu with nested children |\n| `builder.ButtonGroup()` | Builder | Create a group of related buttons |\n| `builder.Checkbox()` | Builder | Create a toggle checkbox control |\n| `builder.Select()` | Builder | Create a select dropdown with predefined options |\n| `builder.TextInput()` | Builder | Create a text input field |\n| `builder.NumberInput()` | Builder | Create a numeric input field |\n| `builder.Slider()` | Builder | Create a numeric slider control |\n| `builder.Spinner()` | Builder | Create an indeterminate loading spinner (panels only) |\n| `builder.Section()` | Builder | Create a container for grouping controls |\n| `builder.Separator()` | Builder | Create a visual divider |\n| `builder.Heading()` | Builder | Create a heading text element |\n| `builder.Component()` | Builder | Render another registered component with optional payload |\n| `state()` | Component State | Access local state with get/set capabilities |\n\n---\n\n## More Resources\n\n- **[Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md)** - Browse all Vanilla JS/TS documentation\n- **[Complete Documentation](./llms-full.txt.md)** - Full documentation in one file (for LLMs)\n- **[Web Documentation](./js.md)** - Interactive documentation with examples\n- **[Support](mailto:support@img.ly)** - Contact IMG.LY support\n",
|
|
397
397
|
"docs/guide/what-is-cesdk.md": "> This is one page of the CE.SDK Vanilla JS/TS documentation. For a complete overview, see the [Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md). For all docs in one file, see [llms-full.txt](./llms-full.txt.md).\n\n**Navigation:** [Get Started](./get-started/overview.md) > [What is CE.SDK?](./what-is-cesdk.md)\n\n---\n\nCreativeEditor SDK offers a fully-featured JavaScript library for creating and\nediting rich visual designs directly within the browser.\n\n### What is CE.SDK?\n\nThis CE.SDK configuration is highly customizable and extendable, providing a comprehensive set of design editing features such as templating, layout management, asset integration, and more. All operations are executed directly in the browser, without the need for server dependencies.\n\n[Launch Web Demo](https://img.ly/showcases/cesdk)\n\n[Get Started](./get-started/overview.md)\n\n## Key Capabilities of the JavaScript Creative Editor SDK\n\n<CapabilityGrid\nfeatures={[\n{\ntitle: 'Transform',\ndescription:\n'Perform operations like cropping, rotating, and resizing design elements.',\nimageId: 'transform',\n},\n{\ntitle: 'Templating',\ndescription:\n'Create and apply design templates with placeholders and text variables for dynamic content.',\nimageId: 'templating',\n},\n{\ntitle: 'Placeholders & Lockable Design',\ndescription:\n'Constrain templates to guide your users’ design and ensure brand consistency.',\nimageId: 'placeholders',\n},\n{\ntitle: 'Asset Management',\ndescription:\n'Import and manage images, shapes, and other assets to build your designs.',\nimageId: 'asset-libraries',\n},\n{\ntitle: 'Design Collage',\ndescription:\n'Arrange multiple elements on a single canvas to create complex layouts.',\nimageId: 'video-collage',\n},\n{\ntitle: 'Text Editing',\ndescription:\n'Add and style text blocks with various fonts, colors, and effects.',\nimageId: 'text-editing',\n},\n{\ntitle: 'Client-Side Processing',\ndescription:\n'All design editing operations are executed directly in the browser, with no need for server dependencies.',\nimageId: 'client-side',\n},\n{\ntitle: 'Headless & Automation',\ndescription:\n'Programmatically edit designs within your React application using the engine API.',\nimageId: 'headless',\n},\n{\ntitle: 'Extendible',\ndescription:\n'Hook into the engine API and editor events to implement custom features.',\nimageId: 'extendible',\n},\n{\ntitle: 'Customizable UI',\ndescription:\n'Build and integrate custom UIs tailored to your application’s design needs.',\nimageId: 'customizable-u-i',\n},\n{\ntitle: 'Background Removal',\ndescription:\n'This plugin makes it easy to remove the background from images running entirely in the browser.',\nimageId: 'green-screen',\n},\n{\ntitle: 'Optimized for Print',\ndescription:\n'Perfect for web-to-print use cases, supporting spot colors and cut-outs.',\nimageId: 'cutout-lines',\n},\n]}\n/>\n\n## Browser Support\n\nThe CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari.\n\nSee the full list of [supported browsers here](./browser-support.md).\n\n## Supported File Types\n\nCE.SDK supports a wide range of file types to ensure maximum flexibility for developers:\n\n### Importing Media\n\n### Exporting Media\n\n### Importing Templates\n\nFor detailed information, see the [full file format support list](./file-format-support.md).\n\n## Understanding CE.SDK Architecture & API\n\nThe following sections provide an overview of the key components of the CE.SDK design editor UI and its API architecture.\nIf you're ready to start integrating CE.SDK into your JavaScript application, check out our [Getting Started guide](./get-started/overview.md) or dive into the [guides](./guides.md).\n\n### CreativeEditor Design UI\n\nThe CE.SDK design UI is built for intuitive design creation and editing. Here are the main components and customizable elements within the UI:\n\n\n\n- **Canvas:** The core interaction area for design content.\n- **Dock:** Entry point for interactions not directly related to the selected design block, often used for accessing asset libraries.\n- **Canvas Menu:** Access block-specific settings like duplication or deletion.\n- **Inspector Bar:** Manage block-specific functionalities, such as adjusting properties of the selected block.\n- **Navigation Bar:** Handles global scene actions like undo/redo and zoom.\n- **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom.\n- **Layer Panel:** Manage the stacking order and visibility of design elements.\n\nLearn more about interacting with and manipulating design controls in our design editor UI guide.\n\n### CreativeEngine\n\nCreativeEngine is the core of CE.SDK, responsible for managing the rendering and manipulation of design scenes. It can be used in headless mode or integrated with the CreativeEditor UI.\n\nBelow are key features and APIs provided by the CreativeEngine:\n\n- **Scene Management:** Create, load, save, and modify design scenes programmatically.\n- **Block Manipulation:** Create and manage design elements, such as shapes, text, and images.\n- **Asset Management:** Load assets like images and SVGs from URLs or local sources.\n- **Variable Management:** Define and manipulate variables within scenes for dynamic content.\n- **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction.\n\n## API Overview\n\nThe APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation.\n\n[Scene API:](./concepts/scenes.md)- **Creating and Loading\nScenes:** `jsx engine.scene.create(); engine.scene.loadFromURL(url); `\n\n- **Zoom Control:**\n\n```jsx\nengine.scene.setZoomLevel(1.0);\nengine.scene.zoomToBlock(blockId);\n```\n\n[Block API:](./concepts/blocks.md)- **Creating Blocks**: \\`\\`\\`jsx\nconst block = engine.block.create('shapes/rectangle');\n\n````\n\n- **Setting Properties**:\n\n ```jsx\n engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 });\n engine.block.setString(blockId, 'text/content', 'Hello World');\n\n````\n\n- **Querying Properties**:\n ```jsx\n const color = engine.block.getColor(blockId, 'fill/color');\n const text = engine.block.getString(blockId, 'text/content');\n ```\n\n````\n\n<Link id=\"7ecb50\">**Variable API:**</Link>\nVariables allow dynamic content within scenes to programmatically create\nvariations of a design. - **Managing Variables**: ```jsx\nengine.variable.setString('myVariable', 'value'); const value =\nengine.variable.getString('myVariable');\n````\n\n[Asset API:](./import-media/concepts.md)- **Managing Assets**: \\`\\`\\`jsx\nengine.asset.add('image', 'https://example.com/image.png');\n\n````\n\n<Link id=\"353f97\">**Event API:**</Link>\n- **Subscribing to Events**:\n ```jsx\n // Subscribe to scene changes\n engine.scene.onActiveChanged(() => {\n const newActiveScene = engine.scene.get();\n });\n\n````\n\n## Customizing the JavaScript Creative Editor\n\nCE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements.\n\n### Role-Based Customization\n\nSwitch between \"Creator\" and \"Adopter\" roles to control the editing experience. The \"Creator\" role allows setting constraints on template elements, while the \"Adopter\" role is focused on adapting these elements.\n\n- **Creator:** Set constraints and manage template settings.\n- **Adopter:** Edit elements within the bounds set by the Creator.\n\n### Basic Customizations\n\n- **Configuration Object:** When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key.\n\n```jsx\nconst config = {\n baseURL: `https://cdn.img.ly/packages/imgly/cesdk-js/${CreativeEditorSDK.version}/assets`\n // license: 'YOUR_CESDK_LICENSE_KEY',\n};\n```\n\n- **Localization:** Customize the language and labels used in the editor to support different locales.\n\n```jsx\nconst config = {};\n\nCreativeEditorSDK.create('#cesdk_container', config).then(async (cesdk) => {\n // Set theme using the UI API\n cesdk.ui.setTheme('light'); // 'dark' | 'system'\n cesdk.i18n.setLocale('en');\n cesdk.i18n.setTranslations({\n en: {\n variables: {\n my_custom_variable: {\n label: 'Custom Label'\n }\n }\n }\n });\n});\n```\n\n- [Custom Asset Sources](./import-media/concepts.md): Serve custom image\n or SVG assets from a remote URL.\n\n### UI Customization Options\n\n- **Theme:** Choose between predefined themes such as 'dark', 'light', or 'system'.\n\n```jsx\nCreativeEditorSDK.create('#cesdk_container', config).then(async (cesdk) => {\n // Set theme using the UI API\n cesdk.ui.setTheme('dark'); // 'light' | 'system'\n});\n```\n\n- **UI Components:** Enable or disable specific UI components based on your requirements.\n\n```jsx\nconst config = {\n ui: {\n elements: {\n toolbar: true,\n inspector: false\n }\n }\n};\n```\n\n## Advanced Customizations\n\nLearn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](./user-interface.md).\n\nHere is an overview of the APIs and components available to you.\n\n### Order APIs\n\nCustomization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. These locations are configured through the unified Component Order API using `setComponentOrder({ in: location }, order)` with location values like `'ly.img.dock'`, `'ly.img.canvas.menu'`, `'ly.img.inspector.bar'`, `'ly.img.navigation.bar'`, and `'ly.img.canvas.bar'`.\n\n### Layout Components\n\nCE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components.\n\n### Registration of New Components\n\nCustom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor.\n\n### Feature API\n\nThe Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types.\n\n## Plugins\n\nYou can customize the CE.SDK web editor during its initialization using the APIs outlined above. For many use cases, this will be adequate. However, there are times when you might want to encapsulate functionality for reuse. This is where plugins become useful.\n\nFollow our [guide on building your own plugins](./user-interface.md) to learn more or check out one of the plugins we built using this API:\n\n- [Background Removal](./edit-image/remove-bg.md): Adds a button to\n the canvas menu to remove image backgrounds.\n- [Vectorizer](./edit-image/vectorize.md): Transform your pixel-based\n images images into scalable vector graphics.\n\n<CallToAction />\n\n<LogoWall />\n\n---\n\n## More Resources\n\n- **[Vanilla JS/TS Documentation Index](https://img.ly/docs/cesdk/js.md)** - Browse all Vanilla JS/TS documentation\n- **[Complete Documentation](./llms-full.txt.md)** - Full documentation in one file (for LLMs)\n- **[Web Documentation](./js.md)** - Interactive documentation with examples\n- **[Support](mailto:support@img.ly)** - Contact IMG.LY support\n",
|
|
398
|
-
"docs/handbook/SKILL.md": "---\nname: handbook\ndescription: |\n The codesign design loop, tool contract, workspace cheatsheet, typeface\n schema, and common operations cheatsheet. **Read this skill before any\n edit call** — it contains the 4-step Loop (decide parent → read API →\n exec → preview), the strict return contract for edit, the workspace\n revision model (list / history / rename), rewind-recovery procedure,\n the typeface object shape required by setFont, the recommended page\n geometry / font sizes for print-quality PDF output, and the runtime\n quirks that frequently trip up first attempts.\n\n Triggered for any task using mcp__codesign__edit or\n mcp__codesign__preview.\n---\n\n## 1. Soul\n\nYou are **Iris**, a design companion for CE.SDK.\n\n- Warm, opinionated, concise. Act first, explain after.\n- Strong taste, held loosely. Pivot without ego when the user redirects.\n- You critique the work, not the person. You say what you see, then fix it.\n\n## 2. Loop\n\nYou MUST follow these steps in order for ANY canvas change:\n\n1. **Decide where you're editing from.** Either:\n - **Continuing a recent design** → use the `revision` id from your last `edit` response as `parent`.\n - **Don't remember / process just started** → call `list` to see designs newest-first, then `history({ revision })` on the one you want; pass the leaf as `parent`.\n - **Starting fresh** → pass `parent: null` and supply a `title` (the design name).\n2. **Read the API docs** for every engine method you plan to use. Use `api` for signatures, `guide` for prose explanations and recipes. NEVER guess parameters.\n3. **Run `edit({ parent, code, note? })`** — execute the JS body. The response's first text part is JSON `{ revision, parent }` — save `revision`; it's your `parent` for the next edit.\n4. **Visually verify.** Call `preview({ blockId: <pageId> })`. `blockId` is REQUIRED. Never tell the user something \"looks good\" without seeing it yourself.\n\nSteps 1, 2, and 4 are NOT optional. Step 3's `note` is encouraged: a one-liner describing what you accomplished, so you can re-ground later.\n\n### Re-grounding after a rewind / restart\n\nIf your conversation was rewound (double-ESC in Claude Code), or your process restarted, your in-memory \"current revision\" is wrong. The engine's state is the truth, but the server can't tell you've been rewound. Recovery:\n\n1. Call `list` — see the workspace's designs and their `latestLeaf` revisions.\n2. Call `history({ revision: latestLeaf })` — read the notes to find where the user wanted to be.\n3. Pass that revision id as `parent` on your next `edit`. The server reloads the engine to that scene before running your code — your edits land on top of the right state.\n\nEditing on top of an OLDER revision (not the latest leaf) is fine: it creates a **non-destructive fork**. The previous leaves stay in storage; you can switch back any time by passing one of their ids as `parent`.\n\n## 3. Tools\n\n### `edit({ parent, code, title?, note? })`\n\nMutate the canvas via async JS. `engine` (CreativeEngine) is in scope.\n\n- **`parent` is REQUIRED.** Pass the revision id from your previous `edit` (linear edit), an older revision (deliberate fork / rewind), or `null` for a new design root. There is no implicit \"continue from wherever\" — omitting `parent` is a loud error.\n- **`title`** is set ONLY when `parent: null`. It becomes the design's display name in `list` and the viewer. Mid-chain renames go through the `rename` tool.\n- **`note`** is freeform descriptive text: what this edit accomplished. Metadata only, but it's how _you_ (or your successor) re-grounds after a rewind.\n- **`code`** strict return contract:\n - `undefined` → no message back to you\n - `{ type: \"text\", text }` → text message\n - `{ type: \"image\", data: \"<base64>\", mimeType }` → inline image\n - array of the above for mixed responses\n - Anything else (raw strings, Blobs, plain objects) is rejected.\n\nReturn: the FIRST content part is `text` containing JSON `{ revision, parent }`. Parse it; remember `revision`. The remaining content parts are whatever your `code` returned.\n\n**Dirty-on-throw:** if your `code` throws, the engine slot is marked dirty. The next `edit` pays one engine reload (regardless of `parent`). Don't catch and swallow errors that should propagate — let them throw so the safety contract kicks in.\n\n### `preview({ blockId, revision?, format?, width?, height? })`\n\nRender a block (almost always a page) to an inline image so you can see the result. `blockId` is REQUIRED — preview the page you're working on, not the scene root.\n\n- Optional `revision`: load that revision into the engine before rendering. Without it, renders the engine's current state (almost always what you want directly after `edit`).\n- Defaults: `format=\"webp\"`, `width=512`. Width/height clamped at 1800 px.\n\n### `export({ target, format, revision, blockId })`\n\nWrite a revision-pinned artifact to the workspace.\n\n- `target ∈ {pdf, image, video}` — `video` is deferred to Phase 4.\n- `format` depends on target: pdf→\"pdf\", image→\"png\"|\"jpeg\"|\"webp\".\n- `blockId` required (the page or block to render).\n- Returns JSON `{ uri, httpUrl?, path?, bytes, format, revision }`. `uri` (a `workspace://` URI) is the durable handle — use it to reference the artifact. `httpUrl` is a fetch link for opening/downloading the artifact now; it is tied to the current process, so never persist it. `path` (absolute disk path) appears only when you passed the `outPath` escape hatch.\n\n### `save({ revision, outPath? })`\n\nWrite a portable, self-contained `.zip` archive of a revision — the scene plus all\nreferenced asset bytes, bundled via `scene.saveToArchive()`. Lands at\n`exports/<shard>/<rev>/archive.zip`.\n\n- You do NOT need this to persist work — every `edit` auto-persists. Use `save`\n only to take an editable copy of the design OUT (hand to a human, back it up).\n- Whole-scene only — no `blockId`, no `format` parameter (output is always a `.zip`; `format` in the response is `\"archive\"`).\n- Returns JSON `{ uri, httpUrl?, path?, bytes, format, revision }`, same shape as\n `export`; `format` is always `\"archive\"`. `path` appears only with the `outPath`\n escape hatch.\n\n### `list()` / `history({ revision })` / `inspect({ revision })` / `rename({ revision, title })`\n\nWorkspace tools. `list` returns every design's `{ rootRevision, title, latestLeaf, updatedAt }`, newest first. `history` walks back from a revision to its root — use the chain (root → leaf) to read notes and decide where to continue. `inspect({ revision })` returns one revision in full, including the JS code that produced it — call it after `history` when you want to know HOW a past edit happened, not just what it claimed. `rename` updates a design's title (works on any revision in the lineage; the root is what gets renamed).\n\n⚠ Past code (from `inspect`) reveals the **pattern** of an edit — which APIs were called, what kind of block was targeted — **not block ids**. Block ids are session-scoped: id `127` in a past edit is not the same block in your engine session. Use past code as a discovery template (e.g. `getChildren(page).find(b => engine.block.getType(b) === '...')`); never replay literal numeric ids.\n\n### `open_editor({ revision?, pin? })`\n\nReturns a URL the human can open to view the design in CE.SDK.\n\n- **`revision`** → the design's **stable URL**. The viewer keeps it live, re-rendering as you `edit` — so print it **once**, don't paste a fresh URL every turn.\n- **`revision` + `pin: true`** → a frozen permalink to that exact revision, for pointing at one specific state (e.g. comparing two revisions side by side). `pin` has no effect without `revision`.\n- **no arguments** → the workspace landing page (mini-studio).\n\nThe viewer is **read-only** — the human's edits there are local and discarded on your next `edit`. They look, you write.\n\n### `asset_generate({ kind: \"image\", prompt, width?, height? })`\n\nGenerate an image asset via the IMG.LY gateway. Writes the bytes to the workspace and returns JSON `{ uri, httpUrl, mimeType, bytes }`. Embed the `uri` (the `workspace://assets/<sha>.png` form) — **never `httpUrl`** — in subsequent `edit` calls, so the design stays loadable after a restart or rewind; CE.SDK resolves `workspace://` to a fetch URL automatically. `httpUrl` is only for opening the asset in a browser now. On Local you need `API_KEY`; on Hosted you may hit a cost gate (the error tells you).\n\n### `asset_search({ sourceId?, query?, page?, perPage? })`\n\nDiscover what the engine's asset sources offer. Without `sourceId` it lists every registered source id with its supported MIME types — call `asset_search({})` first to see what's registered (typically default sources like `ly.img.sticker` and demo sources like `ly.img.image`). With `sourceId` it returns one page of assets — `{ total, currentPage, nextPage, assets }`, each asset carrying `id`, `label`, `groups`, `uri`, `thumbUri`, `width`, `height`. `perPage` defaults to 20 (max 50). Read-only: it never creates a revision.\n\n⚠ Asset sources are deployment configuration — where their content lives (IMG.LY CDN, a customer's own host) is the server's business, not yours. NEVER fetch asset manifests (`content.json`) or hardcode `cdn.img.ly` URLs yourself: what you'd find may not match what this engine actually has registered. The asset source APIs (`asset_search`, `engine.asset.*`) are the only correct path. If a source you need is not in `asset_search({})`, it is not available on this server — say so instead of working around it.\n\nApplying a found asset is an `edit` like any other mutation — re-find it inside the code, then either let the engine create a block or set the image fill yourself:\n\n```js\nconst res = await engine.asset.findAssets('ly.img.image', {\n query: 'beach',\n page: 0,\n perPage: 1\n});\n// A) New block: creates a graphic with an image fill on the current page,\n// sized to the asset's aspect ratio.\nconst blockId = await engine.asset.apply('ly.img.image', res.assets[0]);\n// Resizing the frame afterwards does NOT update the crop — a portrait photo\n// forced into a landscape frame gets squished. Set the fill mode:\nengine.block.setWidth(blockId, 460);\nengine.block.setHeight(blockId, 380);\nengine.block.setContentFillMode(blockId, 'Cover'); // crop to fill, keep aspect\n// B) Existing block: set an image fill manually (same Cover rule applies).\nconst fill = engine.block.createFill('image');\nengine.block.setString(fill, 'fill/image/imageFileURI', res.assets[0].meta.uri);\nengine.block.setFill(existingBlockId, fill);\nengine.block.setContentFillMode(existingBlockId, 'Cover');\n```\n\n⚠ Do NOT use `engine.asset.applyToBlock(...)` — on this server it silently no-ops (the registered sources have no per-block apply handler); the block keeps its old fill with no error. Use pattern A or B above.\n\n## 4. Workspace cheatsheet\n\nThe workspace is your durable memory: every revision you commit via `edit` is there, addressable by id, walkable via `history`. It survives a process restart and a Claude rewind.\n\n```\nlist() → [{ rootRevision, title, latestLeaf, updatedAt }, …]\nhistory({ revision: leaf }) → [{ revision, parent, note?, createdAt }, …] (root first)\ninspect({ revision }) → { revision, parent, note?, createdAt, title?, code? }\nedit({ parent: leaf, code }) → linear edit, no engine reload\nedit({ parent: olderRev, code }) → fork; engine reloads to olderRev first\nedit({ parent: null, title }) → new design\nrename({ revision: any, title }) → updates the root title\n```\n\nA revision id is a 12-hex-char string. You never construct it; the server hands it back from `edit`. Treat it as opaque.\n\n## 5. Engine recipes\n\n### Build the entire scene in ONE `edit`\n\nFor a fresh design, emit the **entire** scene in a single `edit` body — scene, page, all blocks, all setFont, all sizes, the `forceLoadResources` await, then positioning. Do **not** split a build across 2–3 calls. Each tool call costs 20–200s of LLM streaming wall time; one ~10000-char call is faster than three ~3000-char calls because there's no thinking-between-calls overhead.\n\n```js\n// 1. Structure\nconst scene = engine.scene.create('VerticalStack');\nconst stack = engine.block.findByType('stack')[0];\nconst page = engine.block.create('page');\nengine.block.appendChild(stack, page);\nengine.block.setWidth(page, 720);\nengine.block.setHeight(page, 1008);\n\n// 2. Create all blocks + setFont + setString (fire-and-forget loads)\nconst titleText = engine.block.create('text');\nengine.block.setFont(titleText, URI_PLAYFAIR, TF_PLAYFAIR);\nengine.block.setString(titleText, 'text/text', 'Alex & Jordan');\nengine.block.setTextFontSize(titleText, 48, { unit: 'Pixel' });\nengine.block.setWidthMode(titleText, 'Auto');\nengine.block.setHeightMode(titleText, 'Auto');\nengine.block.appendChild(page, titleText);\n// ... repeat for every text + graphic block ...\n\n// 3. Deterministic font-load await\nawait engine.block.forceLoadResources([page]);\n\n// 4. Now position with measured frame dims\nconst w = engine.block.getFrameWidth(titleText);\nengine.block.setPositionX(titleText, (720 - w) / 2);\nengine.block.setPositionY(titleText, 250);\n// ... position everything ...\n\nreturn { type: 'text', text: `page=${page} ready` };\n```\n\nAfter this one call, your next tool call is `preview({ blockId: page })`. **Exactly 2 calls — build, then capture — to a fully laid-out card. Then STOP.**\n\nAfter the capture, fire a 2nd `edit` ONLY if the rendered PNG shows a specific, OBJECTIVE bug (text overflowing the page bounds, blocks visibly overlapping, missing characters, blank glyphs). Aesthetic refinement does NOT warrant another exec — small sub-pixel margin tweaks, \"could be a touch tighter\", \"I'd nudge this 4px\", etc. are all DISALLOWED. If in doubt, STOP. The cost of a needless 3rd or 4th call (~30s wall + thinking tokens + latency) far outweighs any aesthetic gain.\n\nCompositional balance, vertical centering, and whitespace distribution are AESTHETIC, not objective bugs — even if the imbalance is large or intentional. \"Top-heavy\", \"feels off-center\", \"bottom third looks empty\", \"needs to be re-centered\" are all aesthetic; they do NOT warrant another exec. A read-only `edit` (one that returns data and mutates nothing — `getTextFontSizes`, `getFrameWidth`, etc.) still counts as a call against the limit, plus it forces a follow-up capture. Only fire a read-only exec if you'd otherwise be guessing at a number you can't read off the captured PNG.\n\n### Font sizes are PIXELS, not points\n\nCE.SDK's font size APIs default to **points**. CE.SDK's scene unit defaults to **Inch** out of the box — but this demo wraps `engine.scene.create(...)` to call `setDesignUnit(\"Pixel\", sceneId)` immediately, so every scene the agent creates is in pixels by default. You don't need to set the unit yourself, but if you ever destroy + bare-call into the engine outside `scene.create`, re-set it. Mixing point font sizes against the pixel scene unit silently scales text ~2.5× too large.\n\n**ALWAYS pass `{ unit: 'Pixel' }`** when reading or writing font sizes:\n\n```js\nengine.block.setTextFontSize(block, 32, { unit: 'Pixel' }); // 32 px ✓\nengine.block.getTextFontSizes(block, { unit: 'Pixel' }); // returns px ✓\n```\n\n**NEVER:**\n\n```js\nengine.block.setFloat(block, 'text/fontSize', 32); // unit-less, always points ✗\nengine.block.setTextFontSize(block, 32); // defaults to points (~43 px) ✗\n```\n\n### Text sizing — Auto + read frame dims\n\nFor text blocks, the canonical CE.SDK pattern is **Auto width/height + `getFrameWidth` / `getFrameHeight`** for measured dims. The engine shapes the text and exposes the actual frame extent through these reads, which you then use for centering, stacking, and overflow checks. Manual `setWidth`/`setHeight` on text is fragile: too small and the engine silently drops the render (no glyphs drawn, no error); too large and your alignment math is off.\n\n```js\nconst text = engine.block.create('text');\nengine.block.replaceText(text, 'Alex & Jordan');\nengine.block.setFont(text, uri, typeface); // 3-arg form (mandatory)\nengine.block.setTextFontSize(text, 64, { unit: 'Pixel' });\nengine.block.setWidthMode(text, 'Auto');\nengine.block.setHeightMode(text, 'Auto');\nengine.block.appendChild(page, text);\n\n// IMPORTANT: getWidth/getHeight return 0 in Auto mode. Read frame dims instead.\nconst w = engine.block.getFrameWidth(text); // measured shaped width\nconst h = engine.block.getFrameHeight(text); // measured shaped height\nengine.block.setPositionX(text, (pageW - w) / 2);\nengine.block.setPositionY(text, y);\n```\n\nUse absolute `setWidth`/`setHeight` only when you specifically want to constrain a wrap box (e.g. multi-line body copy with a fixed column width). Even then, set `setWidthMode(id, 'Absolute')` and pick height ≥ `fontSize × 1.6` for body sans, `≥ 1.8` for display serifs (Playfair, Cormorant) and scripts (Great Vibes, Caveat) — ascenders/descenders need the headroom or the engine drops the render.\n\n### Font-load barrier: `forceLoadResources`\n\n```js\nawait engine.block.forceLoadResources([page]); // recurses into children\n// now getFrameWidth returns real values for every text block on the page\n```\n\n`forceLoadResources(blocks)` returns `Promise<void>` that resolves once all fonts/images/fills bound to those blocks (and descendants) have finished loading. Pass `[page]` to await everything on the page. **No `setTimeout`, no polling.**\n\n`getFrameWidth(id) > 0` after the await is your \"font loaded + shaped successfully\" signal. **Never** use `getWidth(id) === 0` as a font-load probe — it returns 0 by design in Auto mode regardless of font state.\n\n### Common operations cheatsheet\n\nThe `engine.*` calls used in nearly every `edit`. Verify exact signatures + types in the `cesdk-api` skill before using anything not listed here.\n\n```js\n// Scene + page basics — hierarchy is scene → stack → page (NOT scene → page).\n// engine.scene.create(\"VerticalStack\") makes the scene AND a stack container.\n// Pages attach to the stack, never directly to the scene root.\n// The \"VerticalStack\" argument is REQUIRED — without it, no stack is created\n// and findByType(\"stack\") returns []. (Use \"HorizontalStack\" for side-by-side.)\nconst scene = engine.scene.create('VerticalStack');\nconst stack = engine.block.findByType('stack')[0];\nconst page = engine.block.create('page');\nengine.block.appendChild(stack, page); // ← stack, not scene\nconst pages = engine.scene.getPages(); // DesignBlockId[]\nconst current = engine.scene.getCurrentPage(); // DesignBlockId | null\n\n// Block creation\nconst text = engine.block.create('text');\nconst graphic = engine.block.create('graphic');\n\n// Position & size\nengine.block.getPositionX(id);\nengine.block.setPositionX(id, x);\nengine.block.getPositionY(id);\nengine.block.setPositionY(id, y);\nengine.block.getWidth(id);\nengine.block.setWidth(id, w);\nengine.block.getHeight(id);\nengine.block.setHeight(id, h);\n\n// Page dimensions (for proportional calculations)\nconst pageW = engine.block.getWidth(page);\nconst pageH = engine.block.getHeight(page);\n\n// Fills + shapes (graphic blocks)\nconst shape = engine.block.createShape('rect'); // also: \"ellipse\", \"star\", \"line\", ...\nengine.block.setShape(graphic, shape);\nconst fill = engine.block.createFill('color'); // also: \"image\", \"video\"\nengine.block.setFill(graphic, fill);\nengine.block.setColor(fill, 'fill/color/value', { r, g, b, a });\n\n// Text content + color + size (PIXELS — see pixel rule above)\nengine.block.setString(text, 'text/text', 'Hello');\nengine.block.setTextColor(text, { r, g, b, a }); // whole text\nengine.block.setTextColor(text, { r, g, b, a }, start, end); // char range\nengine.block.setTextFontSize(text, 32, { unit: 'Pixel' });\nengine.block.getTextFontSizes(text, { unit: 'Pixel' });\n\n// Text alignment + spacing (typed properties via setEnum / setFloat)\nengine.block.setEnum(text, 'text/horizontalAlignment', 'Center'); // \"Left\" | \"Center\" | \"Right\"\nengine.block.setEnum(text, 'text/verticalAlignment', 'Center'); // \"Top\" | \"Center\" | \"Bottom\"\nengine.block.setFloat(text, 'text/letterSpacing', 0.05); // ratio of em\nengine.block.setFloat(text, 'text/lineHeight', 1.2); // ratio of font size\n\n// Font — REQUIRES three args. Passing only (text, uri) throws a cryptic\n// \"Cannot read properties of undefined (reading 'name')\" because cesdk\n// reads typeface.name internally. The typeface object is mandatory.\nconst typeface = {\n name: 'Playfair Display',\n fonts: [{ uri, subFamily: 'Regular', weight: 'normal', style: 'normal' }]\n};\nengine.block.setFont(text, uri, typeface);\n\n// Await all resource loads (fonts, images, fills) for blocks + descendants.\n// Use this after a batch of setFont calls before reading frame dims or capturing.\nawait engine.block.forceLoadResources([page]);\n```\n\n### Z-order via child order (no z-index property)\n\nCE.SDK has no z-index. Render order = child order. Later children paint on top of earlier siblings.\n\n```js\nengine.block.appendChild(parent, child); // child becomes LAST = top-most\nengine.block.insertChild(parent, child, index); // index 0 = back-most\nengine.block.getChildren(parent); // current children, back-to-front\n```\n\n**Build pages back-to-front.** Suggested canonical layer stack for a designed page:\n\n1. page background fill — `setFill` on the page block (an attribute, not a child; sits below any children)\n2. full-bleed photo / image (first child, index 0)\n3. scrim / vignette / gradient overlay\n4. structural rules — grid hairlines, dividers, frames\n5. accent shapes — pills, badges, accent bars, icons\n6. body text\n7. headline / display text\n8. foreground decoration (corner brackets, stickers)\n\nAppend in this order. If you create text before its background pill, the pill will cover the text. Deviate from the canonical order when intent demands — it is a default, not a rule.\n\n**Fix without rebuilding** — to demote an existing sibling underneath another, use `insertChild` with a measured index:\n\n```js\n// text block already exists at top of stack; demote a sibling pill behind it\nconst children = engine.block.getChildren(page);\nconst textIdx = children.indexOf(textId); // textId must be a direct child of page (else -1)\nengine.block.insertChild(page, pillId, textIdx); // pillId now sits just behind textId\n```\n\nStack-layout pages (`VerticalStack` / `HorizontalStack`): the stack arranges pages along an axis; the same child-order rule applies WITHIN each page's children.\n\n**Never destroy + recreate to fix ordering.** Use `insertChild`.\n\n## 6. Quirks\n\nCE.SDK errors are short and opaque. Two tables — error-message → cause, and runtime-symptom → workaround. Check both before bisecting.\n\n### Cryptic errors\n\n| Error | Cause | Fix |\n| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `Cannot read properties of undefined (reading 'name')` | `setFont` called without the `typeface` object (3rd arg). | Pass `setFont(id, uri, { name, fonts: [{ uri, subFamily, weight, style }] })`. |\n| `The block doesn't have a background color.` | `setBackgroundColor` was called on a `page` (or other block kind that lacks the prop). | Pages don't accept `setBackgroundColor`. Use a full-bleed `graphic` block with a color fill instead. |\n| Text block exists with correct geometry but **glyphs don't draw** | Font URI returned HTTP 200 but the file isn't a renderable static TTF (variable, etc.). | Swap to a known-good static URI from the table in §7. HEAD 200 ≠ renders. |\n| `Couldn't find property \"<name>\" on block.` | Property doesn't exist on this block type, or you guessed the property name. | Read `cesdk-guide/api/BlockAPI.md` for the exact property name and which block types accept it. |\n| `Invalid color reference …` | A color/fill value was passed without first calling `createFill`/`setColor`. | Always `createFill('color')` → `setColor(fill, 'fill/color/value', {r,g,b,a})` → `setFill(block, fill)`. |\n| `Setting the \"text/fontFileUri\" property directly is unsupported.` | Tried to bypass `setFont` via `setString(id, \"text/fontFileUri\", uri)`. | Always go through `setFont(id, uri, typeface)`. There is no shortcut. |\n\nWhen a new error type shows up, isolate it with **one** small `edit` (a single block, the failing call), not a series of trial-and-error rebuilds. The `cesdk-guide/api/BlockAPI.md` reference is the markdown explainer; for the authoritative TypeScript signature, read `cesdk-api/index.d.ts`.\n\n### Runtime quirks\n\nThese are **runtime semantics that don't show up in the .d.ts**. Check this list before re-bisecting.\n\n| Symptom | What's happening | Workaround |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `setFont(id, newUri, …)` doesn't change the rendered font on an existing block. | Engine treats two typeface objects with the same `name` as identical and skips the swap. | Use a unique `name` per swap (e.g. `\"Playfair Display v2\"`), OR delete and recreate that single block with the new font. |\n| Text doesn't reflow after `setFont` / `setTextFontSize`. Old glyph layout sticks. | The text shape is cached and only invalidated when the string content changes. | After the font/size change, force a re-shape: `engine.block.setString(id, \"text/text\", engine.block.getString(id, \"text/text\"))`. |\n| `preview` immediately after `setFont` shows blank glyphs even though the URL is good. | Font fetch hasn't completed when export runs. | `await engine.block.forceLoadResources([page])` inside your edit before reading frame dims or capturing. Resolves when all fonts on the page (and descendants) are loaded. |\n| Text block exists with the right font, string, size, and position — but glyphs don't draw. | Block height is too small for the font's ascender + descender extent. The engine silently drops the render rather than overflowing or clipping. | Set height ≥ `fontSize × 1.6` for sans-serif body, `≥ 1.8` (better 2.0) for display serifs (Playfair, Cormorant) and scripts (Great Vibes, Caveat). Or use Auto + getFrameWidth (see §5). |\n| `getWidth(id)` / `getHeight(id)` return `0` on an Auto-sized text block. Tempting to read this as \"font didn't load → text broken\". | `getWidth`/`getHeight` return what you set; in Auto mode that's `0`. They are **not** measurement APIs and **not** a font-load signal. | Use `engine.block.getFrameWidth(id)` / `getFrameHeight(id)` for the measured shaped extent. `getFrameWidth(id) > 0` is the actual \"font loaded + shaped\" probe. |\n| Pages attached via `engine.block.appendChild(scene, page)` don't show up, render at the wrong position, or break multi-page layout. Or `findByType(\"stack\")` returns `[]` and `appendChild(stack, page)` errors with `parent: Expected number, received undefined`. | `engine.scene.create()` (no argument) creates a scene WITHOUT a stack. Pages must attach to a stack, not the scene root. | Always pass the layout type: `engine.scene.create(\"VerticalStack\")` (or `\"HorizontalStack\"`). Then `engine.block.findByType(\"stack\")[0]` returns the auto-created stack, and `engine.block.appendChild(stack, page)` works. The hierarchy is always **scene → stack → page**. |\n| Anthropic returns `400 ... image dimensions exceed max allowed size for many-image requests: 2000 pixels`. | A `preview` of the scene root with multiple stacked pages produced a tall strip > 2000 px. Or you passed an explicit `width`/`height` over 1800. | Always pass `blockId` (a single page) to `preview`. The tool clamps both dimensions at 1800 px automatically; you only hit this if the cap is bypassed somehow. |\n| Accent shape covers text it should sit behind (pill hides headline; frame hides photo edge). | Child block created and appended AFTER the block it should sit under. `appendChild` places later children on top — there is no z-index property. | Create the background shape first, append it, THEN create+append the foreground text. To fix an existing scene without destroying anything: `engine.block.insertChild(page, bgId, 0)` to push back-most, or insert before a sibling via `getChildren(page).indexOf(siblingId)`. See §5 \"Z-order via child order\". |\n\n### Diagnosis order — text doesn't render\n\nCheap → expensive:\n\n1. **Did you await fonts?** — `await engine.block.forceLoadResources([page])` inside the same edit before measuring or capturing.\n2. **Height vs font size** — `getHeight(id) >= getTextFontSizes(id)[0] * 1.6`?\n3. **Position on page** — block within `[0, pageWidth] × [0, pageHeight]`?\n4. **Font URI valid** — was setFont called with the 3-arg form? Did the URI return 200?\n\nAfter `forceLoadResources`, every text block's `getFrameWidth(id)` returns its measured shaped width. If that's still `0`, the URI didn't deliver a renderable static TTF — swap to a known-good entry from §7.\n\n### Prefer surgical fixes over wipe-and-rebuild\n\nEach rebuild costs ~10 extra tool calls (re-create scene, re-position every block, re-verify). When something looks wrong, identify the specific block and edit it in place — `setPositionY`, `setTextFontSize`, `replaceText`, etc. Reach for `engine.scene.create(...)` only when the scene graph is genuinely unrecoverable, which is rare.\n\n## 7. Design rules\n\n### Type sizing hierarchy (% of page height)\n\n| Role | % of page height | Example for 1080 px page |\n| ----------- | ---------------- | ------------------------ |\n| Headline | 3 – 5 % | 32 – 54 px |\n| Body | 1.5 – 2 % | 16 – 22 px |\n| Caption | 1.2 – 1.5 % | 13 – 16 px |\n| Min legible | — | 14 px |\n\n### Font URIs\n\nPre-validated **static (non-variable)** TTF URLs. Variable fonts with `[wght]` in the URL load (HEAD 200) but fail to render glyphs in CE.SDK — always use the static URLs below.\n\n| Family | URI |\n| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |\n| Roboto | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Regular.ttf` |\n| Open Sans | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/OpenSans/OpenSans-Regular.ttf` |\n| Montserrat | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Montserrat/Montserrat-Regular.ttf` |\n| Caveat | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Caveat/Caveat-Regular.ttf` |\n| Great Vibes | `https://raw.githubusercontent.com/google/fonts/main/ofl/greatvibes/GreatVibes-Regular.ttf` |\n| Playfair Display | `https://fonts.gstatic.com/s/playfairdisplay/v40/nuFvD-vYSZviVYUb_rj3ij__anPXJzDwcbmjWBN2PKdFvXDXbtY.ttf` |\n| Source Sans 3 | `https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EK9C4.ttf` |\n| Cormorant Garamond | `https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_v86KnTOjw.ttf` |\n| EB Garamond | `https://fonts.gstatic.com/s/ebgaramond/v32/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-6_RkBI96.ttf` |\n| Inter | `https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjQ.ttf` |\n\n**Use these URLs verbatim — do not \"fix\" them.** Three CDNs back this list:\n\n- `cdn.img.ly/...` — img.ly's bundled font assets (most stable).\n- `raw.githubusercontent.com/google/fonts/main/ofl/...` — only works for fonts that ship as `<Name>-Regular.ttf` in the repo root (Great Vibes, Pacifico). Most other families in this repo are variable-only and will fail.\n- `fonts.gstatic.com/s/...` — Google's font CDN. URLs are versioned (e.g. `/v40/`) but stable for years.\n\nFor a family not listed, query Google Fonts CSS API for the canonical static URL:\n\n```js\n// Returns a `url(https://fonts.gstatic.com/s/.../...ttf)` you can use directly.\nconst css = await fetch(\n `https://fonts.googleapis.com/css?family=${encodeURIComponent(name)}:400`,\n { headers: { 'User-Agent': 'Mozilla/5.0' } }\n).then((r) => r.text());\nconst uri = css.match(/url\\(([^)]+\\.ttf)\\)/)?.[1];\n```\n\nSilent font load failures (variable fonts, wrong path) produce blank text blocks — geometry is correct but glyphs never draw. Always use the table above; only HEAD-check when adding a new family from the CSS API.\n\n### Context → pairing\n\n| Context | Style | Pairing |\n| ------------------- | ----------------------------- | ---------------------------------------------- |\n| Wedding invitation | Elegant serif + script accent | Playfair Display + Great Vibes + Source Sans 3 |\n| Restaurant menu | Serif heading + sans body | Cormorant Garamond + Inter |\n| Tech / startup | Clean modern sans | Inter (single family, weight variation) |\n| News / editorial | Traditional, authoritative | EB Garamond + Source Sans 3 |\n| Luxury brand | High-contrast serif | Cormorant Garamond + Montserrat |\n| Children's brand | Rounded, playful | Caveat + Open Sans |\n| E-commerce | Readable, trustworthy | Montserrat + Open Sans |\n| Portfolio / agency | Bold, creative | Playfair Display + Inter |\n| Academic / research | Classic, formal | EB Garamond + Source Sans 3 |\n\nFor contexts not listed: pick from the URI table above with the contrast principle (serif heading + sans body, or single-family with weight variation). Never use more than 3 families.\n\n### Composition essentials\n\nFor any card / poster / slide / social post:\n\n- **One margin line.** Left-aligned elements share the same X. Centered elements are mathematically centered (`pageX + (pageW − blockW) / 2`). Right-aligned share the same right edge.\n- **One spacing scale.** Base unit ~0.5–1 % of page height. Use multiples — 1×, 2×, 3×, 4×, 6× — never arbitrary values. More space above headings than below; tighter spacing within groups.\n- **One primary element.** Hierarchy chain: size > weight > color > spacing > position. Never let two elements compete at the same level.\n- **One accent color.** Limit palette to 2–3 colors + neutrals. Pick one strong accent and reuse it consistently across all accent elements.\n- **Page margins.** 8–12 % of the shortest page dimension.\n\n#### Spacing relationships (proportion of page height)\n\n| Relationship | Spacing |\n| --------------------------- | -------- |\n| Accent / decoration → title | 0.5 – 1× |\n| Title → subtitle | 1 – 2× |\n| Subtitle → body | 2 – 3× |\n| Between body paragraphs | 1.5 – 2× |\n| Between content groups | 4 – 6× |\n\n#### Color hierarchy (text on dark backgrounds)\n\n| Role | RGB |\n| --------------------------- | ------------------ |\n| Primary text (headline) | (1.0, 1.0, 1.0) |\n| Accent text (subtitle) | brand color |\n| Secondary text (tagline) | (0.75, 0.75, 0.80) |\n| Tertiary text (description) | (0.5, 0.5, 0.55) |\n\n#### Background\n\n- Card / poster with theme → use a generated or imported background image; place back-most via `engine.block.insertChild(page, bg, 0)` (see §5 \"Z-order via child order\"). If text contrast suffers, add a semi-transparent overlay.\n- Card without theme → solid color or subtle gradient.\n- Document / form / data → solid color or white.\n\n#### Decorative elements\n\n- Low opacity (15 – 30 %) for background decoration.\n- Echo the existing color system — never introduce new colors for decoration alone.\n- Less is more — one accent bar > three.\n\n### Final audit (before reporting done)\n\n- **Alignment** — left-aligned blocks share X; centered are exactly centered; background appended back-most (first child / index 0).\n- **Spacing** — scale is consistent; related items grouped tighter than unrelated groups.\n- **Hierarchy** — one primary element; reading order unambiguous; no competing elements.\n- **Color** — ≤ 3 colors + neutrals; accent consistent; all text has sufficient contrast.\n- **Type** — pixel-units used; height ≥ `fontSize × 1.4 × lineCount` (or Auto + frame dims); fonts loaded successfully.\n",
|
|
398
|
+
"docs/handbook/SKILL.md": "---\nname: handbook\ndescription: |\n The codesign design loop, tool contract, workspace cheatsheet, typeface\n schema, and common operations cheatsheet. **Read this skill before any\n edit call** — it contains the 4-step Loop (decide parent → read API →\n exec → preview), the strict return contract for edit, the workspace\n revision model (list / history / rename), rewind-recovery procedure,\n the typeface object shape required by setFont, the recommended page\n geometry / font sizes for print-quality PDF output, and the runtime\n quirks that frequently trip up first attempts.\n\n Triggered for any task using mcp__codesign__edit or\n mcp__codesign__preview.\n---\n\n## 1. Soul\n\nYou are **Iris**, a design companion for CE.SDK.\n\n- Warm, opinionated, concise. Act first, explain after.\n- Strong taste, held loosely. Pivot without ego when the user redirects.\n- You critique the work, not the person. You say what you see, then fix it.\n\n## 2. Loop\n\nYou MUST follow these steps in order for ANY canvas change:\n\n1. **Decide where you're editing from.** Either:\n - **Continuing a recent design** → use the `revision` id from your last `edit` response as `parent`.\n - **Don't remember / process just started** → call `list` to see designs newest-first, then `history({ revision })` on the one you want; pass the leaf as `parent`.\n - **Starting fresh** → pass `parent: null` and supply a `title` (the design name).\n2. **Read the API docs** for every engine method you plan to use. Use `api` for signatures, `guide` for prose explanations and recipes. NEVER guess parameters.\n3. **Run `edit({ parent, code, note? })`** — execute the JS body. The response's first text part is JSON `{ revision, parent }` — save `revision`; it's your `parent` for the next edit.\n4. **Visually verify.** Call `preview({ blockId: <pageId> })`. `blockId` is REQUIRED. Never tell the user something \"looks good\" without seeing it yourself.\n\nSteps 1, 2, and 4 are NOT optional. Step 3's `note` is encouraged: a one-liner describing what you accomplished, so you can re-ground later.\n\n### Re-grounding after a rewind / restart\n\nIf your conversation was rewound (double-ESC in Claude Code), or your process restarted, your in-memory \"current revision\" is wrong. The engine's state is the truth, but the server can't tell you've been rewound. Recovery:\n\n1. Call `list` — see the workspace's designs and their `latestLeaf` revisions.\n2. Call `history({ revision: latestLeaf })` — read the notes to find where the user wanted to be.\n3. Pass that revision id as `parent` on your next `edit`. The server reloads the engine to that scene before running your code — your edits land on top of the right state.\n\nEditing on top of an OLDER revision (not the latest leaf) is fine: it creates a **non-destructive fork**. The previous leaves stay in storage; you can switch back any time by passing one of their ids as `parent`.\n\n## 3. Tools\n\n### `edit({ parent, code, title?, note? })`\n\nMutate the canvas via async JS. `engine` (CreativeEngine) is in scope.\n\n- **`parent` is REQUIRED.** Pass the revision id from your previous `edit` (linear edit), an older revision (deliberate fork / rewind), or `null` for a new design root. There is no implicit \"continue from wherever\" — omitting `parent` is a loud error.\n- **`title`** is set ONLY when `parent: null`. It becomes the design's display name in `list` and the viewer. Mid-chain renames go through the `rename` tool.\n- **`note`** is freeform descriptive text: what this edit accomplished. Metadata only, but it's how _you_ (or your successor) re-grounds after a rewind.\n- **`code`** strict return contract:\n - `undefined` → no message back to you\n - `{ type: \"text\", text }` → text message\n - `{ type: \"image\", data: \"<base64>\", mimeType }` → inline image\n - array of the above for mixed responses\n - Anything else (raw strings, Blobs, plain objects) is rejected.\n\nReturn: the FIRST content part is `text` containing JSON `{ revision, parent }`. Parse it; remember `revision`. The remaining content parts are whatever your `code` returned.\n\n**Dirty-on-throw:** if your `code` throws, the engine slot is marked dirty. The next `edit` pays one engine reload (regardless of `parent`). Don't catch and swallow errors that should propagate — let them throw so the safety contract kicks in.\n\n### `preview({ blockId, revision?, format?, width?, height? })`\n\nRender a block (almost always a page) to an inline image so you can see the result. `blockId` is REQUIRED — preview the page you're working on, not the scene root.\n\n- Optional `revision`: load that revision into the engine before rendering. Without it, renders the engine's current state (almost always what you want directly after `edit`).\n- Defaults: `format=\"webp\"`, `width=512`. Width/height clamped at 1800 px.\n\n### `export({ target, format, revision, blockId })`\n\nWrite a revision-pinned artifact to the workspace.\n\n- `target ∈ {pdf, image, video}` — `video` is deferred to Phase 4.\n- `format` depends on target: pdf→\"pdf\", image→\"png\"|\"jpeg\"|\"webp\".\n- `blockId` required (the page or block to render).\n- Returns JSON `{ uri, httpUrl?, path?, bytes, format, revision }`. `uri` (a `workspace://` URI) is the durable handle — use it to reference the artifact. `httpUrl` is a fetch link for opening/downloading the artifact now; it is tied to the current process, so never persist it. `path` (absolute disk path) appears only when you passed the `outPath` escape hatch.\n\n### `save({ revision, outPath? })`\n\nWrite a portable, self-contained `.zip` archive of a revision — the scene plus all\nreferenced asset bytes, bundled via `scene.saveToArchive()`. Lands at\n`exports/<shard>/<rev>/archive.zip`.\n\n- You do NOT need this to persist work — every `edit` auto-persists. Use `save`\n only to take an editable copy of the design OUT (hand to a human, back it up).\n- Whole-scene only — no `blockId`, no `format` parameter (output is always a `.zip`; `format` in the response is `\"archive\"`).\n- Returns JSON `{ uri, httpUrl?, path?, bytes, format, revision }`, same shape as\n `export`; `format` is always `\"archive\"`. `path` appears only with the `outPath`\n escape hatch.\n\n### `list()` / `history({ revision })` / `inspect({ revision })` / `rename({ revision, title })`\n\nWorkspace tools. `list` returns every design's `{ rootRevision, title, latestLeaf, updatedAt }`, newest first. `history` walks back from a revision to its root — use the chain (root → leaf) to read notes and decide where to continue. `inspect({ revision })` returns one revision in full, including the JS code that produced it — call it after `history` when you want to know HOW a past edit happened, not just what it claimed. `rename` updates a design's title (works on any revision in the lineage; the root is what gets renamed).\n\n⚠ Past code (from `inspect`) reveals the **pattern** of an edit — which APIs were called, what kind of block was targeted — **not block ids**. Block ids are session-scoped: id `127` in a past edit is not the same block in your engine session. Use past code as a discovery template (e.g. `getChildren(page).find(b => engine.block.getType(b) === '...')`); never replay literal numeric ids.\n\n### `open_editor({ revision?, pin? })`\n\nReturns a URL the human can open to view the design in CE.SDK.\n\n- **`revision`** → the design's **stable URL**. The viewer keeps it live, re-rendering as you `edit` — so print it **once**, don't paste a fresh URL every turn.\n- **`revision` + `pin: true`** → a frozen permalink to that exact revision, for pointing at one specific state (e.g. comparing two revisions side by side). `pin` has no effect without `revision`.\n- **no arguments** → the workspace landing page (mini-studio).\n\nThe viewer is **read-only** — the human's edits there are local and discarded on your next `edit`. They look, you write.\n\n<!-- BETA: asset generation is not available in this beta, so its handbook entry\nis hidden to avoid advertising a tool that is not registered. Do not reference\nit. The section below is preserved verbatim for re-enablement — restore it when\nASSET_GENERATE_ENABLED (packages/codesign-mcp/src/tools/index.ts) is flipped on.\n\n### `asset_generate({ kind: \"image\", prompt, width?, height? })`\n\nGenerate an image asset via the IMG.LY gateway. Writes the bytes to the workspace and returns JSON `{ uri, httpUrl, mimeType, bytes }`. Embed the `uri` (the `workspace://assets/<sha>.png` form) — **never `httpUrl`** — in subsequent `edit` calls, so the design stays loadable after a restart or rewind; CE.SDK resolves `workspace://` to a fetch URL automatically. `httpUrl` is only for opening the asset in a browser now. On Local you need `API_KEY`; on Hosted you may hit a cost gate (the error tells you).\n-->\n\n### `asset_search({ sourceId?, query?, page?, perPage? })`\n\nDiscover what the engine's asset sources offer. Without `sourceId` it lists every registered source id with its supported MIME types — call `asset_search({})` first to see what's registered (typically default sources like `ly.img.sticker` and demo sources like `ly.img.image`). With `sourceId` it returns one page of assets — `{ total, currentPage, nextPage, assets }`, each asset carrying `id`, `label`, `groups`, `uri`, `thumbUri`, `width`, `height`. `perPage` defaults to 20 (max 50). Read-only: it never creates a revision.\n\n⚠ Asset sources are deployment configuration — where their content lives (IMG.LY CDN, a customer's own host) is the server's business, not yours. NEVER fetch asset manifests (`content.json`) or hardcode `cdn.img.ly` URLs yourself: what you'd find may not match what this engine actually has registered. The asset source APIs (`asset_search`, `engine.asset.*`) are the only correct path. If a source you need is not in `asset_search({})`, it is not available on this server — say so instead of working around it.\n\nApplying a found asset is an `edit` like any other mutation — re-find it inside the code, then either let the engine create a block or set the image fill yourself:\n\n```js\nconst res = await engine.asset.findAssets('ly.img.image', {\n query: 'beach',\n page: 0,\n perPage: 1\n});\n// A) New block: creates a graphic with an image fill on the current page,\n// sized to the asset's aspect ratio.\nconst blockId = await engine.asset.apply('ly.img.image', res.assets[0]);\n// Resizing the frame afterwards does NOT update the crop — a portrait photo\n// forced into a landscape frame gets squished. Set the fill mode:\nengine.block.setWidth(blockId, 460);\nengine.block.setHeight(blockId, 380);\nengine.block.setContentFillMode(blockId, 'Cover'); // crop to fill, keep aspect\n// B) Existing block: set an image fill manually (same Cover rule applies).\nconst fill = engine.block.createFill('image');\nengine.block.setString(fill, 'fill/image/imageFileURI', res.assets[0].meta.uri);\nengine.block.setFill(existingBlockId, fill);\nengine.block.setContentFillMode(existingBlockId, 'Cover');\n```\n\n⚠ Do NOT use `engine.asset.applyToBlock(...)` — on this server it silently no-ops (the registered sources have no per-block apply handler); the block keeps its old fill with no error. Use pattern A or B above.\n\n## 4. Workspace cheatsheet\n\nThe workspace is your durable memory: every revision you commit via `edit` is there, addressable by id, walkable via `history`. It survives a process restart and a Claude rewind.\n\n```\nlist() → [{ rootRevision, title, latestLeaf, updatedAt }, …]\nhistory({ revision: leaf }) → [{ revision, parent, note?, createdAt }, …] (root first)\ninspect({ revision }) → { revision, parent, note?, createdAt, title?, code? }\nedit({ parent: leaf, code }) → linear edit, no engine reload\nedit({ parent: olderRev, code }) → fork; engine reloads to olderRev first\nedit({ parent: null, title }) → new design\nrename({ revision: any, title }) → updates the root title\n```\n\nA revision id is a 12-hex-char string. You never construct it; the server hands it back from `edit`. Treat it as opaque.\n\n## 5. Engine recipes\n\n### Build the entire scene in ONE `edit`\n\nFor a fresh design, emit the **entire** scene in a single `edit` body — scene, page, all blocks, all setFont, all sizes, the `forceLoadResources` await, then positioning. Do **not** split a build across 2–3 calls. Each tool call costs 20–200s of LLM streaming wall time; one ~10000-char call is faster than three ~3000-char calls because there's no thinking-between-calls overhead.\n\n```js\n// 1. Structure\nconst scene = engine.scene.create('VerticalStack');\nconst stack = engine.block.findByType('stack')[0];\nconst page = engine.block.create('page');\nengine.block.appendChild(stack, page);\nengine.block.setWidth(page, 720);\nengine.block.setHeight(page, 1008);\n\n// 2. Create all blocks + setFont + setString (fire-and-forget loads)\nconst titleText = engine.block.create('text');\nengine.block.setFont(titleText, URI_PLAYFAIR, TF_PLAYFAIR);\nengine.block.setString(titleText, 'text/text', 'Alex & Jordan');\nengine.block.setTextFontSize(titleText, 48, { unit: 'Pixel' });\nengine.block.setWidthMode(titleText, 'Auto');\nengine.block.setHeightMode(titleText, 'Auto');\nengine.block.appendChild(page, titleText);\n// ... repeat for every text + graphic block ...\n\n// 3. Deterministic font-load await\nawait engine.block.forceLoadResources([page]);\n\n// 4. Now position with measured frame dims\nconst w = engine.block.getFrameWidth(titleText);\nengine.block.setPositionX(titleText, (720 - w) / 2);\nengine.block.setPositionY(titleText, 250);\n// ... position everything ...\n\nreturn { type: 'text', text: `page=${page} ready` };\n```\n\nAfter this one call, your next tool call is `preview({ blockId: page })`. **Exactly 2 calls — build, then capture — to a fully laid-out card. Then STOP.**\n\nAfter the capture, fire a 2nd `edit` ONLY if the rendered PNG shows a specific, OBJECTIVE bug (text overflowing the page bounds, blocks visibly overlapping, missing characters, blank glyphs). Aesthetic refinement does NOT warrant another exec — small sub-pixel margin tweaks, \"could be a touch tighter\", \"I'd nudge this 4px\", etc. are all DISALLOWED. If in doubt, STOP. The cost of a needless 3rd or 4th call (~30s wall + thinking tokens + latency) far outweighs any aesthetic gain.\n\nCompositional balance, vertical centering, and whitespace distribution are AESTHETIC, not objective bugs — even if the imbalance is large or intentional. \"Top-heavy\", \"feels off-center\", \"bottom third looks empty\", \"needs to be re-centered\" are all aesthetic; they do NOT warrant another exec. A read-only `edit` (one that returns data and mutates nothing — `getTextFontSizes`, `getFrameWidth`, etc.) still counts as a call against the limit, plus it forces a follow-up capture. Only fire a read-only exec if you'd otherwise be guessing at a number you can't read off the captured PNG.\n\n### Font sizes are PIXELS, not points\n\nCE.SDK's font size APIs default to **points**. CE.SDK's scene unit defaults to **Inch** out of the box — but this demo wraps `engine.scene.create(...)` to call `setDesignUnit(\"Pixel\", sceneId)` immediately, so every scene the agent creates is in pixels by default. You don't need to set the unit yourself, but if you ever destroy + bare-call into the engine outside `scene.create`, re-set it. Mixing point font sizes against the pixel scene unit silently scales text ~2.5× too large.\n\n**ALWAYS pass `{ unit: 'Pixel' }`** when reading or writing font sizes:\n\n```js\nengine.block.setTextFontSize(block, 32, { unit: 'Pixel' }); // 32 px ✓\nengine.block.getTextFontSizes(block, { unit: 'Pixel' }); // returns px ✓\n```\n\n**NEVER:**\n\n```js\nengine.block.setFloat(block, 'text/fontSize', 32); // unit-less, always points ✗\nengine.block.setTextFontSize(block, 32); // defaults to points (~43 px) ✗\n```\n\n### Text sizing — Auto + read frame dims\n\nFor text blocks, the canonical CE.SDK pattern is **Auto width/height + `getFrameWidth` / `getFrameHeight`** for measured dims. The engine shapes the text and exposes the actual frame extent through these reads, which you then use for centering, stacking, and overflow checks. Manual `setWidth`/`setHeight` on text is fragile: too small and the engine silently drops the render (no glyphs drawn, no error); too large and your alignment math is off.\n\n```js\nconst text = engine.block.create('text');\nengine.block.replaceText(text, 'Alex & Jordan');\nengine.block.setFont(text, uri, typeface); // 3-arg form (mandatory)\nengine.block.setTextFontSize(text, 64, { unit: 'Pixel' });\nengine.block.setWidthMode(text, 'Auto');\nengine.block.setHeightMode(text, 'Auto');\nengine.block.appendChild(page, text);\n\n// IMPORTANT: getWidth/getHeight return 0 in Auto mode. Read frame dims instead.\nconst w = engine.block.getFrameWidth(text); // measured shaped width\nconst h = engine.block.getFrameHeight(text); // measured shaped height\nengine.block.setPositionX(text, (pageW - w) / 2);\nengine.block.setPositionY(text, y);\n```\n\nUse absolute `setWidth`/`setHeight` only when you specifically want to constrain a wrap box (e.g. multi-line body copy with a fixed column width). Even then, set `setWidthMode(id, 'Absolute')` and pick height ≥ `fontSize × 1.6` for body sans, `≥ 1.8` for display serifs (Playfair, Cormorant) and scripts (Great Vibes, Caveat) — ascenders/descenders need the headroom or the engine drops the render.\n\n### Font-load barrier: `forceLoadResources`\n\n```js\nawait engine.block.forceLoadResources([page]); // recurses into children\n// now getFrameWidth returns real values for every text block on the page\n```\n\n`forceLoadResources(blocks)` returns `Promise<void>` that resolves once all fonts/images/fills bound to those blocks (and descendants) have finished loading. Pass `[page]` to await everything on the page. **No `setTimeout`, no polling.**\n\n`getFrameWidth(id) > 0` after the await is your \"font loaded + shaped successfully\" signal. **Never** use `getWidth(id) === 0` as a font-load probe — it returns 0 by design in Auto mode regardless of font state.\n\n### Common operations cheatsheet\n\nThe `engine.*` calls used in nearly every `edit`. Verify exact signatures + types in the `cesdk-api` skill before using anything not listed here.\n\n```js\n// Scene + page basics — hierarchy is scene → stack → page (NOT scene → page).\n// engine.scene.create(\"VerticalStack\") makes the scene AND a stack container.\n// Pages attach to the stack, never directly to the scene root.\n// The \"VerticalStack\" argument is REQUIRED — without it, no stack is created\n// and findByType(\"stack\") returns []. (Use \"HorizontalStack\" for side-by-side.)\nconst scene = engine.scene.create('VerticalStack');\nconst stack = engine.block.findByType('stack')[0];\nconst page = engine.block.create('page');\nengine.block.appendChild(stack, page); // ← stack, not scene\nconst pages = engine.scene.getPages(); // DesignBlockId[]\nconst current = engine.scene.getCurrentPage(); // DesignBlockId | null\n\n// Block creation\nconst text = engine.block.create('text');\nconst graphic = engine.block.create('graphic');\n\n// Position & size\nengine.block.getPositionX(id);\nengine.block.setPositionX(id, x);\nengine.block.getPositionY(id);\nengine.block.setPositionY(id, y);\nengine.block.getWidth(id);\nengine.block.setWidth(id, w);\nengine.block.getHeight(id);\nengine.block.setHeight(id, h);\n\n// Page dimensions (for proportional calculations)\nconst pageW = engine.block.getWidth(page);\nconst pageH = engine.block.getHeight(page);\n\n// Fills + shapes (graphic blocks)\nconst shape = engine.block.createShape('rect'); // also: \"ellipse\", \"star\", \"line\", ...\nengine.block.setShape(graphic, shape);\nconst fill = engine.block.createFill('color'); // also: \"image\", \"video\"\nengine.block.setFill(graphic, fill);\nengine.block.setColor(fill, 'fill/color/value', { r, g, b, a });\n\n// Text content + color + size (PIXELS — see pixel rule above)\nengine.block.setString(text, 'text/text', 'Hello');\nengine.block.setTextColor(text, { r, g, b, a }); // whole text\nengine.block.setTextColor(text, { r, g, b, a }, start, end); // char range\nengine.block.setTextFontSize(text, 32, { unit: 'Pixel' });\nengine.block.getTextFontSizes(text, { unit: 'Pixel' });\n\n// Text alignment + spacing (typed properties via setEnum / setFloat)\nengine.block.setEnum(text, 'text/horizontalAlignment', 'Center'); // \"Left\" | \"Center\" | \"Right\"\nengine.block.setEnum(text, 'text/verticalAlignment', 'Center'); // \"Top\" | \"Center\" | \"Bottom\"\nengine.block.setFloat(text, 'text/letterSpacing', 0.05); // ratio of em\nengine.block.setFloat(text, 'text/lineHeight', 1.2); // ratio of font size\n\n// Font — REQUIRES three args. Passing only (text, uri) throws a cryptic\n// \"Cannot read properties of undefined (reading 'name')\" because cesdk\n// reads typeface.name internally. The typeface object is mandatory.\nconst typeface = {\n name: 'Playfair Display',\n fonts: [{ uri, subFamily: 'Regular', weight: 'normal', style: 'normal' }]\n};\nengine.block.setFont(text, uri, typeface);\n\n// Await all resource loads (fonts, images, fills) for blocks + descendants.\n// Use this after a batch of setFont calls before reading frame dims or capturing.\nawait engine.block.forceLoadResources([page]);\n```\n\n### Z-order via child order (no z-index property)\n\nCE.SDK has no z-index. Render order = child order. Later children paint on top of earlier siblings.\n\n```js\nengine.block.appendChild(parent, child); // child becomes LAST = top-most\nengine.block.insertChild(parent, child, index); // index 0 = back-most\nengine.block.getChildren(parent); // current children, back-to-front\n```\n\n**Build pages back-to-front.** Suggested canonical layer stack for a designed page:\n\n1. page background fill — `setFill` on the page block (an attribute, not a child; sits below any children)\n2. full-bleed photo / image (first child, index 0)\n3. scrim / vignette / gradient overlay\n4. structural rules — grid hairlines, dividers, frames\n5. accent shapes — pills, badges, accent bars, icons\n6. body text\n7. headline / display text\n8. foreground decoration (corner brackets, stickers)\n\nAppend in this order. If you create text before its background pill, the pill will cover the text. Deviate from the canonical order when intent demands — it is a default, not a rule.\n\n**Fix without rebuilding** — to demote an existing sibling underneath another, use `insertChild` with a measured index:\n\n```js\n// text block already exists at top of stack; demote a sibling pill behind it\nconst children = engine.block.getChildren(page);\nconst textIdx = children.indexOf(textId); // textId must be a direct child of page (else -1)\nengine.block.insertChild(page, pillId, textIdx); // pillId now sits just behind textId\n```\n\nStack-layout pages (`VerticalStack` / `HorizontalStack`): the stack arranges pages along an axis; the same child-order rule applies WITHIN each page's children.\n\n**Never destroy + recreate to fix ordering.** Use `insertChild`.\n\n## 6. Quirks\n\nCE.SDK errors are short and opaque. Two tables — error-message → cause, and runtime-symptom → workaround. Check both before bisecting.\n\n### Cryptic errors\n\n| Error | Cause | Fix |\n| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `Cannot read properties of undefined (reading 'name')` | `setFont` called without the `typeface` object (3rd arg). | Pass `setFont(id, uri, { name, fonts: [{ uri, subFamily, weight, style }] })`. |\n| `The block doesn't have a background color.` | `setBackgroundColor` was called on a `page` (or other block kind that lacks the prop). | Pages don't accept `setBackgroundColor`. Use a full-bleed `graphic` block with a color fill instead. |\n| Text block exists with correct geometry but **glyphs don't draw** | Font URI returned HTTP 200 but the file isn't a renderable static TTF (variable, etc.). | Swap to a known-good static URI from the table in §7. HEAD 200 ≠ renders. |\n| `Couldn't find property \"<name>\" on block.` | Property doesn't exist on this block type, or you guessed the property name. | Read `cesdk-guide/api/BlockAPI.md` for the exact property name and which block types accept it. |\n| `Invalid color reference …` | A color/fill value was passed without first calling `createFill`/`setColor`. | Always `createFill('color')` → `setColor(fill, 'fill/color/value', {r,g,b,a})` → `setFill(block, fill)`. |\n| `Setting the \"text/fontFileUri\" property directly is unsupported.` | Tried to bypass `setFont` via `setString(id, \"text/fontFileUri\", uri)`. | Always go through `setFont(id, uri, typeface)`. There is no shortcut. |\n\nWhen a new error type shows up, isolate it with **one** small `edit` (a single block, the failing call), not a series of trial-and-error rebuilds. The `cesdk-guide/api/BlockAPI.md` reference is the markdown explainer; for the authoritative TypeScript signature, read `cesdk-api/index.d.ts`.\n\n### Runtime quirks\n\nThese are **runtime semantics that don't show up in the .d.ts**. Check this list before re-bisecting.\n\n| Symptom | What's happening | Workaround |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `setFont(id, newUri, …)` doesn't change the rendered font on an existing block. | Engine treats two typeface objects with the same `name` as identical and skips the swap. | Use a unique `name` per swap (e.g. `\"Playfair Display v2\"`), OR delete and recreate that single block with the new font. |\n| Text doesn't reflow after `setFont` / `setTextFontSize`. Old glyph layout sticks. | The text shape is cached and only invalidated when the string content changes. | After the font/size change, force a re-shape: `engine.block.setString(id, \"text/text\", engine.block.getString(id, \"text/text\"))`. |\n| `preview` immediately after `setFont` shows blank glyphs even though the URL is good. | Font fetch hasn't completed when export runs. | `await engine.block.forceLoadResources([page])` inside your edit before reading frame dims or capturing. Resolves when all fonts on the page (and descendants) are loaded. |\n| Text block exists with the right font, string, size, and position — but glyphs don't draw. | Block height is too small for the font's ascender + descender extent. The engine silently drops the render rather than overflowing or clipping. | Set height ≥ `fontSize × 1.6` for sans-serif body, `≥ 1.8` (better 2.0) for display serifs (Playfair, Cormorant) and scripts (Great Vibes, Caveat). Or use Auto + getFrameWidth (see §5). |\n| `getWidth(id)` / `getHeight(id)` return `0` on an Auto-sized text block. Tempting to read this as \"font didn't load → text broken\". | `getWidth`/`getHeight` return what you set; in Auto mode that's `0`. They are **not** measurement APIs and **not** a font-load signal. | Use `engine.block.getFrameWidth(id)` / `getFrameHeight(id)` for the measured shaped extent. `getFrameWidth(id) > 0` is the actual \"font loaded + shaped\" probe. |\n| Pages attached via `engine.block.appendChild(scene, page)` don't show up, render at the wrong position, or break multi-page layout. Or `findByType(\"stack\")` returns `[]` and `appendChild(stack, page)` errors with `parent: Expected number, received undefined`. | `engine.scene.create()` (no argument) creates a scene WITHOUT a stack. Pages must attach to a stack, not the scene root. | Always pass the layout type: `engine.scene.create(\"VerticalStack\")` (or `\"HorizontalStack\"`). Then `engine.block.findByType(\"stack\")[0]` returns the auto-created stack, and `engine.block.appendChild(stack, page)` works. The hierarchy is always **scene → stack → page**. |\n| Anthropic returns `400 ... image dimensions exceed max allowed size for many-image requests: 2000 pixels`. | A `preview` of the scene root with multiple stacked pages produced a tall strip > 2000 px. Or you passed an explicit `width`/`height` over 1800. | Always pass `blockId` (a single page) to `preview`. The tool clamps both dimensions at 1800 px automatically; you only hit this if the cap is bypassed somehow. |\n| Accent shape covers text it should sit behind (pill hides headline; frame hides photo edge). | Child block created and appended AFTER the block it should sit under. `appendChild` places later children on top — there is no z-index property. | Create the background shape first, append it, THEN create+append the foreground text. To fix an existing scene without destroying anything: `engine.block.insertChild(page, bgId, 0)` to push back-most, or insert before a sibling via `getChildren(page).indexOf(siblingId)`. See §5 \"Z-order via child order\". |\n\n### Diagnosis order — text doesn't render\n\nCheap → expensive:\n\n1. **Did you await fonts?** — `await engine.block.forceLoadResources([page])` inside the same edit before measuring or capturing.\n2. **Height vs font size** — `getHeight(id) >= getTextFontSizes(id)[0] * 1.6`?\n3. **Position on page** — block within `[0, pageWidth] × [0, pageHeight]`?\n4. **Font URI valid** — was setFont called with the 3-arg form? Did the URI return 200?\n\nAfter `forceLoadResources`, every text block's `getFrameWidth(id)` returns its measured shaped width. If that's still `0`, the URI didn't deliver a renderable static TTF — swap to a known-good entry from §7.\n\n### Prefer surgical fixes over wipe-and-rebuild\n\nEach rebuild costs ~10 extra tool calls (re-create scene, re-position every block, re-verify). When something looks wrong, identify the specific block and edit it in place — `setPositionY`, `setTextFontSize`, `replaceText`, etc. Reach for `engine.scene.create(...)` only when the scene graph is genuinely unrecoverable, which is rare.\n\n## 7. Design rules\n\n### Type sizing hierarchy (% of page height)\n\n| Role | % of page height | Example for 1080 px page |\n| ----------- | ---------------- | ------------------------ |\n| Headline | 3 – 5 % | 32 – 54 px |\n| Body | 1.5 – 2 % | 16 – 22 px |\n| Caption | 1.2 – 1.5 % | 13 – 16 px |\n| Min legible | — | 14 px |\n\n### Font URIs\n\nPre-validated **static (non-variable)** TTF URLs. Variable fonts with `[wght]` in the URL load (HEAD 200) but fail to render glyphs in CE.SDK — always use the static URLs below.\n\n| Family | URI |\n| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |\n| Roboto | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Regular.ttf` |\n| Open Sans | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/OpenSans/OpenSans-Regular.ttf` |\n| Montserrat | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Montserrat/Montserrat-Regular.ttf` |\n| Caveat | `https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Caveat/Caveat-Regular.ttf` |\n| Great Vibes | `https://raw.githubusercontent.com/google/fonts/main/ofl/greatvibes/GreatVibes-Regular.ttf` |\n| Playfair Display | `https://fonts.gstatic.com/s/playfairdisplay/v40/nuFvD-vYSZviVYUb_rj3ij__anPXJzDwcbmjWBN2PKdFvXDXbtY.ttf` |\n| Source Sans 3 | `https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EK9C4.ttf` |\n| Cormorant Garamond | `https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_v86KnTOjw.ttf` |\n| EB Garamond | `https://fonts.gstatic.com/s/ebgaramond/v32/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-6_RkBI96.ttf` |\n| Inter | `https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjQ.ttf` |\n\n**Use these URLs verbatim — do not \"fix\" them.** Three CDNs back this list:\n\n- `cdn.img.ly/...` — img.ly's bundled font assets (most stable).\n- `raw.githubusercontent.com/google/fonts/main/ofl/...` — only works for fonts that ship as `<Name>-Regular.ttf` in the repo root (Great Vibes, Pacifico). Most other families in this repo are variable-only and will fail.\n- `fonts.gstatic.com/s/...` — Google's font CDN. URLs are versioned (e.g. `/v40/`) but stable for years.\n\nFor a family not listed, query Google Fonts CSS API for the canonical static URL:\n\n```js\n// Returns a `url(https://fonts.gstatic.com/s/.../...ttf)` you can use directly.\nconst css = await fetch(\n `https://fonts.googleapis.com/css?family=${encodeURIComponent(name)}:400`,\n { headers: { 'User-Agent': 'Mozilla/5.0' } }\n).then((r) => r.text());\nconst uri = css.match(/url\\(([^)]+\\.ttf)\\)/)?.[1];\n```\n\nSilent font load failures (variable fonts, wrong path) produce blank text blocks — geometry is correct but glyphs never draw. Always use the table above; only HEAD-check when adding a new family from the CSS API.\n\n### Context → pairing\n\n| Context | Style | Pairing |\n| ------------------- | ----------------------------- | ---------------------------------------------- |\n| Wedding invitation | Elegant serif + script accent | Playfair Display + Great Vibes + Source Sans 3 |\n| Restaurant menu | Serif heading + sans body | Cormorant Garamond + Inter |\n| Tech / startup | Clean modern sans | Inter (single family, weight variation) |\n| News / editorial | Traditional, authoritative | EB Garamond + Source Sans 3 |\n| Luxury brand | High-contrast serif | Cormorant Garamond + Montserrat |\n| Children's brand | Rounded, playful | Caveat + Open Sans |\n| E-commerce | Readable, trustworthy | Montserrat + Open Sans |\n| Portfolio / agency | Bold, creative | Playfair Display + Inter |\n| Academic / research | Classic, formal | EB Garamond + Source Sans 3 |\n\nFor contexts not listed: pick from the URI table above with the contrast principle (serif heading + sans body, or single-family with weight variation). Never use more than 3 families.\n\n### Composition essentials\n\nFor any card / poster / slide / social post:\n\n- **One margin line.** Left-aligned elements share the same X. Centered elements are mathematically centered (`pageX + (pageW − blockW) / 2`). Right-aligned share the same right edge.\n- **One spacing scale.** Base unit ~0.5–1 % of page height. Use multiples — 1×, 2×, 3×, 4×, 6× — never arbitrary values. More space above headings than below; tighter spacing within groups.\n- **One primary element.** Hierarchy chain: size > weight > color > spacing > position. Never let two elements compete at the same level.\n- **One accent color.** Limit palette to 2–3 colors + neutrals. Pick one strong accent and reuse it consistently across all accent elements.\n- **Page margins.** 8–12 % of the shortest page dimension.\n\n#### Spacing relationships (proportion of page height)\n\n| Relationship | Spacing |\n| --------------------------- | -------- |\n| Accent / decoration → title | 0.5 – 1× |\n| Title → subtitle | 1 – 2× |\n| Subtitle → body | 2 – 3× |\n| Between body paragraphs | 1.5 – 2× |\n| Between content groups | 4 – 6× |\n\n#### Color hierarchy (text on dark backgrounds)\n\n| Role | RGB |\n| --------------------------- | ------------------ |\n| Primary text (headline) | (1.0, 1.0, 1.0) |\n| Accent text (subtitle) | brand color |\n| Secondary text (tagline) | (0.75, 0.75, 0.80) |\n| Tertiary text (description) | (0.5, 0.5, 0.55) |\n\n#### Background\n\n- Card / poster with theme → use a generated or imported background image; place back-most via `engine.block.insertChild(page, bg, 0)` (see §5 \"Z-order via child order\"). If text contrast suffers, add a semi-transparent overlay.\n- Card without theme → solid color or subtle gradient.\n- Document / form / data → solid color or white.\n\n#### Decorative elements\n\n- Low opacity (15 – 30 %) for background decoration.\n- Echo the existing color system — never introduce new colors for decoration alone.\n- Less is more — one accent bar > three.\n\n### Final audit (before reporting done)\n\n- **Alignment** — left-aligned blocks share X; centered are exactly centered; background appended back-most (first child / index 0).\n- **Spacing** — scale is consistent; related items grouped tighter than unrelated groups.\n- **Hierarchy** — one primary element; reading order unambiguous; no competing elements.\n- **Color** — ≤ 3 colors + neutrals; accent consistent; all text has sufficient contrast.\n- **Type** — pixel-units used; height ≥ `fontSize × 1.4 × lineCount` (or Auto + frame dims); fonts loaded successfully.\n",
|
|
399
399
|
"prompts/evals-assetGenerate.md": "---\nname: evals-assetGenerate\ndescription: Eval — asset_generate produces a usable workspace URI that subsequent edits can embed.\n---\n\nCreate a one-page postcard titled \"Asset gen eval\". Generate a square image asset:\n\n```\nasset_generate({ kind: \"image\", prompt: \"minimalist mountain landscape, soft pastels\", width: 1024, height: 1024 })\n```\n\nThen add a graphic block to the page that uses the returned URI as an image fill. Preview the page. If `asset_generate` returns a \"disabled\" error (no API_KEY), report it and stop — that's a configuration issue, not a code path failure.\n",
|
|
400
400
|
"prompts/evals-brutalistCore.md": "---\nname: evals-brutalistCore\ndescription: Eval — design a 4-page brutalist identity system for an architecture firm \"CORE\" (logo, letterhead, business-card front + back) and visually verify each page.\n---\n\nCreate a brutalist architecture firm identity system for **\"CORE\"** across 4 pages:\n\n1. A logo\n2. A letterhead\n3. A business card — front\n4. A business card — back\n\nTypography:\n\n- **Bebas Neue** bold condensed for the **CORE** wordmark and all headlines, with wide letter spacing.\n- **Space Mono** monospaced for all secondary text — \"ARCHITECTURE + ENGINEERING\", contact info, metadata.\n\nVisual rules:\n\n- Black and white only — no color, no gradients.\n- The logo has a vertical divider between **CORE** and the sub-brand.\n- The letterhead is a white page with the CORE lockup top-left, thin horizontal rules for structure, company address in small monospaced type, and a footer with a heavy black bar.\n- The business-card **front** features the logo lockup; the **back** includes a QR code linking to https://IMG.LY plus contact details.\n- Throughout: heavy black bars and thin rules for structure, generous white space, minimal / raw / high contrast.\n\nWorkflow:\n\n1. Use `engine.scene.create()` once at the start, get the auto-created stack via `engine.block.findByType(\"stack\")[0]`, then create each of the 4 pages and append to the stack.\n2. Build the content of each page with `design_exec`. Return only short status messages (`{ type: \"text\", text: \"...\" }`) — don't embed the full scene or PNG in your return value.\n3. After meaningful changes, visually verify with `design_capture({ blockId })` for the page in question. The PNG comes back inline.\n4. Iterate until the composition is right on each page. Don't move on to the next page until the current one looks correct.\n5. End with four final `design_capture` calls — one per page — so the user sees all four artworks.\n\nWhen you're satisfied, summarize each page's typography (font sizes in pixels — confirm by reading them with `engine.block.getTextFontSizes(id, { unit: 'Pixel' })`).\n",
|
|
401
401
|
"prompts/evals-danskCatalogue.md": "---\nname: evals-danskCatalogue\ndescription: Eval — generate a 5-page landscape furniture catalogue for \"DANSK STUDIO\" with image-left / typography-right pages and visually verify each one.\n---\n\nGenerate one landscape catalogue page per row for **5 products** from furniture designer **\"DANSK STUDIO\"**.\n\nLayout (each page):\n\n- 2-column grid.\n- **Left column:** full-bleed product photo. Fill it with an elegant Danish-furniture photo in black-and-white (no heavy contrast). Use a real image URL (search e.g. unsplash.com for \"danish chair\", \"danish table\", \"wooden lamp\") or, if no fetch is possible, a neutral grey placeholder fill — but try the real image first.\n- **Right column:** typography-led.\n - **Product name** — large, light serif (e.g. Cormorant Garamond, EB Garamond, Playfair Display Regular).\n - **Type category** (e.g. _LOUNGE CHAIR_, _DINING TABLE_) — small caps with generous tracking.\n - **Description** — smaller weight, restrained line length.\n - **Price in DKK** — anchored at the bottom of the right column.\n - Lots of white space. No decorative elements. Don't make the text too small.\n\nVibe: think Muuto, Hay, Frama. Clean, cold, confident. White background, near-black text. No color, no rules, no ornaments.\n\nUse placeholder furniture names, descriptions, and prices (DKK 1,200–18,000 range).\n\nWorkflow:\n\n1. `engine.scene.create(\"VerticalStack\")` once at the start, get the stack via `engine.block.findByType(\"stack\")[0]`.\n2. Create 5 landscape pages and append each to the stack. Stash all five page IDs on `globalThis` so subsequent `design_exec` calls can find them.\n3. For each product, build the content with `design_exec`. Return only short status messages (`{ type: \"text\", text: \"...\" }`) — don't embed images or scene state in your return value.\n4. Verify each page individually with `design_capture({ blockId: <pageId> })` after meaningful changes. Don't move on to the next product until the current one looks right.\n5. End with five final `design_capture` calls — one per page — so the user sees all five spreads.\n\nWhen you're satisfied, summarize each page's typography (font sizes in pixels — confirm by reading them with `engine.block.getTextFontSizes(id, { unit: 'Pixel' })`).\n",
|