velloo 0.4.1 → 0.6.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 (165) hide show
  1. package/README.md +29 -7
  2. package/canvas/assets/index-CnyI3aig.js +29 -0
  3. package/canvas/assets/index-GNLd0pCG.css +2 -0
  4. package/canvas/assets/lucide-all-CH8LmVh0.js +1 -0
  5. package/canvas/index.html +2 -2
  6. package/{chunk-91pajfnm.js → chunk-08mjtg1e.js} +1 -1
  7. package/{chunk-m1wp8cpt.js → chunk-1gkeepkm.js} +1 -1
  8. package/{chunk-2dm4x17z.js → chunk-1td6z6ey.js} +1 -1
  9. package/{chunk-ykdx12na.js → chunk-2ba6edh8.js} +1 -1
  10. package/chunk-2q561dec.js +2 -0
  11. package/chunk-2web93qz.js +2 -0
  12. package/{chunk-py6sjyzk.js → chunk-3trkmgfr.js} +1 -1
  13. package/chunk-52m26hh4.js +3 -0
  14. package/{chunk-7xffbj5h.js → chunk-56vfy0db.js} +1 -1
  15. package/{chunk-8t5phg0s.js → chunk-56z5xd5b.js} +1 -1
  16. package/{chunk-xbhxzdat.js → chunk-63y5gdzw.js} +1 -1
  17. package/{chunk-t9ygewjx.js → chunk-6813v926.js} +257 -3
  18. package/{chunk-cejmmx3r.js → chunk-69qb8p8s.js} +2 -2
  19. package/chunk-6k05kqnc.js +3 -0
  20. package/chunk-74yxy75r.js +3 -0
  21. package/{chunk-7a45tn73.js → chunk-8cj4swpr.js} +5 -5
  22. package/chunk-8grzf8q1.js +2 -0
  23. package/chunk-8vam2893.js +3 -0
  24. package/{chunk-dj1pp4e3.js → chunk-8vgpffn7.js} +1 -1
  25. package/{chunk-j5axqyan.js → chunk-96qhpth5.js} +1 -1
  26. package/chunk-ab8fga32.js +22 -0
  27. package/chunk-azeqkms7.js +6 -0
  28. package/{chunk-2y9nnzpr.js → chunk-bc7pkjcn.js} +1 -1
  29. package/{chunk-7gtvstne.js → chunk-c4spp880.js} +1 -1
  30. package/{chunk-a9mgvspm.js → chunk-chtv0yj6.js} +211 -110
  31. package/{chunk-r9ekd92e.js → chunk-dar6wbf2.js} +1 -1
  32. package/{chunk-aa3str7g.js → chunk-e2btp1wd.js} +1 -1
  33. package/{chunk-hsrg23gd.js → chunk-eqjtq673.js} +1 -1
  34. package/chunk-esb7n55g.js +244 -0
  35. package/chunk-ey65err5.js +5 -0
  36. package/{chunk-r03aywsb.js → chunk-ff0jfn0v.js} +1 -1
  37. package/chunk-ff2vt7cz.js +12 -0
  38. package/chunk-g185gx8a.js +2 -0
  39. package/{chunk-km3z2czq.js → chunk-h85hv05d.js} +1 -1
  40. package/{chunk-db7mf114.js → chunk-hf4adxe0.js} +9 -7
  41. package/{chunk-6zr596r2.js → chunk-hjc5eg5c.js} +1 -1
  42. package/{chunk-b4jy4hcd.js → chunk-hsfpbjpc.js} +1 -1
  43. package/{chunk-ph94nrc8.js → chunk-jsvkqae2.js} +1 -1
  44. package/{chunk-rfrbnw8r.js → chunk-k423dkjz.js} +1 -1
  45. package/chunk-mfch0b0t.js +3 -0
  46. package/chunk-nbbnb5e9.js +3 -0
  47. package/{chunk-tr49kraa.js → chunk-nx09zqgs.js} +1 -1
  48. package/chunk-p93x4pn2.js +3 -0
  49. package/{chunk-k3dpwjx4.js → chunk-pxaxgkaq.js} +1 -1
  50. package/chunk-qwccndcd.js +5 -0
  51. package/{chunk-vjrn10xr.js → chunk-rjjc9vam.js} +2 -2
  52. package/{chunk-npsg7btt.js → chunk-rk238m15.js} +1 -1
  53. package/chunk-t2x3mztc.js +2 -0
  54. package/chunk-t6qr2ay9.js +3 -0
  55. package/chunk-ta84ehgg.js +2 -0
  56. package/chunk-tac2wvc7.js +2 -0
  57. package/chunk-tbd6wn3q.js +6 -0
  58. package/chunk-v6c3h9ft.js +3 -0
  59. package/chunk-w2naqxqt.js +2 -0
  60. package/chunk-wfhv9jg4.js +2 -0
  61. package/chunk-wnj432fw.js +3 -0
  62. package/chunk-x02z2ccm.js +2 -0
  63. package/{chunk-b58pnj20.js → chunk-xtwx2p9g.js} +1 -1
  64. package/chunk-yfvc8d2g.js +716 -0
  65. package/chunk-z73q4dq9.js +2 -0
  66. package/{chunk-yfttm20w.js → chunk-ztsckexy.js} +2 -2
  67. package/cli.js +1 -1
  68. package/package.json +3 -1
  69. package/pkgs/helpers/src/descriptors.ts +1 -0
  70. package/pkgs/helpers/src/index.ts +7 -4
  71. package/pkgs/helpers/src/lowering.ts +0 -24
  72. package/pkgs/helpers/src/primitive-classes.tsx +43 -0
  73. package/pkgs/provider-antd/src/intro.ts +3 -3
  74. package/pkgs/provider-antd/src/manifest.ts +478 -57
  75. package/pkgs/provider-antd/src/registry.ts +8 -5
  76. package/pkgs/provider-antd/src/render-pass.ts +10 -4
  77. package/pkgs/provider-antd/src/reset-css.d.ts +5 -0
  78. package/pkgs/provider-antd/src/theme.ts +54 -11
  79. package/pkgs/provider-chakra/src/manifest.ts +5 -1
  80. package/pkgs/provider-html/src/index.ts +68 -0
  81. package/pkgs/provider-html/src/registry.ts +121 -0
  82. package/pkgs/provider-html/src/version.ts +1 -0
  83. package/pkgs/provider-mui/src/index.ts +4 -2
  84. package/pkgs/provider-mui/src/manifest.ts +10 -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 +12 -3
  88. package/pkgs/provider-none/src/components.tsx +26 -53
  89. package/pkgs/provider-none/src/manifest.ts +2 -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 +33 -1
  93. package/pkgs/schema/src/css-declarations.ts +41 -0
  94. package/pkgs/schema/src/index.ts +16 -1
  95. package/pkgs/schema/src/node-identity.ts +92 -0
  96. package/pkgs/schema/src/node.ts +13 -4
  97. package/pkgs/schema/src/theme.ts +46 -1
  98. package/pkgs/schema/src/typeset.ts +24 -17
  99. package/pkgs/shadcn-snapshot/dist/manifest.json +1 -0
  100. package/pkgs/shadcn-snapshot/src/components/ui/alert.tsx +2 -4
  101. package/pkgs/shadcn-snapshot/src/components/ui/aspect-ratio.tsx +2 -3
  102. package/pkgs/shadcn-snapshot/src/components/ui/attachment.tsx +3 -4
  103. package/pkgs/shadcn-snapshot/src/components/ui/avatar.tsx +2 -4
  104. package/pkgs/shadcn-snapshot/src/components/ui/badge.tsx +2 -4
  105. package/pkgs/shadcn-snapshot/src/components/ui/breadcrumb.tsx +2 -4
  106. package/pkgs/shadcn-snapshot/src/components/ui/bubble.tsx +2 -4
  107. package/pkgs/shadcn-snapshot/src/components/ui/button-group.tsx +3 -4
  108. package/pkgs/shadcn-snapshot/src/components/ui/button.tsx +2 -4
  109. package/pkgs/shadcn-snapshot/src/components/ui/card.tsx +2 -4
  110. package/pkgs/shadcn-snapshot/src/components/ui/carousel.tsx +3 -4
  111. package/pkgs/shadcn-snapshot/src/components/ui/chart.tsx +1 -1
  112. package/pkgs/shadcn-snapshot/src/components/ui/direction.tsx +2 -3
  113. package/pkgs/shadcn-snapshot/src/components/ui/empty.tsx +2 -4
  114. package/pkgs/shadcn-snapshot/src/components/ui/field.tsx +3 -4
  115. package/pkgs/shadcn-snapshot/src/components/ui/input-group.tsx +3 -4
  116. package/pkgs/shadcn-snapshot/src/components/ui/item.tsx +3 -4
  117. package/pkgs/shadcn-snapshot/src/components/ui/kbd.tsx +2 -3
  118. package/pkgs/shadcn-snapshot/src/components/ui/label.tsx +2 -4
  119. package/pkgs/shadcn-snapshot/src/components/ui/marker.tsx +2 -4
  120. package/pkgs/shadcn-snapshot/src/components/ui/message.tsx +2 -4
  121. package/pkgs/shadcn-snapshot/src/components/ui/native-select.tsx +2 -4
  122. package/pkgs/shadcn-snapshot/src/components/ui/navigation-menu.tsx +2 -4
  123. package/pkgs/shadcn-snapshot/src/components/ui/pagination.tsx +3 -4
  124. package/pkgs/shadcn-snapshot/src/components/ui/progress.tsx +2 -4
  125. package/pkgs/shadcn-snapshot/src/components/ui/radio-group.tsx +2 -4
  126. package/pkgs/shadcn-snapshot/src/components/ui/scroll-area.tsx +2 -4
  127. package/pkgs/shadcn-snapshot/src/components/ui/separator.tsx +2 -4
  128. package/pkgs/shadcn-snapshot/src/components/ui/skeleton.tsx +2 -3
  129. package/pkgs/shadcn-snapshot/src/components/ui/spinner.tsx +2 -3
  130. package/pkgs/shadcn-snapshot/src/components/ui/table.tsx +2 -4
  131. package/pkgs/shadcn-snapshot/src/components/ui/tabs.tsx +2 -4
  132. package/pkgs/shadcn-snapshot/src/components/ui/toggle.tsx +2 -4
  133. package/pkgs/shadcn-snapshot/src/shadcn-tailwind.css +1 -1
  134. package/pkgs/shadcn-snapshot/src/tailwind-entry.css +1 -1
  135. package/plugins/claude/velloo/agents/velloo-design-reviewer.md +56 -13
  136. package/plugins/claude/velloo/agents/velloo-designer.md +4 -1
  137. package/skills/velloo-setup/SKILL.md +19 -55
  138. package/canvas/assets/index-B1OD0Yz5.css +0 -2
  139. package/canvas/assets/index-BWYFx2Rr.js +0 -29
  140. package/canvas/assets/lucide-all-Bcm80WFX.js +0 -1
  141. package/chunk-00t0bz7x.js +0 -244
  142. package/chunk-0446t32z.js +0 -2
  143. package/chunk-0zen7bzb.js +0 -3
  144. package/chunk-2h24va0j.js +0 -2
  145. package/chunk-2jbhv7vh.js +0 -3
  146. package/chunk-5ywkk7d6.js +0 -2
  147. package/chunk-b7famda5.js +0 -3
  148. package/chunk-cvn689d5.js +0 -3
  149. package/chunk-e6nhc3ay.js +0 -6
  150. package/chunk-f5g45xed.js +0 -2
  151. package/chunk-fjbzk4sw.js +0 -3
  152. package/chunk-hv52a6hf.js +0 -2
  153. package/chunk-jep31ffe.js +0 -2
  154. package/chunk-jwzhhhzn.js +0 -3
  155. package/chunk-mcymwefw.js +0 -5
  156. package/chunk-n0kx20s2.js +0 -2
  157. package/chunk-nwf2kn41.js +0 -26
  158. package/chunk-phz993p3.js +0 -2
  159. package/chunk-qan5dnjt.js +0 -688
  160. package/chunk-r5d0jsnd.js +0 -5
  161. package/chunk-shac6bat.js +0 -6
  162. package/chunk-t4zvcece.js +0 -10
  163. package/chunk-tarws372.js +0 -2
  164. package/chunk-yghypth2.js +0 -2
  165. package/chunk-zqtz23xk.js +0 -3
