@snowcone-app/canvas 0.19.1 → 0.21.0

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.
Files changed (86) hide show
  1. package/README.md +124 -13
  2. package/STYLING_CONTRACT.md +427 -0
  3. package/dist/{CanvasRenderer-DUc-MOeR.js → CanvasRenderer-CwiYq8qg.js} +3 -3
  4. package/dist/{CanvasRenderer-DUc-MOeR.js.map → CanvasRenderer-CwiYq8qg.js.map} +1 -1
  5. package/dist/{CanvasStateV1-Bq48qhv5.js → CanvasStateV1-BFabcAKC.js} +35 -30
  6. package/dist/{CanvasStateV1-Bq48qhv5.js.map → CanvasStateV1-BFabcAKC.js.map} +1 -1
  7. package/dist/{ContextualToolbars-CkLjHNPn.js → ContextualToolbars--vTtorNg.js} +5 -5
  8. package/dist/{ContextualToolbars-CkLjHNPn.js.map → ContextualToolbars--vTtorNg.js.map} +1 -1
  9. package/dist/{EditorCanvasImpl-_3xCnLkx.js → EditorCanvasImpl-2ZgIUVpK.js} +18 -17
  10. package/dist/{EditorCanvasImpl-_3xCnLkx.js.map → EditorCanvasImpl-2ZgIUVpK.js.map} +1 -1
  11. package/dist/{ImportManager-D--7JnvW.js → ExportManager-DK7CdI-G.js} +24758 -24758
  12. package/dist/ExportManager-DK7CdI-G.js.map +1 -0
  13. package/dist/{FontAnalyzer-DIlqmyuf.js → FontAnalyzer-BpsTYowD.js} +5 -2
  14. package/dist/FontAnalyzer-BpsTYowD.js.map +1 -0
  15. package/dist/SnowconeCanvas-DqZicdxK.js +1172 -0
  16. package/dist/SnowconeCanvas-DqZicdxK.js.map +1 -0
  17. package/dist/{MenuButton-CeoEAtzP.js → UnifiedFontService-B1PGVu8O.js} +3704 -3429
  18. package/dist/UnifiedFontService-B1PGVu8O.js.map +1 -0
  19. package/dist/advanced.mjs +13 -13
  20. package/dist/api/embed.d.ts +3 -1
  21. package/dist/api/stable.d.ts +2 -2
  22. package/dist/components/editor/EditorAdd.d.ts +2 -1
  23. package/dist/components/editor/EditorBuy.d.ts +2 -1
  24. package/dist/components/editor/EditorCanvas.d.ts +2 -1
  25. package/dist/components/editor/EditorControls.d.ts +2 -1
  26. package/dist/components/editor/EditorHistory.d.ts +2 -1
  27. package/dist/components/editor/EditorLayers.d.ts +2 -1
  28. package/dist/components/editor/EditorMenu.d.ts +2 -1
  29. package/dist/components/editor/EditorPreview.d.ts +2 -1
  30. package/dist/components/editor/EditorRoot.d.ts +2 -1
  31. package/dist/components/editor/EditorTextField.d.ts +2 -1
  32. package/dist/components/editor/useProductSelection.d.ts +2 -1
  33. package/dist/components/embed/SnowconeCanvas.d.ts +10 -2
  34. package/dist/components/embed-realtime/context.d.ts +1 -1
  35. package/dist/contexts/EditorContext.d.ts +1 -1
  36. package/dist/core/ArtboardElement.d.ts +2 -1
  37. package/dist/default-editor/SnowconeEditor.d.ts +40 -0
  38. package/dist/design/design-builder.d.ts +46 -21
  39. package/dist/design/template-fields.d.ts +32 -0
  40. package/dist/design-builder-CPF60RU2.js +367 -0
  41. package/dist/design-builder-CPF60RU2.js.map +1 -0
  42. package/dist/{EditorContext-5QpqAhjh.js → editorErrors-CK1RISFa.js} +382 -358
  43. package/dist/editorErrors-CK1RISFa.js.map +1 -0
  44. package/dist/embed-2tp6Ue90.js +1117 -0
  45. package/dist/embed-2tp6Ue90.js.map +1 -0
  46. package/dist/embed.mjs +33 -31
  47. package/dist/hooks/useArtboards.d.ts +2 -1
  48. package/dist/index.mjs +8 -8
  49. package/dist/internals.mjs +5 -5
  50. package/dist/kits/sections.d.ts +11 -7
  51. package/dist/rendering/serialize-for-server.d.ts +3 -0
  52. package/dist/state/CanvasStateV1.d.ts +12 -0
  53. package/dist/template-fields-FgImixyv.js +476 -0
  54. package/dist/template-fields-FgImixyv.js.map +1 -0
  55. package/dist/testing/assertRenders.d.ts +2 -1
  56. package/dist/testing.mjs +8 -8
  57. package/dist/testing.mjs.map +1 -1
  58. package/dist/types/index.d.ts +10 -0
  59. package/dist/{usePieceGuideBundle-DVOISsB1.js → usePieceGuideBundle-Ctnu5k2D.js} +2 -2
  60. package/dist/{usePieceGuideBundle-DVOISsB1.js.map → usePieceGuideBundle-Ctnu5k2D.js.map} +1 -1
  61. package/dist/{validation-0IscSfRb.js → validation-3YEMnKLB.js} +467 -463
  62. package/dist/validation-3YEMnKLB.js.map +1 -0
  63. package/example/README.md +179 -0
  64. package/example/index.html +12 -0
  65. package/example/package.json +24 -0
  66. package/example/src/Widget.tsx +62 -0
  67. package/example/src/main.tsx +9 -0
  68. package/example/tsconfig.json +14 -0
  69. package/example/vite.config.ts +11 -0
  70. package/llms.txt +45 -0
  71. package/package.json +11 -6
  72. package/dist/EditorContext-5QpqAhjh.js.map +0 -1
  73. package/dist/FontAnalyzer-DIlqmyuf.js.map +0 -1
  74. package/dist/ImportManager-D--7JnvW.js.map +0 -1
  75. package/dist/MenuButton-CeoEAtzP.js.map +0 -1
  76. package/dist/SnowconeCanvas-3PA17na3.js +0 -1166
  77. package/dist/SnowconeCanvas-3PA17na3.js.map +0 -1
  78. package/dist/design-builder-v4935TeS.js +0 -343
  79. package/dist/design-builder-v4935TeS.js.map +0 -1
  80. package/dist/editorErrors-DXNfVXtD.js +0 -29
  81. package/dist/editorErrors-DXNfVXtD.js.map +0 -1
  82. package/dist/embed-D2QOpJXG.js +0 -1090
  83. package/dist/embed-D2QOpJXG.js.map +0 -1
  84. package/dist/template-fields-Dr3fMItw.js +0 -329
  85. package/dist/template-fields-Dr3fMItw.js.map +0 -1
  86. package/dist/validation-0IscSfRb.js.map +0 -1
