0x-lang 0.1.15 → 0.1.17

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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>Write 18 lines. Get 96 lines of production React.</strong><br/>
9
- A programming language that compiles to React, Vue 3, and Svelte 5.
9
+ A full-stack language that compiles to React, Vue 3, Svelte 5, Express, React Native, and Terraform.
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -24,7 +24,7 @@
24
24
 
25
25
  ## What is this?
26
26
 
27
- 0x is a tiny language for building UI. You describe what you want, and the compiler outputs production-ready React, Vue, or Svelte.
27
+ 0x is a tiny language for building apps. You describe what you want, and the compiler outputs production-ready React, Vue, Svelte, Express backends, React Native mobile apps, or Terraform infrastructure.
28
28
 
29
29
  ```python
30
30
  page Counter:
@@ -39,9 +39,9 @@ page Counter:
39
39
  button "+1" style=primary -> increment()
40
40
  ```
41
41
 
42
- That's a complete component. Run `0x build counter.ai --target react` and you get a working React component with `useState`, event handlers, and full styling. Same source also compiles to Vue 3 and Svelte 5.
42
+ That's a complete component. Run `0x build counter.ai --target react` and you get a working React component with `useState`, event handlers, and full styling. Same source also compiles to Vue 3, Svelte 5, and React Native.
43
43
 
44
- **Why?** Most frontend code is boilerplate — imports, hook calls, JSX wrappers, style objects, closing tags. 0x skips all of that. You write what matters, the compiler handles the rest.
44
+ **Why?** Most code is boilerplate — imports, hook calls, JSX wrappers, style objects, closing tags, Express middleware, Terraform blocks. 0x skips all of that. You write what matters, the compiler handles the rest.
45
45
 
46
46
  ## The numbers
47
47
 
@@ -75,12 +75,21 @@ page Hello:
75
75
  Compile it:
76
76
 
77
77
  ```bash
78
- # Pick your framework
78
+ # Frontend
79
79
  0x build hello.ai --target react
80
80
  0x build hello.ai --target vue
81
81
  0x build hello.ai --target svelte
82
82
 
83
- # Or all three at once
83
+ # Mobile
84
+ 0x build app.ai --target react-native
85
+
86
+ # Backend
87
+ 0x build server.ai --target backend
88
+
89
+ # Infrastructure
90
+ 0x build infra.ai --target terraform
91
+
92
+ # Or multiple at once
84
93
  0x build hello.ai --target react,vue,svelte
85
94
  ```
86
95
 
@@ -272,6 +281,72 @@ page Shop:
272
281
  button "Add to Cart" style=primary -> addToCart(product)
273
282
  ```
274
283
 
284
+ ### Backend API — Express server
285
+
286
+ ```python
287
+ page API:
288
+ model User:
289
+ field name: str
290
+ field email: str
291
+ field role: str = "user"
292
+
293
+ auth:
294
+ secret: JWT_SECRET
295
+ endpoint: /auth
296
+
297
+ route GET /api/users:
298
+ users = await db.find("users")
299
+ respond 200 users
300
+
301
+ route POST /api/users:
302
+ user = await db.create("users", body)
303
+ respond 201 user
304
+
305
+ env:
306
+ DATABASE_URL: "postgres://localhost/mydb"
307
+ JWT_SECRET: secret
308
+ ```
309
+
310
+ Compiles to a full Express.js server with JWT auth, CORS, error handling, and CRUD routes.
311
+
312
+ ### React Native Mobile App
313
+
314
+ ```python
315
+ page MobileCounter:
316
+ state count: int = 0
317
+
318
+ fn increment():
319
+ count += 1
320
+
321
+ layout col gap=16 padding=24 center:
322
+ text "Counter" size=2xl bold
323
+ text "{count}" size=4xl color=cyan
324
+ button "+1" style=primary -> increment()
325
+ ```
326
+
327
+ Same 0x syntax compiles to React Native with `View`, `Text`, `TouchableOpacity`, and `StyleSheet`.
328
+
329
+ ### Infrastructure — Terraform
330
+
331
+ ```python
332
+ page Infra:
333
+ deploy myApp:
334
+ provider: aws
335
+ region: us-east-1
336
+ instance: t3.medium
337
+
338
+ storage assets:
339
+ provider: s3
340
+ bucket: my-app-assets
341
+ versioning: true
342
+
343
+ domain mysite:
344
+ provider: route53
345
+ name: "myapp.com"
346
+ ```
347
+
348
+ Compiles to Terraform HCL with provider blocks, resource definitions, and variable declarations.
349
+
275
350
  More examples in [`examples/`](examples/).
276
351
 
277
352
  ---
@@ -430,14 +505,46 @@ page Hello:
430
505
  text "Hello, {name}!" size=2xl bold
431
506
  `;
