{"_id":"@anvilkit/canvas-editor","_rev":"5-c2f56dcddc79bff1dbffd7b1d867585b","name":"@anvilkit/canvas-editor","dist-tags":{"latest":"0.1.2-rc.1"},"versions":{"0.1.0":{"name":"@anvilkit/canvas-editor","version":"0.1.0","license":"MIT","_id":"@anvilkit/canvas-editor@0.1.0","maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"dist":{"shasum":"13b3fd9ed793d95d00156f733e052f7da4361439","tarball":"https://registry.npmjs.org/@anvilkit/canvas-editor/-/canvas-editor-0.1.0.tgz","fileCount":261,"integrity":"sha512-8yorvJPGItnDiPnmEJ4oIbs5gt2LwncyAx9f0g9IwMvoD16nbh9ecTPlB7nA0sFUUR7ewr8kAn0tC7Za0feCgw==","signatures":[{"sig":"MEQCIH6G6VykpimTsy9groRhOY5duvwgPzYgXH1ppkf5FNK/AiAkxW6d4U8zBLmvxkhjirmpQyx5zrZoGEUOxVIC4YqQLg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":375033},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./*":{"import":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"require":{"types":"./dist/*.d.cts","default":"./dist/*.cjs"}}},"scripts":{"dev":"rslib build --watch","lint":"pnpm exec biome lint package.json tsconfig.json rslib.config.ts vitest.config.ts src","size":"size-limit","test":"vitest run","build":"rslib build","format":"pnpm exec biome check --write package.json tsconfig.json rslib.config.ts vitest.config.ts src","publint":"publint","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"anvilkit","email":"ancyloce@gmail.com"},"description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","directories":{},"sideEffects":false,"_nodeVersion":"24.9.0","dependencies":{"zustand":"^5.0.13","use-image":"^1.1.4","@anvilkit/canvas-core":"0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^29.1.1","konva":"^10.3.0","react":"^19.2.6","vitest":"^4.1.7","publint":"^0.3.21","react-dom":"^19.2.6","typescript":"6.0.3","@rslib/core":"^0.21.5","@types/node":"^25.9.1","react-konva":"^19.2.4","@types/react":"19.2.15","@types/react-dom":"19.2.3","@rsbuild/plugin-react":"^2.0.0","@anvilkit/biome-config":"0.0.1","@testing-library/react":"^16.3.2","@anvilkit/vitest-config":"0.0.1","@testing-library/jest-dom":"^6.9.1","@anvilkit/typescript-config":"0.0.1"},"peerDependencies":{"konva":"^9 || ^10","react":">=19.0.0","react-dom":">=19.0.0","react-konva":"^19"},"_npmOperationalInternal":{"tmp":"tmp/canvas-editor_0.1.0_1779354816574_0.04269828940340625","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@anvilkit/canvas-editor","version":"0.1.1","license":"MIT","_id":"@anvilkit/canvas-editor@0.1.1","maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"dist":{"shasum":"531fc1e1874db52db35091e0065783435c2a1e27","tarball":"https://registry.npmjs.org/@anvilkit/canvas-editor/-/canvas-editor-0.1.1.tgz","fileCount":634,"integrity":"sha512-hTyGXKB6JYqSNhSctm0/EBrIpk7LeQevBFVQcDsg2cewLkkgfzMLb9SVeABxoEXeerLpafee3DViLRINPJ4L6w==","signatures":[{"sig":"MEYCIQDHOjWVt5DCZ3BK+a8vhA0M49tVe69eYrpptfz4u2OILwIhAOc0/hRzmdtHnEuN8D9trzLJv6JvJEu6dETH9oCwCz4F","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1087616},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./*":{"import":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"require":{"types":"./dist/*.d.cts","default":"./dist/*.cjs"}},"./collab":{"import":{"types":"./dist/collab/index.d.ts","default":"./dist/collab/index.js"},"require":{"types":"./dist/collab/index.d.cts","default":"./dist/collab/index.cjs"}},"./styles.css":"./dist/styles.css"},"scripts":{"dev":"pnpm build:css && rslib build --watch","lint":"pnpm exec biome lint package.json tsconfig.json rslib.config.ts vitest.config.ts src","size":"size-limit","test":"vitest run","build":"rslib build && pnpm build:css","format":"pnpm exec biome check --write package.json tsconfig.json rslib.config.ts vitest.config.ts src","publint":"publint","build:css":"tailwindcss -i ./src/styles.src.css -o ./dist/styles.css --minify","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"anvilkit","email":"ancyloce@gmail.com"},"description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"zustand":"^5.0.13","use-image":"^1.1.4","@anvilkit/ui":"0.1.6","lucide-react":"^1.7.0","@anvilkit/canvas-core":"0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"yjs":"^13.6.30","jsdom":"^29.1.1","konva":"^10.3.0","react":"^19.2.6","vitest":"^4.1.7","publint":"^0.3.21","react-dom":"^19.2.6","typescript":"6.0.3","@rslib/core":"^0.21.5","@types/node":"^25.9.1","react-konva":"^19.2.4","tailwindcss":"4.2.2","y-protocols":"^1.0.7","@types/react":"19.2.14","tw-animate-css":"^1.4.0","@tailwindcss/cli":"4.2.2","@types/react-dom":"19.2.3","@rsbuild/plugin-react":"^2.0.0","@anvilkit/biome-config":"0.0.1","@testing-library/react":"^16.3.2","@anvilkit/vitest-config":"0.0.1","@testing-library/jest-dom":"^6.9.1","@anvilkit/typescript-config":"0.0.1"},"peerDependencies":{"yjs":"^13.6.30","konva":"^9 || ^10","react":">=19.0.0","react-dom":">=19.0.0","react-konva":"^19","y-protocols":"^1.0.7"},"peerDependenciesMeta":{"yjs":{"optional":true},"y-protocols":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/canvas-editor_0.1.1_1779844325137_0.5265879300243901","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@anvilkit/canvas-editor","version":"0.1.2","license":"MIT","_id":"@anvilkit/canvas-editor@0.1.2","maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"dist":{"shasum":"80c1a99cc8bfefbbca8bed00d08e3358d84c29e3","tarball":"https://registry.npmjs.org/@anvilkit/canvas-editor/-/canvas-editor-0.1.2.tgz","fileCount":652,"integrity":"sha512-pGqSOqSJiiVj0HcKLNaQCZA4Y0bF65sPlcOb9uNDF7FC/cFTpIJ8tHcrQerqTDDFC1i3f8JhSZRbJ5HNCwUnbg==","signatures":[{"sig":"MEUCICYeFe271AS2+8iDgWtjaP1nPXvgOrA/mo6k4i21SnZoAiEA/bW7OCCjpiO2Cbce+uhRh4OrUr1awIOsdOKzyB7Z3M4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1132404},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./*":{"import":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"require":{"types":"./dist/*.d.cts","default":"./dist/*.cjs"}},"./collab":{"import":{"types":"./dist/collab/index.d.ts","default":"./dist/collab/index.js"},"require":{"types":"./dist/collab/index.d.cts","default":"./dist/collab/index.cjs"}},"./styles.css":"./dist/styles.css"},"scripts":{"dev":"pnpm build:css && rslib build --watch","lint":"pnpm exec biome lint package.json tsconfig.json rslib.config.ts vitest.config.ts src","size":"size-limit","test":"vitest run","build":"rslib build && pnpm build:css","format":"pnpm exec biome check --write package.json tsconfig.json rslib.config.ts vitest.config.ts src","publint":"publint","build:css":"tailwindcss -i ./src/styles.src.css -o ./dist/styles.css --minify","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"anvilkit","email":"ancyloce@gmail.com"},"description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"zustand":"^5.0.13","use-image":"^1.1.4","@anvilkit/ui":"0.1.6","lucide-react":"^1.7.0","@anvilkit/canvas-core":"0.1.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"yjs":"^13.6.30","jsdom":"^29.1.1","konva":"^10.3.0","react":"^19.2.6","vitest":"^4.1.7","publint":"^0.3.21","react-dom":"^19.2.6","typescript":"6.0.3","@rslib/core":"^0.21.5","@types/node":"^25.9.1","react-konva":"^19.2.4","tailwindcss":"4.2.2","y-protocols":"^1.0.7","@types/react":"19.2.14","tw-animate-css":"^1.4.0","@tailwindcss/cli":"4.2.2","@types/react-dom":"19.2.3","@rsbuild/plugin-react":"^2.0.0","@anvilkit/biome-config":"0.0.1","@testing-library/react":"^16.3.2","@anvilkit/vitest-config":"0.0.1","@testing-library/jest-dom":"^6.9.1","@anvilkit/typescript-config":"0.0.1"},"peerDependencies":{"yjs":"^13.6.30","konva":"^9 || ^10","react":">=19.0.0","react-dom":">=19.0.0","react-konva":"^19","y-protocols":"^1.0.7"},"peerDependenciesMeta":{"yjs":{"optional":true},"y-protocols":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/canvas-editor_0.1.2_1780009497970_0.328814125354671","host":"s3://npm-registry-packages-npm-production"}},"0.1.2-rc.0":{"name":"@anvilkit/canvas-editor","version":"0.1.2-rc.0","license":"MIT","_id":"@anvilkit/canvas-editor@0.1.2-rc.0","maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"dist":{"shasum":"33dbc158f92dae1a3787dc369807c27843de64f5","tarball":"https://registry.npmjs.org/@anvilkit/canvas-editor/-/canvas-editor-0.1.2-rc.0.tgz","fileCount":714,"integrity":"sha512-sSzaEPAqRzQdcombKWNZ5VFI3qOA6JqPw+coU3q2aWPKdFZWjbV8Q9no6dL+Phb5mNh3ia6oGxe+oUi0NAAkIA==","signatures":[{"sig":"MEQCIEY/Vr/RNHwzQ9pYNVhweNQicWgOrDfuBKsSm9wqXLKuAiAp0X4kLGoRrPUJJtBwXCd9p0vVzWq+j8pbIFMqPgewRw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1313697},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./*":{"import":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"require":{"types":"./dist/*.d.cts","default":"./dist/*.cjs"}},"./collab":{"import":{"types":"./dist/collab/index.d.ts","default":"./dist/collab/index.js"},"require":{"types":"./dist/collab/index.d.cts","default":"./dist/collab/index.cjs"}},"./internal":{"import":{"types":"./dist/internal.d.ts","default":"./dist/internal.js"},"require":{"types":"./dist/internal.d.cts","default":"./dist/internal.cjs"}},"./styles.css":"./dist/styles.css","./i18n/en.json":"./i18n/messages/en.json","./i18n/zh.json":"./i18n/messages/zh.json"},"scripts":{"dev":"pnpm build:css && rslib build --watch","lint":"pnpm exec biome lint package.json tsconfig.json rslib.config.ts vitest.config.ts src","size":"size-limit","test":"vitest run","build":"rslib build && pnpm build:css","format":"pnpm exec biome check --write package.json tsconfig.json rslib.config.ts vitest.config.ts src","publint":"publint","build:css":"tailwindcss -i ./src/styles.src.css -o ./dist/styles.css --minify","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"anvilkit","email":"ancyloce@gmail.com"},"description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"zustand":"^5.0.14","use-image":"^1.1.4","@anvilkit/ui":"0.1.15","lucide-react":"^1.17.0","@anvilkit/canvas-core":"0.1.2-rc.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"yjs":"^13.6.31","jsdom":"^29.1.1","konva":"^10.3.0","react":"^19.2.6","vitest":"^4.1.7","publint":"^0.3.21","react-dom":"^19.2.6","typescript":"6.0.3","@rslib/core":"^0.22.0","@types/node":"^25.9.1","react-konva":"^19.2.4","tailwindcss":"4.2.2","y-protocols":"^1.0.7","@types/react":"^19.2.14","tw-animate-css":"^1.4.0","@tailwindcss/cli":"4.2.2","@types/react-dom":"19.2.3","@rsbuild/plugin-react":"^2.0.0","@anvilkit/biome-config":"0.0.1","@testing-library/react":"^16.3.2","@anvilkit/vitest-config":"0.0.1","@testing-library/jest-dom":"^6.9.1","@anvilkit/typescript-config":"0.0.1"},"peerDependencies":{"yjs":"^13.6.31","konva":"^10.3.0","react":">=19.0.0","react-dom":">=19.0.0","react-konva":"^19.2.4","y-protocols":"^1.0.7"},"peerDependenciesMeta":{"yjs":{"optional":true},"y-protocols":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/canvas-editor_0.1.2-rc.0_1781242682362_0.07591976693132163","host":"s3://npm-registry-packages-npm-production"}},"0.1.2-rc.1":{"name":"@anvilkit/canvas-editor","version":"0.1.2-rc.1","description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","type":"module","engines":{"node":">=22.13"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/cjs/index.d.cts","default":"./dist/index.cjs"}},"./collab":{"import":{"types":"./dist/collab/index.d.ts","default":"./dist/collab/index.js"},"require":{"types":"./dist/cjs/collab/index.d.cts","default":"./dist/collab/index.cjs"}},"./internal":{"import":{"types":"./dist/internal.d.ts","default":"./dist/internal.js"},"require":{"types":"./dist/cjs/internal.d.cts","default":"./dist/internal.cjs"}},"./styles.css":"./dist/styles.css","./i18n/en.json":"./i18n/messages/en.json","./i18n/zh.json":"./i18n/messages/zh.json","./i18n/ja.json":"./i18n/messages/ja.json","./i18n/ko.json":"./i18n/messages/ko.json","./*":{"import":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"require":{"types":"./dist/cjs/*.d.cts","default":"./dist/*.cjs"}}},"license":"MIT","sideEffects":["**/*.css"],"publishConfig":{"access":"public"},"peerDependencies":{"konva":"^10.3.0","react":">=19.0.0","react-dom":">=19.0.0","react-konva":"^19.2.5","y-protocols":"^1.0.7","yjs":"^13.6.31"},"peerDependenciesMeta":{"konva":{"optional":false},"react":{"optional":false},"react-dom":{"optional":false},"react-konva":{"optional":false},"y-protocols":{"optional":true},"yjs":{"optional":true}},"dependencies":{"lucide-react":"^1.26.0","use-image":"^1.1.4","zustand":"^5.0.14","@anvilkit/canvas-core":"0.1.2-rc.1","@anvilkit/ui":"0.1.18"},"devDependencies":{"@rsbuild/plugin-react":"^2.1.0","@rslib/core":"^0.23.2","@tailwindcss/cli":"^4.3.3","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.2","@types/node":"^26.1.1","@types/react":"^19.2.17","@types/react-dom":"19.2.3","axe-core":"4.11.3","esbuild":"^0.28.1","jsdom":"^29.1.1","konva":"^10.3.0","madge":"^8.0.0","publint":"^0.3.22","react":"^19.2.8","react-dom":"^19.2.8","react-konva":"^19.2.5","tailwindcss":"^4.3.3","tw-animate-css":"^1.4.0","typedoc":"^0.28.20","typedoc-plugin-markdown":"^4.12.0","typescript":"7.0.2","vitest":"^4.1.10","vitest-axe":"^0.1.0","y-protocols":"^1.0.7","yjs":"^13.6.31","@anvilkit/biome-config":"0.0.1","@anvilkit/typescript-config":"0.0.1","@anvilkit/vitest-config":"0.0.1"},"scripts":{"build":"rslib build --lib esm && rslib build --lib cjs --no-clean && pnpm build:css","build:css":"tailwindcss -i ./src/styles.src.css -o ./dist/styles.css --minify","dev":"rslib build && pnpm build:css && rslib build --watch --no-clean","lint":"pnpm exec biome lint package.json tsconfig.json rslib.config.ts vitest.config.ts src","format":"pnpm exec biome check --write package.json tsconfig.json rslib.config.ts vitest.config.ts src","typecheck":"tsc --noEmit","test":"vitest run","test:coverage":"vitest run --coverage","test:watch":"vitest","publint":"publint","check:publint":"node ./scripts/check-publint.mjs","check:circular":"madge --circular --extensions ts,tsx src/","check:layering":"node ./scripts/check-layering.mjs","check:peer-deps":"node ./scripts/check-peer-deps.mjs","check:bundle-budget":"node ./scripts/check-bundle-budget.mjs","check:api-snapshot":"node ./scripts/check-api-snapshot.mjs","check:all":"pnpm check:publint && pnpm check:circular && pnpm check:layering && pnpm check:peer-deps && pnpm check:bundle-budget && pnpm check:api-snapshot","verify":"pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm build && pnpm check:all","update:api-snapshot":"node ./scripts/check-api-snapshot.mjs --update","docs:shortcuts":"node ./scripts/generate-shortcut-reference.mjs","size":"size-limit"},"_nodeVersion":"22.23.1","_id":"@anvilkit/canvas-editor@0.1.2-rc.1","dist":{"integrity":"sha512-SqJCMN51ZStoCqeIZkPz6VDq4pCejRgHOlbHb8rEJt+dVaTbBCa2346WSstqXuSPS7Y3s2d5B0oVLkjRm0sxpA==","shasum":"4c3ddd1751f3a8a6b6910dea779caccca7ceb5b1","tarball":"https://registry.npmjs.org/@anvilkit/canvas-editor/-/canvas-editor-0.1.2-rc.1.tgz","fileCount":1329,"unpackedSize":3504632,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIC0n7Dz6vHshh3AjqpuV5GGJm3VhQa+ETiMgjxgxeznsAiEArsjz5cCEXhYoPwopjy8f77TwoQtpo110lqp5r0MNpvg="}]},"_npmUser":{"name":"anvilkit","email":"ancyloce@gmail.com"},"directories":{},"maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/canvas-editor_0.1.2-rc.1_1785229971517_0.914113499554805"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-21T09:13:36.457Z","modified":"2026-07-28T09:12:51.837Z","0.1.0":"2026-05-21T09:13:36.774Z","0.1.1":"2026-05-27T01:12:05.413Z","0.1.2":"2026-05-28T23:04:58.217Z","0.1.2-rc.0":"2026-06-12T05:38:02.584Z","0.1.2-rc.1":"2026-07-28T09:12:51.700Z"},"license":"MIT","description":"React + Konva editor UI for AnvilKit Canvas Studio. Loads inside <Studio> via next/dynamic({ ssr: false }).","maintainers":[{"name":"anvilkit","email":"ancyloce@gmail.com"}],"readme":"# @anvilkit/canvas-editor\n\nThe React + Konva editor UI for **AnvilKit Canvas Studio**. It renders the\nheadless [`@anvilkit/canvas-core`](../core) IR onto a Konva stage and gives you a\nFigma-style authoring surface: tools, selection and transform handles, smart\nguides, multi-page artboards, panels, export, and real-time presence.\n\nThis package is the **stateful, client-only view layer**. `@anvilkit/canvas-core`\nis the React-free, Konva-free data layer (the IR, validators, immutable\nmutations, and the undoable command runtime); this package turns that IR into\npixels and turns user gestures back into core commands. It must run in the\nbrowser — mount it via `next/dynamic(() => …, { ssr: false })`.\n\n```bash\npnpm add @anvilkit/canvas-editor @anvilkit/canvas-core\n```\n\nPeer dependencies: `react` / `react-dom` (>= 19), `konva`, and `react-konva`.\n`yjs` + `y-protocols` are **optional** peers — install them only if you use the\ncollaboration entry (`@anvilkit/canvas-editor/collab`).\n\n**Status.** Pre-1.0 (`0.x`, currently shipping release-candidate versions —\nsee `package.json`'s `version`). Collaboration specifically is an alpha\nprototype (see above) — everything else here is more mature but the public\nAPI can still change between minor versions; breaking changes are called out\nin `CHANGELOG.md`.\n\n**Development model.** This package is developed inside the `anvilkit-studio`\nmonorepo as a git submodule with its own independent version line and publish\nlifecycle (see `docs/architecture/repository-structure.md`'s Submodule\nPolicy). `@anvilkit/canvas-core`, `@anvilkit/ui`, and the\n`@anvilkit/biome-config`/`typescript-config`/`vitest-config` tooling\ndevDependencies all resolve via `workspace:*` — a bare `git clone` of just\nthis submodule plus `pnpm install` does **not** build or test standalone;\ncheck it out inside the parent workspace for local development. Consuming it\nas a published npm dependency (`pnpm add`, above) is unaffected.\n\n## Core features\n\n- **Uncontrolled IR + undo/redo** — seed the document with `initialIR`; every\n  edit flows through the command runtime, which records an inverse for undo.\n- **Tool-driven editing** — `select`, `rect`, `ellipse`, `line`, `pen` (bezier\n  paths), `text`, `image`, `hand` (pan), plus AI gesture tools (`ai-image`,\n  `ai-brush`).\n- **Smart-guide snapping** — alignment guides and snap-to-grid / snap-to-object\n  while drawing and moving.\n- **Selection & transforms** — marquee + click selection, resize/rotate handles,\n  image cropping, and on-canvas path-point editing.\n- **Multi-page artboards** — a built-in `<PageNavigator>` (suppressible) and\n  page actions (`addPage`, `clonePage`, `switchToPage`, …).\n- **Mountable UI** — `<LayerPanel>`, `<PropertyInspector>`, `<ElementsPanel>`,\n  `<BrandPanel>`, `<ExportMenu>`, and inspector field primitives you compose\n  into your own chrome (or use the `<CanvasWorkspace>` shell).\n- **Brand kit** — pass shared colors + fonts via `brandKit`, read them with\n  `useBrandKit`.\n- **i18n seam** — host-injected `canvas.*` message catalog (`messages` prop);\n  bundled English, Chinese, Japanese, and Korean catalogs ship under `./i18n`\n  (four-locale parity is CI-enforced).\n- **Collaboration (alpha prototype)** — optional Yjs binding and remote\n  cursor / selection presence via the `./collab` entry. **Consistency model:\n  whole-document, last-writer-wins** — the entire `CanvasIR` is one JSON blob\n  under one Yjs key, so two peers editing different nodes concurrently do\n  **not** merge; one write wins outright and the other is silently discarded.\n  Presence (cursors/selections) is fine-grained; document CONTENT is not. See\n  `createCanvasYjsBinding`'s doc comment before using this in a multi-writer\n  setting. `CanvasCollabAdapter` is a transport-agnostic interface a future\n  fine-grained (per-node CRDT / command-log) adapter can implement as a\n  drop-in replacement.\n- **Export** — the FR-150 export dialog (`createCanvasExportPlugin`) with all\n  six formats built in (`png`/`jpeg`/`webp`/`svg`/`pdf`/`json` — no host\n  serializer injection needed; SVG/PDF code-split). Page scope covers current\n  page, all pages, and the current selection (FR-031/052); options include\n  scale presets, custom width/height with aspect lock, quality, transparent /\n  include background, a sanitized file name, and FR-154 progress with\n  cancellation. Node-menu \"Export selection\" and page-menu \"Export page\" open\n  it preselected. Per-format fidelity:\n  [docs/export-capability-matrix.md](./docs/export-capability-matrix.md).\n- **Accessibility** — roving keyboard focus ring, an off-screen scene tree, and\n  a live tool announcer.\n- **Extensible** — register custom node kinds (renderers + inspectors) and\n  custom tools through the `extensions` prop.\n- **Runtime injection** — pass a `createCanvasRuntime(...)` instance via the\n  `runtime` prop and the commit/history pipeline (`commit`, `commitBatch`,\n  `undo`, `redo`) dispatches through it, so custom commands registered on\n  that runtime participate in undo/redo exactly like built-ins. Pair it with\n  the same runtime at decode time (`decodeCanvasIR(raw, runtime)`) and export\n  time. Omit it for the default, built-in-only runtime (unchanged behavior).\n\n## Built-in node kind capability matrix (P1-1)\n\nEvery one of core's 15 built-in node kinds has an explicit rendering policy\nhere — none is silently invisible. `group`/`frame` are containers (recurse\ninto `children`); every other kind is a leaf. Walker/mutation/command support\nis uniform across all leaf kinds (the same generic `insertNode`/`removeNode`/\n`updateNode`/command machinery) and across the two containers, so it is\nomitted as a column below.\n\n| Kind | Editor Konva render | SVG / PDF export¹ | Unsupported-state behavior |\n|---|---|---|---|\n| `group` | ✅ | ✅ | — |\n| `frame` | ✅ (clip, background) | ✅ | empty image/logo well: dashed outline + label (editor chrome only) |\n| `rect` / `ellipse` / `polygon` / `star` / `line` / `path` | ✅ | ✅ | — |\n| `text` | ✅ | ✅ | — |\n| `rich-text` | ✅ (one Konva.Text per styled run) | ✅ (one `<tspan>` per run) | — |\n| `image` | ✅ | ✅ (inlines remote images) | missing asset: renders nothing (no crash) |\n| `svg` | ✅ (rendered as `<image>`, no inline vector) | ✅ (same, `SVG_INLINE_UNSUPPORTED` warning) | missing asset: renders nothing |\n| `ai-placeholder` | ✅ (status chrome: pending/complete/error + Cancel) | skipped, `AI_PLACEHOLDER_SKIPPED` warning (no static representation) | n/a — always has a representation |\n| `video` | poster image if resolved, else an editor-chrome-only placeholder box (P1-1 fix — previously invisible) | poster image if resolved else nothing, `VIDEO_UNSUPPORTED` warning | no poster: chrome-only placeholder in the live editor; nothing in an export/rasterize pass, matching core's SVG policy |\n| `audio` | editor-chrome-only placeholder box (P1-1 fix — previously invisible) | nothing, `AUDIO_UNSUPPORTED` warning (no visual representation at all) | same placeholder-in-editor / nothing-in-export split as `video` |\n\n¹ PDF is raster-embed over the SAME rendering path as `rasterizePage` (no\nstudio context), so PDF fidelity mirrors the raster/Konva path — including\nits documented approximations (shadow `spread` widened into blur, no\nstandalone blur on vector shapes) — not the SVG column. Per-feature detail:\n[docs/export-capability-matrix.md](./docs/export-capability-matrix.md).\n\nInspector field coverage and the accessibility scene tree (`SceneAccessibilityTree`)\nwere not re-audited kind-by-kind in this pass — treat that as a follow-up if a\ngap is suspected there.\n\n## Documentation\n\nIntegration guides live in [`docs/`](./docs):\n\n- [Workspace composition](./docs/workspace-composition.md) — shell anatomy,\n  headless vs `<CanvasWorkspace>`, every composition seam and opt-out.\n- [Adapter integration](./docs/adapters.md) — persistence, asset picker,\n  uploader, template provider, recovery.\n- [Persistence](./docs/persistence.md) — save states, checkpoint/revision\n  semantics, auto-save tuning, leave protection, local recovery.\n- [Assets](./docs/assets.md) — asset model, entry paths, upload lifecycle,\n  fit modes and adjustments.\n- [Export capability matrix](./docs/export-capability-matrix.md) —\n  per-format fidelity, incl. the PDF raster-embed disclosure.\n- [Keyboard shortcut reference](./docs/shortcut-reference.md) — **generated\n  from the registry** (`pnpm docs:shortcuts`); regenerate after changing\n  `shortcut-registry.ts`.\n- [Migration guide](./docs/migration.md) — PRD 0012 behavior changes and\n  opt-outs, host E2E impacts.\n\n## Core Architecture\n\n`<CanvasStudio>` is the root: it creates and owns every store, exposes them\nthrough a two-tier React context, and renders the Konva stage. User gestures\nenter through the tool layer, become `@anvilkit/canvas-core` commands, and run\nthrough a single **commit cycle** that updates the live IR and notifies the\nhost. Rendering is a one-way projection of that IR onto Konva layers.\n\n```\n                    +-------------------------------------+\n                    |              Host app               |\n                    |  next/dynamic(() => ..., ssr:0)     |\n                    +------------------+------------------+\n                                       |  props in  /  IR + changes out\n                                       v\n                    +------------------+------------------+\n                    |           <CanvasStudio>            |\n                    |      creates 14 Zustand stores      |\n                    +------------------+------------------+\n                                       |  provides\n         +-----------------------------+-----------------------------+\n         |                 React context (two-tier)                  |\n         |   Stable: stores . getIR . commit . commitBatch           |\n         |   Full  : + live ir . activePageId . Konva stage          |\n         +----+------------------------+------------------------+----+\n              |                        |                        |\n         read |                   read |                   read |\n              v                        v                        v\n   +----------+----------+  +----------+----------+  +----------+----------+\n   |   Zustand stores    |  |     Tool layer      |  |   Render pipeline   |\n   |  scene (live IR)    |  | ToolInteraction-    |  | <CanvasStage>       |\n   | history (undo/redo) |  | Layer dispatches    |  | stacked Konva       |\n   | selection . focus   |  | pointer to the      |  | layers (low->high)  |\n   | tool . viewport     |  | active tool:        |  | background . grid   |\n   | draft . guides      |  | select rect line    |  | objects . drag      |\n   | editing . crop      |  | pen text image      |  | selection overlays  |\n   | pen . pathEdit . ai |  | hand ai-image ...   |  | presence (collab)   |\n   +----------+----------+  +----------+----------+  +----------+----------+\n              ^                        |                        ^\n              | setIR + inverse        | commit(cmd)            | redraw on\n              |                        v                        |  new IR\n   +----------+------------------------+------------------------+----------+\n   |   commit cycle  (CanvasStudio.commit):                                |\n   |   1. historyStore.commit(ir, cmd)                                     |\n   |      -> applyCommand(ir, cmd) -> { ir, inverse }                      |\n   |   2. sceneStore.setIR(next)  ->  re-render                            |\n   |   3. onChange / onChanges    ->  host                                 |\n   +-----------------------------------+-----------------------------------+\n                                       |  uses (pure, headless)\n                                       v\n   +-----------------------------------+-----------------------------------+\n   |                       @anvilkit/canvas-core                           |\n   |   CanvasIR . CanvasNode . CanvasCommand . Change                      |\n   |   applyCommand (returns inverse) . node factories                     |\n   |   geometry . snap . SVG / PDF serializers                             |\n   +-----------------------------------------------------------------------+\n```\n\n**Reading the diagram**\n\n- **Stores** — 14 framework-agnostic Zustand stores. `scene` holds the live\n  `CanvasIR` (the single source of truth); `history` records inverses for\n  undo/redo; the rest hold transient UI state (selection, active tool, viewport,\n  in-progress drafts, snap guides, pen anchors, crop, AI jobs).\n- **Context (two-tier)** — the *Stable* half (stores + `getIR` / `commit` /\n  `commitBatch`) never changes identity, so toolbars and panels reading it don't\n  re-render on every edit. The *Full* context adds the live `ir`, `activePageId`,\n  and Konva `stage` for components that need them.\n- **Tool layer** — `ToolInteractionLayer` dispatches coalesced Konva pointer\n  events to the active tool. Tools write transient `draft` / `guides` state while\n  dragging, then call `commit(cmd)` on release. Drawing tools build nodes with\n  core factories (`createRect`, `createText`, …).\n- **Render pipeline** — `<CanvasStage>` stacks Konva `RenderLayer`s\n  (background → objects → drag → selection overlays → presence).\n  `CanvasNodeRenderer` projects each IR node to a Konva shape and defers to\n  extension `kindRenderers` for custom node kinds.\n- **Commit cycle** — the one path that mutates the document: `commit` runs\n  core's pure `applyCommand`, stores the inverse for undo, swaps the new IR into\n  `sceneStore`, and fires `onChange` / `onChanges` so the host can mirror,\n  autosave, or sync the change. Because the render pipeline reads `scene` through\n  context, `setIR` is what drives the redraw.\n\n## How to use\n\nMount `<CanvasStudio>` client-side with an IR built from the core factories, and\nmirror edits out through `onChange`:\n\n```tsx\n\"use client\";\n\nimport dynamic from \"next/dynamic\";\nimport { createCanvasIR, createRect } from \"@anvilkit/canvas-core\";\nimport \"@anvilkit/canvas-editor/styles.css\";\n\nconst CanvasStudio = dynamic(\n\t() => import(\"@anvilkit/canvas-editor\").then((m) => m.CanvasStudio),\n\t{ ssr: false },\n);\n\nconst initialIR = createCanvasIR({ title: \"Untitled\" });\ninitialIR.pages[0].root.children.push(\n\tcreateRect({ bounds: { width: 240, height: 160 }, fill: \"#38bdf8\" }),\n);\n\nexport function Editor() {\n\treturn (\n\t\t<CanvasStudio\n\t\t\tinitialIR={initialIR}\n\t\t\tonChange={(ir, command) => {\n\t\t\t\t// persist / mirror into a host store; `command` is the applied action\n\t\t\t}}\n\t\t\tonPickAsset={async () => {\n\t\t\t\t// open your asset picker, return an asset id (needed for the image tool)\n\t\t\t\treturn \"asset-id\";\n\t\t\t}}\n\t\t/>\n\t);\n}\n```\n\nThe editor ships **no toolbar of its own** — tool selection is host-driven.\nRender your own chrome and panels in one of two ways, both of which run inside\nthe editor's context so they can call `useCanvasStudio()`:\n\n- pass them as `children` of `<CanvasStudio>`, or\n- use the `<CanvasWorkspace>` shell (exported from this package) for a complete\n  rail + panel layout, or compose the stage yourself with the `renderShell` prop.\n\nMountable panels (`<LayerPanel>`, `<PropertyInspector>`, `<ElementsPanel>`,\n`<BrandPanel>`, `<PageNavigator>`, `<ExportMenu>`) bind to the active instance's\nstores automatically when rendered inside the provider.\n\n### Entry points\n\n| Subpath                              | Use it for                                                                                   |\n| ------------------------------------ | -------------------------------------------------------------------------------------------- |\n| `@anvilkit/canvas-editor`            | Stable host API: `CanvasStudio`, `CanvasWorkspace`, hooks, panels, export                    |\n| `@anvilkit/canvas-editor/collab`     | Yjs binding + presence (requires the optional `yjs` / `y-protocols` peers)                   |\n| `@anvilkit/canvas-editor/internal`   | Advanced internals: store factories, tools, stage + snap primitives (no stability guarantee) |\n| `@anvilkit/canvas-editor/styles.css` | Compiled editor styles — import once in the host                                             |\n| `@anvilkit/canvas-editor/i18n/en.json`, `…/zh.json` | Bundled message catalogs for the `messages` prop                              |\n\n## Extending\n\nRegister custom node kinds and tools through the `extensions` prop. Each\n`CanvasEditorExtension` may contribute `renderers` (kind → Konva renderer),\n`inspectors` (kind → inspector UI), and `tools`. Pair it with\n`createCanvasRuntime` in `@anvilkit/canvas-core`, which supplies the matching\nschema / command / serializer extensions for the same kinds.\n\n```tsx\n<CanvasStudio\n\tinitialIR={initialIR}\n\textensions={[\n\t\t{\n\t\t\tid: \"my-widget\",\n\t\t\trenderers: [{ kind: \"my-widget\", render: ({ node }) => /* Konva */ null }],\n\t\t\tinspectors: [{ kind: \"my-widget\", render: (node, commit) => /* fields */ null }],\n\t\t},\n\t]}\n/>\n```\n\n## Notes\n\n- **IR is uncontrolled.** `initialIR` seeds the editor once; later prop updates\n  do **not** replace the internal document. Mirror state out with `onChange`.\n  To load a genuinely different, unrelated document IN PLACE (a document\n  switch, a template loaded as a new document, crash recovery, or a `./collab`\n  remote/joined snapshot), call `useCanvasStudio().replaceDocument(ir, source)`\n  — it resets undo/redo history and clears selection/focus/draft/editing/\n  crop/pen/path-edit/guides/AI-jobs and reconciles the active page, instead of\n  leaving them stale against the old document. Remounting via a React `key` is\n  still the right call when you want a fully fresh instance (new stores, not\n  just a new document).\n- **Client-only.** The editor depends on Konva and the DOM — always load it with\n  `ssr: false`. There is no server render path.\n- **Styles must be imported.** Tailwind utilities and parent-document CSS do not\n  reach the Konva canvas; import `@anvilkit/canvas-editor/styles.css` in the host.\n- **`./internal` is unstable, and so is every other deep subpath (E-21).** The\n  package's `./*` export wildcard lets a specific module be imported directly\n  (`@anvilkit/canvas-editor/stores/viewport-store`, say) without going through\n  `/internal` — but it carries the exact same no-semver-guarantee posture,\n  including for modules `/internal` deliberately does NOT re-export (e.g.\n  `header/export-runner`). Deep imports (store factories, tools, stage / snap\n  primitives) can be renamed or removed without notice — prefer the root entry\n  where possible, and treat any deep import as pinned to this exact version.\n- **Collaboration peers are optional.** `yjs` and `y-protocols` are only needed\n  for the `./collab` entry; omit them for single-user editing.\n\n## Release gates\n\n`pnpm check:all` runs the release-gate chain: `check:publint` (packed-tarball publint), `check:circular` (madge), `check:peer-deps` (dependency-cone rules), and `check:bundle-budget` (esbuild-based, budget and externals read from `.size-limit.json` so the two size gates cannot drift). `check:api-snapshot` (typedoc JSON diff of the public API; regenerate with `pnpm update:api-snapshot` and commit the result).\n\nGates assume a **full package build** first — always run `pnpm build`: for this package, a bare `rslib build` wipes `dist/styles.css` (the `build:css` step regenerates it).\n\n## License\n\nMIT\n","readmeFilename":""}