@@ -0,0 +1,179 @@
1
+ <!-- Assembled by scripts/assemble-example.mjs from the repo's canonical
2
+ fixture (packages/canvas/fixtures/no-canvas-widget) plus this standalone
3
+ scaffolding, so the example builds against the PUBLISHED package. -->
4
+
5
+ # Read me first (you are inside the npm tarball)
6
+
7
+ This directory is the canonical `Editor.*` integration, shipped inside
8
+ `@snowcone-app/canvas` so an agent in a host repo can onboard without any
9
+ network fetch:
10
+
11
+ - **`src/Widget.tsx` is the integration to copy** — `Editor.Root` +
12
+ `Editor.Preview` + `Editor.TextField` + `Editor.Buy`; the design-editor
13
+ chunk never loads in this composition.
14
+ - `npm install && npm run build` here typechecks + builds it against the
15
+ published package. `npm run dev` serves it — rendering live needs a real
16
+ shop key and a grant route (see the docs link below).
17
+ - **`../STYLING_CONTRACT.md`** is the generated restyling contract: every
18
+ `data-sc-part` value, the DOM each part renders, and every `.sc-root`
19
+ theming token — restyle without reading package source.
20
+ - `../llms.txt` indexes everything agent-facing in this tarball. The full
21
+ hosted docs are one fetch: <https://developers.snowcone.app/llms-full.txt>.
22
+
23
+ One caveat on the protocol below: it was written for contributors inside the
24
+ snowcone monorepo — its `fixtures/<your-name>` paths and `FakeRenderSession`
25
+ test harness are repo-internal, not shipped. In YOUR app the protocol is:
26
+ copy `src/Widget.tsx`, change the three things it marks, and verify with
27
+ `assertRenders` + `expectNoEditorErrors` from `@snowcone-app/canvas/testing`.
28
+
29
+ ---
30
+
31
+ # Personalization widget — the canonical `Editor.*` example (copy me)
32
+
33
+ This directory is a **template, not just an exhibit**. The fastest path to your
34
+ own widget — measured at **zero failed iterations** in a cold-start agent run —
35
+ is: copy this folder, change three things, write one test, run one command.
36
+
37
+ ## The protocol
38
+
39
+ 1. **Copy `src/Widget.tsx`** to `fixtures/<your-name>/src/Widget.tsx` (the
40
+ other files here — `index.html`, `vite.config.ts`, `src/main.tsx` — are
41
+ this fixture's CI plumbing; you don't need them unless you want a runnable
42
+ page).
43
+ 2. **Change three things in `src/Widget.tsx`:**
44
+ - the `design(…)` — your artboard size + layers (name every layer you want
45
+ to bind: `.text('…', { name: 'headline' })`);
46
+ - the `product` — your catalog ids (`productId`, `mockupIds`; get them from
47
+ `getProduct()` in `@snowcone-app/sdk` — the ids here are demo values).
48
+ A product with a COLOR variant axis also needs `variantId`, or the render
49
+ hangs (pass `options` too so it fails fast instead);
50
+ - the parts — `<Editor.TextField layer={d.layer('…')}/>` per bindable
51
+ layer (`optional` ⇒ doesn't gate Buy), `<Editor.Preview/>`,
52
+ `<Editor.Buy onAddToCart={…}/>`.
53
+ 3. **Write your test** at `src/components/editor/__tests__/<your-name>.test.tsx`,
54
+ copying the pattern in `canonicalExample.test.tsx` (it imports this widget).
55
+ 4. **Run `pnpm -C packages/canvas verify`** — typecheck + tests + the
56
+ bundle-isolation gate. Green = your integration is proven, no browser needed.
57
+
58
+ `index.html` / `vite.config.ts` / `src/main.tsx` here are load-bearing only for
59
+ THIS canonical fixture (the CI import-graph check builds it). Your copy doesn't
60
+ need them unless you also want a runnable page — the test is the proof.
61
+
62
+ ## The test-harness contract (what the canonical test uses and why)
63
+
64
+ ```ts
65
+ import { FakeRenderSession, mountFlushed } from './testHarness.js';
66
+ import { expectNoEditorErrors } from '../../../testing/editorErrors.js';
67
+ ```
68
+
69
+ - **Two `vi.mock` blocks are required**, copy them verbatim:
70
+ - `@snowcone-app/sdk` → swaps `RenderSession` for `FakeRenderSession` (no
71
+ network; you drive mockups by hand);
72
+ - `@iconify/react` → the embed entry registers icons at module load, and the
73
+ registry's async timers leak across happy-dom tests without the stub.
74
+ - `FakeRenderSession.reset()` in `beforeEach`; `FakeRenderSession.last` is the
75
+ session your Root constructed.
76
+ - `session.renderState` is a spy — every render push, with the serialized
77
+ state as call arg `[1]` (assert your personalization landed there).
78
+ - `session.emitMockups([{ mockupId, imageUrl }])` simulates the server
79
+ answering. `mockupId` must be one of your `product.mockupIds` when your
80
+ Preview pins `mockupId=…`; an un-pinned Preview shows the first result.
81
+ - `mountFlushed(<YourWidget/>)` renders and flushes effects + any settled
82
+ lazy chunks; returns `{ container, unmount }`.
83
+ - `expectNoEditorErrors()` in `afterEach` — REQUIRED. Root's boundary contains
84
+ composition errors behind an error card in prod; this is what makes a
85
+ contained error fail your test instead of passing green.
86
+
87
+ ## Selector vocabulary (testing or driving the widget)
88
+
89
+ | Selector | Means |
90
+ |---|---|
91
+ | `[data-sc-part="preview"]` | the live mockup `<img>` |
92
+ | `[data-sc-part="preview-error"]` | the Preview's render/grant-failure surface (`role="alert"`, message in `data-sc-error`) — shown when a render fails before any mockup exists; a stale mockup beats a blank, so later errors keep the image |
93
+ | `[data-sc-part="textfield"]` | a bound text input; `aria-label` defaults to the **layer name**, so `input[aria-label="headline"]` targets one field |
94
+ | `[data-sc-part="buy"]` | the add-to-cart button |
95
+ | `[data-sc-buy-state]` | `ready` / `incomplete` / `busy` |
96
+ | `[data-sc-buy-missing]` | comma-joined reasons while incomplete, in a STABLE evaluation order you can assert on: `option:<key>`… (variant axes unselected, attribute order) → `variant` (complete selection matching no combination) → `layer:<name>` / `layer-seed:<name>` (per mounted field, mount order; `layer-seed` = still the placeholder) → `empty-design` → `quantity` |
97
+ | `[data-sc-part="error"]` | Root's contained-error card (`[data-sc-part="error-retry"]` when retryable) |
98
+
99
+ Multiple `<Editor.Preview/>` AND multiple `<Editor.TextField/>` under one Root
100
+ are valid (one field per named layer). One `<Editor.Canvas/>` max.
101
+
102
+ ## Variant selection + quantity (Buy-owned)
103
+
104
+ `Editor.Buy` owns variant selection and quantity; YOU render the variant UI
105
+ with `useProductSelection()` (same barrel). `select(key, choice)` re-resolves
106
+ the variant and the live preview follows — no remount, no session rebuild.
107
+ `<Editor.Buy defaultQuantity={2}/>` seeds quantity; `setQuantity` changes it
108
+ live. Don't drive variant switching through the Root `product.variantId` prop
109
+ — that re-keys the whole render session (initial `variantId` on a color
110
+ product is still required, it seeds the selection).
111
+
112
+ A variant product needs an `options` block on the Root `product` — pass your
113
+ catalog product's `options` verbatim; the shape is:
114
+
115
+ ```ts
116
+ const product = {
117
+ productId: 'BEEB77',
118
+ mockupIds: ['FV1qjO'],
119
+ placements: ['Front'],
120
+ variantId: 'gv-black-s', // initial selection (REQUIRED for color axes)
121
+ options: {
122
+ attributes: {
123
+ Color: { choices: [{ label: 'Black', hex: '#000' }, { label: 'White', hex: '#fff' }] },
124
+ Size: { choices: [{ label: 'S' }, { label: 'M' }] }, // no hex = non-color axis
125
+ },
126
+ combinations: [
127
+ { variantId: 'gv-black-s', Color: 'Black', Size: 'S', price: 1900 },
128
+ { variantId: 'gv-black-m', Color: 'Black', Size: 'M', price: 2100 },
129
+ ],
130
+ },
131
+ };
132
+ ```
133
+
134
+ **The currency rule:** `attributes[key].choices` are always `{ label, hex? }`
135
+ OBJECTS (use `hex` for swatches), but `select()`, `selection`, and
136
+ `disabledChoices` all speak the choice's **`label` string** — pass
137
+ `choice.label`, never the object. The hook's full surface:
138
+ `{ attributes, selection, select, disabledChoices, variantId, price,
139
+ quantity, setQuantity, complete, missing }`.
140
+
141
+ ```tsx
142
+ function VariantPicker() {
143
+ const { attributes, selection, select, disabledChoices, price, quantity, setQuantity } =
144
+ useProductSelection();
145
+ return (
146
+ <>
147
+ {Object.entries(attributes).map(([key, attr]) => (
148
+ <div key={key}>
149
+ {attr.choices.map(({ label = '', hex }) => (
150
+ <button
151
+ key={label}
152
+ style={hex ? { background: hex } : undefined}
153
+ disabled={disabledChoices[key]?.includes(label)}
154
+ aria-pressed={selection[key] === label}
155
+ onClick={() => select(key, label)}
156
+ >
157
+ {label}
158
+ </button>
159
+ ))}
160
+ </div>
161
+ ))}
162
+ <button onClick={() => setQuantity(quantity + 1)}>qty {quantity} +</button>
163
+ </>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ## Failure-mode fixtures (build your error UI against real failures)
169
+
170
+ From `@snowcone-app/canvas/testing`: `test403Design()` (asset-origin
171
+ allowlist denial — deterministic 403), `testIncompleteDesign()` (Buy gates
172
+ out of the box), `failingGrant()` (grant fetch rejects). For live contract
173
+ checks, `assertRenders(design, product, { shop })` renders twice (seed +
174
+ mutated bindings) and throws structured errors (`E_BINDING_INERT`,
175
+ `E_ASSET_FORBIDDEN`, `E_RENDER_FAILED`) — network-real, so nightly/acceptance
176
+ jobs, not per-PR unit tests.
177
+
178
+ All `Editor.*` parts are stable (ADR-0085 — the default editor is rebuilt from
179
+ them, CI-gated); the public surface is additive-only.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Personalization widget — no-canvas Editor.* composition</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "snowcone-personalization-widget-example",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "description": "The canonical @snowcone-app/canvas Editor.* example: a no-canvas personalization widget (live preview + bound text field + smart add-to-cart).",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "typecheck": "tsc --noEmit",
10
+ "build": "tsc --noEmit && vite build"
11
+ },
12
+ "dependencies": {
13
+ "@snowcone-app/canvas": "^0.21.0",
14
+ "react": "^19.1.0",
15
+ "react-dom": "^19.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^19.2.2",
19
+ "@types/react-dom": "^19.2.2",
20
+ "@vitejs/plugin-react": "^4.7.0",
21
+ "typescript": "^5.9.3",
22
+ "vite": "^5.4.21"
23
+ }
24
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * THE canonical personalization example (ADR-0085 / plan D7): a no-canvas
3
+ * merch widget — live product preview, ONE personalized text layer, smart
4
+ * add-to-cart — composed from the public `Editor.*` parts. The full canvas
5
+ * editor never loads; CI asserts this build's static import graph excludes
6
+ * the rendering engine (`scripts/check-import-graph.mjs`).
7
+ *
8
+ * This fixture is also the P0.5 hard-gate exhibit: the same public surface
9
+ * (`Editor.Root` + parts) serves both this composition and the full
10
+ * canvas-mounting editor (see the dev app's scrolling editor page).
11
+ */
12
+
13
+ import React from 'react';
14
+ import { Editor, design, type EditorBuyPayload } from '@snowcone-app/canvas/embed';
15
+
16
+ const SHOP_KEY = import.meta.env.VITE_SNOWCONE_SHOP ?? 'demo-shop';
17
+
18
+ // The design is the single source of truth: it seeds the first render AND
19
+ // mints the typed layer tokens the TextField binds with.
20
+ const nameDrop = design({ name: 'Front', width: 1480, height: 2328 })
21
+ .background('https://dqMwU8Jct3.storage.snowcone.app/demo/demo-008.jpg', {
22
+ aspectRatio: 1480 / 2328,
23
+ name: 'art',
24
+ })
25
+ .text('YOUR NAME', { anchor: 'top', name: 'name' });
26
+
27
+ const product = {
28
+ productId: 'BEEB77',
29
+ mockupIds: ['FV1qjO'],
30
+ placements: ['Front'],
31
+ };
32
+
33
+ export function PersonalizationWidget({
34
+ onAddToCart,
35
+ }: {
36
+ /** Your cart handler. NOTE the payload's `designState` is the EDITOR shape
37
+ * (flat `elements` + artboard metadata) — persist it as-is. It is NOT the
38
+ * server render shape (`artboards[].elements`) the session pushes;
39
+ * `payload.serverRequest` carries that one if you need it. */
40
+ onAddToCart?: (payload: EditorBuyPayload) => void;
41
+ } = {}): React.ReactElement {
42
+ return (
43
+ <Editor.Root shop={SHOP_KEY} product={product} design={nameDrop} grantUrl="/api/realtime/grant">
44
+ <div style={{ display: 'grid', gap: 12, maxWidth: 320 }}>
45
+ <Editor.Preview alt="Your personalized tee" fallback={<div aria-busy="true">Rendering…</div>} />
46
+ <Editor.TextField layer={nameDrop.layer('name')} placeholder="Your name" />
47
+ <Editor.Buy
48
+ onAddToCart={
49
+ onAddToCart ??
50
+ (({ productId, quantity, designState }) => {
51
+ // The host's one line: persist the designState (the
52
+ // re-renderable source of truth) with the cart line.
53
+ console.log('add-to-cart', { productId, quantity, designState });
54
+ })
55
+ }
56
+ >
57
+ Add to cart · $24
58
+ </Editor.Buy>
59
+ </div>
60
+ </Editor.Root>
61
+ );
62
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { PersonalizationWidget } from './Widget.js';
4
+
5
+ createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <PersonalizationWidget />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": ["vite/client"]
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ // Standalone build of the canonical example against the PUBLISHED
5
+ // @snowcone-app/canvas (the in-repo fixture variant of this file resolves the
6
+ // package to workspace source instead; scripts/assemble-example.mjs swaps in
7
+ // this one when the example ships in the tarball or runs in the
8
+ // published-pins CI gate).
9
+ export default defineConfig({
10
+ plugins: [react()],
11
+ });
package/llms.txt ADDED
@@ -0,0 +1,45 @@
1
+ # @snowcone-app/canvas — agent index (ships inside the npm tarball)
2
+
3
+ Zero-network onboarding: everything listed here is included in this package.
4
+ Read these before fetching anything.
5
+
6
+ - example/ — THE canonical integration (a no-canvas personalization widget:
7
+ Editor.Root + Editor.Preview + Editor.TextField + Editor.Buy; the design
8
+ editor chunk never loads). Copy example/src/Widget.tsx into your app;
9
+ example/README.md has the copy protocol, the data-sc-* selector vocabulary,
10
+ variant selection + quantity via useProductSelection(), and the
11
+ failure-mode fixtures.
12
+ - STYLING_CONTRACT.md — GENERATED restyling contract: every data-sc-part
13
+ value, the DOM each part renders, and every .sc-root theming token.
14
+ Restyle the parts without reading package source.
15
+ - README.md — package overview: the canonical example first, then the
16
+ standalone design editor (SnowconeCanvas).
17
+
18
+ Entry points:
19
+
20
+ - @snowcone-app/canvas/embed — the Editor.* parts and SnowconeEditor (the
21
+ turnkey default composed from those same parts), the design() builder and
22
+ typed layer tokens, useProductSelection(), the turnkey
23
+ RealtimeEmbed/RealtimeCanvas/RealtimeMockup trio, and the chrome-building
24
+ hooks (useEditor, useLayers, useCommands, ...).
25
+ - @snowcone-app/canvas — SnowconeCanvas, the standalone design editor.
26
+ - @snowcone-app/canvas/testing — assertRenders (two-render binding-liveness
27
+ check), expectNoEditorErrors (REQUIRED in every integration test), and the
28
+ deterministic failure fixtures (test403Design, testIncompleteDesign,
29
+ failingGrant).
30
+ - @snowcone-app/canvas/style.css — the required stylesheet. Fully namespaced
31
+ (.sc-root scope); it cannot collide with host CSS.
32
+
33
+ Contracts to rely on:
34
+
35
+ - All Editor.* throws are structured: EditorError with a stable `code` +
36
+ `error.data` (valid names, offending values, docSlug). Assert on codes,
37
+ never message prose.
38
+ - Buy incompleteness is state, not an error: [data-sc-buy-state="incomplete"]
39
+ with machine-readable reason codes in [data-sc-buy-missing].
40
+ - The public embed surface is additive-only (CI-pinned).
41
+
42
+ Hosted docs (one fetch, the source of truth):
43
+ https://developers.snowcone.app/llms-full.txt
44
+ The primitives guide (grant-route framework variants, catalog ids, CMS/typed
45
+ layer notes): https://developers.snowcone.app/primitives
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/canvas",
3
- "version": "0.19.1",
3
+ "version": "0.21.0",
4
4
  "description": "Self-contained canvas editor component for e-commerce product customization",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -41,7 +41,10 @@
41
41
  },
42
42
  "files": [
43
43
  "dist",
44
- "README.md"
44
+ "README.md",
45
+ "llms.txt",
46
+ "STYLING_CONTRACT.md",
47
+ "example"
45
48
  ],
46
49
  "sideEffects": [
47
50
  "**/*.css",
@@ -107,7 +110,7 @@
107
110
  "react-colorful": "^5.6.1",
108
111
  "tailwind-merge": "^3.0.0",
109
112
  "tailwind-variants": "^3.1.1",
110
- "@snowcone-app/sdk": "0.15.0"
113
+ "@snowcone-app/sdk": "0.16.0"
111
114
  },
112
115
  "devDependencies": {
113
116
  "@axe-core/playwright": "^4.11.0",
@@ -169,7 +172,7 @@
169
172
  "vite-plugin-node-polyfills": "^0.24.0",
170
173
  "vitest": "^3.2.4",
171
174
  "zod": "^4.1.12",
172
- "@snowcone-app/ui": "0.2.5"
175
+ "@snowcone-app/ui": "0.2.6"
173
176
  },
174
177
  "scripts": {
175
178
  "dev": "vite",
@@ -183,10 +186,12 @@
183
186
  "dev:lib": "pnpm run bundle:worker && vite build --config vite.lib.config.ts --watch",
184
187
  "preview": "vite preview",
185
188
  "typecheck": "tsc --noEmit -p tsconfig.json",
189
+ "typecheck:verify": "tsc --noEmit -p tsconfig.verify.json",
186
190
  "generate": "node scripts/generate.js",
187
- "test": "pnpm run test:unit && pnpm run test:component && pnpm run test:ssr && pnpm run check:graph",
191
+ "gen:styling-contract": "STYLING_CONTRACT_WRITE=1 vitest run src/components/editor/__tests__/stylingContract.test.tsx",
192
+ "test": "pnpm run typecheck:verify && pnpm run test:unit && pnpm run test:component && pnpm run test:ssr && pnpm run check:graph",
188
193
  "check:graph": "node scripts/check-import-graph.mjs",
189
- "verify": "pnpm run typecheck && pnpm run test:unit && pnpm run test:component && pnpm run check:graph",
194
+ "verify": "pnpm run typecheck && pnpm run typecheck:verify && pnpm run test:unit && pnpm run test:component && pnpm run check:graph",
190
195
  "test:unit": "vitest run",
191
196
  "test:ssr": "vitest run --config vitest.ssr.config.ts",
192
197
  "test:unit:watch": "vitest",