velloo 0.2.0 → 0.3.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 (88) hide show
  1. package/README.md +1 -1
  2. package/canvas/assets/index-CqXW6xA_.js +29 -0
  3. package/canvas/assets/index-gB0StCrx.css +2 -0
  4. package/canvas/assets/lucide-all-C_CNpsMs.js +1 -0
  5. package/canvas/assets/{radix-C3HaOB-d.js → radix-gmpYzWRH.js} +1 -1
  6. package/canvas/index.html +3 -3
  7. package/chunk-0zen7bzb.js +3 -0
  8. package/{chunk-er1fxb7d.js → chunk-1jzhw3p4.js} +1 -1
  9. package/{chunk-j11fyeqh.js → chunk-1n5kmsxb.js} +1 -1
  10. package/{chunk-08sv3k41.js → chunk-2fgfex9b.js} +1 -1
  11. package/{chunk-hcb68zt7.js → chunk-3g3dqej3.js} +1 -1
  12. package/{chunk-vdkrbzh1.js → chunk-3tmsrvaj.js} +1 -1
  13. package/chunk-49cgk5s2.js +2 -0
  14. package/{chunk-ek71z2fv.js → chunk-4nqr089t.js} +1 -1
  15. package/{chunk-sn3x2m0m.js → chunk-52hxv18g.js} +1 -1
  16. package/chunk-5ezzc1h2.js +688 -0
  17. package/{chunk-0fe83yzt.js → chunk-5wv5a52y.js} +1 -1
  18. package/chunk-6nzqhfqq.js +5 -0
  19. package/{chunk-bwnvcncx.js → chunk-6zz9kfbd.js} +1 -1
  20. package/chunk-788t8y5x.js +2 -0
  21. package/{chunk-x4f4k9pp.js → chunk-7bvvbsqy.js} +7 -7
  22. package/{chunk-xt5ayg7g.js → chunk-7k7dpj5y.js} +1 -1
  23. package/{chunk-dy41n60j.js → chunk-7njj1qmq.js} +5 -5
  24. package/{chunk-1h55s5he.js → chunk-9f22qnge.js} +1 -1
  25. package/{chunk-mnapdp1y.js → chunk-9g4tr0r6.js} +1 -1
  26. package/{chunk-vgmr98cv.js → chunk-9nhw49vv.js} +1 -1
  27. package/{chunk-pp86v6m6.js → chunk-as08hzp3.js} +1 -1
  28. package/{chunk-ngnymg2q.js → chunk-bhx8k09w.js} +1 -1
  29. package/{chunk-dgwjpbp9.js → chunk-bpeezw7w.js} +1 -1
  30. package/{chunk-jr0ss0mm.js → chunk-bvmtq0vn.js} +2 -2
  31. package/{chunk-3e8vez4d.js → chunk-c4fym47a.js} +63 -19
  32. package/{chunk-jb21jkp1.js → chunk-c6hywdvq.js} +1 -1
  33. package/{chunk-zb6hc1y0.js → chunk-cxzkkn12.js} +1 -1
  34. package/{chunk-fsbsksga.js → chunk-d9p6s05n.js} +1 -1
  35. package/{chunk-2c7jbx46.js → chunk-dsks52m7.js} +1 -1
  36. package/{chunk-80r93t2e.js → chunk-hfzg98c8.js} +1 -1
  37. package/{chunk-jfx9xjmr.js → chunk-js6mz7h1.js} +1 -1
  38. package/{chunk-7eq7nx37.js → chunk-jx11jvq8.js} +1 -1
  39. package/{chunk-v40fn08f.js → chunk-k6n249vb.js} +2 -2
  40. package/{chunk-4dn75m6t.js → chunk-k7sehkm8.js} +1 -1
  41. package/{chunk-sjgqnykd.js → chunk-k9219kmj.js} +1 -1
  42. package/{chunk-dg6z4vdd.js → chunk-kz4423th.js} +1 -1
  43. package/{chunk-a84n72m2.js → chunk-m3czg1tt.js} +1 -1
  44. package/{chunk-ccszg02q.js → chunk-maz6z7tz.js} +1 -1
  45. package/{chunk-ydhwv29b.js → chunk-n9kcxaj7.js} +2 -2
  46. package/chunk-n9t36h0q.js +2 -0
  47. package/{chunk-mh2pr7cq.js → chunk-nr3ej3qf.js} +1 -1
  48. package/{chunk-40xgcth8.js → chunk-nte0t7v3.js} +2 -2
  49. package/{chunk-ayrb3qnm.js → chunk-nycs0z7h.js} +1 -1
  50. package/{chunk-4fj5tahv.js → chunk-qdnb2t6a.js} +1 -1
  51. package/{chunk-735ezz23.js → chunk-qgwhd4n6.js} +2 -2
  52. package/{chunk-avns4dbf.js → chunk-sn9vg42q.js} +1 -1
  53. package/{chunk-rpwpeph5.js → chunk-t5bb30zy.js} +1 -1
  54. package/{chunk-7eyb8yrf.js → chunk-tbmkq7wy.js} +1 -1
  55. package/{chunk-jv0xggh0.js → chunk-tc4mzzfw.js} +1 -1
  56. package/chunk-td5z2sm4.js +6 -0
  57. package/{chunk-r22epxz2.js → chunk-wa6qamwh.js} +1 -1
  58. package/{chunk-6w6ch4ce.js → chunk-wcqqx6tz.js} +1 -1
  59. package/{chunk-ampmdzz4.js → chunk-wzngjjeb.js} +1 -1
  60. package/chunk-xyfed1j0.js +2 -0
  61. package/{chunk-n9gs6yx9.js → chunk-y5r2w2z3.js} +1 -1
  62. package/{chunk-6nqghjda.js → chunk-yeh9gb5w.js} +1 -1
  63. package/cli.js +1 -1
  64. package/package.json +1 -1
  65. package/pkgs/helpers/src/descriptors.ts +7 -1
  66. package/pkgs/provider-antd/src/index.ts +1 -0
  67. package/pkgs/provider-chakra/src/index.ts +1 -0
  68. package/pkgs/provider-mui/src/index.ts +1 -0
  69. package/pkgs/provider-none/src/index.ts +42 -1
  70. package/pkgs/provider-none/src/intro.ts +2 -2
  71. package/pkgs/schema/src/config.ts +19 -0
  72. package/pkgs/schema/src/extension.ts +26 -0
  73. package/pkgs/schema/src/index.ts +10 -0
  74. package/pkgs/schema/src/node.ts +99 -0
  75. package/pkgs/schema/src/screen.ts +7 -0
  76. package/pkgs/shadcn-snapshot/dist/manifest.json +9 -1
  77. package/skills/velloo-setup/SKILL.md +70 -48
  78. package/canvas/assets/index-BCvDZiBo.css +0 -2
  79. package/canvas/assets/index-ClaBqcaS.js +0 -28
  80. package/canvas/assets/lucide-all-iIF-pmzN.js +0 -1
  81. package/chunk-308ynsxd.js +0 -2
  82. package/chunk-9vg3yafz.js +0 -3
  83. package/chunk-eyr8hrzf.js +0 -2
  84. package/chunk-k17gfwq3.js +0 -2
  85. package/chunk-rgasy862.js +0 -5
  86. package/chunk-t448rrfr.js +0 -2
  87. package/chunk-tdsk5wxa.js +0 -451
  88. package/chunk-vsv8z729.js +0 -6