432
507
 
508
+ // Frontend targets
433
509
  const react = compile(source, { target: 'react' });
434
510
  const vue = compile(source, { target: 'vue' });
435
511
  const svelte = compile(source, { target: 'svelte' });
436
512
 
513
+ // Mobile
514
+ const rn = compile(source, { target: 'react-native' });
515
+
516
+ // Backend & infrastructure
517
+ const server = compile(backendSource, { target: 'backend' });
518
+ const infra = compile(infraSource, { target: 'terraform' });
519
+
520
+ // Options
521
+ const result = compile(source, {
522
+ target: 'react',
523
+ validate: true, // Run validator (default: true)
524
+ sourceMap: true, // Add source line comments (default: true)
525
+ useClient: true, // Add 'use client' for Next.js (default: auto)
526
+ compact: true, // AI-optimized compact output — strips comments
527
+ });
528
+
437
529
  console.log(react.code); // Full React component
438
530
  console.log(react.lineCount); // Line count
439
531
  ```
440
532
 
533
+ ### AI Bridge
534
+
535
+ ```typescript
536
+ import { getLanguageSpec, generatePrompt, compileFromDescription } from '0x-lang';
537
+
538
+ // Get the full 0x language spec for an LLM
539
+ const spec = getLanguageSpec();
540
+
541
+ // Generate a structured prompt for AI code generation
542
+ const prompt = generatePrompt("todo app with authentication", 'react');
543
+
544
+ // Auto-generate a 0x skeleton from a natural language description
545
+ const skeleton = compileFromDescription("dashboard with charts and API");
546
+ ```
547
+
441
548
  ### Pipeline access
442
549
 
443
550
  ```typescript
@@ -461,6 +568,10 @@ const output = generateReact(ast); // React JSX string
461
568
  | `0x-lang/generators/react` | `generateReact()` |
462
569
  | `0x-lang/generators/vue` | `generateVue()` |
463
570
  | `0x-lang/generators/svelte` | `generateSvelte()` |
571
+ | `0x-lang/generators/backend` | `generateBackend()` |
572
+ | `0x-lang/generators/react-native` | `generateReactNative()` |
573
+ | `0x-lang/generators/terraform` | `generateTerraform()` |
574
+ | `0x-lang/generators/ai-bridge` | `getLanguageSpec()`, `generatePrompt()`, `compileFromDescription()` |
464
575
 
465
576
  ---
466
577
 
@@ -541,39 +652,30 @@ See [mcp-server/](mcp-server/) for full docs.
541
652
 
542
653
  ## For AI agent builders
543
654
 
544
- If you're building tools that generate UI code, 0x works well as an intermediate representation:
655
+ If you're building tools that generate code, 0x works well as an intermediate representation:
545
656
 
546
657
  ```typescript
547
658
  import { compile } from '0x-lang/compiler';
659
+ import { compileFromDescription, generatePrompt } from '0x-lang';
548
660
 
549
- // Your AI generates compact 0x
550
- const aiOutput = `
551
- page Dashboard:
552
- state metrics: list[object] = []
553
-
554
- on mount:
555
- metrics = await api.getMetrics()
661
+ // Auto-generate 0x skeleton from natural language
662
+ const skeleton = compileFromDescription("e-commerce product page");
556
663
 
557
- layout col gap=24 padding=32:
558
- text "Dashboard" size=3xl bold
559
- layout grid cols=3 gap=16:
560
- for metric in metrics:
561
- layout col padding=20 bg=white rounded=lg shadow:
562
- text "{metric.label}" size=sm color=gray
563
- text "{metric.value}" size=2xl bold
564
- `;
664
+ // Or use the AI bridge to generate a prompt for your LLM
665
+ const prompt = generatePrompt("todo app with auth", 'react');
565
666
 
566
- // Compile to whatever the user needs
567
- const react = compile(aiOutput, { target: 'react' });
568
- const vue = compile(aiOutput, { target: 'vue' });
569
- const svelte = compile(aiOutput, { target: 'svelte' });
667
+ // Compile with compact mode for AI-optimized output
668
+ const react = compile(source, { target: 'react', compact: true });
669
+ const server = compile(source, { target: 'backend', compact: true });
570
670
  ```