@@ -14,7 +14,9 @@ const PRIMITIVE_DESCRIPTORS: ComponentDescriptor[] = [
14
14
  id: "Box",
15
15
  category: "ui",
16
16
  source: "velloo",
17
+ plainElement: true,
17
18
  props: [
19
+ { name: "as", type: "string", optional: true, control: "string" },
18
20
  {
19
21
  name: "className",
20
22
  type: "string | undefined",
@@ -100,11 +100,14 @@ export const CanvasNoteSchema = z
100
100
  */
101
101
  x: z.number().optional(),
102
102
  y: z.number().optional(),
103
+ /** User-resizable width. */
104
+ width: z.number().positive(),
103
105
  /**
104
- * User-resizable width. Height is always derived from content (notes
105
- * never scroll; the box expands downward).
106
+ * The height the author drew or resized the note to. Absent, the note is
107
+ * exactly as tall as its text; set, it is this tall, and text that runs
108
+ * past it scrolls inside the note.
106
109
  */
107
- width: z.number().positive(),
110
+ height: z.number().positive().optional(),
108
111
  /** Markdown body. */
109
112
  body: z.string(),
110
113
  /** Present when the note is about a specific node rather than the board. */
@@ -116,4 +116,13 @@ export type CommentAnchorState =
116
116
  | { status: "attached"; resolvedPath: number[] }
117
117
  | { status: "stale" };
118
118
 
119
- export type CommentThreadView = CommentThread & { anchorState: CommentAnchorState };
119
+ export type CommentThreadView = CommentThread & {
120
+ anchorState: CommentAnchorState;
121
+ /**
122
+ * False when the viewer can read the thread but not resolve or delete it — a
123
+ * cloud thread on a link they no longer manage. Absent means they can.
124
+ */
125
+ manage?: false;
126
+ /** The git branch a cloud thread's link was last published from, where known. */
127
+ branch?: string;
128
+ };
@@ -28,7 +28,7 @@ export const LibrarySchema = z.object({
28
28
  * Component provider id. The server's provider loader maps ids to
29
29
  * factories; `"shadcn-upstream"` is the default for new folders.
30
30
  */
31
- id: z.enum(["shadcn-upstream", "none", "mui", "antd", "chakra"]),
31
+ id: z.enum(["shadcn-upstream", "none", "html", "mui", "antd", "chakra"]),
32
32
  version: z.string().min(1),
33
33
  source: z.enum(["binary", "cache", "in-repo"]),
34
34
  /** Where the components live, relative to the design folder root. */
@@ -50,6 +50,19 @@ const CodegenConfigSchema = z.object({
50
50
 
51
51
  export type CodegenConfig = z.infer<typeof CodegenConfigSchema>;
52
52
 
53
+ /**
54
+ * A path inside the host app, relative to its root. The config is committed
55
+ * with the repo and the agents are told to open what it names, so a cloned
56
+ * repo must not be able to aim it anywhere else: absolute paths and `..`
57
+ * segments make the file invalid rather than being resolved.
58
+ */
59
+ const AppRelativePathSchema = z
60
+ .string()
61
+ .min(1)
62
+ .refine((path) => !/^(?:[\\/~]|[A-Za-z]:)/.test(path) && !path.split(/[\\/]/).includes(".."), {
63
+ message: "must be relative to the app root, with no absolute path or `..` segment",
64
+ });
65
+
53
66
  /**
54
67
  * Where the host app lives, used by the live-island bundler to resolve
55
68
  * a `render:"live"` extension's `importPath` (and its dependencies, e.g.
@@ -73,6 +86,11 @@ export const HostAppSchema = z.object({
73
86
  * built-in framework recipe, then no wrapper.
74
87
  */
75
88
  preview: z.string().min(1).optional(),
89
+ /**
90
+ * The app's stylesheets an HTML design is styled by, in cascade order. The
91
+ * design keeps copies of them under `assets/host/` (`store_host_files`).
92
+ */
93
+ stylesheets: z.array(z.string().regex(/^(?:\/(?!\/)|https:\/\/)/)).optional(),
76
94
  /**
77
95
  * Bounds repository-component discovery beyond what the app's entries and
78
96
  * routes import: `include` adds component roots (files or directories,
@@ -171,6 +189,20 @@ export const ConfigSchema = z
171
189
  styling: z.object({ framework: z.enum(["tailwind", "none"]) }).optional(),
172
190
  /** Host app location for the live-island bundler. See `HostAppSchema`. */
173
191
  hostApp: HostAppSchema.optional(),
192
+ /**
193
+ * A design-system document this folder follows — normally the repo's
194
+ * `DESIGN.md`.
195
+ *
196
+ * A path, never a copy. The file belongs to the repo and goes on being
197
+ * edited there; a copy inside the design folder would drift away from the
198
+ * rules it was supposed to state, which is the one thing a design system
199
+ * must not do. Relative to the host app root, so the usual value is
200
+ * `DESIGN.md`.
201
+ *
202
+ * Optional because the folder also finds one by convention — this records
203
+ * a file somewhere unconventional, or pins the choice when several exist.
204
+ */
205
+ designSystem: z.object({ path: AppRelativePathSchema }).optional(),
174
206
  /**
175
207
  * Named host apps for monorepos — the multi-app twin of `hostApp`, keyed
176
208
  * by a short app name (`"web"`, `"admin"`; `init`'s multi-app scan uses
@@ -0,0 +1,41 @@
1
+ const CSS_PROPERTY = /^(--[\w-]+|-?[a-z][a-z-]*)$/;
2
+
3
+ /**
4
+ * CSS declaration text as a React style object (`background-size` ⇒
5
+ * `backgroundSize`, custom properties kept), or null when the text is not a
6
+ * declaration list — a class string, say. Semicolons inside `url(…)` or quotes
7
+ * do not split.
8
+ */
9
+ export function styleObjectFromCss(text: string): Record<string, string> | null {
10
+ const declarations: string[] = [];
11
+ let depth = 0;
12
+ let quote: string | null = null;
13
+ let start = 0;
14
+ for (let i = 0; i < text.length; i++) {
15
+ const char = text[i];
16
+ if (quote) {
17
+ if (char === quote) quote = null;
18
+ } else if (char === '"' || char === "'") quote = char;
19
+ else if (char === "(") depth++;
20
+ else if (char === ")") depth = Math.max(0, depth - 1);
21
+ else if (char === ";" && depth === 0) {
22
+ declarations.push(text.slice(start, i));
23
+ start = i + 1;
24
+ }
25
+ }
26
+ declarations.push(text.slice(start));
27
+ const style: Record<string, string> = {};
28
+ for (const declaration of declarations) {
29
+ if (declaration.trim() === "") continue;
30
+ const colon = declaration.indexOf(":");
31
+ if (colon < 0) return null;
32
+ const property = declaration.slice(0, colon).trim().toLowerCase();
33
+ const value = declaration.slice(colon + 1).trim();
34
+ if (!CSS_PROPERTY.test(property) || value === "") return null;
35
+ const key = property.startsWith("--")
36
+ ? property
37
+ : property.replace(/^-(ms)-/, "$1-").replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
38
+ style[key] = value;
39
+ }
40
+ return Object.keys(style).length > 0 ? style : null;
41
+ }
@@ -56,6 +56,7 @@ export {
56
56
  type ViewportPreset,
57
57
  ViewportPresetSchema,
58
58
  } from "./config.ts";
59
+ export { styleObjectFromCss } from "./css-declarations.ts";
59
60
  export {
60
61
  isCssIdent,
61
62
  neutralizeCssText,
@@ -93,6 +94,7 @@ export {
93
94
  } from "./migrate.ts";
94
95
  export {
95
96
  type ComponentNode,
97
+ type EmitAsRef,
96
98
  isComponentNode,
97
99
  isParamRef,
98
100
  isRepoNode,
@@ -109,6 +111,17 @@ export {
109
111
  repoKey,
110
112
  type SnippetInstance,
111
113
  } from "./node.ts";
114
+ export {
115
+ type EmitAsNode,
116
+ isNode,
117
+ isSyntheticRef,
118
+ type NodeShape,
119
+ nodeShape,
120
+ PARAM_TAG_REF,
121
+ type RepoNode,
122
+ STATIC_REF,
123
+ type SyntheticRef,
124
+ } from "./node-identity.ts";
112
125
  export {
113
126
  DesignNameSchema,
114
127
  designNameIssue,
@@ -139,6 +152,8 @@ export {
139
152
  ColorsSchema,
140
153
  type LooseTokenGroup,
141
154
  resolveColors,
155
+ spacingCssTokens,
156
+ TAILWIND_SIZE_NAMES,
142
157
  type Theme,
143
158
  ThemeSchema,
144
159
  TypographySchema,
@@ -173,7 +188,7 @@ export {
173
188
  typesetSizePx,
174
189
  typesetThemeTokens,
175
190
  typesetUtilityClasses,
176
- typesetV3FontSize,
191
+ typesetV3Theme,
177
192
  typesetVars,
178
193
  } from "./typeset.ts";
179
194
  export { type DuplicateId, findDuplicateIds } from "./validate-ids.ts";
@@ -0,0 +1,92 @@
1
+ import {
2
+ type ComponentNode,
3
+ type EmitAsRef,
4
+ isComponentNode,
5
+ isParamRef,
6
+ isSnippetInstance,
7
+ type Node,
8
+ type ParamRef,
9
+ type RepoComponentRef,
10
+ type SnippetInstance,
11
+ } from "./node.ts";
12
+
13
+ /**
14
+ * A `$ref` the renderer resolves itself rather than through any registry.
15
+ * Synthetic refs are never persisted — a preview synthesizes them — and every
16
+ * provider has to be able to draw them, so they can't be library components
17
+ * (a `none` folder has no `Badge` to draw a param slot with).
18
+ */
19
+ export const PARAM_TAG_REF = "velloo:param-tag";
20
+
21
+ /**
22
+ * The ref a node serializes to when the canvas bundle has no browser source
23
+ * for it and the client must drop in its server render instead.
24
+ */
25
+ export const STATIC_REF = "velloo:static";
26
+
27
+ export type SyntheticRef = typeof PARAM_TAG_REF | typeof STATIC_REF;
28
+
29
+ const SYNTHETIC_REFS: readonly SyntheticRef[] = [PARAM_TAG_REF, STATIC_REF];
30
+
31
+ export function isSyntheticRef(ref: string): ref is SyntheticRef {
32
+ return (SYNTHETIC_REFS as readonly string[]).includes(ref);
33
+ }
34
+
35
+ /** A component node carrying a repository identity (see {@link ComponentNode.$repo}). */
36
+ export type RepoNode = ComponentNode & { $repo: RepoComponentRef };
37
+
38
+ /** A component node carrying an emit-time host facade (see {@link ComponentNode.$emitAs}). */
39
+ export type EmitAsNode = ComponentNode & { $emitAs: EmitAsRef };
40
+
41
+ /**
42
+ * What a node's own fields say it is. Purely structural: no registry, no
43
+ * catalog, no I/O — a `named` shape is as far as the node alone can go, and
44
+ * `resolveNodeIdentity` in `@velloo/provider` takes it from there.
45
+ *
46
+ * The order of the `$`-keys is the contract, stated here once and nowhere
47
+ * else: `$snippet`, then `$param`, then a component — where an identity
48
+ * (`$repo`) beats an emit-time facade (`$emitAs`) beats a synthetic ref beats
49
+ * a plain name. A malformed value carrying both `$snippet` and `$ref` is a
50
+ * snippet instance, as every walker has always read it.
51
+ */
52
+ export type NodeShape =
53
+ | { kind: "repo"; node: RepoNode; ref: string; repo: RepoComponentRef }
54
+ | { kind: "emit-as"; node: EmitAsNode; ref: string; emitAs: EmitAsRef }
55
+ | { kind: "synthetic"; node: ComponentNode; ref: SyntheticRef }
56
+ | { kind: "named"; node: ComponentNode; ref: string }
57
+ | { kind: "snippet"; node: SnippetInstance }
58
+ | { kind: "param"; node: ParamRef }
59
+ /** Not a node at all — a raw scalar or object sitting in a node position. */
60
+ | { kind: "invalid"; value: unknown };
61
+
62
+ /** Discriminate a value by its own fields. Accepts `unknown`: node positions are a trust boundary. */
63
+ export function nodeShape(value: unknown): NodeShape {
64
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
65
+ return { kind: "invalid", value };
66
+ }
67
+ const node = value as Node;
68
+ if (isSnippetInstance(node)) return { kind: "snippet", node };
69
+ if (isParamRef(node)) return { kind: "param", node };
70
+ if (isComponentNode(node)) {
71
+ if (node.$repo !== undefined) {
72
+ return { kind: "repo", node: node as RepoNode, ref: node.$ref, repo: node.$repo };
73
+ }
74
+ if (node.$emitAs !== undefined) {
75
+ return { kind: "emit-as", node: node as EmitAsNode, ref: node.$ref, emitAs: node.$emitAs };
76
+ }
77
+ if (isSyntheticRef(node.$ref)) {
78
+ return { kind: "synthetic", node, ref: node.$ref };
79
+ }
80
+ return { kind: "named", node, ref: node.$ref };
81
+ }
82
+ return { kind: "invalid", value };
83
+ }
84
+
85
+ /**
86
+ * Whether a raw JSON value is itself a node. The check every walker needs when
87
+ * it meets a *prop* value — a `children` prop carrying inline rich text, a slot
88
+ * prop holding an icon — where a node and a plain value are both legal.
89
+ */
90
+ export function isNode(value: unknown): value is Node {
91
+ return nodeShape(value).kind !== "invalid";
92
+ }
@@ -46,18 +46,27 @@ export type ComponentNode = {
46
46
  * subtree — preserving the app's real component identity through
47
47
  * capture → design → emit. Absent ⇒ the node emits as itself.
48
48
  */
49
- $emitAs?: { name: string; importPath: string } | undefined;
49
+ $emitAs?: EmitAsRef | undefined;
50
50
  /**
51
51
  * Repository component identity: this node IS a component the host app
52
52
  * imports (a package export such as Mantine's `Tabs`, or the app's own
53
53
  * `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.
54
+ * prints; the identity decides rendering and imports. Where it sits against
55
+ * the node's other keys is `nodeShape`'s to say (`./node-identity.ts`).
57
56
  */
58
57
  $repo?: RepoComponentRef | undefined;
59
58
  };
60
59
 
60
+ /**
61
+ * A host component a node emits *as*, in place of its own subtree. Superseded
62
+ * in practice by {@link RepoComponentRef}, which renders for real; kept because
63
+ * design folders carry it.
64
+ */
65
+ export type EmitAsRef = {
66
+ name: string;
67
+ importPath: string;
68
+ };
69
+
61
70
  /**
62
71
  * Where a repository component comes from. The import form is kept exactly as
63
72
  * code generation must print it; the resolved file is runtime cache data and
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { isCssIdent } from "./css-sanitize.ts";
2
3
 
3
4
  const NumOrCssLen = z.union([z.number(), z.string().min(1)]);
4
5
 
@@ -56,6 +57,45 @@ export type Colors = z.infer<typeof ColorsSchema>;
56
57
  */
57
58
  export type ColorsOverride = { [K in keyof Colors]?: Colors[K] | undefined };
58
59
 
60
+ /**
61
+ * Spacing names Tailwind v4 already reads as a size scale. Its `max-w-*`,
62
+ * `w-*` and `min-w-*` utilities resolve a `--spacing-<name>` ahead of
63
+ * `--container-<name>`, so a design system's t-shirt spacing (`xl: 1.5rem`)
64
+ * emitted under one of these names turns every `max-w-xl` into 24px and a
65
+ * paragraph into one word per line.
66
+ */
67
+ export const TAILWIND_SIZE_NAMES: ReadonlySet<string> = new Set([
68
+ "3xs",
69
+ "2xs",
70
+ "xs",
71
+ "sm",
72
+ "md",
73
+ "lg",
74
+ "xl",
75
+ "2xl",
76
+ "3xl",
77
+ "4xl",
78
+ "5xl",
79
+ "6xl",
80
+ "7xl",
81
+ "prose",
82
+ ]);
83
+
84
+ /**
85
+ * The named spacing tokens that become `--spacing-<name>` in CSS. Numeric
86
+ * steps are Tailwind's built-in scale and names Tailwind reads as sizes stay
87
+ * in the theme only; the canvas stylesheet and the emitted globals.css both
88
+ * come from this list, so they cannot disagree.
89
+ */
90
+ export function spacingCssTokens(spacing: Theme["spacing"]): Array<[string, string | number]> {
91
+ const out: Array<[string, string | number]> = [];
92
+ for (const [name, value] of Object.entries(spacing ?? {})) {
93
+ if (!Number.isNaN(Number(name)) || !isCssIdent(name) || TAILWIND_SIZE_NAMES.has(name)) continue;
94
+ if (typeof value === "string" || typeof value === "number") out.push([name, value]);
95
+ }
96
+ return out;
97
+ }
98
+
59
99
  /**
60
100
  * The effective colors for a mode: `colors`, with `colorsDark` layered on when
61
101
  * rendering dark.
@@ -91,7 +131,12 @@ export function resolveColors(theme: Theme, dark = false): Colors {
91
131
  * CSS-safe ident so they can't inject into the emitted stylesheet.
92
132
  */
93
133
  const PaletteSchema = z.record(
94
- 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
+ ),
95
140
  z.string().min(1),
96
141
  );
97
142
 
@@ -287,24 +287,31 @@ export function typesetUtilityClasses(): string[] {
287
287
  }
288
288
 
289
289
  /**
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.
290
+ * The scale as Tailwind v3 `theme.extend` maps. `fontSize` carries each role's
291
+ * paired leading and tracking (so `text-h1` alone sets all three), and
292
+ * `lineHeight` / `letterSpacing` make the standalone `leading-<role>` /
293
+ * `tracking-<role>` utilities exist — v3 derives no utility from a fontSize
294
+ * tuple's extras, and `TYPESET_CLASSES` spells all three. Every value is a
295
+ * `var()` into the typeset sheet, so a v3 app re-derives inside a
296
+ * `.typeset-<preset>` region exactly like a v4 one.
294
297
  */
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
- );
298
+ export function typesetV3Theme(): {
299
+ fontSize: Record<string, [string, { lineHeight: string; letterSpacing: string }]>;
300
+ lineHeight: Record<string, string>;
301
+ letterSpacing: Record<string, string>;
302
+ } {
303
+ const fontSize: Record<string, [string, { lineHeight: string; letterSpacing: string }]> = {};
304
+ const lineHeight: Record<string, string> = {};
305
+ const letterSpacing: Record<string, string> = {};
306
+ for (const role of TYPESET_SCALE_NAMES) {
307
+ fontSize[role] = [
308
+ `var(--text-${role})`,
309
+ { lineHeight: `var(--leading-${role})`, letterSpacing: `var(--tracking-${role})` },
310
+ ];
311
+ lineHeight[role] = `var(--leading-${role})`;
312
+ letterSpacing[role] = `var(--tracking-${role})`;
313
+ }
314
+ return { fontSize, lineHeight, letterSpacing };
308
315
  }
309
316
 
310
317
  /**
@@ -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",
@@ -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";