velloo 0.6.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.
Files changed (114) hide show
  1. package/README.md +121 -225
  2. package/THIRD-PARTY-NOTICES.md +3 -3
  3. package/canvas/assets/index-CcPwhD7W.js +31 -0
  4. package/canvas/assets/index-CkVteRZ9.css +2 -0
  5. package/canvas/assets/lucide-all-Cezxn40A.js +1 -0
  6. package/canvas/index.html +2 -2
  7. package/{chunk-rjjc9vam.js → chunk-07fxzrx2.js} +2 -2
  8. package/{chunk-ff0jfn0v.js → chunk-32cq4wpr.js} +1 -1
  9. package/{chunk-nx09zqgs.js → chunk-3612470d.js} +1 -1
  10. package/{chunk-hf4adxe0.js → chunk-45wv1fs5.js} +8 -8
  11. package/chunk-4g4c9h7k.js +2 -0
  12. package/{chunk-8vgpffn7.js → chunk-6w42sr61.js} +1 -1
  13. package/{chunk-esb7n55g.js → chunk-8n07n0cr.js} +3 -3
  14. package/{chunk-96qhpth5.js → chunk-8qf0ne1p.js} +1 -1
  15. package/{chunk-56z5xd5b.js → chunk-8ztgcajz.js} +1 -1
  16. package/chunk-9g45c5md.js +815 -0
  17. package/{chunk-w2naqxqt.js → chunk-aefn23tr.js} +1 -1
  18. package/{chunk-jsvkqae2.js → chunk-asqpx5v7.js} +1 -1
  19. package/chunk-b4hny6hw.js +3 -0
  20. package/chunk-bntsn3wj.js +2 -0
  21. package/{chunk-bc7pkjcn.js → chunk-c4164x55.js} +1 -1
  22. package/{chunk-mfch0b0t.js → chunk-cg0sjdqt.js} +1 -1
  23. package/{chunk-3trkmgfr.js → chunk-cq2q36d1.js} +1 -1
  24. package/chunk-d19ttv1a.js +4 -0
  25. package/{chunk-8grzf8q1.js → chunk-djwvt6jx.js} +1 -1
  26. package/chunk-dqqpbmxt.js +22 -0
  27. package/{chunk-1td6z6ey.js → chunk-dy7qwj3t.js} +1 -1
  28. package/{chunk-dar6wbf2.js → chunk-ejpccmcj.js} +1 -1
  29. package/{chunk-pxaxgkaq.js → chunk-emty5315.js} +1 -1
  30. package/chunk-esg0x95a.js +5 -0
  31. package/{chunk-k423dkjz.js → chunk-ez720ked.js} +1 -1
  32. package/{chunk-h85hv05d.js → chunk-ffm2jpxp.js} +1 -1
  33. package/{chunk-6813v926.js → chunk-g3k8k6fq.js} +3 -3
  34. package/{chunk-63y5gdzw.js → chunk-h3ser37q.js} +1 -1
  35. package/{chunk-74yxy75r.js → chunk-hsk91jms.js} +1 -1
  36. package/chunk-jxgwdz2r.js +5 -0
  37. package/chunk-kvq7pcph.js +2 -0
  38. package/chunk-m0v3d581.js +2 -0
  39. package/chunk-majd3gx9.js +3 -0
  40. package/{chunk-t2x3mztc.js → chunk-mqtnj8ws.js} +1 -1
  41. package/{chunk-xtwx2p9g.js → chunk-mqx96m2g.js} +1 -1
  42. package/{chunk-c4spp880.js → chunk-nrxqr4v5.js} +1 -1
  43. package/{chunk-g185gx8a.js → chunk-nx0ex7qw.js} +1 -1
  44. package/{chunk-qwccndcd.js → chunk-q7wwqn03.js} +1 -1
  45. package/chunk-qgbat4fv.js +6 -0
  46. package/{chunk-08mjtg1e.js → chunk-qqagacne.js} +1 -1
  47. package/{chunk-69qb8p8s.js → chunk-qx83fdv2.js} +1 -1
  48. package/{chunk-ff2vt7cz.js → chunk-r5y1sh4f.js} +1 -1
  49. package/{chunk-2ba6edh8.js → chunk-rg86bhtb.js} +2 -2
  50. package/{chunk-x02z2ccm.js → chunk-rhfzy9c0.js} +1 -1
  51. package/{chunk-t6qr2ay9.js → chunk-rrrn1mkq.js} +1 -1
  52. package/{chunk-56vfy0db.js → chunk-s1cwck4c.js} +1 -1
  53. package/{chunk-tac2wvc7.js → chunk-sjxp2bx3.js} +1 -1
  54. package/chunk-skk6yvnw.js +3 -0
  55. package/{chunk-52m26hh4.js → chunk-t08391mh.js} +1 -1
  56. package/{chunk-2q561dec.js → chunk-t61vyqpd.js} +1 -1
  57. package/{chunk-ta84ehgg.js → chunk-v6ks5v53.js} +1 -1
  58. package/{chunk-hjc5eg5c.js → chunk-w30zfv3m.js} +1 -1
  59. package/{chunk-tbd6wn3q.js → chunk-x84qrz4q.js} +1 -1
  60. package/{chunk-chtv0yj6.js → chunk-xg49s85z.js} +147 -30
  61. package/{chunk-8cj4swpr.js → chunk-xxw67jpk.js} +3 -3
  62. package/{chunk-eqjtq673.js → chunk-yjwtdm71.js} +1 -1
  63. package/{chunk-8vam2893.js → chunk-z6r6wxm6.js} +1 -1
  64. package/{chunk-hsfpbjpc.js → chunk-zgwvkgsz.js} +1 -1
  65. package/cli.js +1 -1
  66. package/launcher.cjs +3 -1
  67. package/package.json +1 -1
  68. package/pkgs/helpers/src/box.tsx +12 -6
  69. package/pkgs/helpers/src/descriptors.ts +6 -0
  70. package/pkgs/helpers/src/icon.tsx +5 -3
  71. package/pkgs/helpers/src/index.ts +2 -2
  72. package/pkgs/helpers/src/text.tsx +29 -5
  73. package/pkgs/provider-antd/src/index.ts +1 -0
  74. package/pkgs/provider-antd/src/manifest.ts +1 -0
  75. package/pkgs/provider-chakra/src/manifest.ts +1 -0
  76. package/pkgs/provider-html/src/index.ts +1 -0
  77. package/pkgs/provider-mui/src/index.ts +10 -0
  78. package/pkgs/provider-mui/src/manifest.ts +1 -0
  79. package/pkgs/provider-none/src/components-inline.tsx +20 -14
  80. package/pkgs/provider-none/src/components.tsx +2 -4
  81. package/pkgs/provider-none/src/index.ts +1 -0
  82. package/pkgs/schema/src/board.ts +13 -1
  83. package/pkgs/schema/src/config.ts +38 -1
  84. package/pkgs/schema/src/flow.ts +230 -0
  85. package/pkgs/schema/src/frame.ts +7 -0
  86. package/pkgs/schema/src/index.ts +24 -0
  87. package/pkgs/schema/src/node-identity.ts +54 -1
  88. package/pkgs/schema/src/node.ts +114 -26
  89. package/pkgs/schema/src/snippet-resolve.ts +18 -12
  90. package/pkgs/schema/src/svg-sanitize.ts +3 -0
  91. package/pkgs/schema/src/theme.ts +56 -0
  92. package/pkgs/schema/src/typeset.ts +21 -4
  93. package/pkgs/shadcn-snapshot/dist/manifest.json +6 -0
  94. package/plugins/claude/velloo/agents/velloo-designer.md +7 -4
  95. package/plugins/claude/velloo/commands/setup.md +13 -0
  96. package/plugins/gemini/GEMINI.md +9 -5
  97. package/plugins/gemini/commands/velloo/setup.toml +11 -0
  98. package/skills/velloo-design/SKILL.md +11 -6
  99. package/skills/velloo-setup/SKILL.md +179 -36
  100. package/canvas/assets/index-CnyI3aig.js +0 -29
  101. package/canvas/assets/index-GNLd0pCG.css +0 -2
  102. package/canvas/assets/lucide-all-CH8LmVh0.js +0 -1
  103. package/chunk-1gkeepkm.js +0 -2
  104. package/chunk-2web93qz.js +0 -2
  105. package/chunk-ab8fga32.js +0 -22
  106. package/chunk-azeqkms7.js +0 -6
  107. package/chunk-e2btp1wd.js +0 -3
  108. package/chunk-ey65err5.js +0 -5
  109. package/chunk-rk238m15.js +0 -4
  110. package/chunk-wfhv9jg4.js +0 -2
  111. package/chunk-wnj432fw.js +0 -3
  112. package/chunk-yfvc8d2g.js +0 -716
  113. package/chunk-z73q4dq9.js +0 -2
  114. package/chunk-ztsckexy.js +0 -5
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{fail}from"./chunk-s23gtejf.js";import"./chunk-ff0jfn0v.js";import{SHELLS,completionScript,commandSpecs,detectShell,installCompletions}from"./chunk-jsvkqae2.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM,defineCommand}from"./chunk-2smsg2ct.js";var import_picocolors=__toESM(require_picocolors(),1);var completions_default=defineCommand({meta:{name:"completions",description:"Shell TAB completions \u2014 print the script, or install it with --install"},args:{shell:{type:"positional",required:!1,description:`Shell: ${SHELLS.join(" | ")} (default: detected from $SHELL)`},install:{type:"boolean",default:!1,description:"Write the script + wire it into your shell (idempotent)"}},async run({args}){let shell=args.shell??detectShell();if(!shell||!SHELLS.includes(shell))fail("completions",`unsupported or undetected shell${args.shell?` "${args.shell}"`:""} \u2014 pass one of: ${SHELLS.join(", ")}.`);if(!args.install){console.log(completionScript(shell,await commandSpecs()));return}let r=await installCompletions(shell);if(console.log(""),console.log(import_picocolors.default.green(`\u2713 ${shell} completions installed.`)),console.log(` script ${import_picocolors.default.cyan(r.scriptPath)}`),r.rcPath)console.log(r.rcUpdated?` rc ${import_picocolors.default.cyan(r.rcPath)} ${import_picocolors.default.dim("(source line appended)")}`:` rc ${import_picocolors.default.cyan(r.rcPath)} ${import_picocolors.default.dim("(already wired)")}`);console.log(""),console.log(import_picocolors.default.dim(shell==="fish"?" New fish sessions pick it up automatically.":` Restart your shell (or \`source ${r.rcPath}\`) to activate TAB completion.`))}});export{completions_default as default};
2
+ import{fail}from"./chunk-s23gtejf.js";import"./chunk-32cq4wpr.js";import{SHELLS,completionScript,commandSpecs,detectShell,installCompletions}from"./chunk-asqpx5v7.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM,defineCommand}from"./chunk-2smsg2ct.js";var import_picocolors=__toESM(require_picocolors(),1);var completions_default=defineCommand({meta:{name:"completions",description:"Shell TAB completions \u2014 print the script, or install it with --install"},args:{shell:{type:"positional",required:!1,description:`Shell: ${SHELLS.join(" | ")} (default: detected from $SHELL)`},install:{type:"boolean",default:!1,description:"Write the script + wire it into your shell (idempotent)"}},async run({args}){let shell=args.shell??detectShell();if(!shell||!SHELLS.includes(shell))fail("completions",`unsupported or undetected shell${args.shell?` "${args.shell}"`:""} \u2014 pass one of: ${SHELLS.join(", ")}.`);if(!args.install){console.log(completionScript(shell,await commandSpecs()));return}let r=await installCompletions(shell);if(console.log(""),console.log(import_picocolors.default.green(`\u2713 ${shell} completions installed.`)),console.log(` script ${import_picocolors.default.cyan(r.scriptPath)}`),r.rcPath)console.log(r.rcUpdated?` rc ${import_picocolors.default.cyan(r.rcPath)} ${import_picocolors.default.dim("(source line appended)")}`:` rc ${import_picocolors.default.cyan(r.rcPath)} ${import_picocolors.default.dim("(already wired)")}`);console.log(""),console.log(import_picocolors.default.dim(shell==="fish"?" New fish sessions pick it up automatically.":` Restart your shell (or \`source ${r.rcPath}\`) to activate TAB completion.`))}});export{completions_default as default};
@@ -1,3 +1,3 @@
1
1
  // @bun