571
671
 
572
672
  Why this matters for AI:
573
673
 
574
674
  - **80% fewer output tokens** — less generation cost, lower latency
575
675
  - **One syntax, zero decisions** — no "which React pattern?" hallucinations
576
- - **Multi-framework** — one generation covers React, Vue, and Svelte users
676
+ - **Full-stack** — frontend, backend, mobile, and infrastructure from one language
677
+ - **Compact mode** — strips comments and whitespace for minimal token usage
678
+ - **AI Bridge** — built-in spec, prompt generation, and skeleton creation
577
679
  - **Deterministic** — same input always produces the same output
578
680
  - **Validated** — the compiler catches errors before they reach the user
579
681
 
@@ -591,12 +693,16 @@ Parser ────── Recursive descent → typed AST
591
693
  Validator ─── Circular deps · unused state · type checks
592
694
 
593
695
  Generator
594
- ├── React ─── JSX + hooks + CSS-in-JS
595
- ├── Vue ───── SFC + Composition API + scoped styles
596
- └── Svelte ── Runes ($state, $derived) + styles
696
+ ├── React ────────── JSX + hooks + CSS-in-JS
697
+ ├── Vue ──────────── SFC + Composition API + scoped styles
698
+ ├── Svelte ──────── Runes ($state, $derived) + styles
699
+ ├── React Native ── View + StyleSheet + TouchableOpacity
700
+ ├── Backend ─────── Express + JWT + CRUD + middleware
701
+ ├── Terraform ───── HCL + providers + resources
702
+ └── AI Bridge ───── Spec + prompts + skeleton generation
597
703
  ```
598
704
 
599
- ~9,800 lines of TypeScript. Zero runtime dependencies.
705
+ ~12,000 lines of TypeScript. Zero runtime dependencies (except gen-mapping for source maps).
600
706
 
601
707
  | Module | Lines | |
602
708
  |:---|---:|:---|
@@ -605,8 +711,12 @@ Generator
605
711
  | AST Types | 1,065 | TypeScript definitions |
606
712
  | Vue Generator | 822 | SFC with `<script setup>` and `ref()` |
607
713
  | Svelte Generator | 743 | Svelte 5 with `$state()` and `$derived()` |
714
+ | React Native Generator | 690 | View + StyleSheet + native components |
715
+ | Backend Generator | 412 | Express + JWT + CRUD + middleware |
716
+ | Terraform Generator | 370 | HCL + multi-provider (AWS, Vercel, Fly.io) |
608
717
  | Validator | 355 | Static analysis + error reporting |
609
718
  | Tokenizer | 348 | Indentation-aware lexer |
719
+ | AI Bridge | 291 | Spec + prompt generation + skeleton |
610
720
  | CLI | 223 | build · dev · bench · init |
611
721
 
612
722
  ## CLI
@@ -618,7 +728,7 @@ Generator
618
728
  0x init [project-name]
619
729
 
620
730
  Flags:
621
- --target, -t react, vue, svelte (comma-separated)
731
+ --target, -t react, vue, svelte, react-native, backend, terraform (comma-separated)
622
732
  --output, -o Output directory (default: ./dist/)
623
733
  --help, -h Show help
624
734
  ```
