velloo 0.5.0 → 0.7.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 (161) hide show
  1. package/README.md +116 -227
  2. package/THIRD-PARTY-NOTICES.md +3 -3
  3. package/canvas/assets/index-BQ5710lu.js +29 -0
  4. package/canvas/assets/index-GNLd0pCG.css +2 -0
  5. package/canvas/assets/lucide-all-DIRZdJPK.js +1 -0
  6. package/canvas/index.html +2 -2
  7. package/{chunk-qpv28z8d.js → chunk-0e5j6nqs.js} +1 -1
  8. package/{chunk-vrvkvb0v.js → chunk-0pxm5v0b.js} +3 -3
  9. package/{chunk-a16mz9zf.js → chunk-0vaff40g.js} +1 -1
  10. package/{chunk-42bj6r30.js → chunk-3m28pvzr.js} +1 -1
  11. package/{chunk-33t0csxs.js → chunk-4y2xy1kf.js} +2 -2
  12. package/{chunk-d4r0myxr.js → chunk-531xzfq5.js} +1 -1
  13. package/{chunk-1h3gqy7w.js → chunk-5s1wjhtj.js} +1 -1
  14. package/{chunk-4pkw6d9n.js → chunk-5x4nw0zz.js} +5 -5
  15. package/{chunk-b3x80jc1.js → chunk-5yhnnhkd.js} +1 -1
  16. package/{chunk-675tr675.js → chunk-64pex3mw.js} +1 -1
  17. package/chunk-667ag8ts.js +2 -0
  18. package/chunk-7gyzh0vq.js +5 -0
  19. package/{chunk-4sjvdakg.js → chunk-82x2jkfq.js} +1 -1
  20. package/{chunk-5y9hem1j.js → chunk-84n18xgx.js} +257 -3
  21. package/{chunk-edgwx026.js → chunk-abr2hgay.js} +1 -1
  22. package/{chunk-cqt1dpxd.js → chunk-acsv6m3n.js} +1 -1
  23. package/{chunk-j7tzbt3d.js → chunk-asfgvzse.js} +1 -1
  24. package/{chunk-0wqxb8r9.js → chunk-b8vcbbrg.js} +1 -1
  25. package/chunk-bms1sg4c.js +2 -0
  26. package/chunk-brnkprt5.js +723 -0
  27. package/{chunk-afe25dpr.js → chunk-bys7pqdb.js} +1 -1
  28. package/chunk-d0j8dg1e.js +3 -0
  29. package/chunk-d93kpv7s.js +12 -0
  30. package/chunk-dvh0b2r4.js +3 -0
  31. package/{chunk-5ma8zbty.js → chunk-eh2xp4v6.js} +1 -1
  32. package/{chunk-z78c1kf8.js → chunk-ejppcfa5.js} +1 -1
  33. package/{chunk-ngbqd7kb.js → chunk-ex36z3b3.js} +317 -119
  34. package/{chunk-6ytv1v2r.js → chunk-fmcm06xb.js} +1 -1
  35. package/{chunk-3ffs39e8.js → chunk-fvezyap4.js} +1 -1
  36. package/chunk-g7d065ek.js +2 -0
  37. package/{chunk-tw4kw4ps.js → chunk-g8xrwjvq.js} +2 -2
  38. package/{chunk-df557bn4.js → chunk-gbq5wv7x.js} +1 -1
  39. package/chunk-gcce587w.js +3 -0
  40. package/{chunk-cdqah1hy.js → chunk-gfppsgvv.js} +1 -1
  41. package/{chunk-5f0xv80x.js → chunk-j306gr0c.js} +1 -1
  42. package/chunk-j4fn2a2b.js +2 -0
  43. package/chunk-jbgde2v9.js +2 -0
  44. package/{chunk-wkqrb2tz.js → chunk-kkvf0xk2.js} +1 -1
  45. package/{chunk-d9kx7v12.js → chunk-md4mpvqc.js} +1 -1
  46. package/{chunk-vryadew3.js → chunk-mdgwdqnq.js} +2 -2
  47. package/chunk-p93x4pn2.js +3 -0
  48. package/chunk-pjkkp7xa.js +2 -0
  49. package/chunk-qp3wahg4.js +2 -0
  50. package/{chunk-f45p6kcb.js → chunk-qqrac4va.js} +1 -1
  51. package/chunk-qrnkrkkt.js +2 -0
  52. package/chunk-rqd4swxx.js +2 -0
  53. package/{chunk-xzkczmgg.js → chunk-rxkvjyst.js} +1 -1
  54. package/chunk-s84vxcdv.js +4 -0
  55. package/chunk-sjjnm4a1.js +2 -0
  56. package/chunk-skk6yvnw.js +3 -0
  57. package/{chunk-ahm4zd56.js → chunk-sme6hv6f.js} +1 -1
  58. package/chunk-snak5zpr.js +5 -0
  59. package/{chunk-gn9wcc1b.js → chunk-srp5gyr1.js} +1 -1
  60. package/{chunk-rd65xm6r.js → chunk-vdbr81a3.js} +1 -1
  61. package/chunk-vs8j3x6t.js +3 -0
  62. package/{chunk-hr3gbak5.js → chunk-xg71h7nb.js} +1 -1
  63. package/chunk-xzf52wvn.js +6 -0
  64. package/chunk-ykpctq15.js +6 -0
  65. package/chunk-zv17t1n7.js +3 -0
  66. package/cli.js +1 -1
  67. package/launcher.cjs +3 -1
  68. package/package.json +1 -1
  69. package/pkgs/helpers/src/box.tsx +12 -6
  70. package/pkgs/helpers/src/descriptors.ts +7 -0
  71. package/pkgs/helpers/src/icon.tsx +5 -3
  72. package/pkgs/helpers/src/index.ts +9 -6
  73. package/pkgs/helpers/src/lowering.ts +0 -24
  74. package/pkgs/helpers/src/primitive-classes.tsx +43 -0
  75. package/pkgs/helpers/src/text.tsx +29 -5
  76. package/pkgs/provider-antd/src/intro.ts +3 -3
  77. package/pkgs/provider-antd/src/manifest.ts +479 -57
  78. package/pkgs/provider-antd/src/registry.ts +8 -5
  79. package/pkgs/provider-antd/src/render-pass.ts +10 -4
  80. package/pkgs/provider-antd/src/reset-css.d.ts +5 -0
  81. package/pkgs/provider-antd/src/theme.ts +54 -11
  82. package/pkgs/provider-chakra/src/manifest.ts +6 -1
  83. package/pkgs/provider-mui/src/index.ts +4 -2
  84. package/pkgs/provider-mui/src/manifest.ts +11 -1
  85. package/pkgs/provider-mui/src/render-pass.ts +15 -1
  86. package/pkgs/provider-mui/src/theme.ts +10 -4
  87. package/pkgs/provider-none/src/components-inline.tsx +20 -14
  88. package/pkgs/provider-none/src/components.tsx +17 -54
  89. package/pkgs/provider-none/src/manifest.ts +1 -0
  90. package/pkgs/schema/src/annotation.ts +6 -3
  91. package/pkgs/schema/src/comment.ts +10 -1
  92. package/pkgs/schema/src/config.ts +4 -1
  93. package/pkgs/schema/src/index.ts +16 -1
  94. package/pkgs/schema/src/node-identity.ts +97 -0
  95. package/pkgs/schema/src/node.ts +49 -23
  96. package/pkgs/schema/src/snippet-resolve.ts +18 -12
  97. package/pkgs/schema/src/svg-sanitize.ts +3 -0
  98. package/pkgs/schema/src/theme.ts +6 -1
  99. package/pkgs/schema/src/typeset.ts +45 -21
  100. package/pkgs/shadcn-snapshot/dist/manifest.json +7 -0
  101. package/pkgs/shadcn-snapshot/src/components/ui/alert.tsx +2 -4
  102. package/pkgs/shadcn-snapshot/src/components/ui/aspect-ratio.tsx +2 -3
  103. package/pkgs/shadcn-snapshot/src/components/ui/attachment.tsx +3 -4
  104. package/pkgs/shadcn-snapshot/src/components/ui/avatar.tsx +2 -4
  105. package/pkgs/shadcn-snapshot/src/components/ui/badge.tsx +2 -4
  106. package/pkgs/shadcn-snapshot/src/components/ui/breadcrumb.tsx +2 -4
  107. package/pkgs/shadcn-snapshot/src/components/ui/bubble.tsx +2 -4
  108. package/pkgs/shadcn-snapshot/src/components/ui/button-group.tsx +3 -4
  109. package/pkgs/shadcn-snapshot/src/components/ui/button.tsx +2 -4
  110. package/pkgs/shadcn-snapshot/src/components/ui/card.tsx +2 -4
  111. package/pkgs/shadcn-snapshot/src/components/ui/carousel.tsx +3 -4
  112. package/pkgs/shadcn-snapshot/src/components/ui/chart.tsx +1 -1
  113. package/pkgs/shadcn-snapshot/src/components/ui/direction.tsx +2 -3
  114. package/pkgs/shadcn-snapshot/src/components/ui/empty.tsx +2 -4
  115. package/pkgs/shadcn-snapshot/src/components/ui/field.tsx +3 -4
  116. package/pkgs/shadcn-snapshot/src/components/ui/input-group.tsx +3 -4
  117. package/pkgs/shadcn-snapshot/src/components/ui/item.tsx +3 -4
  118. package/pkgs/shadcn-snapshot/src/components/ui/kbd.tsx +2 -3
  119. package/pkgs/shadcn-snapshot/src/components/ui/label.tsx +2 -4
  120. package/pkgs/shadcn-snapshot/src/components/ui/marker.tsx +2 -4
  121. package/pkgs/shadcn-snapshot/src/components/ui/message.tsx +2 -4
  122. package/pkgs/shadcn-snapshot/src/components/ui/native-select.tsx +2 -4
  123. package/pkgs/shadcn-snapshot/src/components/ui/navigation-menu.tsx +2 -4
  124. package/pkgs/shadcn-snapshot/src/components/ui/pagination.tsx +3 -4
  125. package/pkgs/shadcn-snapshot/src/components/ui/progress.tsx +2 -4
  126. package/pkgs/shadcn-snapshot/src/components/ui/radio-group.tsx +2 -4
  127. package/pkgs/shadcn-snapshot/src/components/ui/scroll-area.tsx +2 -4
  128. package/pkgs/shadcn-snapshot/src/components/ui/separator.tsx +2 -4
  129. package/pkgs/shadcn-snapshot/src/components/ui/skeleton.tsx +2 -3
  130. package/pkgs/shadcn-snapshot/src/components/ui/spinner.tsx +2 -3
  131. package/pkgs/shadcn-snapshot/src/components/ui/table.tsx +2 -4
  132. package/pkgs/shadcn-snapshot/src/components/ui/tabs.tsx +2 -4
  133. package/pkgs/shadcn-snapshot/src/components/ui/toggle.tsx +2 -4
  134. package/pkgs/shadcn-snapshot/src/shadcn-tailwind.css +1 -1
  135. package/pkgs/shadcn-snapshot/src/tailwind-entry.css +1 -1
  136. package/canvas/assets/index-BDBBHdHH.css +0 -2
  137. package/canvas/assets/index-QdsLJNY9.js +0 -29
  138. package/canvas/assets/lucide-all-R8DUyY-B.js +0 -1
  139. package/chunk-0rdf6trc.js +0 -2
  140. package/chunk-0wafc9aw.js +0 -4
  141. package/chunk-10hap8kc.js +0 -6
  142. package/chunk-18eqh3s1.js +0 -2
  143. package/chunk-19xyzep8.js +0 -2
  144. package/chunk-49w26e94.js +0 -5
  145. package/chunk-66qgm91k.js +0 -708
  146. package/chunk-6cgvzwec.js +0 -3
  147. package/chunk-75sc3adm.js +0 -2
  148. package/chunk-7cexzg71.js +0 -2
  149. package/chunk-98rann8t.js +0 -2
  150. package/chunk-asvdqkdp.js +0 -6
  151. package/chunk-emv1j50t.js +0 -2
  152. package/chunk-ewgdcgrk.js +0 -2
  153. package/chunk-mm8wv3bg.js +0 -3
  154. package/chunk-nkqtnwzz.js +0 -2
  155. package/chunk-q3kf8243.js +0 -3
  156. package/chunk-ryga3ny1.js +0 -3
  157. package/chunk-sb5h27w9.js +0 -3
  158. package/chunk-vgx47anc.js +0 -3
  159. package/chunk-w6f40y2x.js +0 -11
  160. package/chunk-we8hvy6e.js +0 -2
  161. package/chunk-ycrddh47.js +0 -5