@@ -47,6 +47,37 @@ export type ComponentNode = {
47
47
  * capture → design → emit. Absent ⇒ the node emits as itself.
48
48
  */
49
49
  $emitAs?: { name: string; importPath: string } | undefined;
50
+ /**
51
+ * Repository component identity: this node IS a component the host app
52
+ * imports (a package export such as Mantine's `Tabs`, or the app's own
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.
57
+ */
58
+ $repo?: RepoComponentRef | undefined;
59
+ };
60
+
61
+ /**
62
+ * Where a repository component comes from. The import form is kept exactly as
63
+ * code generation must print it; the resolved file is runtime cache data and
64
+ * never persisted.
65
+ */
66
+ export type RepoComponentRef = {
67
+ /**
68
+ * Module specifier. Bare (`@mantine/core`) and aliased (`@/components/card`)
69
+ * specifiers are kept as the app writes them; a `./`-relative one is relative
70
+ * to the host app root, since the importing file varies per call site.
71
+ */
72
+ importPath: string;
73
+ /** The export binding, or `"default"` for a default export. */
74
+ exportName: string;
75
+ /** Static member path of a compound part: `"List"` for `Tabs.List`. */
76
+ member?: string | undefined;
77
+ /** `config.hostApps` key in a monorepo; absent ⇒ the default host app. */
78
+ app?: string | undefined;
79
+ /** Snippet id drawn in the component's place when it can't render for real. */
80
+ proxy?: string | undefined;
50
81
  };