package/dist/compiler.js CHANGED
@@ -69,6 +69,8 @@ function compactify(result) {
69
69
  code = code.replace(/\/\*[\s\S]*?\*\//g, '');
70
70
  // Strip single-line comments (but not URLs or sourceMappingURL)
71
71
  code = code.replace(/^(\s*)\/\/(?!#).*$/gm, '');
72
+ // Strip hash comments (Terraform-style)
73
+ code = code.replace(/^#\s+Generated by 0x.*$/gm, '');
72
74
  // Strip "Generated by 0x" header comments
73
75
  code = code.replace(/^\/\/ Generated by 0x.*$/gm, '');
74
76
  // Collapse multiple empty lines to single empty line
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgB1C,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,OAAuB;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAE1B,8BAA8B;IAC9B,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClG,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,IAAI,MAAqB,CAAC;IAC1B,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,OAAO;YACV,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM;QACR,KAAK,KAAK;YACR,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM;QACR,KAAK,SAAS;YACZ,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM;QACR,KAAK,cAAc;YACjB,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM;QACR,KAAK,WAAW;YACd,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YAChC,MAAM;QACR;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,wEAAwE;IACxE,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QAChC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IACzI,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAC5B,oDAAoD;QACpD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,qEAAqE,GAAG,IAAI,EAAE,CAAC;IAC3H,CAAC;IAED,0DAA0D;IAC1D,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC9D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,EAAE,CAAC;IAChF,CAAC;IAED,qDAAqD;IACrD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,MAAqB;IACvC,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACvB,4BAA4B;IAC5B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC7C,uBAAuB;IACvB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC7C,gEAAgE;IAChE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAChD,wCAAwC;IACxC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACrD,0CAA0C;IAC1C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;IACtD,qDAAqD;IACrD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACvC,0CAA0C;IAC1C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACrC,OAAO;IACP,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAEnB,OAAO;QACL,GAAG,MAAM;QACT,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;QAClC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM;QAC9D,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC"}
@@ -1,5 +1,5 @@
1
1
  // 0x → React Code Generator
2
- import { SIZE_MAP, unquote, capitalize, parseGradient, addPx } from './shared.js';
2
+ import { SIZE_MAP, unquote, capitalize, parseGradient, addPx, getPassthroughProps, KNOWN_LAYOUT_PROPS, KNOWN_TEXT_PROPS, KNOWN_BUTTON_PROPS, KNOWN_INPUT_PROPS, KNOWN_IMAGE_PROPS, KNOWN_LINK_PROPS, KNOWN_TOGGLE_PROPS, KNOWN_SELECT_PROPS } from './shared.js';
3
3
  import { SourceMapBuilder } from './source-map.js';
4
4
  export function ctx() {
5
5
  return {
@@ -369,7 +369,8 @@ function genDerived(node, c) {
369
369
  c.readOnly = prevReadOnly;
370
370
  const { deps, warning } = extractDepsWithWarning(node.expression, c);
371
371
  const warnComment = warning ? ` ${warning}` : '';
372
- return `${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
372
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
373
+ return `${sc}${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
373
374
  }
374
375
  function genCheck(node, c) {
375
376
  const cond = genExpr(node.condition, c);
@@ -400,32 +401,36 @@ function genFunction(node, c) {
400
401
  // requires
401
402
  const requireChecks = node.requires.map(r => `if (!(${genExpr(r, c)})) throw new Error('Precondition failed');`).join('\n ');
402
403
  const allBody = [requireChecks, body].filter(Boolean).join('\n ');
403
- return `const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
404
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
405
+ return `${sc}const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
404
406
  }
405
407
  // ── Lifecycle ───────────────────────────────────────
406
408
  function genOnMount(node, c) {
407
409
  c.imports.add('useEffect');
408
410
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
409
411
  const hasAwait = bodyContainsAwait(node.body);
412
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
410
413
  if (hasAwait) {
411
- return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, []);`;
414
+ return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, []);`;
412
415
  }
413
- return `useEffect(() => {\n ${body}\n }, []);`;
416
+ return `${sc}useEffect(() => {\n ${body}\n }, []);`;
414
417
  }
415
418
  function genOnDestroy(node, c) {
416
419
  c.imports.add('useEffect');
417
420
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
418
- return `useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
421
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
422
+ return `${sc}useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
419
423
  }
420
424
  function genWatch(node, c) {
421
425
  c.imports.add('useEffect');
422
426
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
423
427
  const vars = (node.variables || [node.variable]).join(', ');
424
428
  const hasAwait = bodyContainsAwait(node.body);
429
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
425
430
  if (hasAwait) {
426
- return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
431
+ return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
427
432
  }
428
- return `useEffect(() => {\n ${body}\n }, [${vars}]);`;
433
+ return `${sc}useEffect(() => {\n ${body}\n }, [${vars}]);`;
429
434
  }
430
435
  // ── UI Nodes ────────────────────────────────────────
431
436
  function srcComment(node) {
@@ -522,10 +527,14 @@ function genLayout(node, c) {
522
527
  style['flexDirection'] = node.direction === 'row' ? 'row' : 'column';
523
528
  }
524
529
  // Process layout props
530
+ let className = null;
525
531
  for (const [key, val] of Object.entries(node.props)) {
526
532
  const v = genExpr(val, c);
527
533
  const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
528
534
  switch (key) {
535
+ case 'class':
536
+ className = unquote(v);
537
+ break;
529
538
  case 'gap':
530
539
  style['gap'] = addPx(v);
531
540
  break;
@@ -600,17 +609,23 @@ function genLayout(node, c) {
600
609
  }
601
610
  }
602
611
  const styleStr = genStyleObj(style, dynamicKeys);
612
+ const classAttr = className ? ` className="${className}"` : '';
613
+ const extra = getPassthroughProps(node.props, KNOWN_LAYOUT_PROPS, e => genExpr(e, c), 'react');
603
614
  const children = node.children.map(ch => genUINode(ch, c)).join('\n');
604
615
  const sc = srcComment(node);
605
- return `${sc}<div style={${styleStr}}>\n${children}\n</div>`;
616
+ return `${sc}<div${classAttr} style={${styleStr}}${extra}>\n${children}\n</div>`;
606
617
  }
607
618
  function genText(node, c) {
608
619
  const style = {};
609
620
  const dynamicKeys = new Set();
621
+ let className = null;
610
622
  for (const [key, val] of Object.entries(node.props)) {
611
623
  const v = genExpr(val, c);
612
624
  const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
613
625
  switch (key) {
626
+ case 'class':
627
+ className = unquote(v);
628
+ break;
614
629
  case 'size': {
615
630
  const uv = unquote(v);
616
631
  style['fontSize'] = SIZE_MAP[uv] || `${uv}px`;
@@ -658,12 +673,14 @@ function genText(node, c) {
658
673
  }
659
674
  }
660
675
  const content = genTextContent(node.content, c);
676
+ const classAttr = className ? ` className="${className}"` : '';
661
677
  const styleStr = Object.keys(style).length > 0 ? ` style={${genStyleObj(style, dynamicKeys)}}` : '';
678
+ const extra = getPassthroughProps(node.props, KNOWN_TEXT_PROPS, e => genExpr(e, c), 'react');
662
679
  // Badge prop: render a badge indicator next to content
663
680
  const badgeExpr = node.props['badge'];
664
681
  const tooltipExpr = node.props['tooltip'];
665
682
  const sc = srcComment(node);
666
- let result = `${sc}<span${styleStr}>${content}</span>`;
683
+ let result = `${sc}<span${classAttr}${styleStr}${extra}>${content}</span>`;
667
684
  if (badgeExpr) {
668
685
  const badge = genExpr(badgeExpr, c);
669
686
  result = `<span style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>\n<span${styleStr}>${content}</span>\n<span style={{ marginLeft: '6px', padding: '2px 6px', fontSize: '12px', fontWeight: 'bold', borderRadius: '9999px', backgroundColor: '#ef4444', color: '#fff', minWidth: '20px', textAlign: 'center' }}>{${badge}}</span>\n</span>`;
@@ -678,24 +695,37 @@ function genButton(node, c) {
678
695
  const label = genTextContent(node.label, c);
679
696
  const actionCode = genActionExpr(node.action, c);
680
697
  const styleProps = [];
698
+ let className = null;
681
699
  for (const [key, val] of Object.entries(node.props)) {
682
700
  const v = genExpr(val, c);
683
701
  switch (key) {
684
- case 'style':
685
- styleProps.push(`className="${v}"`);
702
+ case 'class':
703
+ className = unquote(v);
686
704
  break;
705
+ case 'style': {
706
+ const sv = unquote(v);
707
+ if (sv === 'primary')
708
+ styleProps.push('style={{ backgroundColor: "#3b82f6", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
709
+ else if (sv === 'danger')
710
+ styleProps.push('style={{ backgroundColor: "#ef4444", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
711
+ break;
712
+ }
687
713
  case 'disabled':
688
714
  styleProps.push(`disabled={${v}}`);
689
715
  break;
690
716
  case 'size': /* handled in style */ break;
691
717
  }
692
718
  }
719
+ if (className)
720
+ styleProps.push(`className="${className}"`);
721
+ const extra = getPassthroughProps(node.props, KNOWN_BUTTON_PROPS, e => genExpr(e, c), 'react');
693
722
  const propsStr = styleProps.join(' ');
694
723
  const sc = srcComment(node);
695
- return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}>${label}</button>`;
724
+ return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}${extra}>${label}</button>`;
696
725
  }
697
726
  function genInput(node, c) {
698
727
  const setter = 'set' + capitalize(node.binding);
728
+ let className = null;
699
729
  const props = [
700
730
  `value={${node.binding}}`,
701
731
  `onChange={e => ${setter}(e.target.value)}`,
@@ -703,6 +733,9 @@ function genInput(node, c) {
703
733
  for (const [key, val] of Object.entries(node.props)) {
704
734
  const v = genExpr(val, c);
705
735
  switch (key) {
736
+ case 'class':
737
+ className = unquote(v);
738
+ break;
706
739
  case 'placeholder':
707
740
  props.push(`placeholder=${quoteJsx(v)}`);
708
741
  break;
@@ -718,16 +751,23 @@ function genInput(node, c) {
718
751
  props.push(`onKeyPress={e => ${handler}(e.key)}`);
719
752
  }
720
753
  }
754
+ if (className)
755
+ props.push(`className="${className}"`);
756
+ const extra = getPassthroughProps(node.props, KNOWN_INPUT_PROPS, e => genExpr(e, c), 'react');
721
757
  const sc = srcComment(node);
722
- return `${sc}<input ${props.join(' ')} />`;
758
+ return `${sc}<input ${props.join(' ')}${extra} />`;
723
759
  }
724
760
  function genImage(node, c) {
725
761
  const src = genExpr(node.src, c);
726
762
  const props = [`src={${src}}`];
727
763
  const style = {};
764
+ let className = null;
728
765
  for (const [key, val] of Object.entries(node.props)) {
729
766
  const v = genExpr(val, c);
730
767
  switch (key) {
768
+ case 'class':
769
+ className = unquote(v);
770
+ break;
731
771
  case 'width':
732
772
  props.push(`width="${v}"`);
733
773
  break;
@@ -751,16 +791,26 @@ function genImage(node, c) {
751
791
  }
752
792
  }
753
793
  }
794
+ if (className)
795
+ props.push(`className="${className}"`);
754
796
  if (Object.keys(style).length > 0) {
755
797
  const entries = Object.entries(style).map(([k, v]) => `${k}: ${v}`).join(', ');
756
798
  props.push(`style={{ ${entries} }}`);
757
799
  }
758
- return `<img ${props.join(' ')} />`;
800
+ const extra = getPassthroughProps(node.props, KNOWN_IMAGE_PROPS, e => genExpr(e, c), 'react');
801
+ return `<img ${props.join(' ')}${extra} />`;
759
802
  }
760
803
  function genLink(node, c) {
761
804
  const label = genTextContent(node.label, c);
762
805
  const href = genExpr(node.href, c);
763
- return `<a href={${href}}>${label}</a>`;
806
+ let className = null;
807
+ for (const [key, val] of Object.entries(node.props || {})) {
808
+ if (key === 'class')
809
+ className = unquote(genExpr(val, c));
810
+ }
811
+ const classAttr = className ? ` className="${className}"` : '';
812
+ const extra = getPassthroughProps(node.props || {}, KNOWN_LINK_PROPS, e => genExpr(e, c), 'react');
813
+ return `<a href={${href}}${classAttr}${extra}>${label}</a>`;
764
814
  }
765
815
  function genToggle(node, c) {
766
816
  const binding = node.binding;
@@ -773,12 +823,26 @@ function genToggle(node, c) {
773
823
  else {
774
824
  setter = `set${capitalize(parts[0])}(prev => ({...prev, ${parts.slice(1).join('.')}: !prev.${parts.slice(1).join('.')}}))`;
775
825
  }
776
- return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}} />`;
826
+ let className = null;
827
+ for (const [key, val] of Object.entries(node.props || {})) {
828
+ if (key === 'class')
829
+ className = unquote(genExpr(val, c));
830
+ }
831
+ const classAttr = className ? ` className="${className}"` : '';
832
+ const extra = getPassthroughProps(node.props || {}, KNOWN_TOGGLE_PROPS, e => genExpr(e, c), 'react');
833
+ return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}}${classAttr}${extra} />`;
777
834
  }
778
835
  function genSelect(node, c) {
779
836
  const setter = 'set' + capitalize(node.binding);
780
837
  const options = genExpr(node.options, c);
781
- return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
838
+ let className = null;
839
+ for (const [key, val] of Object.entries(node.props || {})) {
840
+ if (key === 'class')
841
+ className = unquote(genExpr(val, c));
842
+ }
843
+ const classAttr = className ? ` className="${className}"` : '';
844
+ const extra = getPassthroughProps(node.props || {}, KNOWN_SELECT_PROPS, e => genExpr(e, c), 'react');
845
+ return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}${classAttr}${extra}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
782
846
  }
783
847
  function genComponentCall(node, c) {
784
848
  const parts = [];