@@ -0,0 +1,97 @@
1
+ import {
2
+ type ComponentNode,
3
+ type EmitAsRef,
4
+ isComponentNode,
5
+ isParamRef,
6
+ isSnippetInstance,
7
+ isTextNode,
8
+ type Node,
9
+ type ParamRef,
10
+ type RepoComponentRef,
11
+ type SnippetInstance,
12
+ type TextNode,
13
+ } from "./node.ts";
14
+
15
+ /**
16
+ * A `$ref` the renderer resolves itself rather than through any registry.
17
+ * Synthetic refs are never persisted — a preview synthesizes them — and every
18
+ * provider has to be able to draw them, so they can't be library components
19
+ * (a `none` folder has no `Badge` to draw a param slot with).
20
+ */
21
+ export const PARAM_TAG_REF = "velloo:param-tag";
22
+
23
+ /**
24
+ * The ref a node serializes to when the canvas bundle has no browser source
25
+ * for it and the client must drop in its server render instead.
26
+ */
27
+ export const STATIC_REF = "velloo:static";
28
+
29
+ export type SyntheticRef = typeof PARAM_TAG_REF | typeof STATIC_REF;
30
+
31
+ const SYNTHETIC_REFS: readonly SyntheticRef[] = [PARAM_TAG_REF, STATIC_REF];
32
+
33
+ export function isSyntheticRef(ref: string): ref is SyntheticRef {
34
+ return (SYNTHETIC_REFS as readonly string[]).includes(ref);
35
+ }
36
+
37
+ /** A component node carrying a repository identity (see {@link ComponentNode.$repo}). */
38
+ export type RepoNode = ComponentNode & { $repo: RepoComponentRef };
39
+
40
+ /** A component node carrying an emit-time host facade (see {@link ComponentNode.$emitAs}). */
41
+ export type EmitAsNode = ComponentNode & { $emitAs: EmitAsRef };
42
+
43
+ /**
44
+ * What a node's own fields say it is. Purely structural: no registry, no
45
+ * catalog, no I/O — a `named` shape is as far as the node alone can go, and
46
+ * `resolveNodeIdentity` in `@velloo/provider` takes it from there.
47
+ *
48
+ * The order of the `$`-keys is the contract, stated here once and nowhere
49
+ * else: `$snippet`, then `$param`, then a component — where an identity
50
+ * (`$repo`) beats an emit-time facade (`$emitAs`) beats a synthetic ref beats
51
+ * a plain name — then `$text`. A malformed value carrying both `$snippet` and `$ref` is a
52
+ * snippet instance, as every walker has always read it.
53
+ */
54
+ export type NodeShape =
55
+ | { kind: "repo"; node: RepoNode; ref: string; repo: RepoComponentRef }
56
+ | { kind: "emit-as"; node: EmitAsNode; ref: string; emitAs: EmitAsRef }
57
+ | { kind: "synthetic"; node: ComponentNode; ref: SyntheticRef }
58
+ | { kind: "named"; node: ComponentNode; ref: string }
59
+ | { kind: "snippet"; node: SnippetInstance }
60
+ | { kind: "param"; node: ParamRef }
61
+ /** A run of text beside elements: a bare text node, no element of its own. */
62
+ | { kind: "text"; node: TextNode; text: string }
63
+ /** Not a node at all — a raw scalar or object sitting in a node position. */
64
+ | { kind: "invalid"; value: unknown };
65
+
66
+ /** Discriminate a value by its own fields. Accepts `unknown`: node positions are a trust boundary. */
67
+ export function nodeShape(value: unknown): NodeShape {
68
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
69
+ return { kind: "invalid", value };
70
+ }
71
+ const node = value as Node;
72
+ if (isSnippetInstance(node)) return { kind: "snippet", node };
73
+ if (isParamRef(node)) return { kind: "param", node };
74
+ if (isComponentNode(node)) {
75
+ if (node.$repo !== undefined) {
76
+ return { kind: "repo", node: node as RepoNode, ref: node.$ref, repo: node.$repo };
77
+ }
78
+ if (node.$emitAs !== undefined) {
79
+ return { kind: "emit-as", node: node as EmitAsNode, ref: node.$ref, emitAs: node.$emitAs };
80
+ }
81
+ if (isSyntheticRef(node.$ref)) {
82
+ return { kind: "synthetic", node, ref: node.$ref };
83
+ }
84
+ return { kind: "named", node, ref: node.$ref };
85
+ }
86
+ if (isTextNode(node)) return { kind: "text", node, text: node.$text };
87
+ return { kind: "invalid", value };
88
+ }
89
+
90
+ /**
91
+ * Whether a raw JSON value is itself a node. The check every walker needs when
92
+ * it meets a *prop* value — a `children` prop carrying inline rich text, a slot
93
+ * prop holding an icon — where a node and a plain value are both legal.
94
+ */
95
+ export function isNode(value: unknown): value is Node {
96
+ return nodeShape(value).kind !== "invalid";
97
+ }
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  /**
4
- * A node tree is a discriminated union over three shapes, distinguished by
4
+ * A node tree is a discriminated union over four shapes, distinguished by
5
5
  * which `$`-prefixed key is present:
6
6
  *
7
7
  * - `ComponentNode` — a real shadcn / velloo component (`{ $ref }`).
@@ -13,6 +13,11 @@ import { z } from "zod";
13
13
  * substituted with the corresponding instance arg at render time. May
14
14
  * appear as a child node OR anywhere inside a `props` value via the
15
15
  * normal JSON walk during substitution.
16
+ * - `TextNode` — `{ $text }`. A run of text sitting beside elements
17
+ * (`<li>Remote <a>Apply</a></li>`). It renders as a bare text node, with no
18
+ * element of its own, so no selector in the app's stylesheet can tell the
19
+ * design's DOM from the app's. Text that is a component's *whole* content
20
+ * stays in `props.children`.
16
21
  *
17
22
  * Optional fields are spelled `?: T | undefined` rather than `?: T`. Under
18
23
  * `exactOptionalPropertyTypes` those differ, but not for a shape that round-
@@ -46,18 +51,27 @@ export type ComponentNode = {
46
51
  * subtree — preserving the app's real component identity through
47
52
  * capture → design → emit. Absent ⇒ the node emits as itself.
48
53
  */