2
- import{CURRENT_SCHEMA_VERSION}from"./chunk-8cj4swpr.js";import{snapshotVersion2}from"./chunk-p93x4pn2.js";import{TOOL_VERSION}from"./chunk-t6qr2ay9.js";function buildDefaultConfig(opts={}){return{schemaVersion:CURRENT_SCHEMA_VERSION,toolVersion:TOOL_VERSION,name:opts.name??"design",folderId:crypto.randomUUID(),libraries:{default:opts.library??{id:"shadcn-upstream",version:snapshotVersion2,source:"binary",componentsPath:"binary"}},defaultLibrary:"default",viewportPresets:[{name:"Mobile",w:390,h:844},{name:"Tablet",w:768,h:1024},{name:"Desktop",w:1440,h:900}],...opts.defaultScreen?{defaultScreen:opts.defaultScreen}:{},...opts.defaultBoard?{defaultBoard:opts.defaultBoard}:{},...opts.hostApp?{hostApp:opts.hostApp}:{},...opts.hostApps?{hostApps:opts.hostApps}:{},...opts.feedback?{feedback:opts.feedback}:{},...opts.styling?{styling:opts.styling}:{},...opts.codegen?{codegen:opts.codegen}:{},...opts.designSystem?{designSystem:opts.designSystem}:{}}}
2
+ import{CURRENT_SCHEMA_VERSION}from"./chunk-xxw67jpk.js";import{snapshotVersion2}from"./chunk-p93x4pn2.js";import{TOOL_VERSION}from"./chunk-rrrn1mkq.js";function buildDefaultConfig(opts={}){return{schemaVersion:CURRENT_SCHEMA_VERSION,toolVersion:TOOL_VERSION,name:opts.name??"design",folderId:crypto.randomUUID(),libraries:{default:opts.library??{id:"shadcn-upstream",version:snapshotVersion2,source:"binary",componentsPath:"binary"}},defaultLibrary:"default",viewportPresets:[{name:"Mobile",w:390,h:844},{name:"Tablet",w:768,h:1024},{name:"Desktop",w:1440,h:900}],...opts.defaultScreen?{defaultScreen:opts.defaultScreen}:{},...opts.defaultBoard?{defaultBoard:opts.defaultBoard}:{},...opts.hostApp?{hostApp:opts.hostApp}:{},...opts.hostApps?{hostApps:opts.hostApps}:{},...opts.feedback?{feedback:opts.feedback}:{},...opts.styling?{styling:opts.styling}:{},...opts.codegen?{codegen:opts.codegen}:{},...opts.designSystem?{designSystem:opts.designSystem}:{},...opts.themeSource?{themeSource:opts.themeSource}:{}}}
3
3
  export{buildDefaultConfig};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import"./chunk-1gkeepkm.js";import"./chunk-8cj4swpr.js";import{normalizeCloudUrl,loadCredential,listCredentials,deleteCredential,clearCredentials,revokeCredential}from"./chunk-2ba6edh8.js";import{defaultCloudUrl}from"./chunk-hjc5eg5c.js";import{defineCommand}from"./chunk-2smsg2ct.js";var logout_default=defineCommand({meta:{name:"logout",description:"Log out of velloo-cloud (signs the CLI token out and removes it)"},args:{url:{type:"string",description:"velloo-cloud base URL (default: $VELLOO_CLOUD_URL or the built-in default)"},all:{type:"boolean",default:!1,description:"Remove saved credentials for every cloud"}},async run({args}){if(args.all){await Promise.all(Object.entries(await listCredentials()).map(([cloudUrl,credential])=>revokeCredential(cloudUrl,credential.token)));let count=await clearCredentials();console.log(count>0?`velloo logout: removed credentials for ${count} cloud${count===1?"":"s"}`:"velloo logout: no saved credentials");return}let cloudUrl=args.url?normalizeCloudUrl(args.url):defaultCloudUrl(),existing=await loadCredential(cloudUrl);if(existing)await revokeCredential(cloudUrl,existing.token);if(await deleteCredential(cloudUrl))console.log(`velloo logout: logged out of ${cloudUrl}${existing?.email?` (${existing.email})`:""}`);else console.log(`velloo logout: not logged in to ${cloudUrl}`)}});export{logout_default as default};