51
82
 
52
83
  export type SnippetInstance = {
@@ -126,6 +157,42 @@ const EmitAsSchema = z.object({
126
157
  importPath: z.string().min(1),
127
158
  });
128
159
 
160
+ /**
161
+ * A specifier codegen may print verbatim inside `import … from "…"` and the
162
+ * bundler may resolve against the host app. Conservative charset (no quotes,
163
+ * spaces, semicolons), and no absolute or `..` paths: a design folder is data a
164
+ * cloned repo supplies, and it must not be able to reach outside the app.
165
+ */
166
+ export function repoImportIssue(importPath: string): string | null {
167
+ if (!/^[\w@./~-]+$/.test(importPath)) return "has characters an import specifier can't carry";
168
+ if (importPath.startsWith("/")) return "must not be an absolute path";
169
+ if (importPath.split("/").includes("..")) return "must not climb out of the host app with ..";
170
+ return null;
171
+ }
172
+
173
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
174
+
175
+ export const RepoComponentRefSchema = z.object({
176
+ importPath: z
177
+ .string()
178
+ .min(1)
179
+ .superRefine((value, ctx) => {
180
+ const issue = repoImportIssue(value);
181
+ if (issue) ctx.addIssue({ code: "custom", message: `importPath ${issue}` });
182
+ }),
183
+ exportName: z
184
+ .string()
185
+ .regex(IDENTIFIER, { message: 'exportName must be an identifier or "default"' }),
186
+ member: z
187
+ .string()
188
+ .regex(/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/, {
189
+ message: 'member must be a dotted identifier path like "List"',
190
+ })
191
+ .optional(),
192
+ app: z.string().min(1).optional(),
193
+ proxy: z.string().min(1).optional(),
194
+ });
195
+
129
196
  const ComponentNodeSchema: z.ZodType<ComponentNode> = z.lazy(() =>
130
197
  z.object({
131
198
  $ref: z.string().min(1),
@@ -136,9 +203,41 @@ const ComponentNodeSchema: z.ZodType<ComponentNode> = z.lazy(() =>
136
203
  .transform((items) => items.map(wrapScalarChild))
137
204
  .optional(),
138
205
  $emitAs: EmitAsSchema.optional(),
206
+ $repo: RepoComponentRefSchema.optional(),
139
207
  }),
140
208
  );
141
209
 
210
+ /** True when the node is a repository component (see ComponentNode.$repo). */
211
+ export function isRepoNode(n: Node): n is ComponentNode & { $repo: RepoComponentRef } {
212
+ return isComponentNode(n) && n.$repo !== undefined;
213
+ }
214
+
215
+ /**
216
+ * The runtime key a repository component registers under — identity only, so
217
+ * two apps' `Button`s (or Mantine's vs the provider's) never collide.
218
+ */
219
+ export function repoKey(ref: RepoComponentRef): string {
220
+ const member = ref.member ? `.${ref.member}` : "";
221
+ return `repo:${encodeURIComponent(ref.app ?? "")}:${ref.importPath}#${ref.exportName}${member}`;
222
+ }
223
+
224
+ /**
225
+ * Invert {@link repoKey}. A bundle URL carries only keys, so the runtime can
226
+ * resolve a screen's components from identity alone — no catalog lookup, which
227
+ * keeps a design renderable while discovery is cold or the app has moved on.
228
+ */
229
+ export function parseRepoKey(key: string): RepoComponentRef | null {
230
+ const match = /^repo:([^:]*):([^#]+)#([A-Za-z_$][\w$]*)(?:\.(.+))?$/.exec(key);
231
+ if (!match) return null;
232
+ const parsed = RepoComponentRefSchema.safeParse({
233
+ importPath: match[2],
234
+ exportName: match[3],
235
+ ...(match[4] ? { member: match[4] } : {}),
236
+ ...(match[1] ? { app: decodeURIComponent(match[1]) } : {}),
237
+ });
238
+ return parsed.success ? parsed.data : null;
239
+ }
240
+
142
241
  const SnippetInstanceSchema: z.ZodType<SnippetInstance> = z.object({
143
242
  $snippet: z.string().min(1),
144
243
  $id: NodeIdSchema.optional(),
@@ -24,6 +24,13 @@ export const ScreenSchema = z.object({
24
24
  * components and extensions resolve against this library's registry.
25
25
  */
26
26
  library: z.string().min(1).optional(),
27
+ /**
28
+ * The app route this screen stands for ("/", "/settings/account"). Set by
29
+ * the route scan `init` runs, and editable per screen. The canvas mount
30
+ * hands it to the host framework's router contexts, so a repository nav
31
+ * lights the item the real page would.
32
+ */
33
+ route: z.string().min(1).optional(),
27
34
  tree: NodeSchema,
28
35
  });
29
36
 
@@ -460,7 +460,15 @@
460
460
  "group": "layout",
461
461
  "family": "Box",
462
462
  "source": "velloo",
463
- "props": [],
463
+ "props": [
464
+ {
465
+ "name": "as",
466
+ "type": "string",
467
+ "optional": true,
468
+ "control": "string"
469
+ }
470
+ ],
471
+ "designModeNotes": "A plain div by default; `as` renders a lowercase HTML tag instead — \"span\" for an inline run inside a sentence, \"ul\"/\"li\", \"section\". Anything else falls back to div.",
464
472
  "example": {
465
473
  "className": "flex flex-col gap-6 px-8 py-12"
466
474
  }
@@ -11,18 +11,41 @@ description: >-
11
11
 
12
12
  # Calibrating Velloo to an existing app
13
13
 
14
- For shadcn folders, the canvas can import client-safe components directly from
15
- the app's configured `components/ui` directory. That is a bounded capability,
16
- not a promise that arbitrary application React can run inside a design iframe:
17
- portal/state-heavy families use named canvas-safe adaptations, and a missing or
18
- unbuildable file falls back independently. Other providers follow their own
19
- adapter contract. The fidelity is observable through `component_status`; never
20
- infer it from `installedInApp` or from a component merely appearing on screen.
14
+ The canvas renders the app's own components — whatever library they come from
15
+ (Mantine, a private design system, a hand-rolled `components/` directory) —
16
+ as real, nested, editable nodes. `list_components` lists them on **Repo**
17
+ shelves, found from what the app's routes actually render. For shadcn folders
18
+ the configured `components/ui` files additionally back the library itself.
19
+ None of that is a promise that arbitrary application React runs inside a
20
+ design iframe: the components need the context the app gives them (providers,
21
+ global CSS), portal-heavy families use adaptations, and a component that can't
22
+ build or render falls back on its own. Fidelity is observable through
23
+ `component_status`; never infer it from a component merely appearing on screen.
21
24
 
22
25
  Your job here is to close that gap **before** you start designing, and to say
23
26
  honestly what's left open. Do this once per design folder. Skip it only for a
24
27
  greenfield folder with no app to match.
25
28
 
29
+ ## 0. The preview entry — when the app has components
30
+
31
+ Call `preview_status` first. It reports `state` (`absent` / `valid` /
32
+ `failing`), the providers and stylesheets the app's own entry uses
33
+ (`appWrappers`, `appStylesheets`), and mounts one real component to prove the
34
+ setup works — a missing provider or an unstyled render shows up here, not
35
+ halfway through a design.
36
+
37
+ - **`valid` with a recipe** (Mantine and friends): a built-in wrapper is doing
38
+ the work, themed from Velloo's tokens. Good enough to design with; write your
39
+ own entry only when the app's theme, router or data providers matter.
40
+ - **`absent` / `failing`**: adapt `suggestedPreviewEntry` — it is lifted from
41
+ the app's entry — and call `set_preview_entry { source }`. Keep the app's
42
+ global CSS imports, swap network-backed providers for fixtures (a
43
+ `QueryClient` with seeded data, a memory router), never pass credentials. The
44
+ tool re-probes and answers with the new state; iterate until `valid`.
45
+
46
+ A snippet can't do this job: it is node data, so it can't import CSS, build a
47
+ router or wrap the whole screen. Snippets are for step 4 below.
48
+
26
49
  ## 1. Theme first — it buys the most
27
50
 
28
51
  `import_theme { cssPath, apply: true }` against the app's stylesheet. This is
@@ -78,26 +101,28 @@ live data legitimately differ.
78
101
 
79
102
  Only after theme parity, call `component_status { screen: "<id>" }` for each
80
103
  screen you're about to design or verify (or `{ ids: [...] }` before a screen
81
- exists). Check `mounted` first: the mount is all-or-nothing, so a single
82
- `unavailable` component keeps the whole screen — and every screenshot and
83
- `compare_to_url` of it — on Velloo's bundled components, whatever the other
84
- statuses say. Fix or replace the blocking component before trusting any
85
- `exact`. Treat the statuses as part of the design brief:
86
-
87
- - **`exact`** — Velloo compile-checked and selected the app's source file for
88
- the whole-screen canvas mount. Custom CVA variants and ordinary explicit
89
- props are also reflected into discovery when their syntax is recognizable.
90
- - **`adapted`** — the real family depends on portals, runtime state, browser
91
- layout, or another interaction that conflicts with a static selectable
92
- canvas. Velloo deliberately renders a canvas-safe counterpart. Preserve the
93
- component identity and props, but do not claim pixel-identical behavior.
94
- - **`fallback`** — the app file is absent or failed the browser preflight, so a
95
- bundled provider component or Velloo helper is rendering. Read the returned
96
- note/errors before deciding whether the visual difference matters.
97
- - **`unavailable`** — there is no usable canvas source, so a screen using it
98
- does not mount at all (`mounted: false`); read its errors — a resolution
99
- failure usually means the recorded app root is wrong
100
- (`velloo design set-app-root`).
104
+ exists — repo catalog ids work too). For library components, check `mounted`
105
+ first: that mount is all-or-nothing, so a single `unavailable` library
106
+ component keeps the whole screen on Velloo's bundled components. A screen with
107
+ the app's own components always mounts, and each of those falls back alone.
108
+ Treat the statuses as part of the design brief:
109
+
110
+ - **`exact`** — the app's own source (or package export) renders in the canvas
111
+ mount. Custom CVA variants and ordinary explicit props are also reflected
112
+ into discovery when their syntax is recognizable.
113
+ - **`adapted`** — the real component renders through a design-time wrapper
114
+ (an overlay kept inside the frame, focus trapping off). Preserve its identity
115
+ and props, but do not claim pixel-identical behavior.
116
+ - **`unstyled`** — it rendered, but its stylesheet never loaded: import the
117
+ library's CSS in the preview entry.
118
+ - **`fallback`** — a bundled provider component or Velloo helper is rendering
119
+ instead of the app's file. Read the returned note/errors.
120
+ - **`proxy`** — the app's component can't render here, and the node's proxy
121
+ snippet stands in for it; emitted code still imports the real one.
122
+ - **`unavailable`** — nothing renders it; read its `code` and `remedy`
123
+ (`missing-provider`, `resolve-failed`, `compile-failed`, `server-only`,
124
+ `render-threw`, `missing-export`). A resolution failure usually means the
125
+ recorded app root is wrong (`velloo design set-app-root`).
101
126
 
102
127
  Host-source edits invalidate the canvas bundle automatically. After changing a
103
128
  component, wait for the frame to reload and call `component_status` again; do
@@ -106,25 +131,22 @@ not restart the daemon merely to pick up a normal source edit.
106
131
  ## 4. Close only the important remaining gaps
107
132
 
108
133
  If an on-screen component is not `exact` and the difference is load-bearing,
109
- read its source and choose the smallest honest adaptation:
110
-
111
- - **A snippet** (`add_snippet`) that composes library primitives to match the
112
- component's appearance. It stays editable on the canvas and is the preferred
113
- answer for a bespoke compound component that is outside the shadcn library.
114
- Library compound components whose files are `exact` already preserve their
115
- children in the whole-screen mount and do not need a snippet. `render_snippet`
116
- immediately after defining one.
117
- - **`$emitAs { name, importPath }`** on the node when the preview can be an
118
- approximation but the generated code must import the real component. Design
119
- with primitives, emit `<DataTable />`. Use this for anything whose appearance
120
- you cannot reasonably rebuild but whose identity in the code matters.
121
- - **A live extension** (`add_extension` with `render: "live"`) only for the
122
- genuinely dynamic minority — charts above all. It bundles the real host file
123
- and client-mounts it, so it is the one path that renders the user's actual
124
- code. Know its limits before reaching for it: children are stripped, the
125
- mount is visual-only, and it breaks whenever the host file doesn't compile.
126
- Never the default for library components, and never use it to work around a
127
- compound component with children.
134
+ choose the smallest honest adaptation:
135
+
136
+ - **Fix the context** first: most `unavailable` app components are missing a
137
+ provider or fixture the preview entry can supply.
138
+ - **A proxy snippet** for a component that genuinely can't render in a static
139
+ canvas (it needs live data, a server, a browser API). `add_snippet` composing
140
+ primitives to match its appearance, then point the node at it — `update_props`
141
+ can't set identity, so place it with `add_node { repo: { importPath,
142
+ exportName }, … }` and a `proxy`, or record `"proxy": "<snippet-id>"` for it in
143
+ the design folder's `repo-components.json`. The snippet draws on the canvas;
144
+ `emit_code` still imports the real component with the design's props.
145
+ - **A snippet** (`add_snippet`) for a composition that isn't a component in the
146
+ app at all. `render_snippet` immediately after defining one.
147
+ - **A live extension** (`add_extension` with `render: "live"`) only for a
148
+ dynamic leaf the preview entry can't make render as a normal node. Its
149
+ children are stripped and the mount is visual-only.
128
150
 
129
151
  Do not replace an `adapted` overlay merely because its status is not `exact`;
130
152
  the adaptation is what keeps dialogs, menus, popovers, and similar components
@@ -138,8 +160,8 @@ session — and the user — knows where things stand. Three headings, honest:
138
160
 
139
161
  - **Exact** — theme imported from `<path>`, fonts, and the components reported
140
162
  `exact` by `component_status`.
141
- - **Adapted / fallback** — the status, reason, and any snippet / `$emitAs` / live
142
- decision made to close a load-bearing difference.
163
+ - **Adapted / fallback / proxy** — the status, reason, and any preview-entry,
164
+ proxy-snippet or live decision made to close a load-bearing difference.
143
165
  - **Unverified** — screens behind auth you couldn't reach, pages whose dev
144
166
  server wouldn't start, tokens the stylesheet didn't declare.
145
167