49
- $emitAs?: { name: string; importPath: string } | undefined;
54
+ $emitAs?: EmitAsRef | undefined;
50
55
  /**
51
56
  * Repository component identity: this node IS a component the host app
52
57
  * imports (a package export such as Mantine's `Tabs`, or the app's own
53
58
  * `StatCard`), not a library component. `$ref` stays the JSX name codegen
54
- * prints; the identity decides rendering and imports. Takes precedence over
55
- * any provider component or extension of the same name, so shadowing is
56
- * explicit on the node rather than a registry-order accident.
59
+ * prints; the identity decides rendering and imports. Where it sits against
60
+ * the node's other keys is `nodeShape`'s to say (`./node-identity.ts`).
57
61
  */
58
62
  $repo?: RepoComponentRef | undefined;
59
63
  };
60
64
 
65
+ /**
66
+ * A host component a node emits *as*, in place of its own subtree. Superseded
67
+ * in practice by {@link RepoComponentRef}, which renders for real; kept because
68
+ * design folders carry it.
69
+ */
70
+ export type EmitAsRef = {
71
+ name: string;
72
+ importPath: string;
73
+ };
74
+
61
75
  /**
62
76
  * Where a repository component comes from. The import form is kept exactly as
63
77
  * code generation must print it; the resolved file is runtime cache data and
@@ -107,7 +121,11 @@ export type ParamRef = {
107
121
  $param: string;
108
122
  };
109
123
 
110
- export type Node = ComponentNode | SnippetInstance | ParamRef;
124
+ export type TextNode = {
125
+ $text: string;
126
+ };
127
+
128
+ export type Node = ComponentNode | SnippetInstance | ParamRef | TextNode;
111
129
 
112
130
  export function isComponentNode(n: Node): n is ComponentNode {
113
131
  return typeof (n as { $ref?: unknown }).$ref === "string";
@@ -121,6 +139,10 @@ export function isParamRef(n: Node): n is ParamRef {
121
139
  return typeof (n as { $param?: unknown }).$param === "string";
122
140
  }
123
141
 
142
+ export function isTextNode(n: Node): n is TextNode {
143
+ return typeof (n as { $text?: unknown }).$text === "string";
144
+ }
145
+
124
146
  /**
125
147
  * Format constraint for `$id` values. Must start with a letter; the rest
126
148
  * is letters / digits / dashes / underscores. Matches typical anchor
@@ -138,18 +160,16 @@ export const NodeIdSchema = z
138
160
 
139
161
  /**
140
162
  * A bare string/number in a `children` array is a common first-try shape
141
- * ("just put the label here"). Rather than reject it — `children` renders
142
- * nodes only — auto-wrap it into an inline `Box as="span"` so it renders as
143
- * text without stacking. Wrapping (vs. allowing scalars through) keeps
144
- * `children` typed `Node[]` for every downstream consumer. The styled-run
145
- * idiom (a mixed array in the `children` *prop*) is still preferred for rich
146
- * text; this just removes a needless failure.
163
+ * ("just put the label here"), and it means what it says: text. It becomes a
164
+ * text node rather than being rejected, which keeps `children` typed `Node[]`
165
+ * for every downstream consumer. An empty string is nothing to hold.
147
166
  */