2
+ import"./chunk-bntsn3wj.js";import"./chunk-xxw67jpk.js";import{normalizeCloudUrl,loadCredential,listCredentials,deleteCredential,clearCredentials,revokeCredential}from"./chunk-rg86bhtb.js";import{defaultCloudUrl}from"./chunk-w30zfv3m.js";import{defineCommand}from"./chunk-2smsg2ct.js";var logout_default=defineCommand({meta:{name:"logout",description:"Log out of velloo-cloud (signs the CLI token out and removes it)"},args:{url:{type:"string",description:"velloo-cloud base URL (default: $VELLOO_CLOUD_URL or the built-in default)"},all:{type:"boolean",default:!1,description:"Remove saved credentials for every cloud"}},async run({args}){if(args.all){await Promise.all(Object.entries(await listCredentials()).map(([cloudUrl,credential])=>revokeCredential(cloudUrl,credential.token)));let count=await clearCredentials();console.log(count>0?`velloo logout: removed credentials for ${count} cloud${count===1?"":"s"}`:"velloo logout: no saved credentials");return}let cloudUrl=args.url?normalizeCloudUrl(args.url):defaultCloudUrl(),existing=await loadCredential(cloudUrl);if(existing)await revokeCredential(cloudUrl,existing.token);if(await deleteCredential(cloudUrl))console.log(`velloo logout: logged out of ${cloudUrl}${existing?.email?` (${existing.email})`:""}`);else console.log(`velloo logout: not logged in to ${cloudUrl}`)}});export{logout_default as default};
package/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import"./chunk-s23gtejf.js";import{COMMANDS}from"./chunk-ff0jfn0v.js";import{TOOL_VERSION}from"./chunk-t6qr2ay9.js";import{maybeNotifyAboutUpdate}from"./chunk-rk238m15.js";import{defineCommand,runMain}from"./chunk-2smsg2ct.js";var main=defineCommand({meta:{name:"velloo",version:TOOL_VERSION,description:"Open-source, local-first canvas for agent-driven design."},subCommands:COMMANDS});await runMain(main);var command=process.argv[2];if(!["mcp","__daemon","__update_check","run","upgrade"].includes(command??""))await maybeNotifyAboutUpdate();
3
+ import"./chunk-s23gtejf.js";import{COMMANDS}from"./chunk-32cq4wpr.js";import{TOOL_VERSION}from"./chunk-rrrn1mkq.js";import{maybeNotifyAboutUpdate}from"./chunk-d19ttv1a.js";import{defineCommand,runMain}from"./chunk-2smsg2ct.js";var main=defineCommand({meta:{name:"velloo",version:TOOL_VERSION,description:"Open-source, local-first canvas for agent-driven design."},subCommands:COMMANDS});await runMain(main);var command=process.argv[2];if(!["mcp","__daemon","__update_check","run","upgrade"].includes(command??""))await maybeNotifyAboutUpdate();
package/launcher.cjs CHANGED
@@ -80,7 +80,9 @@ const runtime = join(runtimeRoot, "bin", process.platform === "win32" ? "bun.exe
80
80
  if (!existsSync(runtime)) die(`the Bun runtime package ${runtimeName} is incomplete`);
81
81
 
82
82
  const app = join(__dirname, "cli.js");
83
- const child = spawnSync(runtime, [app, ...process.argv.slice(2)], {
83
+ // `--no-install`: Bun must never answer an import from its global cache or
84
+ // fetch a package to do it — see BUN_RUNTIME_FLAGS in src/bun-runtime.ts.
85
+ const child = spawnSync(runtime, ["--no-install", app, ...process.argv.slice(2)], {
84
86
  stdio: "inherit",
85
87
  env: {
86
88
  ...process.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "velloo",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Local-first, code-shaped design canvas — your AI agent designs with your real components, in your repo",
6
6
  "license": "Apache-2.0",
@@ -9,13 +9,19 @@ import type * as React from "react";
9
9
  import { createElement } from "react";
10
10
  import { cn } from "./cn.ts";
11
11
 
12
- // Restrict `as` to a lowercase HTML tag name. Anything else (a component
13
- // reference, an injection attempt) falls back to div. Rendering the real tag
14
- // means inline elements (span/strong/a/…) get their natural inline display
15
- // from the UA stylesheet, instead of a div's block stacking inline runs.
16
- const TAG = /^[a-z][a-z0-9]*$/;
12
+ /**
13
+ * An element name: an HTML tag, or one of SVG's camelCase ones
14
+ * (`linearGradient`, `clipPath`). Shared by everything that has to agree on
15
+ * what `as` may hold — compose's lowering, each `Box`, and codegen — because a
16
+ * tag one of them accepts and another doesn't renders as a `div`.
17
+ */
18
+ export const ELEMENT_TAG = /^[a-z][a-zA-Z0-9]*$/;
17
19
 
20
+ // Restrict `as` to an element name. Anything else (a component reference, an
21
+ // injection attempt) falls back to div. Rendering the real tag means inline
22
+ // elements (span/strong/a/…) get their natural inline display from the UA
23
+ // stylesheet, instead of a div's block stacking inline runs.
18
24
  export function Box({ as, className, ...props }: React.ComponentProps<"div"> & { as?: string }) {
19
- const tag = typeof as === "string" && TAG.test(as) ? as : "div";
25
+ const tag = typeof as === "string" && ELEMENT_TAG.test(as) ? as : "div";
20
26
  return createElement(tag, { "data-slot": "box", className: cn(className), ...props });
21
27
  }
@@ -233,6 +233,12 @@ export const HELPER_DESCRIPTORS: readonly ComponentDescriptor[] = [
233
233
  props: [
234
234
  { name: "content", type: "string | undefined", optional: true, control: "string" },
235
235
  { name: "viewBox", type: "string | undefined", optional: true, control: "string" },
236
+ {
237
+ name: "preserveAspectRatio",
238
+ type: "string | undefined",
239
+ optional: true,
240
+ control: "string",
241
+ },
236
242
  { name: "color", type: "string | undefined", optional: true, control: "color" },
237
243
  ],
238
244
  example: {
@@ -27,11 +27,13 @@ function lookup(name: string): IconNode | undefined {
27
27
  return viaPascal === undefined ? undefined : ICON_NODES[viaPascal];
28
28
  }
29
29
 
30
- const FALLBACK = lookup("HelpCircle");
31
-
32
30
  export function Icon({ name, className, size = 16, strokeWidth = 2, ...props }: IconProps) {
33
31
  const hit = lookup(name);
34
- const node = hit ?? FALLBACK;
32
+ // Looked up here, not once at module level: a call at the top of this file
33
+ // is a side effect to a bundler, which then keeps the module — and the 0.6 MB
34
+ // of icon data behind it — in every bundle that imports anything at all from
35
+ // the helpers index, `Icon` on the screen or not.
36
+ const node = hit ?? lookup("HelpCircle");
35
37
  if (!node) return null;
36
38
  const tint = hit ? className : cn("text-[var(--color-fg-muted)]", className);
37
39
  return (
@@ -1,4 +1,4 @@
1
- export { Box } from "./box.tsx";
1
+ export { Box, ELEMENT_TAG } from "./box.tsx";
2
2
  export { cn, mergeTailwind } from "./cn.ts";
3
3
  export { HELPER_DESCRIPTORS, helperDescriptors } from "./descriptors.ts";
4
4
  export { Divider, type DividerProps } from "./divider.tsx";
@@ -29,4 +29,4 @@ export {
29
29
  export { Prose, type ProseProps } from "./prose.tsx";
30
30
  export { helpersRegistry } from "./registry.ts";
31
31
  export { SVG, type SVGProps } from "./svg.tsx";
32
- export { Text, type TextProps } from "./text.tsx";
32
+ export { Text, type TextProps, TextRunContext, textTag } from "./text.tsx";
@@ -3,14 +3,38 @@ import { textClasses } from "@velloo/schema/typeset";
3
3
  import * as React from "react";
4
4
  import { cn } from "./cn.ts";
5
5
 
6
- export interface TextProps extends React.HTMLAttributes<HTMLParagraphElement> {
6
+ export interface TextProps extends React.HTMLAttributes<HTMLElement> {
7
7
  /** Visual weight. Selects a rung of the theme's type ladder plus a tone. */
8
8
  variant?: "default" | "muted" | "small" | "lead";
9
9
  }
10
10
 
11
- export const Text = React.forwardRef<HTMLParagraphElement, TextProps>(
12
- ({ variant = "default", className, ...props }, ref) => (
13
- <p ref={ref} className={cn(textClasses(variant), className)} {...props} />
14
- ),
11
+ /**
12
+ * True beneath a `Text`. A paragraph cannot hold a paragraph: an HTML parser
13
+ * ends the outer `<p>` where the inner one starts, so markup that is rendered
14
+ * on the server and parsed again (a screenshot, a comparison) lays the inner
15
+ * text out as a sibling, while the same tree mounted on the client keeps it
16
+ * nested. A `Text` inside a `Text` is a run of that paragraph, so it renders
17
+ * the element a run is — and every path draws the same line.
18
+ */
19
+ export const TextRunContext = React.createContext(false);
20
+
21
+ /** The element a `Text` renders: a paragraph, or a run inside one. */
22
+ export function textTag(run: boolean): "p" | "span" {
23
+ return run ? "span" : "p";
24
+ }
25
+
26
+ export const Text = React.forwardRef<HTMLElement, TextProps>(
27
+ ({ variant, className, ...props }, ref) => {
28
+ const run = React.useContext(TextRunContext);
29
+ return (
30
+ <TextRunContext.Provider value={true}>
31
+ {React.createElement(textTag(run), {
32
+ ref,
33
+ className: cn(textClasses(variant, run), className) || undefined,
34
+ ...props,
35
+ })}
36
+ </TextRunContext.Provider>
37
+ );
38
+ },
15
39
  );
16
40
  Text.displayName = "Text";
@@ -54,6 +54,7 @@ export function createProvider(): FrameworkAdapter {
54
54
  ),
55
55
  styleChannel: STYLE_PROP,
56
56
  styleChannels: ["style"],
57
+ themeRoles: ["background", "foreground", "primary", "destructive", "card"],
57
58
  mcpIntro: () => [...ANTD_INTRO],
58
59
  renderPass: (theme, dark) => makeRenderPass(theme, dark ?? false),
59
60
  codegenModule: "antd",
@@ -838,6 +838,7 @@ export const ANTD_MANIFEST: Manifest = [
838
838
  [
839
839
  { name: "content", type: "string", optional: false, control: "string" },
840
840
  { name: "viewBox", type: "string", optional: false, control: "string" },
841
+ { name: "preserveAspectRatio", type: "string", optional: true, control: "string" },
841
842
  ],
842
843
  "Raw inline SVG markup.",
843
844
  ),
@@ -403,6 +403,7 @@ export const CHAKRA_MANIFEST: Manifest = [
403
403
  [
404
404
  { name: "content", type: "string", optional: false, control: "string" },
405
405
  { name: "viewBox", type: "string", optional: false, control: "string" },
406
+ { name: "preserveAspectRatio", type: "string", optional: true, control: "string" },
406
407
  ],
407
408
  "Raw inline SVG markup.",
408
409
  ),
@@ -59,6 +59,7 @@ export function createProvider(): FrameworkAdapter {
59
59
  loadManifest: async () => HTML_MANIFEST,
60
60
  styleChannel: STYLE_PROP,
61
61
  styleChannels: ["style"],
62
+ themeRoles: ["background", "foreground", "card", "muted", "border", "input"],
62
63
  registryForChannel: () => registry,
63
64
  mcpIntro: () => HTML_INTRO,
64
65
  hostStylesheets: true,
@@ -57,6 +57,16 @@ export function createProvider(): FrameworkAdapter {
57
57
  ),
58
58
  styleChannel: SX_PROP,
59
59
  styleChannels: ["sx"],
60
+ themeRoles: [
61
+ "background",
62
+ "foreground",
63
+ "primary",
64
+ "secondary",
65
+ "destructive",
66
+ "card",
67
+ "muted",
68
+ "border",
69
+ ],
60
70
  mcpIntro: () => [...MUI_INTRO],
61
71
  renderPass: (theme, dark) => makeRenderPass(theme, dark ?? false),
62
72
  codegenModule: "@mui/material",
@@ -250,6 +250,7 @@ export const MUI_MANIFEST: Manifest = [
250
250
  [
251
251
  { name: "content", type: "string", optional: false, control: "string" },
252
252
  { name: "viewBox", type: "string", optional: false, control: "string" },
253
+ { name: "preserveAspectRatio", type: "string", optional: true, control: "string" },
253
254
  ],
254
255
  "Raw inline SVG markup.",
255
256
  ),
@@ -1,12 +1,7 @@
1
+ import { ELEMENT_TAG, TextRunContext, textTag } from "@velloo/helpers";
1
2
  import { headingInlineStyle, textInlineStyle } from "@velloo/schema/typeset";
2
3
  import * as React from "react";
3
- import {
4
- BOX_TAG,
5
- type ButtonProps,
6
- type ContainerProps,
7
- type InputProps,
8
- type StackProps,
9
- } from "./components.tsx";
4
+ import type { ButtonProps, ContainerProps, InputProps, StackProps } from "./components.tsx";
10
5
 
11
6
  /**
12
7
  * Inline-styled variants of the no-library primitives, for a `none`-CSS folder
@@ -38,7 +33,10 @@ export const Box = React.forwardRef<
38
33
  HTMLDivElement,
39
34
  React.HTMLAttributes<HTMLDivElement> & { as?: string }
40
35
  >(({ as, ...rest }, ref) =>
41
- React.createElement(typeof as === "string" && BOX_TAG.test(as) ? as : "div", { ref, ...rest }),
36
+ React.createElement(typeof as === "string" && ELEMENT_TAG.test(as) ? as : "div", {
37
+ ref,
38
+ ...rest,
39
+ }),
42
40
  );
43
41
  Box.displayName = "Box";
44
42
 
@@ -201,13 +199,21 @@ export const Heading = React.forwardRef<HTMLHeadingElement, HeadingProps>(
201
199
  );
202
200
  Heading.displayName = "Heading";
203
201
 
204
- interface TextProps extends React.HTMLAttributes<HTMLParagraphElement> {
202
+ interface TextProps extends React.HTMLAttributes<HTMLElement> {
205
203
  variant?: "default" | "muted" | "small" | "lead";
206
204
  }
207
205
 
208
- export const Text = React.forwardRef<HTMLParagraphElement, TextProps>(
209
- ({ variant = "default", style, ...rest }, ref) => (
210
- <p ref={ref} style={merge(textInlineStyle(variant), style)} {...rest} />
211
- ),
212
- );
206
+ // A `Text` inside a `Text` is a run of it — see `TextRunContext`.
207
+ export const Text = React.forwardRef<HTMLElement, TextProps>(({ variant, style, ...rest }, ref) => {
208
+ const run = React.useContext(TextRunContext);
209
+ return (
210
+ <TextRunContext.Provider value={true}>
211
+ {React.createElement(textTag(run), {
212
+ ref,
213
+ style: merge(textInlineStyle(variant, run), style),
214
+ ...rest,
215
+ })}
216
+ </TextRunContext.Provider>
217
+ );
218
+ });
213
219
  Text.displayName = "Text";
@@ -3,6 +3,7 @@ import {
3
3
  BUTTON_VARIANT_CLASS,
4
4
  CARD_CLASS,
5
5
  CONTAINER_WIDTH_CLASS,
6
+ ELEMENT_TAG,
6
7
  INPUT_CLASS,
7
8
  STACK_ALIGN_CLASS,
8
9
  STACK_JUSTIFY_CLASS,
@@ -23,12 +24,9 @@ import * as React from "react";
23
24
 
24
25
  interface DivProps extends React.HTMLAttributes<HTMLDivElement> {}
25
26
 
26
- /** `as` takes a lowercase HTML tag only — anything else renders the div. */
27
- export const BOX_TAG = /^[a-z][a-z0-9]*$/;
28
-
29
27
  export const Box = React.forwardRef<HTMLDivElement, DivProps & { as?: string }>(
30
28
  ({ as, className, ...rest }, ref) =>
31
- React.createElement(typeof as === "string" && BOX_TAG.test(as) ? as : "div", {
29
+ React.createElement(typeof as === "string" && ELEMENT_TAG.test(as) ? as : "div", {
32
30
  ref,
33
31
  className: clsx(className),
34
32
  ...rest,
@@ -59,6 +59,7 @@ export function createProvider(): FrameworkAdapter {
59
59
  // is `none` resolves to the inline-`style` channel instead.
60
60
  styleChannel: TAILWIND_CLASSNAME,
61
61
  styleChannels: ["tailwind-classname", "style"],
62
+ themeRoles: ["background", "foreground", "card", "muted", "border", "input"],
62
63
  mcpIntro: (channel) => [...(channel === "style" ? NONE_INLINE_INTRO : NONE_INTRO)],
63
64
  // Inline-styled primitives for the `style` channel; Tailwind-classed for
64
65
  // every other channel. Lets a `none/none` folder paint with the JIT off.
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { FlowEdgeSchema } from "./flow.ts";
2
3
  import { FrameSchema } from "./frame.ts";
3
4
  import { ResourceIdSchema } from "./ids.ts";
4
5
 
@@ -12,7 +13,11 @@ import { ResourceIdSchema } from "./ids.ts";
12
13
  export const BoardGroupSchema = z.object({
13
14
  id: ResourceIdSchema,
14
15
  name: z.string().min(1),
15
- /** CSS color value; canvas uses it for the group region tint and tag chip. */
16
+ /**
17
+ * CSS color value. The sidebar uses a board group's for its chip and rail;
18
+ * a frame group may carry one, but the canvas draws nothing for frame
19
+ * groups yet.
20
+ */
16
21
  color: z.string().optional(),
17
22
  });
18
23
 
@@ -61,6 +66,13 @@ export const BoardSchema = z.object({
61
66
  frames: z.array(FrameSchema).default([]),
62
67
  /** Frame regions within this board (see {@link BoardGroupSchema}). */
63
68
  groups: z.array(BoardGroupSchema).default([]),
69
+ /**
70
+ * Arrows between this board's frames (see {@link FlowEdgeSchema}). Absent ⇒
71
+ * a board of unconnected frames, which is every existing one — and the field
72
+ * is dropped again when the last arrow goes, so such a board's file never
73
+ * changes shape.
74
+ */
75
+ edges: z.array(FlowEdgeSchema).optional(),
64
76
  });
65
77
 
66
78
  export type Board = z.infer<typeof BoardSchema>;
@@ -106,6 +106,38 @@ export const HostAppSchema = z.object({
106
106
 
107
107
  export type HostApp = z.infer<typeof HostAppSchema>;
108
108
 
109
+ /**
110
+ * Where the design's theme came from in the app: what `velloo init` read (or
111
+ * left alone), and what an `import_theme` from a stylesheet into the default
112
+ * theme records again. Agents are told by it what checking the theme against
113
+ * the app means — a theme read from a JavaScript theme object (`mantine`,
114
+ * `mui`) is compared with that module, since no stylesheet holds it.
115
+ *
116
+ * "stylesheet" — the app's stylesheet, and what it brings with it.
117
+ * "design-md" — a DESIGN.md, layered on the stylesheet.
118
+ * "mantine" / "mui" — the theme object the app hands that library.
119
+ * "library-defaults" — the app uses `library` and sets no theme of its own,
120
+ * so the design is on that library's.
121
+ * "preset" — nothing of the app's: a built-in preset.
122
+ */
123
+ export const ThemeSourceSchema = z.object({
124
+ kind: z.enum(["stylesheet", "design-md", "mantine", "mui", "library-defaults", "preset"]),
125
+ /**
126
+ * The app's files it was read from, relative to the app root, the one
127
+ * `kind` names first: a stylesheet is followed by the sheets it imports,
128
+ * the tailwind.config it merged and the modules whose font loader calls
129
+ * set a face. Absent when nothing was read from a file in the app.
130
+ */
131
+ files: z.array(AppRelativePathSchema).min(1).optional(),
132
+ /** The library whose own theme it is — for `"library-defaults"`. */
133
+ library: z.string().min(1).optional(),
134
+ });
135
+
136
+ export type ThemeSource = z.infer<typeof ThemeSourceSchema>;
137
+
138
+ /** The CSS framework a folder styles with. See `ConfigSchema.styling`. */
139
+ export const StylingSchema = z.object({ framework: z.enum(["tailwind", "none"]) });
140
+
109
141
  /**
110
142
  * Folder config. Multi-library: a folder registers N libraries by id
111
143
  * and pins one as the default; each screen optionally declares which
@@ -186,7 +218,7 @@ export const ConfigSchema = z
186
218
  * server rejects a folder whose CSS framework no registered library
187
219
  * supports (e.g. shadcn + none).
188
220
  */
189
- styling: z.object({ framework: z.enum(["tailwind", "none"]) }).optional(),
221
+ styling: StylingSchema.optional(),
190
222
  /** Host app location for the live-island bundler. See `HostAppSchema`. */
191
223
  hostApp: HostAppSchema.optional(),
192
224
  /**
@@ -203,6 +235,11 @@ export const ConfigSchema = z
203
235
  * a file somewhere unconventional, or pins the choice when several exist.
204
236
  */
205
237
  designSystem: z.object({ path: AppRelativePathSchema }).optional(),
238
+ /**
239
+ * Where the default theme was read from in the app. See `ThemeSourceSchema`.
240
+ * Absent for a design made before it was recorded: nothing is assumed.
241
+ */
242
+ themeSource: ThemeSourceSchema.optional(),
206
243
  /**
207
244
  * Named host apps for monorepos — the multi-app twin of `hostApp`, keyed
208
245
  * by a short app name (`"web"`, `"admin"`; `init`'s multi-app scan uses