@dowel-ui/registry 0.5.0 → 0.8.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.
@@ -0,0 +1,71 @@
1
+ import { a as RegistryIndex, o as RegistryIndexEntry } from "./schema-BmYeKe9e.js";
2
+ //#region src/generate.d.ts
3
+ interface PlanEntry {
4
+ entry: RegistryIndexEntry;
5
+ /** Why this was chosen, in words, so a wrong pick is arguable. */
6
+ because: string;
7
+ }
8
+ interface UiPlan {
9
+ /** The prompt, as given. */
10
+ prompt: string;
11
+ /** Whole sections, which bring their own components. */
12
+ blocks: PlanEntry[];
13
+ /** Individual components, none of which a chosen block already installs. */
14
+ components: PlanEntry[];
15
+ /** Everything to install, in one list. */
16
+ install: string[];
17
+ /** True when nothing matched, so callers can say so rather than emit nothing. */
18
+ empty: boolean;
19
+ }
20
+ interface PlanOptions {
21
+ /** How many components to suggest beyond the blocks. */
22
+ maxComponents?: number;
23
+ /** How many blocks to suggest. */
24
+ maxBlocks?: number;
25
+ }
26
+ declare function planUi(prompt: string, index: RegistryIndex, options?: PlanOptions): UiPlan;
27
+ interface RenderOptions {
28
+ /** Where components are imported from in the target project. */
29
+ importFrom?: string;
30
+ /**
31
+ * Where blocks are imported from.
32
+ *
33
+ * Separate because blocks are not exported from the component package at
34
+ * all — they are only ever installed as source — so deriving their path from
35
+ * a package specifier produces an import that does not exist. Derived from
36
+ * `importFrom` when it is a project alias, and otherwise the conventional
37
+ * install location.
38
+ */
39
+ blocksImportFrom?: string;
40
+ cliPackage?: string;
41
+ docsUrl?: string;
42
+ }
43
+ /**
44
+ * Where an installed block lives.
45
+ *
46
+ * A project alias like `@/components/ui` has a sibling `@/components/blocks`. A
47
+ * bare package specifier has no block path at all, so the conventional install
48
+ * location is used instead of inventing one under the package.
49
+ */
50
+ declare function blocksPathFor(importFrom: string): string;
51
+ /**
52
+ * The plan as a starting file.
53
+ *
54
+ * Imports and composition only. Every element carries the page its props are
55
+ * documented on, because the registry does not publish prop shapes and a
56
+ * plausible invented prop is worse than an obvious gap — one is a TODO, the
57
+ * other is a bug wearing the costume of working code.
58
+ */
59
+ declare function renderPlan(plan: UiPlan, options?: RenderOptions): string;
60
+ /**
61
+ * The plan as a brief for a coding agent.
62
+ *
63
+ * The most useful thing this can produce. The agent has the project, the
64
+ * editor and the ability to write the props; what it lacks is the knowledge
65
+ * that these components exist and that it must not invent others. That is
66
+ * exactly what a grounded plan supplies.
67
+ */
68
+ declare function renderBrief(plan: UiPlan, options?: RenderOptions): string;
69
+ //#endregion
70
+ export { PlanEntry, PlanOptions, type RegistryIndex, type RegistryIndexEntry, RenderOptions, UiPlan, blocksPathFor, planUi, renderBrief, renderPlan };
71
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","names":[],"sources":["../src/generate.ts"],"mappings":";;UAsQiB;EACf,OAAO;;EAEP;;UAGe;;EAEf;;EAEA,QAAQ;;EAER,YAAY;;EAEZ;;EAEA;;UA8Ge;;EAEf;;EAEA;;iBAGc,OACd,gBACA,OAAO,eACP,UAAS,cACR;UAkEc;;EAEf;;;;;;;;;;EAUA;EACA;EACA;;;;;;;;;iBAUc,cAAc;;;;;;;;;iBAgBd,WAAW,MAAM,QAAQ,UAAS;;;;;;;;;iBAuDlC,YAAY,MAAM,QAAQ,UAAS"}
@@ -0,0 +1,550 @@
1
+ //#region src/generate.ts
2
+ /**
3
+ * Turning a description of a screen into a plan made of components that exist.
4
+ *
5
+ * The hard part of generating UI is not writing JSX. It is not inventing: a
6
+ * model asked for a billing page will cheerfully produce `<PricingTable>` and
7
+ * `<InvoiceList>` and a `variant="subtle"` that was never implemented, and the
8
+ * result reads perfectly and compiles nowhere.
9
+ *
10
+ * So this resolves a prompt against the registry first, and everything it
11
+ * emits afterwards is drawn from what came back. It cannot name a component
12
+ * that is not installable, because it only ever repeats names the registry
13
+ * gave it.
14
+ *
15
+ * It does not guess at props. The registry publishes what a component *is* and
16
+ * what it depends on, not the shape of its arguments, so the output stops at
17
+ * the composition and points at the page where the props are documented.
18
+ * Emitting a plausible prop is worse than emitting none — one is a gap, the
19
+ * other is a bug that looks like working code.
20
+ */
21
+ /** Words that carry no signal about which component is wanted. */
22
+ const STOPWORDS = /* @__PURE__ */ new Set([
23
+ "a",
24
+ "an",
25
+ "the",
26
+ "and",
27
+ "or",
28
+ "of",
29
+ "for",
30
+ "with",
31
+ "to",
32
+ "in",
33
+ "on",
34
+ "at",
35
+ "by",
36
+ "is",
37
+ "are",
38
+ "be",
39
+ "make",
40
+ "build",
41
+ "create",
42
+ "want",
43
+ "need",
44
+ "add",
45
+ "show",
46
+ "page",
47
+ "screen",
48
+ "app",
49
+ "application",
50
+ "ui",
51
+ "interface",
52
+ "component",
53
+ "components",
54
+ "me",
55
+ "my",
56
+ "our",
57
+ "i",
58
+ "it",
59
+ "that",
60
+ "this",
61
+ "some",
62
+ "using",
63
+ "use",
64
+ "like"
65
+ ]);
66
+ /**
67
+ * Words a reader would use that are not the words the registry uses.
68
+ *
69
+ * Hand-written, and deliberately so. "Sign in" is what someone types and
70
+ * `login` is what the item is called; no amount of string similarity bridges
71
+ * that, and pretending otherwise produces a matcher that works on the examples
72
+ * it was tuned against and nothing else.
73
+ */
74
+ const SYNONYMS = {
75
+ "sign in": ["login"],
76
+ signin: ["login"],
77
+ "log in": ["login"],
78
+ "sign up": ["signup"],
79
+ register: ["signup"],
80
+ registration: ["signup"],
81
+ "forgot password": ["forgot-password"],
82
+ "reset password": ["forgot-password"],
83
+ chat: [
84
+ "ai-chat",
85
+ "ai-conversation",
86
+ "ai-prompt-input"
87
+ ],
88
+ conversation: ["ai-chat"],
89
+ assistant: ["ai-chat"],
90
+ copilot: ["ai-chat"],
91
+ agent: [
92
+ "agent-console",
93
+ "ai-agent-status",
94
+ "ai-agent-plan"
95
+ ],
96
+ agents: ["agent-console"],
97
+ tool: ["ai-tool"],
98
+ approval: ["ai-approval-request"],
99
+ approve: ["ai-approval-request"],
100
+ undo: ["ai-action-ledger"],
101
+ audit: ["ai-action-ledger", "activity-feed"],
102
+ tokens: ["ai-token-usage"],
103
+ spend: ["ai-dashboard", "billing"],
104
+ cost: ["ai-dashboard", "billing"],
105
+ usage: ["ai-dashboard", "billing"],
106
+ subscription: ["billing", "pricing"],
107
+ invoice: ["billing"],
108
+ invoices: ["billing"],
109
+ payment: ["billing"],
110
+ plan: ["pricing", "billing"],
111
+ plans: [
112
+ "pricing",
113
+ "pricing-three-tier",
114
+ "pricing-two-tier",
115
+ "pricing-single-plan"
116
+ ],
117
+ metrics: [
118
+ "analytics",
119
+ "dashboard",
120
+ "metric-delta"
121
+ ],
122
+ chart: [
123
+ "analytics",
124
+ "dither-bar",
125
+ "dither-line",
126
+ "dither-area",
127
+ "dither-donut"
128
+ ],
129
+ charts: [
130
+ "analytics",
131
+ "dither-canvas",
132
+ "dither-bar",
133
+ "dither-line",
134
+ "dither-area",
135
+ "dither-donut",
136
+ "dither-heatmap"
137
+ ],
138
+ graph: [
139
+ "analytics",
140
+ "dither-line",
141
+ "dither-area",
142
+ "contribution-graph"
143
+ ],
144
+ stats: [
145
+ "dashboard",
146
+ "analytics",
147
+ "stats-grid",
148
+ "stats-trend-cards"
149
+ ],
150
+ overview: ["dashboard"],
151
+ grid: ["data-table", "table"],
152
+ spreadsheet: ["data-table"],
153
+ list: ["table", "data-table"],
154
+ search: [
155
+ "command",
156
+ "combobox",
157
+ "expanding-search",
158
+ "faq-searchable"
159
+ ],
160
+ palette: ["command", "palette-generator"],
161
+ shortcut: ["command", "shortcut-recorder"],
162
+ modal: ["dialog"],
163
+ popup: ["dialog", "popover"],
164
+ dropdown: ["dropdown-menu", "select"],
165
+ toast: ["toast"],
166
+ notification: [
167
+ "toast",
168
+ "activity-feed",
169
+ "notification-badge",
170
+ "notify-button"
171
+ ],
172
+ notifications: ["toast", "settings"],
173
+ upload: ["file-upload"],
174
+ file: ["file-upload"],
175
+ date: ["date-picker", "calendar"],
176
+ time: ["time-range-picker"],
177
+ schedule: ["cron-editor"],
178
+ cron: ["cron-editor"],
179
+ team: [
180
+ "admin-users",
181
+ "settings",
182
+ "team-grid",
183
+ "team-carousel"
184
+ ],
185
+ members: ["admin-users"],
186
+ users: ["admin-users"],
187
+ admin: ["admin-users"],
188
+ permissions: ["permission-matrix"],
189
+ roles: ["permission-matrix"],
190
+ profile: ["settings"],
191
+ preferences: ["settings"],
192
+ account: ["settings", "billing"],
193
+ setup: ["onboarding"],
194
+ checklist: [
195
+ "onboarding",
196
+ "animated-checklist",
197
+ "todo-tower"
198
+ ],
199
+ wizard: ["onboarding", "stepper"],
200
+ logs: ["log-viewer"],
201
+ log: ["log-viewer"],
202
+ diff: ["diff-viewer", "record-diff"],
203
+ secret: ["secret-field"],
204
+ "api key": ["secret-field"],
205
+ key: ["secret-field"],
206
+ dns: ["dns-record"],
207
+ loader: [
208
+ "spinner",
209
+ "dots-loader",
210
+ "ring-loader",
211
+ "bar-loader",
212
+ "shape-loader",
213
+ "text-loader",
214
+ "grid-loader",
215
+ "ai-loader"
216
+ ],
217
+ loaders: [
218
+ "dots-loader",
219
+ "ring-loader",
220
+ "bar-loader",
221
+ "shape-loader",
222
+ "text-loader",
223
+ "grid-loader"
224
+ ],
225
+ loading: [
226
+ "spinner",
227
+ "dots-loader",
228
+ "ring-loader",
229
+ "bar-loader",
230
+ "skeleton",
231
+ "text-loader"
232
+ ],
233
+ spinner: ["spinner", "ring-loader"],
234
+ animation: [
235
+ "text-effect",
236
+ "text-swap",
237
+ "shader-transition"
238
+ ],
239
+ animated: [
240
+ "text-effect",
241
+ "morph-button",
242
+ "effect-button",
243
+ "number-flow"
244
+ ],
245
+ "text animation": [
246
+ "text-effect",
247
+ "text-swap",
248
+ "shimmer-text",
249
+ "scramble-text",
250
+ "typewriter-text"
251
+ ],
252
+ typewriter: ["typewriter-text"],
253
+ shimmer: ["shimmer-text", "skeleton"],
254
+ counter: ["number-flow", "drag-stepper"],
255
+ price: ["number-flow", "pricing"],
256
+ carousel: [
257
+ "carousel-3d",
258
+ "swipe-carousel",
259
+ "reviews-carousel",
260
+ "invite-carousel",
261
+ "time-stack"
262
+ ],
263
+ slider: [
264
+ "slider",
265
+ "exposure-slider",
266
+ "slosh-slider",
267
+ "scrubber",
268
+ "range-dial"
269
+ ],
270
+ slideshow: [
271
+ "carousel-3d",
272
+ "swipe-carousel",
273
+ "photo-stack"
274
+ ],
275
+ marquee: ["marquee", "logo-marquee"],
276
+ ticker: ["marquee"],
277
+ cards: [
278
+ "card",
279
+ "card-spread",
280
+ "card-stack",
281
+ "glow-card",
282
+ "tilt-card",
283
+ "expandable-cards"
284
+ ],
285
+ hero: [
286
+ "hero-grid",
287
+ "hero-product",
288
+ "hero-split-image",
289
+ "hero-perspective-grid",
290
+ "hero-spotlight",
291
+ "hero-minimal"
292
+ ],
293
+ landing: [
294
+ "hero-grid",
295
+ "features-bento",
296
+ "cta-centered",
297
+ "footer-newsletter"
298
+ ],
299
+ cta: [
300
+ "cta-centered",
301
+ "cta-split-image",
302
+ "cta-banner"
303
+ ],
304
+ "call to action": [
305
+ "cta-centered",
306
+ "cta-split-image",
307
+ "cta-banner"
308
+ ],
309
+ features: [
310
+ "features-icon-grid",
311
+ "features-bento",
312
+ "features-alternating"
313
+ ],
314
+ logos: [
315
+ "logo-cloud-simple",
316
+ "logo-marquee",
317
+ "logo-links-marquee",
318
+ "logo-grid-tooltips"
319
+ ],
320
+ testimonials: [
321
+ "testimonial-rotator",
322
+ "testimonial-spotlight",
323
+ "testimonial-star-grid",
324
+ "reviews-carousel"
325
+ ],
326
+ reviews: ["reviews-carousel", "testimonial-star-grid"],
327
+ faq: [
328
+ "faq-accordion",
329
+ "faq-searchable",
330
+ "faq-categorized",
331
+ "faq-tabbed-grid"
332
+ ],
333
+ footer: [
334
+ "footer-simple",
335
+ "footer-newsletter",
336
+ "footer-mega",
337
+ "footer-minimal"
338
+ ],
339
+ newsletter: ["footer-newsletter", "footer-mega"],
340
+ heatmap: ["dither-heatmap", "contribution-graph"],
341
+ donut: ["dither-donut"],
342
+ pie: ["dither-donut"],
343
+ gauge: ["dither-gauge", "meter"],
344
+ funnel: ["dither-funnel"],
345
+ uptime: ["uptime-matrix"],
346
+ "status page": ["uptime-matrix"],
347
+ otp: ["otp-input"],
348
+ "one-time code": ["otp-input"],
349
+ "verification code": ["otp-input"],
350
+ steps: ["stepper"],
351
+ "context menu": ["context-menu"],
352
+ "right click": ["context-menu"],
353
+ reorder: ["reorder-list", "browser-tabs"],
354
+ sortable: ["reorder-list"],
355
+ drag: [
356
+ "reorder-list",
357
+ "goo-ball",
358
+ "swipe-carousel"
359
+ ],
360
+ dock: ["magnify-dock"],
361
+ toolbar: ["canvas-toolbar", "magnify-dock"],
362
+ confirm: [
363
+ "slide-to-confirm",
364
+ "inline-confirm",
365
+ "confirm-typed"
366
+ ],
367
+ delete: ["inline-confirm", "confirm-typed"],
368
+ toggle: ["switch", "liquid-toggle"],
369
+ copy: ["copy-button"],
370
+ clipboard: ["copy-button"],
371
+ refresh: ["pull-to-refresh"],
372
+ music: ["now-playing"],
373
+ player: ["now-playing"],
374
+ orb: ["gradient-orb", "orb-face"],
375
+ transition: ["shader-transition", "text-swap"],
376
+ badge: ["badge", "notification-badge"],
377
+ avatar: [
378
+ "avatar",
379
+ "avatar-group",
380
+ "pixel-avatar"
381
+ ],
382
+ tweet: ["tweet-card"],
383
+ dial: ["dial", "range-dial"],
384
+ knob: ["dial"]
385
+ };
386
+ function normalise(prompt) {
387
+ return prompt.toLowerCase().replace(/[^a-z0-9\s-]/g, " ");
388
+ }
389
+ function words(prompt) {
390
+ return normalise(prompt).split(/\s+/).filter((word) => word.length > 2 && !STOPWORDS.has(word));
391
+ }
392
+ function synonymHits(prompt) {
393
+ const text = normalise(prompt);
394
+ const hits = /* @__PURE__ */ new Map();
395
+ const consumed = /* @__PURE__ */ new Set();
396
+ for (const [phrase, names] of Object.entries(SYNONYMS)) {
397
+ if (!new RegExp(`(^|\\s)${phrase.replace(/\s+/g, "\\s+")}(\\s|$)`).test(text)) continue;
398
+ for (const word of phrase.split(/\s+/)) consumed.add(word);
399
+ for (const name of names) if (!hits.has(name)) hits.set(name, phrase);
400
+ }
401
+ return {
402
+ hits,
403
+ consumed
404
+ };
405
+ }
406
+ /** Below this a match is coincidence rather than intent. */
407
+ const MINIMUM_SCORE = 20;
408
+ function scoreEntry(entry, terms, synonyms) {
409
+ const name = entry.name.toLowerCase();
410
+ const title = entry.title.toLowerCase();
411
+ const description = entry.description.toLowerCase();
412
+ let score = 0;
413
+ const reasons = [];
414
+ const synonym = synonyms.get(entry.name);
415
+ if (synonym) {
416
+ score += 60;
417
+ reasons.push(`"${synonym}"`);
418
+ }
419
+ for (const term of terms) if (name === term) {
420
+ score += 50;
421
+ reasons.push(`named "${term}"`);
422
+ } else if (name.split("-").includes(term)) {
423
+ score += 30;
424
+ reasons.push(`"${term}" in its name`);
425
+ } else if (title.includes(term)) {
426
+ score += 20;
427
+ reasons.push(`"${term}" in its title`);
428
+ } else if (entry.category === term) {
429
+ score += 12;
430
+ reasons.push(`the ${term} category`);
431
+ } else if (description.includes(term)) {
432
+ score += 8;
433
+ reasons.push(`"${term}" in its description`);
434
+ }
435
+ if (score === 0) return void 0;
436
+ const rank = score + (entry.type === "registry:block" ? 25 : 0);
437
+ return {
438
+ entry,
439
+ score,
440
+ rank,
441
+ because: [...new Set(reasons)].slice(0, 3).join(", ")
442
+ };
443
+ }
444
+ function planUi(prompt, index, options = {}) {
445
+ const { maxBlocks = 3, maxComponents = 6 } = options;
446
+ const { hits: synonyms, consumed } = synonymHits(prompt);
447
+ const terms = words(prompt).filter((term) => !consumed.has(term));
448
+ const scored = index.items.filter((entry) => entry.type === "registry:ui" || entry.type === "registry:block").map((entry) => scoreEntry(entry, terms, synonyms)).filter((candidate) => candidate !== void 0).filter((candidate) => candidate.score >= MINIMUM_SCORE).sort((a, b) => b.rank - a.rank || a.entry.name.localeCompare(b.entry.name));
449
+ const blocks = scored.filter((candidate) => candidate.entry.type === "registry:block").slice(0, maxBlocks);
450
+ const covered = new Set(blocks.flatMap((candidate) => [candidate.entry.name, ...candidate.entry.registryDependencies]));
451
+ const components = scored.filter((candidate) => candidate.entry.type === "registry:ui" && !covered.has(candidate.entry.name)).slice(0, maxComponents);
452
+ const toEntry = (candidate) => ({
453
+ entry: candidate.entry,
454
+ because: candidate.because
455
+ });
456
+ return {
457
+ prompt,
458
+ blocks: blocks.map(toEntry),
459
+ components: components.map(toEntry),
460
+ install: [...blocks, ...components].map((candidate) => candidate.entry.name),
461
+ empty: blocks.length === 0 && components.length === 0
462
+ };
463
+ }
464
+ /** PascalCase export name for a registry name, e.g. "ai-tool" -> "AiTool". */
465
+ function tag(name) {
466
+ return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
467
+ }
468
+ /** Blocks export a `…Block` component; components export their own name. */
469
+ function componentName(entry) {
470
+ const base = tag(entry.name);
471
+ return entry.type === "registry:block" ? `${base}Block` : base;
472
+ }
473
+ /**
474
+ * Where an installed block lives.
475
+ *
476
+ * A project alias like `@/components/ui` has a sibling `@/components/blocks`. A
477
+ * bare package specifier has no block path at all, so the conventional install
478
+ * location is used instead of inventing one under the package.
479
+ */
480
+ function blocksPathFor(importFrom) {
481
+ if (importFrom.endsWith("/ui")) return `${importFrom.slice(0, -3)}/blocks`;
482
+ if (importFrom.startsWith("@/") || importFrom.startsWith("~/")) return `${importFrom}/blocks`;
483
+ return "@/components/blocks";
484
+ }
485
+ /**
486
+ * The plan as a starting file.
487
+ *
488
+ * Imports and composition only. Every element carries the page its props are
489
+ * documented on, because the registry does not publish prop shapes and a
490
+ * plausible invented prop is worse than an obvious gap — one is a TODO, the
491
+ * other is a bug wearing the costume of working code.
492
+ */
493
+ function renderPlan(plan, options = {}) {
494
+ const { importFrom = "@/components/ui", docsUrl = "https://dowel-eight.vercel.app", blocksImportFrom = blocksPathFor(importFrom) } = options;
495
+ if (plan.empty) return `// Nothing in the registry matched "${plan.prompt}".\n`;
496
+ const chosen = [...plan.blocks, ...plan.components];
497
+ const imports = chosen.map((item) => {
498
+ const from = item.entry.type === "registry:block" ? `${blocksImportFrom}/${item.entry.name}` : `${importFrom}/${item.entry.name}`;
499
+ return `import { ${componentName(item.entry)} } from "${from}";`;
500
+ }).sort();
501
+ const body = chosen.map((item) => {
502
+ const name = componentName(item.entry);
503
+ return [` {/* ${item.entry.title} — props: ${docsUrl}/docs/${item.entry.type === "registry:block" ? "blocks" : "components"}/${item.entry.name} */}`, ` <${name} />`].join("\n");
504
+ }).join("\n\n");
505
+ return `${imports.join("\n")}
506
+
507
+ export default function Page() {
508
+ return (
509
+ <div className="flex flex-col gap-6">
510
+ ${body}
511
+ </div>
512
+ );
513
+ }
514
+ `;
515
+ }
516
+ /**
517
+ * The plan as a brief for a coding agent.
518
+ *
519
+ * The most useful thing this can produce. The agent has the project, the
520
+ * editor and the ability to write the props; what it lacks is the knowledge
521
+ * that these components exist and that it must not invent others. That is
522
+ * exactly what a grounded plan supplies.
523
+ */
524
+ function renderBrief(plan, options = {}) {
525
+ const { cliPackage = "@dowel-ui/cli", docsUrl = "https://dowel-eight.vercel.app", importFrom = "@/components/ui" } = options;
526
+ if (plan.empty) return `Nothing in the registry matched "${plan.prompt}". Search the catalogue at ${docsUrl}/docs/components before building anything by hand.`;
527
+ const lines = [
528
+ `Build: ${plan.prompt}`,
529
+ "",
530
+ "Use these, which are already in the registry. Do not write your own versions,",
531
+ "and do not use any component not listed here without checking the catalogue first.",
532
+ ""
533
+ ];
534
+ if (plan.blocks.length > 0) {
535
+ lines.push("Blocks (whole sections — each installs its own components):");
536
+ for (const item of plan.blocks) lines.push(`- ${item.entry.name} — ${item.entry.description}`);
537
+ lines.push("");
538
+ }
539
+ if (plan.components.length > 0) {
540
+ lines.push("Components:");
541
+ for (const item of plan.components) lines.push(`- ${item.entry.name} — ${item.entry.description}`);
542
+ lines.push("");
543
+ }
544
+ lines.push("Install first:", "", ` npx ${cliPackage} add ${plan.install.join(" ")}`, "", `Import from \`${importFrom}\`. Each component's props are on its page under`, `${docsUrl}/docs/components — read the page rather than guessing a prop name.`, "", "Style with semantic tokens only (bg-background, text-muted-foreground). Never raw", "hex, never Tailwind's own palette — those do not follow the theme.");
545
+ return lines.join("\n");
546
+ }
547
+ //#endregion
548
+ export { blocksPathFor, planUi, renderBrief, renderPlan };
549
+
550
+ //# sourceMappingURL=generate.js.map