148
- function wrapScalarChild(item: string | number | Node): Node {
149
- if (typeof item === "string" || typeof item === "number") {
150
- return { $ref: "Box", props: { as: "span", children: item } };
151
- }
152
- return item;
167
+ function scalarChildren(items: (string | number | Node)[]): Node[] {
168
+ return items.flatMap((item): Node[] => {
169
+ if (typeof item !== "string" && typeof item !== "number") return [item];
170
+ const text = String(item);
171
+ return text === "" ? [] : [{ $text: text }];
172
+ });
153
173
  }
154
174
 
155
175
  const EmitAsSchema = z.object({
@@ -200,7 +220,7 @@ const ComponentNodeSchema: z.ZodType<ComponentNode> = z.lazy(() =>
200
220
  props: z.record(z.string(), z.unknown()).optional(),
201
221
  children: z
202
222
  .array(z.union([z.string(), z.number(), NodeSchema]))
203
- .transform((items) => items.map(wrapScalarChild))
223
+ .transform(scalarChildren)
204
224
  .optional(),
205
225
  $emitAs: EmitAsSchema.optional(),
206
226
  $repo: RepoComponentRefSchema.optional(),
@@ -258,10 +278,14 @@ const ParamRefSchema: z.ZodType<ParamRef> = z.object({
258
278
  $param: z.string().min(1),
259
279
  });
260
280
 
281
+ const TextNodeSchema: z.ZodType<TextNode> = z.strictObject({
282
+ $text: z.string().min(1),
283
+ });
284
+
261
285
  /**
262
286
  * Key-routed parse instead of `z.union`: a malformed node yields the
263
287
  * issues of the *one* branch its `$`-key selects (with a full path),
264
- * not a three-branch union explosion. The MCP layer surfaces these
288
+ * not a four-branch union explosion. The MCP layer surfaces these
265
289
  * issues verbatim to agents, so error shape is part of the tool UX.
266
290
  */
267
291
  export const NodeSchema: z.ZodType<Node> = z
@@ -271,7 +295,7 @@ export const NodeSchema: z.ZodType<Node> = z
271
295
  ctx.addIssue({
272
296
  code: "custom",
273
297
  message:
274
- 'node must be an object with one of "$ref" (component), "$snippet" (instance), or "$param" (param ref)',
298
+ 'node must be an object with one of "$ref" (component), "$snippet" (instance), "$param" (param ref), or "$text" (text beside elements)',
275
299
  });
276
300
  return z.NEVER;
277
301
  }
@@ -283,12 +307,14 @@ export const NodeSchema: z.ZodType<Node> = z
283
307
  ? SnippetInstanceSchema
284
308
  : typeof v.$param === "string"
285
309
  ? ParamRefSchema
286
- : null;
310
+ : typeof v.$text === "string"
311
+ ? TextNodeSchema
312
+ : null;
287
313
  if (!branch) {
288
314
  ctx.addIssue({
289
315
  code: "custom",
290
316
  message:
291
- 'node needs exactly one of "$ref" (component), "$snippet" (snippet instance), or "$param" (param ref, snippet bodies only)',
317
+ 'node needs exactly one of "$ref" (component), "$snippet" (snippet instance), "$param" (param ref, snippet bodies only), or "$text" (text beside elements)',
292
318
  });
293
319
  return z.NEVER;
294
320
  }
@@ -323,6 +349,6 @@ export const NodeSchema: z.ZodType<Node> = z
323
349
  * have to narrow by node kind first.
324
350
  */
325
351
  export function nodeId(n: Node): string | undefined {
326
- if (isParamRef(n)) return undefined;
352
+ if (isParamRef(n) || isTextNode(n)) return undefined;
327
353
  return (n as { $id?: string }).$id;
328
354
  }
@@ -48,7 +48,7 @@ function isTruthy(v: unknown): boolean {
48
48
  return true;
49
49
  }
50
50
 
51
- /** A `$param` that substituted to a scalar where a child node was expected. */
51
+ /** A `$param` that substituted to a boolean or null where a child node was expected. */
52
52
  export interface InvalidParamPlacement {
53
53
  param: string;
54
54
  /** typeof the resolved value (or "null") — for a precise error message. */
@@ -63,13 +63,17 @@ export interface InvalidParamPlacement {
63
63
  * Unknown param names are collected in `missing` (the offending
64
64
  * substitution resolves to undefined) rather than thrown.
65
65
  *
66
- * `invalid` collects scalar params dropped into a *node* position — a
67
- * component node's own `children` array, where only subtrees render. This
68
- * is the classic mis-wire (a `string` param used as a child instead of as
69
- * a prop value); flagging it lets the renderer name the param and point at
70
- * the fix instead of failing opaquely deep in React. A `children` *prop*
71
- * value (`props.children`) is plain content, not a node position, so a
72
- * scalar param there is correct and never flagged.
66
+ * A string or number param in a *node* position — a component node's own
67
+ * `children` array — is the text it holds, and becomes a text node, exactly
68
+ * as a bare string written there does (`scalarChildren` in node.ts). It was a
69
+ * refusal before text nodes existed, and one that only surfaced at the next
70
+ * render: the write was accepted, and the screenshot after it failed. An
71
+ * empty string is nothing to hold.
72
+ *
73
+ * `invalid` collects what is left: a boolean or null param in a node position,
74
+ * which is a flag wired where content goes. A `children` *prop* value
75
+ * (`props.children`) is plain content, not a node position, so any scalar
76
+ * param there is correct and never flagged.
73
77
  */
74
78
  export function substituteSnippetParams(
75
79
  value: unknown,
@@ -92,11 +96,13 @@ export function substituteSnippetParams(
92
96
  const resolved = args[name];
93
97
  // An omitted optional param resolves to nothing — never a mis-wire.
94
98
  if (resolved === OMITTED) return DROP;
95
- if (inNodePosition && (resolved === null || typeof resolved !== "object")) {
96
- invalid.push({ param: name, valueType: resolved === null ? "null" : typeof resolved });
97
- return undefined;
99
+ if (!inNodePosition || (resolved !== null && typeof resolved === "object")) return resolved;
100
+ if (typeof resolved === "string" || typeof resolved === "number") {
101
+ const text = String(resolved);
102
+ return text === "" ? DROP : { $text: text };
98
103
  }
99
- return resolved;
104
+ invalid.push({ param: name, valueType: resolved === null ? "null" : typeof resolved });
105
+ return undefined;
100
106
  }
101
107
  if (typeof (v as { $if?: unknown }).$if === "string") {
102
108
  const cond = v as { $if: string; eq?: unknown; then?: unknown; else?: unknown };
@@ -76,6 +76,9 @@ const ALLOWED_ELEMENTS: ReadonlyMap<string, string> = new Map(
76
76
  ].map((name) => [name.toLowerCase(), name]),
77
77
  );
78
78
 
79
+ /** The static SVG elements, by their canonical (camelCase) names. */
80
+ export const SVG_ELEMENT_NAMES: ReadonlySet<string> = new Set(ALLOWED_ELEMENTS.values());
81
+
79
82
  /**
80
83
  * Elements whose presence means the input tried to run or load something.
81
84
  * They are dropped like any unknown element; naming them is what lets
@@ -131,7 +131,12 @@ export function resolveColors(theme: Theme, dark = false): Colors {
131
131
  * CSS-safe ident so they can't inject into the emitted stylesheet.
132
132
  */
133
133
  const PaletteSchema = z.record(
134
- z.string().regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/),
134
+ z
135
+ .string()
136
+ .regex(
137
+ /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,
138
+ 'palette keys are lowercase kebab-case, letter first — "primary-soft", "success-500", "border-primary-300"',
139
+ ),
135
140
  z.string().min(1),
136
141
  );
137
142
 
@@ -231,8 +231,21 @@ export function headingClasses(level: unknown): string {
231
231
  return TYPESET_CLASSES[role];
232
232
  }
233
233
 
234
- /** The Tailwind classes a `Text` of this variant renders with, tone included. */
235
- export function textClasses(variant: unknown): string {
234
+ /**
235
+ * A `Text` inside a `Text` with no variant of its own is that paragraph's
236
+ * text: a run changes a word's colour or weight, and restating the body rung
237
+ * on it would resize `12 km` inside a 17px line to the default 14.
238
+ */
239
+ function inheritsItsParagraph(variant: unknown, run: boolean): boolean {
240
+ return run && variant === undefined;
241
+ }
242
+
243
+ /**
244
+ * The Tailwind classes a `Text` of this variant renders with, tone included.
245
+ * `run` is true beneath another `Text`.
246
+ */
247
+ export function textClasses(variant: unknown, run = false): string {
248
+ if (inheritsItsParagraph(variant, run)) return "";
236
249
  const v = textVariant(variant);
237
250
  const tone = TEXT_TONE_BY_VARIANT[v];
238
251
  return tone
@@ -246,8 +259,12 @@ export function headingInlineStyle(level: unknown): Record<string, string | numb
246
259
  return { ...TYPESET_INLINE_STYLE[role] };
247
260
  }
248
261
 
249
- /** The inline `style` a `Text` of this variant renders with, on the `none`-CSS channel. */
250
- export function textInlineStyle(variant: unknown): Record<string, string | number> {
262
+ /**
263
+ * The inline `style` a `Text` of this variant renders with, on the `none`-CSS
264
+ * channel. `run` is true beneath another `Text`.
265
+ */
266
+ export function textInlineStyle(variant: unknown, run = false): Record<string, string | number> {
267
+ if (inheritsItsParagraph(variant, run)) return {};
251
268
  const v = textVariant(variant);
252
269
  const tone = TEXT_TONE_BY_VARIANT[v];
253
270
  return {
@@ -287,24 +304,31 @@ export function typesetUtilityClasses(): string[] {
287
304
  }
288
305
 
289
306
  /**
290
- * The scale as a Tailwind v3 `theme.extend.fontSize` map. Each entry carries its
291
- * paired leading and tracking, and every value is a `var()` into the typeset
292
- * sheet — so a v3 app re-derives inside a `.typeset-<preset>` region exactly
293
- * like a v4 one.
307
+ * The scale as Tailwind v3 `theme.extend` maps. `fontSize` carries each role's
308
+ * paired leading and tracking (so `text-h1` alone sets all three), and
309
+ * `lineHeight` / `letterSpacing` make the standalone `leading-<role>` /
310
+ * `tracking-<role>` utilities exist — v3 derives no utility from a fontSize
311
+ * tuple's extras, and `TYPESET_CLASSES` spells all three. Every value is a
312
+ * `var()` into the typeset sheet, so a v3 app re-derives inside a
313
+ * `.typeset-<preset>` region exactly like a v4 one.
294
314
  */
295
- export function typesetV3FontSize(): Record<
296
- string,
297
- [string, { lineHeight: string; letterSpacing: string }]
298
- > {
299
- return Object.fromEntries(
300
- TYPESET_SCALE_NAMES.map((role) => [
301
- role,
302
- [
303
- `var(--text-${role})`,
304
- { lineHeight: `var(--leading-${role})`, letterSpacing: `var(--tracking-${role})` },
305
- ],
306
- ]),
307
- );
315
+ export function typesetV3Theme(): {
316
+ fontSize: Record<string, [string, { lineHeight: string; letterSpacing: string }]>;
317
+ lineHeight: Record<string, string>;
318
+ letterSpacing: Record<string, string>;
319
+ } {
320
+ const fontSize: Record<string, [string, { lineHeight: string; letterSpacing: string }]> = {};
321
+ const lineHeight: Record<string, string> = {};
322
+ const letterSpacing: Record<string, string> = {};
323
+ for (const role of TYPESET_SCALE_NAMES) {
324
+ fontSize[role] = [
325
+ `var(--text-${role})`,
326
+ { lineHeight: `var(--leading-${role})`, letterSpacing: `var(--tracking-${role})` },
327
+ ];
328
+ lineHeight[role] = `var(--leading-${role})`;
329
+ letterSpacing[role] = `var(--tracking-${role})`;
330
+ }
331
+ return { fontSize, lineHeight, letterSpacing };
308
332
  }
309
333
 
310
334
  /**
@@ -460,6 +460,7 @@
460
460
  "group": "layout",
461
461
  "family": "Box",
462
462
  "source": "velloo",
463
+ "plainElement": true,
463
464
  "props": [
464
465
  {
465
466
  "name": "as",
@@ -7477,6 +7478,12 @@
7477
7478
  "optional": true,
7478
7479
  "control": "string"
7479
7480
  },
7481
+ {
7482
+ "name": "preserveAspectRatio",
7483
+ "type": "string | undefined",
7484
+ "optional": true,
7485
+ "control": "string"
7486
+ },
7480
7487
  {
7481
7488
  "name": "color",
7482
7489
  "type": "string | undefined",
@@ -1,11 +1,9 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/alert).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { cva, type VariantProps } from "class-variance-authority";
7
6
  import type * as React from "react";
8
-
9
7
  import { cn } from "../../lib/utils.ts";
10
8
 
11
9
  const alertVariants = cva(
@@ -1,7 +1,6 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/aspect-ratio).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  "use client";
6
5
 
7
6
  import { AspectRatio as AspectRatioPrimitive } from "radix-ui";
@@ -1,13 +1,12 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/attachment).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { cva, type VariantProps } from "class-variance-authority";
7
6
  import { Slot } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
9
+
11
10
  import { Button } from "./button.tsx";
12
11
 
13
12
  const attachmentVariants = cva(
@@ -1,12 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/avatar).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  "use client";
6
5
 
7
6
  import { Avatar as AvatarPrimitive } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
11
9
 
12
10
  function Avatar({
@@ -1,12 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/badge).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { cva, type VariantProps } from "class-variance-authority";
7
6
  import { Slot } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
11
9
 
12
10
  const badgeVariants = cva(
@@ -1,12 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/breadcrumb).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
7
6
  import { Slot } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
11
9
 
12
10
  function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
@@ -1,12 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/bubble).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { cva, type VariantProps } from "class-variance-authority";
7
6
  import { Slot } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
11
9
 
12
10
  function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
@@ -1,11 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/button-group).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  import { cva, type VariantProps } from "class-variance-authority";
6
5
  import { Slot } from "radix-ui";
7
-
8
6
  import { cn } from "../../lib/utils.ts";
7
+
9
8
  import { Separator } from "./separator.tsx";
10
9
 
11
10
  const buttonGroupVariants = cva(
@@ -1,12 +1,10 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/button).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
 
6
5
  import { cva, type VariantProps } from "class-variance-authority";
7
6
  import { Slot } from "radix-ui";
8
7
  import type * as React from "react";
9
-
10
8
  import { cn } from "../../lib/utils.ts";
11
9
 
12
10
  const buttonVariants = cva(
@@ -1,9 +1,7 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/card).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  import type * as React from "react";
6
-
7
5
  import { cn } from "../../lib/utils.ts";
8
6
 
9
7
  function Card({
@@ -1,14 +1,13 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/carousel).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  "use client";
6
5
 
7
6
  import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
8
7
  import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
9
8
  import * as React from "react";
10
-
11
9
  import { cn } from "../../lib/utils.ts";
10
+
12
11
  import { Button } from "./button.tsx";
13
12
 
14
13
  type CarouselApi = UseEmblaCarouselType[1];
@@ -1,4 +1,4 @@
1
- // Velloo-flavored Chart. shadcn's `chart` is a recharts wrapper that needs a
1
+ // Canvas-safe: Velloo-flavored Chart. shadcn's `chart` is a recharts wrapper that needs a
2
2
  // measured DOM (ResponsiveContainer), so it renders nothing under the
3
3
  // renderer's static SSR. We render a realistic preview server-side via
4
4
  // echarts' headless SSR-to-SVG mode (see chart-option.ts) — bar/line/area/
@@ -1,7 +1,6 @@
1
1
  // Vendored from shadcn-ui (https://ui.shadcn.com/docs/components/radix/direction).
2
- // Snapshot version: see packages/shadcn-snapshot/package.json#snapshotVersion.
3
- // Regenerate with `bun run vendor` — do not hand-edit unless you are adding a
4
- // canvas adaptation, in which case add the id to vendor.ts's ADAPTED set.
2
+ // Design-mode fork for the Velloo canvas — not the shadcn the IDE chrome renders.
3
+ // Regenerate with `bun run vendor` — the pipeline is scripts/vendor-shadcn/.
5
4
  "use client";
6
5
 
7
6
  import { Direction } from "radix-ui";