From 310f9c1b2f3ee3604f0376e80a53098bc438c124 Mon Sep 17 00:00:00 2001 From: daniellee-ux <54412342+daniellee-ux@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:42:55 +0800 Subject: [PATCH 1/2] feat(core): arrange multi-board designs in rows, columns, or a grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple Scenes could only auto-flow into one horizontal row, so a design could not put a new version directly beneath an existing set of boards. `layoutBoards` now splits scenes into lines and flows them along a configurable axis: - `export const layout: BoardLayout` — `direction` (row/column), `wrap` (boards per line), `gap`/`crossGap`, `align` (cross-axis within a line), `justify` (a short line along the flow axis). - `Scene.break` — start a new line at this board, the local alternative to a module-wide `wrap` and the way to author uneven rows (6 then 5). Defaults reproduce the old layout exactly: one row, 96px gaps, shorter boards vertically centered. Positions stay baked into the returned boards, so Stage's render and zoom-to-board (viewport.fitTo) keep one source of truth, and lines stay in scene order — export and DOM order are unchanged. Documented in the canva-authoring skill (new "Arranging multiple boards" section), create-design, and the README; skills re-synced. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- .../.agents/skills/canva-authoring/SKILL.md | 37 +++++++- .../.agents/skills/create-design/SKILL.md | 2 +- packages/core/skills/canva-authoring/SKILL.md | 37 +++++++- packages/core/skills/create-design/SKILL.md | 2 +- packages/core/src/app/app.tsx | 3 +- packages/core/src/app/components/Stage.tsx | 85 +++++++++++++++---- packages/core/src/index.ts | 2 +- packages/core/src/sdk.ts | 49 ++++++++++- 9 files changed, 195 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b236575..a933e6e 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ export const meta: DesignMeta = { title: 'My poster', theme: 'sunset', createdAt export default [Poster] satisfies Scene[]; ``` -- `export default` is `Scene[]` — one component per artboard (most designs have one; several = a carousel / size set). +- `export default` is `Scene[]` — one component per artboard (most designs have one; several = a carousel / size set). Multiple boards flow in a row by default; `export const layout` (`{ wrap: 3 }`, `{ direction: 'column' }`) or a scene's `break` arranges them as a grid or a vertical stack. - Objects: ``, ``, ``, ``, ``, ``, ``, `` — positioned with `x/y/w/h`, rotated with `rotate`, themed with `var(--ox-*)` tokens. - Themes: `ember`, `noir`, `sunset`, `mint`, `blueprint`, `bubblegum` (see `/themes`), or roll your own `DesignSystem`. diff --git a/apps/demo/.agents/skills/canva-authoring/SKILL.md b/apps/demo/.agents/skills/canva-authoring/SKILL.md index e37cb05..9aeddbf 100644 --- a/apps/demo/.agents/skills/canva-authoring/SKILL.md +++ b/apps/demo/.agents/skills/canva-authoring/SKILL.md @@ -38,11 +38,12 @@ export const meta: DesignMeta = { title: 'My poster', theme: 'sunset', createdAt export default [Poster] satisfies Scene[]; ``` -- `export default` is a **non-empty array of zero-prop React components** (`Scene[]`), one per **artboard**, in order. Most designs have a single Scene; multiple Scenes form a carousel / multi-board set (laid side by side on the canvas). +- `export default` is a **non-empty array of zero-prop React components** (`Scene[]`), one per **artboard**, in order. Most designs have a single Scene; multiple Scenes form a carousel / multi-board set (auto-arranged on the canvas — one row by default, or a stack / grid, see **Arranging multiple boards**). - Scene metadata is set as **static properties** on the component: - `Scene.id` — stable id for the layers panel + deep links. Always set it. - `Scene.label` — human name shown in the board switcher. **Never `Scene.name`** — `Function.name` is a read-only built-in and assigning to it throws. - `Scene.artboard` — per-scene size override (e.g. one story board among square boards). + - `Scene.break` — start a new row (or column) at this board. See below. - `export const artboard: Artboard = { w, h, background? }` — the default size for every scene. Use a preset size (see below). Omit `background` to inherit the theme's `--ox-bg`; set it (a color or gradient) to override. - `export const meta: DesignMeta` — `title`, optional `theme` (a preset name — the `create-design` skill's theme catalog describes each one), and a **quoted ISO 8601 `createdAt`** (run `node -e "console.log(new Date().toISOString())"`; never type it from memory). - Optional: `export const design: DesignSystem` to fully customize tokens instead of using a named `theme`. @@ -61,6 +62,40 @@ Import `artboardPresets` or just write the numbers. Common sizes: | `poster-a4-portrait` | 1240 × 1754 | Print posters | | `business-card` | 1050 × 600 | Cards | +## Arranging multiple boards + +Boards **auto-flow** on the canvas — by default a single horizontal row, left to right in `export default` order. `export const layout: BoardLayout` changes that, so a set can be a vertical stack or a grid (e.g. six versions in a row with a new version directly beneath them). You never position boards in pixels; you pick the flow and where it wraps. + +```tsx +import type { BoardLayout } from '@opencanva/core'; + +export const layout: BoardLayout = { wrap: 3 }; // grid: 3 per row, wrapping downward +// { direction: 'column' } // one vertical stack, top → bottom +// { direction: 'column', wrap: 4 } // 4 per column, wrapping rightward +// { wrap: 6, justify: 'center', crossGap: 200 } // 6 per row; short rows centered, roomier row gap +``` + +| Field | Default | Meaning | +| --- | --- | --- | +| `direction` | `'row'` | Flow axis: `'row'` runs left→right, `'column'` stacks top→bottom. | +| `wrap` | none | Wrap onto a new line after N boards — boards per row (`row`) / per column (`column`). | +| `gap` | `96` | Gap along the flow axis, in artboard px. | +| `crossGap` | `gap` | Gap between rows (or columns). | +| `align` | `'center'` | How boards of unequal size sit **across** the flow axis inside their line (a short board among tall ones). `start` \| `center` \| `end`. | +| `justify` | `'start'` | How a **short line** sits along the flow axis relative to the longest one (one board under a row of six). `start` \| `center` \| `end`. | + +To stack **without** touching the module layout, set `break` on the scene that should start the next line — the local, additive move when adding a new version below an existing row: + +```tsx +const V7: Scene = () => (/* … */); +V7.id = 'v7'; +V7.label = 'V7 — new version'; +V7.break = true; // V7 (and everything after it) drops to the next row +export default [V1, V2, V3, V4, V5, V6, V7] satisfies Scene[]; +``` + +Boards never overlap: each line is sized by its tallest (row) / widest (column) board. Board order in `export default` stays the export order and the layers-panel order regardless of arrangement. + ## Object primitive kit (import from `@opencanva/core`) Every object takes `x, y` (position), `w, h` (size), and optional `rotate` (deg, around center), `z` (stacking; later siblings are on top by default), `opacity`. diff --git a/apps/demo/.agents/skills/create-design/SKILL.md b/apps/demo/.agents/skills/create-design/SKILL.md index c207118..138b861 100644 --- a/apps/demo/.agents/skills/create-design/SKILL.md +++ b/apps/demo/.agents/skills/create-design/SKILL.md @@ -94,7 +94,7 @@ Also decide the **logical structure**: which objects form self-contained units ( Read **`canva-authoring`** first, then write. Place objects with literal pixel coordinates; use `var(--ox-*)` tokens for color so the theme drives the palette. Add `export const artboard`, and `export const meta` with `title`, `theme`, and a real `createdAt` (run `node -e "console.log(new Date().toISOString())"`). -For a carousel/multi-board, export several Scenes; give each an `id` and `label`. +For a carousel/multi-board, export several Scenes; give each an `id` and `label`. They land in one row by default — add `export const layout` (`{ wrap: 3 }` for a grid, `{ direction: 'column' }` for a vertical stack) or set `break` on a scene to start a new row, e.g. when adding a new version beneath an existing row of boards. See "Arranging multiple boards" in `canva-authoring`. ## Step 5 — Self-review diff --git a/packages/core/skills/canva-authoring/SKILL.md b/packages/core/skills/canva-authoring/SKILL.md index e37cb05..9aeddbf 100644 --- a/packages/core/skills/canva-authoring/SKILL.md +++ b/packages/core/skills/canva-authoring/SKILL.md @@ -38,11 +38,12 @@ export const meta: DesignMeta = { title: 'My poster', theme: 'sunset', createdAt export default [Poster] satisfies Scene[]; ``` -- `export default` is a **non-empty array of zero-prop React components** (`Scene[]`), one per **artboard**, in order. Most designs have a single Scene; multiple Scenes form a carousel / multi-board set (laid side by side on the canvas). +- `export default` is a **non-empty array of zero-prop React components** (`Scene[]`), one per **artboard**, in order. Most designs have a single Scene; multiple Scenes form a carousel / multi-board set (auto-arranged on the canvas — one row by default, or a stack / grid, see **Arranging multiple boards**). - Scene metadata is set as **static properties** on the component: - `Scene.id` — stable id for the layers panel + deep links. Always set it. - `Scene.label` — human name shown in the board switcher. **Never `Scene.name`** — `Function.name` is a read-only built-in and assigning to it throws. - `Scene.artboard` — per-scene size override (e.g. one story board among square boards). + - `Scene.break` — start a new row (or column) at this board. See below. - `export const artboard: Artboard = { w, h, background? }` — the default size for every scene. Use a preset size (see below). Omit `background` to inherit the theme's `--ox-bg`; set it (a color or gradient) to override. - `export const meta: DesignMeta` — `title`, optional `theme` (a preset name — the `create-design` skill's theme catalog describes each one), and a **quoted ISO 8601 `createdAt`** (run `node -e "console.log(new Date().toISOString())"`; never type it from memory). - Optional: `export const design: DesignSystem` to fully customize tokens instead of using a named `theme`. @@ -61,6 +62,40 @@ Import `artboardPresets` or just write the numbers. Common sizes: | `poster-a4-portrait` | 1240 × 1754 | Print posters | | `business-card` | 1050 × 600 | Cards | +## Arranging multiple boards + +Boards **auto-flow** on the canvas — by default a single horizontal row, left to right in `export default` order. `export const layout: BoardLayout` changes that, so a set can be a vertical stack or a grid (e.g. six versions in a row with a new version directly beneath them). You never position boards in pixels; you pick the flow and where it wraps. + +```tsx +import type { BoardLayout } from '@opencanva/core'; + +export const layout: BoardLayout = { wrap: 3 }; // grid: 3 per row, wrapping downward +// { direction: 'column' } // one vertical stack, top → bottom +// { direction: 'column', wrap: 4 } // 4 per column, wrapping rightward +// { wrap: 6, justify: 'center', crossGap: 200 } // 6 per row; short rows centered, roomier row gap +``` + +| Field | Default | Meaning | +| --- | --- | --- | +| `direction` | `'row'` | Flow axis: `'row'` runs left→right, `'column'` stacks top→bottom. | +| `wrap` | none | Wrap onto a new line after N boards — boards per row (`row`) / per column (`column`). | +| `gap` | `96` | Gap along the flow axis, in artboard px. | +| `crossGap` | `gap` | Gap between rows (or columns). | +| `align` | `'center'` | How boards of unequal size sit **across** the flow axis inside their line (a short board among tall ones). `start` \| `center` \| `end`. | +| `justify` | `'start'` | How a **short line** sits along the flow axis relative to the longest one (one board under a row of six). `start` \| `center` \| `end`. | + +To stack **without** touching the module layout, set `break` on the scene that should start the next line — the local, additive move when adding a new version below an existing row: + +```tsx +const V7: Scene = () => (/* … */); +V7.id = 'v7'; +V7.label = 'V7 — new version'; +V7.break = true; // V7 (and everything after it) drops to the next row +export default [V1, V2, V3, V4, V5, V6, V7] satisfies Scene[]; +``` + +Boards never overlap: each line is sized by its tallest (row) / widest (column) board. Board order in `export default` stays the export order and the layers-panel order regardless of arrangement. + ## Object primitive kit (import from `@opencanva/core`) Every object takes `x, y` (position), `w, h` (size), and optional `rotate` (deg, around center), `z` (stacking; later siblings are on top by default), `opacity`. diff --git a/packages/core/skills/create-design/SKILL.md b/packages/core/skills/create-design/SKILL.md index c207118..138b861 100644 --- a/packages/core/skills/create-design/SKILL.md +++ b/packages/core/skills/create-design/SKILL.md @@ -94,7 +94,7 @@ Also decide the **logical structure**: which objects form self-contained units ( Read **`canva-authoring`** first, then write. Place objects with literal pixel coordinates; use `var(--ox-*)` tokens for color so the theme drives the palette. Add `export const artboard`, and `export const meta` with `title`, `theme`, and a real `createdAt` (run `node -e "console.log(new Date().toISOString())"`). -For a carousel/multi-board, export several Scenes; give each an `id` and `label`. +For a carousel/multi-board, export several Scenes; give each an `id` and `label`. They land in one row by default — add `export const layout` (`{ wrap: 3 }` for a grid, `{ direction: 'column' }` for a vertical stack) or set `break` on a scene to start a new row, e.g. when adding a new version beneath an existing row of boards. See "Arranging multiple boards" in `canva-authoring`. ## Step 5 — Self-review diff --git a/packages/core/src/app/app.tsx b/packages/core/src/app/app.tsx index 8927cfc..053c624 100644 --- a/packages/core/src/app/app.tsx +++ b/packages/core/src/app/app.tsx @@ -97,7 +97,7 @@ function DesignPage({ id }: { id: string }) { const scenes = mod?.default ?? []; const title = mod?.meta?.title ?? id; const design = useMemo(() => resolveDesign({ design: mod?.design, theme: mod?.meta?.theme }), [mod]); - const layout = useMemo(() => layoutBoards(scenes, mod?.artboard), [scenes, mod]); + const layout = useMemo(() => layoutBoards(scenes, mod?.artboard, mod?.layout), [scenes, mod]); const vp = useViewport(stageRef, { w: layout.w, h: layout.h }); // Focus a board: make it active (toolbar, layers panel) AND bring it into @@ -329,6 +329,7 @@ function DesignPage({ id }: { id: string }) { canvasRef={canvasRef} scenes={scenes} moduleArtboard={mod.artboard} + layout={mod.layout} design={design} viewport={vp} /> diff --git a/packages/core/src/app/components/Stage.tsx b/packages/core/src/app/components/Stage.tsx index 73512bb..48aed1e 100644 --- a/packages/core/src/app/components/Stage.tsx +++ b/packages/core/src/app/components/Stage.tsx @@ -1,27 +1,78 @@ import { type RefObject } from 'react'; import type { DesignSystem } from '../../design'; -import { type Artboard, resolveArtboard, type Scene } from '../../sdk'; +import { type Artboard, type BoardLayout, resolveArtboard, type Scene } from '../../sdk'; import type { Viewport } from '../lib/viewport'; import { Board } from './Board'; export const BOARD_GAP = 96; // px between boards on the canvas, in artboard units -/** Lay scenes out in a horizontal row; return content bounds + per-board offsets. */ -export function layoutBoards(scenes: Scene[], moduleArtboard?: Artboard) { - let x = 0; - let maxH = 0; - const boards = scenes.map((scene, i) => { +interface PlacedBoard { + x: number; + y: number; + artboard: Artboard; + scene: Scene; + index: number; +} + +/** Offset of a board (or a line) inside the space it is aligned within. */ +function offsetFor(align: 'start' | 'center' | 'end', slack: number) { + return align === 'start' ? 0 : align === 'end' ? slack : slack / 2; +} + +/** + * Auto-arrange scenes on the canvas; return content bounds + per-board offsets. + * + * Boards flow along one axis (`layout.direction`, default `row`) and wrap onto a + * new line every `layout.wrap` boards — or wherever a scene sets `break` — so a + * design can be a row, a vertical stack, or a grid (e.g. six boards in a row and + * a new version directly beneath them). Positions are baked in here so the Stage + * render and zoom-to-board (`viewport.fitTo`) share one source of truth. + */ +export function layoutBoards(scenes: Scene[], moduleArtboard?: Artboard, layout?: BoardLayout) { + const vertical = layout?.direction === 'column'; + const gap = layout?.gap ?? BOARD_GAP; + const crossGap = layout?.crossGap ?? gap; + // Guard against 0 / negative / fractional wrap counts — they'd make every + // board its own line (or loop forever in the reader's head). + const wrap = layout?.wrap && layout.wrap >= 1 ? Math.floor(layout.wrap) : Infinity; + const align = layout?.align ?? 'center'; + const justify = layout?.justify ?? 'start'; + + // Split into lines, keeping scene order (export + DOM order depend on it). + const lines: PlacedBoard[][] = []; + scenes.forEach((scene, i) => { const artboard = resolveArtboard(scene, moduleArtboard); - const at = { x, y: 0, artboard, scene, index: i }; - x += artboard.w + BOARD_GAP; - maxH = Math.max(maxH, artboard.h); - return at; + const line = lines[lines.length - 1]; + if (!line || scene.break || line.length >= wrap) lines.push([{ x: 0, y: 0, artboard, scene, index: i }]); + else line.push({ x: 0, y: 0, artboard, scene, index: i }); + }); + + // Main axis = the flow axis (x in a row, y in a column); cross = the other one. + const mainSize = (a: Artboard) => (vertical ? a.h : a.w); + const crossSize = (a: Artboard) => (vertical ? a.w : a.h); + + const lineMains = lines.map((line) => line.reduce((sum, b) => sum + mainSize(b.artboard), 0) + gap * (line.length - 1)); + const totalMain = lineMains.length ? Math.max(...lineMains) : 0; + + let cross = 0; + lines.forEach((line, li) => { + const lineCross = Math.max(...line.map((b) => crossSize(b.artboard))); + let main = offsetFor(justify, totalMain - lineMains[li]); + for (const b of line) { + const off = cross + offsetFor(align, lineCross - crossSize(b.artboard)); + b.x = vertical ? off : main; + b.y = vertical ? main : off; + main += mainSize(b.artboard) + gap; + } + cross += lineCross + crossGap; }); - // Shorter boards sit vertically centered in the row; bake that into y so the - // Stage render and zoom-to-board (viewport.fitTo) share one source of truth. - for (const b of boards) b.y = (maxH - b.artboard.h) / 2; - const w = boards.length ? x - BOARD_GAP : 0; - return { boards, w, h: maxH }; + const totalCross = lines.length ? cross - crossGap : 0; + + return { + boards: lines.flat(), + w: vertical ? totalCross : totalMain, + h: vertical ? totalMain : totalCross, + }; } /** @@ -33,6 +84,7 @@ export function Stage({ canvasRef, scenes, moduleArtboard, + layout, design, viewport, }: { @@ -40,10 +92,11 @@ export function Stage({ canvasRef: RefObject; scenes: Scene[]; moduleArtboard?: Artboard; + layout?: BoardLayout; design: DesignSystem; viewport: Viewport; }) { - const { boards } = layoutBoards(scenes, moduleArtboard); + const { boards } = layoutBoards(scenes, moduleArtboard, layout); const total = scenes.length; return ( diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9193078..8fb1b91 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,5 @@ // Public API of @opencanva/core — what designs under designs// import. -export type { Scene, DesignMeta, DesignModule, Artboard } from './sdk'; +export type { Scene, DesignMeta, DesignModule, Artboard, BoardLayout } from './sdk'; export { DEFAULT_ARTBOARD, artboardPresets, resolveArtboard } from './sdk'; export type { DesignSystem } from './design'; export { defaultDesign, designPresets, designToCssVars, resolveDesign } from './design'; diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index fb45e9f..7442ca4 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -8,7 +8,9 @@ import type { DesignSystem } from './design'; * * Most designs have a single Scene (one poster / one social post). Multiple * Scenes form a carousel / multi-board set (e.g. an Instagram carousel or size - * variations), laid out side by side on the infinite canvas. + * variations), auto-arranged on the infinite canvas — a single row by default, + * or a vertical stack / wrapped grid via `export const layout` (see + * `BoardLayout`). * * Scene metadata lives as static properties on the component (like open-slide's * `Page.transition`); `Stage` reads them at render time. @@ -21,6 +23,13 @@ export type Scene = FC & { label?: string; /** Per-scene artboard size, overriding the module-level `artboard`. */ artboard?: Artboard; + /** + * Start a new line at this board — a new row in `row` direction, a new column + * in `column` direction. The local way to stack: leave the module `layout` + * alone and set `NewVersion.break = true` to drop this board (and everything + * after it) onto the next row. Ignored on the first scene. + */ + break?: boolean; }; export interface DesignMeta { @@ -45,12 +54,50 @@ export interface Artboard { background?: string; } +/** + * How multiple Scenes are arranged on the canvas. Boards auto-flow along one + * axis and wrap onto new lines, so a design can be one row (the default), a + * vertical stack, or a grid — e.g. six versions in a row with a seventh + * directly beneath them. + * + * Boards never overlap: each line is sized by its tallest (or widest) board. + */ +export interface BoardLayout { + /** + * Flow axis. `'row'` (default) runs boards left→right; `'column'` stacks them + * top→bottom. + */ + direction?: 'row' | 'column'; + /** + * Wrap onto a new line after this many boards — boards per row in `row` + * direction, boards per column in `column` direction. Omit for one unbroken + * line. A scene's own `break` starts a new line regardless. + */ + wrap?: number; + /** Gap along the flow axis, in artboard px. Default 96. */ + gap?: number; + /** Gap across the flow axis — between rows (or columns). Defaults to `gap`. */ + crossGap?: number; + /** + * How boards of unequal size sit across the flow axis within their line — + * e.g. a short board among tall ones in a row. Default `'center'`. + */ + align?: 'start' | 'center' | 'end'; + /** + * How a short line sits along the flow axis relative to the longest one — + * e.g. a single board under a row of six. Default `'start'`. + */ + justify?: 'start' | 'center' | 'end'; +} + export interface DesignModule { default: Scene[]; meta?: DesignMeta; design?: DesignSystem; /** Default artboard size for every scene that doesn't set its own. */ artboard?: Artboard; + /** Multi-board arrangement on the canvas. Default: a single horizontal row. */ + layout?: BoardLayout; } /** From 017ecc58b1a2cbcb8fc735e1d358656381b22599 Mon Sep 17 00:00:00 2001 From: daniellee-ux <54412342+daniellee-ux@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:44:37 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore(release):=200.4.0=20=E2=80=94=20@open?= =?UTF-8?q?canva/core=20and=20create-opencanva?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor bump for both packages: the multi-board layout feature (BoardLayout / Scene.break — rows, vertical stacks, grids) landed since 0.3.0 and is additive, so existing designs are unaffected. create-opencanva's @opencanva/core dependency moves to ^0.4.0 to match, per the established publish order (core first, verify it is live, then the wrapper pinned to it) — a caret on ^0.3.0 would keep scaffolding the old core. Publishing to npm is not done in this commit. Co-Authored-By: Claude Opus 5 --- package-lock.json | 6 +++--- packages/core/package.json | 2 +- packages/create-opencanva/package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72655b7..5c06879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2777,7 +2777,7 @@ }, "packages/core": { "name": "@opencanva/core", - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "@babel/parser": "^7.29.2", @@ -2804,10 +2804,10 @@ } }, "packages/create-opencanva": { - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { - "@opencanva/core": "^0.3.0" + "@opencanva/core": "^0.4.0" }, "bin": { "create-opencanva": "bin/create-opencanva.mjs" diff --git a/packages/core/package.json b/packages/core/package.json index 1dcca62..ad14ff4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@opencanva/core", - "version": "0.3.0", + "version": "0.4.0", "description": "OpenCanva runtime, Vite plugin, and CLI — the agent-native graphic design framework.", "license": "Apache-2.0", "author": "Daniel Lee", diff --git a/packages/create-opencanva/package.json b/packages/create-opencanva/package.json index 42eb129..d66bd4e 100644 --- a/packages/create-opencanva/package.json +++ b/packages/create-opencanva/package.json @@ -1,6 +1,6 @@ { "name": "create-opencanva", - "version": "0.3.0", + "version": "0.4.0", "description": "Scaffold a new OpenCanva project — `npm create opencanva@latest`.", "license": "Apache-2.0", "author": "Daniel Lee", @@ -20,6 +20,6 @@ "node": "^20.19.0 || >=22.12.0" }, "dependencies": { - "@opencanva/core": "^0.3.0" + "@opencanva/core": "^0.4.0" } }