0x-lang 0.1.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 (54) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +357 -0
  3. package/dist/ast.d.ts +905 -0
  4. package/dist/ast.js +3 -0
  5. package/dist/ast.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +190 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/compiler.d.ts +6 -0
  10. package/dist/compiler.js +28 -0
  11. package/dist/compiler.js.map +1 -0
  12. package/dist/generators/react.d.ts +4 -0
  13. package/dist/generators/react.js +2304 -0
  14. package/dist/generators/react.js.map +1 -0
  15. package/dist/generators/shared.d.ts +3 -0
  16. package/dist/generators/shared.js +15 -0
  17. package/dist/generators/shared.js.map +1 -0
  18. package/dist/generators/svelte.d.ts +2 -0
  19. package/dist/generators/svelte.js +607 -0
  20. package/dist/generators/svelte.js.map +1 -0
  21. package/dist/generators/vue.d.ts +2 -0
  22. package/dist/generators/vue.js +660 -0
  23. package/dist/generators/vue.js.map +1 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init.d.ts +1 -0
  28. package/dist/init.js +101 -0
  29. package/dist/init.js.map +1 -0
  30. package/dist/parser.d.ts +7 -0
  31. package/dist/parser.js +3586 -0
  32. package/dist/parser.js.map +1 -0
  33. package/dist/react/chat.jsx +44 -0
  34. package/dist/react/counter.jsx +30 -0
  35. package/dist/react/dashboard.jsx +60 -0
  36. package/dist/react/ecommerce.jsx +63 -0
  37. package/dist/react/todo.jsx +38 -0
  38. package/dist/svelte/chat.svelte +41 -0
  39. package/dist/svelte/counter.svelte +27 -0
  40. package/dist/svelte/dashboard.svelte +57 -0
  41. package/dist/svelte/ecommerce.svelte +59 -0
  42. package/dist/svelte/todo.svelte +35 -0
  43. package/dist/tokenizer.d.ts +13 -0
  44. package/dist/tokenizer.js +318 -0
  45. package/dist/tokenizer.js.map +1 -0
  46. package/dist/validator.d.ts +16 -0
  47. package/dist/validator.js +315 -0
  48. package/dist/validator.js.map +1 -0
  49. package/dist/vue/chat.vue +44 -0
  50. package/dist/vue/counter.vue +30 -0
  51. package/dist/vue/dashboard.vue +60 -0
  52. package/dist/vue/ecommerce.vue +62 -0
  53. package/dist/vue/todo.vue +38 -0
  54. package/package.json +63 -0
@@ -0,0 +1,2304 @@
1
+ // 0x → React Code Generator
2
+ import { SIZE_MAP, unquote, capitalize } from './shared.js';
3
+ function ctx() {
4
+ return {
5
+ imports: new Set(),
6
+ states: new Map(),
7
+ derivedNames: new Set(),
8
+ styles: new Map(),
9
+ indent: 1,
10
+ };
11
+ }
12
+ function ind(c) {
13
+ return ' '.repeat(c.indent);
14
+ }
15
+ export function generateReact(ast) {
16
+ const parts = [];
17
+ for (const node of ast) {
18
+ if (node.type === 'Model') {
19
+ parts.push(genModelCode(node));
20
+ }
21
+ else if (node.type === 'AuthDecl') {
22
+ parts.push(genAuthCode(node));
23
+ }
24
+ else if (node.type === 'RouteDecl') {
25
+ parts.push(genRouteCode(node));
26
+ }
27
+ else if (node.type === 'RoleDecl') {
28
+ parts.push(genRoleCode(node));
29
+ }
30
+ else if (node.type === 'Automation') {
31
+ parts.push(genAutomationCode(node));
32
+ }
33
+ else if (node.type === 'Dev') {
34
+ parts.push(genDevCode(node));
35
+ // Phase 4: Infrastructure
36
+ }
37
+ else if (node.type === 'Deploy') {
38
+ parts.push(genDeployCode(node));
39
+ }
40
+ else if (node.type === 'Env') {
41
+ parts.push(genEnvCode(node));
42
+ }
43
+ else if (node.type === 'Docker') {
44
+ parts.push(genDockerCode(node));
45
+ }
46
+ else if (node.type === 'Ci') {
47
+ parts.push(genCiCode(node));
48
+ }
49
+ else if (node.type === 'Domain') {
50
+ parts.push(genDomainCode(node));
51
+ }
52
+ else if (node.type === 'Cdn') {
53
+ parts.push(genCdnCode(node));
54
+ }
55
+ else if (node.type === 'Monitor') {
56
+ parts.push(genMonitorCode(node));
57
+ }
58
+ else if (node.type === 'Backup') {
59
+ parts.push(genBackupCode(node));
60
+ // Phase 4: Backend
61
+ }
62
+ else if (node.type === 'Endpoint') {
63
+ parts.push(genEndpointCode(node));
64
+ }
65
+ else if (node.type === 'Middleware') {
66
+ parts.push(genMiddlewareCode(node));
67
+ }
68
+ else if (node.type === 'Queue') {
69
+ parts.push(genQueueCode(node));
70
+ }
71
+ else if (node.type === 'Cron') {
72
+ parts.push(genCronCode(node));
73
+ }
74
+ else if (node.type === 'Cache') {
75
+ parts.push(genCacheCode(node));
76
+ }
77
+ else if (node.type === 'Migrate') {
78
+ parts.push(genMigrateCode(node));
79
+ }
80
+ else if (node.type === 'Seed') {
81
+ parts.push(genSeedCode(node));
82
+ }
83
+ else if (node.type === 'Webhook') {
84
+ parts.push(genWebhookCode(node));
85
+ }
86
+ else if (node.type === 'Storage') {
87
+ parts.push(genStorageCode(node));
88
+ // Phase 4: Testing
89
+ }
90
+ else if (node.type === 'Test') {
91
+ parts.push(genTestCode(node));
92
+ }
93
+ else if (node.type === 'E2e') {
94
+ parts.push(genE2eCode(node));
95
+ }
96
+ else if (node.type === 'Mock') {
97
+ parts.push(genMockCode(node));
98
+ }
99
+ else if (node.type === 'Fixture') {
100
+ parts.push(genFixtureCode(node));
101
+ // Phase 4: i18n
102
+ }
103
+ else if (node.type === 'I18n') {
104
+ parts.push(genI18nCode(node));
105
+ }
106
+ else if (node.type === 'Locale') {
107
+ parts.push(genLocaleCode(node));
108
+ }
109
+ else if (node.type === 'Rtl') {
110
+ parts.push(genRtlCode(node));
111
+ }
112
+ else if (node.type === 'Page' || node.type === 'Component' || node.type === 'App') {
113
+ parts.push(generateTopLevel(node));
114
+ }
115
+ }
116
+ const code = parts.join('\n\n');
117
+ const importLines = code.match(/^import .+$/gm) || [];
118
+ return {
119
+ code,
120
+ filename: 'Component.jsx',
121
+ imports: importLines,
122
+ lineCount: code.split('\n').length,
123
+ tokenCount: code.split(/\s+/).length,
124
+ };
125
+ }
126
+ /** Generate framework-agnostic backend/infrastructure code for a non-UI node. Returns null if unrecognized. */
127
+ export function generateBackendCode(node) {
128
+ switch (node.type) {
129
+ case 'Model': return genModelCode(node);
130
+ case 'AuthDecl': return genAuthCode(node);
131
+ case 'RouteDecl': return genRouteCode(node);
132
+ case 'RoleDecl': return genRoleCode(node);
133
+ case 'Automation': return genAutomationCode(node);
134
+ case 'Dev': return genDevCode(node);
135
+ case 'Deploy': return genDeployCode(node);
136
+ case 'Env': return genEnvCode(node);
137
+ case 'Docker': return genDockerCode(node);
138
+ case 'Ci': return genCiCode(node);
139
+ case 'Domain': return genDomainCode(node);
140
+ case 'Cdn': return genCdnCode(node);
141
+ case 'Monitor': return genMonitorCode(node);
142
+ case 'Backup': return genBackupCode(node);
143
+ case 'Endpoint': return genEndpointCode(node);
144
+ case 'Middleware': return genMiddlewareCode(node);
145
+ case 'Queue': return genQueueCode(node);
146
+ case 'Cron': return genCronCode(node);
147
+ case 'Cache': return genCacheCode(node);
148
+ case 'Migrate': return genMigrateCode(node);
149
+ case 'Seed': return genSeedCode(node);
150
+ case 'Webhook': return genWebhookCode(node);
151
+ case 'Storage': return genStorageCode(node);
152
+ case 'Test': return genTestCode(node);
153
+ case 'E2e': return genE2eCode(node);
154
+ case 'Mock': return genMockCode(node);
155
+ case 'Fixture': return genFixtureCode(node);
156
+ case 'I18n': return genI18nCode(node);
157
+ case 'Locale': return genLocaleCode(node);
158
+ case 'Rtl': return genRtlCode(node);
159
+ default: return null;
160
+ }
161
+ }
162
+ function generateTopLevel(node) {
163
+ const c = ctx();
164
+ // First pass: collect states, derived, styles
165
+ for (const child of node.body) {
166
+ if (child.type === 'StateDecl')
167
+ c.states.set(child.name, child);
168
+ if (child.type === 'DerivedDecl')
169
+ c.derivedNames.add(child.name);
170
+ if (child.type === 'StyleDecl')
171
+ c.styles.set(child.name, child);
172
+ }
173
+ // Generate body parts
174
+ const hookLines = [];
175
+ const jsxParts = [];
176
+ const fnLines = [];
177
+ for (const child of node.body) {
178
+ switch (child.type) {
179
+ case 'StateDecl':
180
+ hookLines.push(genState(child, c));
181
+ break;
182
+ case 'DerivedDecl':
183
+ hookLines.push(genDerived(child, c));
184
+ break;
185
+ case 'PropDecl':
186
+ // Handled in function signature
187
+ break;
188
+ case 'TypeDecl':
189
+ // Type-only, skip in output (could generate JSDoc or TS type)
190
+ break;
191
+ case 'FnDecl':
192
+ fnLines.push(genFunction(child, c));
193
+ break;
194
+ case 'OnMount':
195
+ hookLines.push(genOnMount(child, c));
196
+ break;
197
+ case 'OnDestroy':
198
+ hookLines.push(genOnDestroy(child, c));
199
+ break;
200
+ case 'WatchBlock':
201
+ hookLines.push(genWatch(child, c));
202
+ break;
203
+ case 'CheckDecl':
204
+ hookLines.push(genCheck(child, c));
205
+ break;
206
+ case 'ApiDecl':
207
+ hookLines.push(genApi(child, c));
208
+ break;
209
+ case 'StoreDecl':
210
+ hookLines.push(genStore(child, c));
211
+ break;
212
+ case 'DataDecl':
213
+ hookLines.push(genDataDecl(child, c));
214
+ break;
215
+ case 'FormDecl':
216
+ hookLines.push(genFormDecl(child, c));
217
+ break;
218
+ case 'RealtimeDecl':
219
+ hookLines.push(genRealtimeDecl(child, c));
220
+ break;
221
+ case 'Model':
222
+ case 'AuthDecl':
223
+ case 'RouteDecl':
224
+ case 'RoleDecl':
225
+ case 'Deploy':
226
+ case 'Env':
227
+ case 'Docker':
228
+ case 'Ci':
229
+ case 'Domain':
230
+ case 'Cdn':
231
+ case 'Monitor':
232
+ case 'Backup':
233
+ case 'Endpoint':
234
+ case 'Middleware':
235
+ case 'Queue':
236
+ case 'Cron':
237
+ case 'Cache':
238
+ case 'Migrate':
239
+ case 'Seed':
240
+ case 'Webhook':
241
+ case 'Storage':
242
+ case 'Test':
243
+ case 'E2e':
244
+ case 'Mock':
245
+ case 'Fixture':
246
+ case 'I18n':
247
+ case 'Locale':
248
+ case 'Rtl':
249
+ // Handled at top level
250
+ break;
251
+ case 'StyleDecl':
252
+ // Collected already, used by reference
253
+ break;
254
+ case 'Comment':
255
+ // Skip
256
+ break;
257
+ default:
258
+ // UI element
259
+ jsxParts.push(genUINode(child, c));
260
+ break;
261
+ }
262
+ }
263
+ // Collect props
264
+ const props = node.body.filter(n => n.type === 'PropDecl');
265
+ const propsArg = props.length > 0
266
+ ? `{ ${props.map(p => p.defaultValue ? `${p.name} = ${genExpr(p.defaultValue, c)}` : p.name).join(', ')} }`
267
+ : '';
268
+ // Build import line
269
+ const reactImports = ['React'];
270
+ const hookNames = Array.from(c.imports).sort();
271
+ if (hookNames.length > 0) {
272
+ reactImports.push(...hookNames);
273
+ }
274
+ const importLine = `import ${reactImports[0]}, { ${hookNames.join(', ')} } from 'react';`;
275
+ // Build component
276
+ const isComponent = node.type === 'Component';
277
+ const exportKw = isComponent ? '' : 'export default ';
278
+ const lines = [
279
+ `// Generated by 0x`,
280
+ hookNames.length > 0 ? importLine : `import React from 'react';`,
281
+ '',
282
+ `${exportKw}function ${node.name}(${propsArg}) {`,
283
+ ...hookLines.map(l => ` ${l}`),
284
+ ...fnLines.map(l => ` ${l}`),
285
+ '',
286
+ ' return (',
287
+ ...jsxParts.map(l => ` ${l}`),
288
+ ' );',
289
+ '}',
290
+ ];
291
+ // If component, also export it
292
+ if (isComponent) {
293
+ lines.push('', `export { ${node.name} };`);
294
+ }
295
+ return lines.filter(l => l !== undefined).join('\n');
296
+ }
297
+ // ── State ───────────────────────────────────────────
298
+ function genState(node, c) {
299
+ c.imports.add('useState');
300
+ const setter = 'set' + capitalize(node.name);
301
+ const init = genExpr(node.initial, c);
302
+ return `const [${node.name}, ${setter}] = useState(${init});`;
303
+ }
304
+ function genDerived(node, c) {
305
+ c.imports.add('useMemo');
306
+ const expr = genExpr(node.expression, c);
307
+ const deps = extractDeps(node.expression, c);
308
+ return `const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
309
+ }
310
+ function genCheck(node, c) {
311
+ const cond = genExpr(node.condition, c);
312
+ return `if (!(${cond})) console.error('0x check failed: ${node.message}');`;
313
+ }
314
+ function genApi(node, c) {
315
+ return `const ${node.name} = async (params) => {\n const res = await fetch('${node.url}' + (params ? '?' + new URLSearchParams(params).toString() : ''), { method: '${node.method}' });\n return res.json();\n };`;
316
+ }
317
+ function genStore(node, c) {
318
+ c.imports.add('useState');
319
+ c.imports.add('useEffect');
320
+ const setter = 'set' + capitalize(node.name);
321
+ const init = genExpr(node.initial, c);
322
+ const key = node.name;
323
+ const lines = [
324
+ `const [${node.name}, ${setter}] = useState(() => {`,
325
+ ` const stored = localStorage.getItem('${key}');`,
326
+ ` return stored ? JSON.parse(stored) : ${init};`,
327
+ ` });`,
328
+ ];
329
+ return lines.join('\n ');
330
+ }
331
+ // ── Functions ───────────────────────────────────────
332
+ function genFunction(node, c) {
333
+ const params = node.params.map(p => p.name).join(', ');
334
+ const asyncKw = node.isAsync ? 'async ' : '';
335
+ const body = node.body.map(s => genStatement(s, c)).join('\n ');
336
+ // requires
337
+ const requireChecks = node.requires.map(r => `if (!(${genExpr(r, c)})) throw new Error('Precondition failed');`).join('\n ');
338
+ const allBody = [requireChecks, body].filter(Boolean).join('\n ');
339
+ return `const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
340
+ }
341
+ // ── Lifecycle ───────────────────────────────────────
342
+ function genOnMount(node, c) {
343
+ c.imports.add('useEffect');
344
+ const body = node.body.map(s => genStatement(s, c)).join('\n ');
345
+ return `useEffect(() => {\n ${body}\n }, []);`;
346
+ }
347
+ function genOnDestroy(node, c) {
348
+ c.imports.add('useEffect');
349
+ const body = node.body.map(s => genStatement(s, c)).join('\n ');
350
+ return `useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
351
+ }
352
+ function genWatch(node, c) {
353
+ c.imports.add('useEffect');
354
+ const body = node.body.map(s => genStatement(s, c)).join('\n ');
355
+ return `useEffect(() => {\n ${body}\n }, [${node.variable}]);`;
356
+ }
357
+ // ── UI Nodes ────────────────────────────────────────
358
+ function genUINode(node, c) {
359
+ switch (node.type) {
360
+ case 'Layout': return genLayout(node, c);
361
+ case 'Text': return genText(node, c);
362
+ case 'Button': return genButton(node, c);
363
+ case 'Input': return genInput(node, c);
364
+ case 'Image': return genImage(node, c);
365
+ case 'Link': return genLink(node, c);
366
+ case 'Toggle': return genToggle(node, c);
367
+ case 'Select': return genSelect(node, c);
368
+ case 'IfBlock': return genIf(node, c);
369
+ case 'ForBlock': return genFor(node, c);
370
+ case 'ShowBlock': return genShow(node, c);
371
+ case 'HideBlock': return genHide(node, c);
372
+ case 'ComponentCall': return genComponentCall(node, c);
373
+ case 'Table': return genTableUI(node, c);
374
+ case 'Chart': return genChartUI(node, c);
375
+ case 'Stat': return genStatUI(node, c);
376
+ case 'Nav': return genNavUI(node, c);
377
+ case 'Upload': return genUploadUI(node, c);
378
+ case 'Modal': return genModalUI(node, c);
379
+ case 'Toast': return genToastUI(node, c);
380
+ case 'Comment': return `{/* ${node.text} */}`;
381
+ // Phase 3 nodes
382
+ case 'Crud': return genCrudUI(node, c);
383
+ case 'List': return genListUI(node, c);
384
+ case 'Drawer': return genDrawerUI(node, c);
385
+ case 'Command': return genCommandUI(node, c);
386
+ case 'Confirm': return genConfirmUI(node, c);
387
+ case 'Pay': return genPayUI(node, c);
388
+ case 'Cart': return genCartUI(node, c);
389
+ case 'Media': return genMediaUI(node, c);
390
+ case 'Notification': return genNotificationUI(node, c);
391
+ case 'Search': return genSearchUI(node, c);
392
+ case 'Filter': return genFilterUI(node, c);
393
+ case 'Social': return genSocialUI(node, c);
394
+ case 'Profile': return genProfileUI(node, c);
395
+ case 'Hero': return genHeroUI(node, c);
396
+ case 'Features': return genFeaturesUI(node, c);
397
+ case 'Pricing': return genPricingUI(node, c);
398
+ case 'Faq': return genFaqUI(node, c);
399
+ case 'Testimonial': return genTestimonialUI(node, c);
400
+ case 'Footer': return genFooterUI(node, c);
401
+ case 'Admin': return genAdminUI(node, c);
402
+ case 'Seo': return genSeoUI(node, c);
403
+ case 'A11y': return `{/* a11y configured */}`;
404
+ case 'Animate': return genAnimateUI(node, c);
405
+ case 'Gesture': return genGestureUI(node, c);
406
+ case 'Ai': return genAiUI(node, c);
407
+ case 'Emit': return `{/* emit: ${genExpr(node.channel, c)} */}`;
408
+ case 'Responsive': return genResponsiveUI(node, c);
409
+ case 'Breadcrumb': return genBreadcrumbUI(node, c);
410
+ case 'StatsGrid': return genStatsGridUI(node, c);
411
+ case 'LayoutShell': return `<div className="layout-shell">${node.body.map(ch => genUINode(ch, c)).join('\n')}</div>`;
412
+ case 'SlideOver': return genDrawerUI(node, c);
413
+ case 'Automation': return `{/* automation configured */}`;
414
+ case 'Dev': return `{/* dev tools configured */}`;
415
+ // Phase 4: Error/Loading as UI
416
+ case 'Error': return genErrorUI(node, c);
417
+ case 'Loading': return genLoadingUI(node, c);
418
+ case 'Offline': return genOfflineUI(node, c);
419
+ case 'Retry': return `{/* retry: max=${genExpr(node.maxRetries, c)} */}`;
420
+ case 'Log': return `{/* log: ${genExpr(node.message, c)} */}`;
421
+ default: return `{/* unsupported: ${node.type} */}`;
422
+ }
423
+ }
424
+ function genLayout(node, c) {
425
+ const style = {};
426
+ if (node.direction === 'grid') {
427
+ style['display'] = 'grid';
428
+ if (node.props['cols']) {
429
+ const cols = genExpr(node.props['cols'], c);
430
+ style['gridTemplateColumns'] = `repeat(${cols}, 1fr)`;
431
+ }
432
+ }
433
+ else if (node.direction === 'stack') {
434
+ style['position'] = 'relative';
435
+ }
436
+ else {
437
+ style['display'] = 'flex';
438
+ style['flexDirection'] = node.direction === 'row' ? 'row' : 'column';
439
+ }
440
+ // Process layout props
441
+ for (const [key, val] of Object.entries(node.props)) {
442
+ const v = genExpr(val, c);
443
+ switch (key) {
444
+ case 'gap':
445
+ style['gap'] = `${v}px`;
446
+ break;
447
+ case 'padding':
448
+ style['padding'] = `${v}px`;
449
+ break;
450
+ case 'margin':
451
+ style['margin'] = v;
452
+ break;
453
+ case 'maxWidth':
454
+ style['maxWidth'] = `${v}px`;
455
+ break;
456
+ case 'height':
457
+ style['height'] = v;
458
+ break;
459
+ case 'bg':
460
+ style['backgroundColor'] = v;
461
+ break;
462
+ case 'center':
463
+ style['alignItems'] = 'center';
464
+ break;
465
+ case 'middle':
466
+ style['justifyContent'] = 'center';
467
+ break;
468
+ case 'between':
469
+ style['justifyContent'] = 'space-between';
470
+ break;
471
+ case 'end':
472
+ style['justifyContent'] = 'flex-end';
473
+ break;
474
+ case 'grow':
475
+ style['flexGrow'] = v;
476
+ break;
477
+ case 'scroll':
478
+ style['overflow' + (v === 'y' ? 'Y' : 'X')] = 'auto';
479
+ break;
480
+ case 'radius':
481
+ style['borderRadius'] = `${v}px`;
482
+ break;
483
+ case 'shadow':
484
+ if (v === 'sm')
485
+ style['boxShadow'] = '0 1px 2px rgba(0,0,0,0.1)';
486
+ else if (v === 'md')
487
+ style['boxShadow'] = '0 4px 6px rgba(0,0,0,0.1)';
488
+ else if (v === 'lg')
489
+ style['boxShadow'] = '0 10px 15px rgba(0,0,0,0.1)';
490
+ break;
491
+ }
492
+ }
493
+ // Apply style class
494
+ if (node.styleClass && c.styles.has(node.styleClass)) {
495
+ const styleDecl = c.styles.get(node.styleClass);
496
+ for (const prop of styleDecl.properties) {
497
+ if (!prop.responsive) {
498
+ const key = cssPropToJs(prop.name);
499
+ const val = genExpr(prop.value, c);
500
+ style[key] = formatStyleValue(prop.name, val);
501
+ }
502
+ }
503
+ }
504
+ const styleStr = genStyleObj(style);
505
+ const children = node.children.map(ch => genUINode(ch, c)).join('\n');
506
+ return `<div style={${styleStr}}>\n${children}\n</div>`;
507
+ }
508
+ function genText(node, c) {
509
+ const style = {};
510
+ for (const [key, val] of Object.entries(node.props)) {
511
+ const v = genExpr(val, c);
512
+ switch (key) {
513
+ case 'size': {
514
+ const uv = unquote(v);
515
+ style['fontSize'] = SIZE_MAP[uv] || `${uv}px`;
516
+ break;
517
+ }
518
+ case 'bold':
519
+ style['fontWeight'] = 'bold';
520
+ break;
521
+ case 'color':
522
+ style['color'] = v;
523
+ break;
524
+ case 'bg':
525
+ style['backgroundColor'] = v;
526
+ break;
527
+ case 'center':
528
+ style['textAlign'] = 'center';
529
+ break;
530
+ case 'end':
531
+ style['textAlign'] = 'right';
532
+ break;
533
+ case 'strike': {
534
+ // strike={condition} → conditional textDecoration
535
+ const cond = genExpr(val, c);
536
+ style['textDecoration'] = `\${${cond} ? 'line-through' : 'none'}`;
537
+ break;
538
+ }
539
+ }
540
+ }
541
+ const content = genTextContent(node.content, c);
542
+ const styleStr = Object.keys(style).length > 0 ? ` style={${genStyleObj(style)}}` : '';
543
+ return `<span${styleStr}>${content}</span>`;
544
+ }
545
+ function genButton(node, c) {
546
+ const label = genTextContent(node.label, c);
547
+ const actionCode = genActionExpr(node.action, c);
548
+ const styleProps = [];
549
+ for (const [key, val] of Object.entries(node.props)) {
550
+ const v = genExpr(val, c);
551
+ switch (key) {
552
+ case 'style':
553
+ styleProps.push(`className="${v}"`);
554
+ break;
555
+ case 'disabled':
556
+ styleProps.push(`disabled={${v}}`);
557
+ break;
558
+ case 'size': /* handled in style */ break;
559
+ }
560
+ }
561
+ const propsStr = styleProps.join(' ');
562
+ return `<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}>${label}</button>`;
563
+ }
564
+ function genInput(node, c) {
565
+ const setter = 'set' + capitalize(node.binding);
566
+ const props = [
567
+ `value={${node.binding}}`,
568
+ `onChange={e => ${setter}(e.target.value)}`,
569
+ ];
570
+ for (const [key, val] of Object.entries(node.props)) {
571
+ const v = genExpr(val, c);
572
+ switch (key) {
573
+ case 'placeholder':
574
+ props.push(`placeholder=${quoteJsx(v)}`);
575
+ break;
576
+ case 'type':
577
+ props.push(`type=${quoteJsx(v)}`);
578
+ break;
579
+ }
580
+ }
581
+ // Check for @keypress event
582
+ for (const [key, val] of Object.entries(node.props)) {
583
+ if (key === '@keypress') {
584
+ const handler = genExpr(val, c);
585
+ props.push(`onKeyPress={e => ${handler}(e.key)}`);
586
+ }
587
+ }
588
+ return `<input ${props.join(' ')} />`;
589
+ }
590
+ function genImage(node, c) {
591
+ const src = genExpr(node.src, c);
592
+ const props = [`src={${src}}`];
593
+ for (const [key, val] of Object.entries(node.props)) {
594
+ const v = genExpr(val, c);
595
+ switch (key) {
596
+ case 'width':
597
+ props.push(`width="${v}"`);
598
+ break;
599
+ case 'height':
600
+ props.push(`height="${v}"`);
601
+ break;
602
+ case 'alt':
603
+ props.push(`alt=${quoteJsx(v)}`);
604
+ break;
605
+ }
606
+ }
607
+ return `<img ${props.join(' ')} />`;
608
+ }
609
+ function genLink(node, c) {
610
+ const label = genTextContent(node.label, c);
611
+ const href = genExpr(node.href, c);
612
+ return `<a href={${href}}>${label}</a>`;
613
+ }
614
+ function genToggle(node, c) {
615
+ const binding = node.binding;
616
+ // Handle member expressions like item.done
617
+ const parts = binding.split('.');
618
+ let setter;
619
+ if (parts.length === 1) {
620
+ setter = `set${capitalize(parts[0])}(!${binding})`;
621
+ }
622
+ else {
623
+ setter = `set${capitalize(parts[0])}(prev => ({...prev, ${parts.slice(1).join('.')}: !prev.${parts.slice(1).join('.')}}))`;
624
+ }
625
+ return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}} />`;
626
+ }
627
+ function genSelect(node, c) {
628
+ const setter = 'set' + capitalize(node.binding);
629
+ const options = genExpr(node.options, c);
630
+ return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
631
+ }
632
+ function genComponentCall(node, c) {
633
+ const props = Object.entries(node.args)
634
+ .map(([key, val]) => {
635
+ if (key.startsWith('_arg')) {
636
+ // Positional arg — use as spread or first prop
637
+ return genExpr(val, c);
638
+ }
639
+ return `${key}={${genExpr(val, c)}}`;
640
+ });
641
+ // If positional args, treat first one as spread prop
642
+ const posArgs = Object.entries(node.args).filter(([k]) => k.startsWith('_arg'));
643
+ if (posArgs.length > 0) {
644
+ const propsStr = posArgs.map(([, v]) => `{...${genExpr(v, c)}}`).join(' ');
645
+ return `<${node.name} ${propsStr} />`;
646
+ }
647
+ return `<${node.name} ${props.join(' ')} />`;
648
+ }
649
+ // ── Control Flow ────────────────────────────────────
650
+ function genIf(node, c) {
651
+ const cond = genExpr(node.condition, c);
652
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
653
+ if (node.elseBody && node.elseBody.length > 0) {
654
+ const elseBody = node.elseBody.map(ch => genUINode(ch, c)).join('\n');
655
+ if (node.elifs.length > 0) {
656
+ let result = `{${cond} ? (\n${body}\n)`;
657
+ for (const elif of node.elifs) {
658
+ const elifCond = genExpr(elif.condition, c);
659
+ const elifBody = elif.body.map(ch => genUINode(ch, c)).join('\n');
660
+ result += ` : ${elifCond} ? (\n${elifBody}\n)`;
661
+ }
662
+ result += ` : (\n${elseBody}\n)}`;
663
+ return result;
664
+ }
665
+ return `{${cond} ? (\n${body}\n) : (\n${elseBody}\n)}`;
666
+ }
667
+ return `{${cond} && (\n${body}\n)}`;
668
+ }
669
+ function genFor(node, c) {
670
+ const item = node.item;
671
+ const iter = genExpr(node.iterable, c);
672
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
673
+ const params = node.index ? `(${item}, ${node.index})` : `(${item})`;
674
+ return `{${iter}.map(${params} => (\n${body}\n))}`;
675
+ }
676
+ function genShow(node, c) {
677
+ const cond = genExpr(node.condition, c);
678
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
679
+ return `<div style={{ display: ${cond} ? 'block' : 'none' }}>\n${body}\n</div>`;
680
+ }
681
+ function genHide(node, c) {
682
+ const cond = genExpr(node.condition, c);
683
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
684
+ return `<div style={{ display: ${cond} ? 'none' : 'block' }}>\n${body}\n</div>`;
685
+ }
686
+ // ── Statements ──────────────────────────────────────
687
+ function genStatement(stmt, c) {
688
+ switch (stmt.kind) {
689
+ case 'expr_stmt':
690
+ return genExpr(stmt.expression, c) + ';';
691
+ case 'return':
692
+ return stmt.value ? `return ${genExpr(stmt.value, c)};` : 'return;';
693
+ case 'assignment_stmt': {
694
+ const target = genExpr(stmt.target, c);
695
+ const value = genExpr(stmt.value, c);
696
+ // If target is a state, use setter
697
+ const stateName = extractStateName(stmt.target);
698
+ if (stateName && c.states.has(stateName)) {
699
+ const setter = 'set' + capitalize(stateName);
700
+ if (stmt.op === '=') {
701
+ return `${setter}(${value});`;
702
+ }
703
+ else if (stmt.op === '+=') {
704
+ return `${setter}(prev => prev + ${value});`;
705
+ }
706
+ else if (stmt.op === '-=') {
707
+ return `${setter}(prev => prev - ${value});`;
708
+ }
709
+ }
710
+ return `${target} ${stmt.op} ${value};`;
711
+ }
712
+ case 'var_decl':
713
+ return `const ${stmt.name} = ${genExpr(stmt.value, c)};`;
714
+ case 'if_stmt': {
715
+ const cond = genExpr(stmt.condition, c);
716
+ const body = stmt.body.map(s => genStatement(s, c)).join('\n ');
717
+ let result = `if (${cond}) {\n ${body}\n }`;
718
+ for (const elif of stmt.elifs) {
719
+ const ec = genExpr(elif.condition, c);
720
+ const eb = elif.body.map(s => genStatement(s, c)).join('\n ');
721
+ result += ` else if (${ec}) {\n ${eb}\n }`;
722
+ }
723
+ if (stmt.elseBody) {
724
+ const eb = stmt.elseBody.map(s => genStatement(s, c)).join('\n ');
725
+ result += ` else {\n ${eb}\n }`;
726
+ }
727
+ return result;
728
+ }
729
+ case 'for_stmt': {
730
+ const iter = genExpr(stmt.iterable, c);
731
+ const body = stmt.body.map(s => genStatement(s, c)).join('\n ');
732
+ return `for (const ${stmt.item} of ${iter}) {\n ${body}\n }`;
733
+ }
734
+ }
735
+ }
736
+ // ── Expressions ─────────────────────────────────────
737
+ function genExpr(expr, c) {
738
+ switch (expr.kind) {
739
+ case 'number': return String(expr.value);
740
+ case 'string': return `'${expr.value}'`;
741
+ case 'boolean': return String(expr.value);
742
+ case 'null': return 'null';
743
+ case 'identifier': return expr.name;
744
+ case 'member': return `${genExpr(expr.object, c)}.${expr.property}`;
745
+ case 'index': return `${genExpr(expr.object, c)}[${genExpr(expr.index, c)}]`;
746
+ case 'call': {
747
+ const callee = genExpr(expr.callee, c);
748
+ const args = expr.args.map(a => genExpr(a, c)).join(', ');
749
+ // Handle state mutation methods: items.push(), items.filter(), etc.
750
+ if (expr.callee.kind === 'member') {
751
+ const objName = extractStateName(expr.callee.object);
752
+ const method = expr.callee.property;
753
+ if (objName && c.states.has(objName)) {
754
+ const setter = 'set' + capitalize(objName);
755
+ if (method === 'push') {
756
+ return `${setter}(prev => [...prev, ${args}])`;
757
+ }
758
+ if (method === 'filter') {
759
+ return `${setter}(prev => prev.filter(${args}))`;
760
+ }
761
+ if (method === 'remove') {
762
+ return `${setter}(prev => prev.filter(item => item.id !== ${args}))`;
763
+ }
764
+ }
765
+ }
766
+ return `${callee}(${args})`;
767
+ }
768
+ case 'binary': return `${genExpr(expr.left, c)} ${expr.op} ${genExpr(expr.right, c)}`;
769
+ case 'unary': return `${expr.op}${genExpr(expr.operand, c)}`;
770
+ case 'ternary': return `${genExpr(expr.condition, c)} ? ${genExpr(expr.consequent, c)} : ${genExpr(expr.alternate, c)}`;
771
+ case 'arrow': {
772
+ const params = expr.params.join(', ');
773
+ if (Array.isArray(expr.body)) {
774
+ const body = expr.body.map(s => genStatement(s, c)).join('\n');
775
+ return `(${params}) => { ${body} }`;
776
+ }
777
+ return `${params} => ${genExpr(expr.body, c)}`;
778
+ }
779
+ case 'array': {
780
+ const els = expr.elements.map(e => genExpr(e, c)).join(', ');
781
+ return `[${els}]`;
782
+ }
783
+ case 'object_expr': {
784
+ const props = expr.properties.map(p => {
785
+ if (p.key === p.value.kind && p.value.kind === 'identifier') {
786
+ return p.key; // shorthand
787
+ }
788
+ return `${p.key}: ${genExpr(p.value, c)}`;
789
+ }).join(', ');
790
+ return `{ ${props} }`;
791
+ }
792
+ case 'template': {
793
+ const parts = expr.parts.map(p => {
794
+ if (typeof p === 'string')
795
+ return p;
796
+ return `\${${genExpr(p, c)}}`;
797
+ }).join('');
798
+ return '`' + parts + '`';
799
+ }
800
+ case 'assignment': {
801
+ const target = genExpr(expr.target, c);
802
+ const value = genExpr(expr.value, c);
803
+ const stateName = extractStateName(expr.target);
804
+ if (stateName && c.states.has(stateName)) {
805
+ const setter = 'set' + capitalize(stateName);
806
+ if (expr.op === '=')
807
+ return `${setter}(${value})`;
808
+ if (expr.op === '+=')
809
+ return `${setter}(prev => prev + ${value})`;
810
+ if (expr.op === '-=')
811
+ return `${setter}(prev => prev - ${value})`;
812
+ }
813
+ return `${target} ${expr.op} ${value}`;
814
+ }
815
+ case 'await': return `await ${genExpr(expr.expression, c)}`;
816
+ case 'old': return genExpr(expr.expression, c); // old() is for contracts, simplified
817
+ }
818
+ }
819
+ function genTextContent(expr, c) {
820
+ if (expr.kind === 'string')
821
+ return expr.value;
822
+ if (expr.kind === 'template') {
823
+ return expr.parts.map(p => {
824
+ if (typeof p === 'string')
825
+ return p;
826
+ return `{${genExpr(p, c)}}`;
827
+ }).join('');
828
+ }
829
+ return `{${genExpr(expr, c)}}`;
830
+ }
831
+ function genActionExpr(expr, c) {
832
+ if (Array.isArray(expr)) {
833
+ return `{ ${expr.map(s => genStatement(s, c)).join('; ')} }`;
834
+ }
835
+ // Assignment expression for actions like count += 1
836
+ if (expr.kind === 'assignment') {
837
+ const stateName = extractStateName(expr.target);
838
+ if (stateName && c.states.has(stateName)) {
839
+ const setter = 'set' + capitalize(stateName);
840
+ const value = genExpr(expr.value, c);
841
+ if (expr.op === '+=')
842
+ return `${setter}(prev => prev + ${value})`;
843
+ if (expr.op === '-=')
844
+ return `${setter}(prev => prev - ${value})`;
845
+ if (expr.op === '=')
846
+ return `${setter}(${value})`;
847
+ }
848
+ }
849
+ if (expr.kind === 'call') {
850
+ return genExpr(expr, c);
851
+ }
852
+ return genExpr(expr, c);
853
+ }
854
+ // ── Phase 1 Advanced: Model ─────────────────────────
855
+ function genModelCode(node) {
856
+ const name = node.name;
857
+ const lines = [
858
+ `// Generated by 0x — Model: ${name}`,
859
+ '',
860
+ ];
861
+ // Generate TypeScript-style interface as JSDoc
862
+ lines.push(`/** @typedef {Object} ${name}`);
863
+ for (const f of node.fields) {
864
+ lines.push(` * @property {${typeExprToJs(f.fieldType)}} ${f.name}`);
865
+ }
866
+ lines.push(' */');
867
+ lines.push('');
868
+ // Generate validation function
869
+ if (node.validate.length > 0) {
870
+ lines.push(`function validate${name}(data) {`);
871
+ lines.push(' const errors = [];');
872
+ for (const v of node.validate) {
873
+ const cond = genExprStandalone(v.condition);
874
+ lines.push(` if (!(${cond.replace(/\b([a-zA-Z_]\w*)\.(\w+)/g, 'data.$1 && data.$1.$2').replace(/^(?!data\.)([a-zA-Z_]\w*)/gm, (m) => m.startsWith('data.') ? m : `data.${m}`)})) errors.push('${v.message}');`);
875
+ }
876
+ lines.push(" return errors;");
877
+ lines.push('}');
878
+ lines.push('');
879
+ }
880
+ // Generate CRUD API functions
881
+ const lower = name.toLowerCase();
882
+ lines.push(`// CRUD API for ${name}`);
883
+ lines.push(`const ${name}API = {`);
884
+ lines.push(` findAll: async (params = {}) => {`);
885
+ lines.push(` const query = new URLSearchParams(params).toString();`);
886
+ lines.push(` const res = await fetch(\`/api/${lower}s\${query ? '?' + query : ''}\`);`);
887
+ lines.push(` return res.json();`);
888
+ lines.push(` },`);
889
+ lines.push(` findById: async (id) => {`);
890
+ lines.push(` const res = await fetch(\`/api/${lower}s/\${id}\`);`);
891
+ lines.push(` return res.json();`);
892
+ lines.push(` },`);
893
+ lines.push(` create: async (data) => {`);
894
+ if (node.validate.length > 0) {
895
+ lines.push(` const errors = validate${name}(data);`);
896
+ lines.push(` if (errors.length > 0) throw new Error(errors.join(', '));`);
897
+ }
898
+ lines.push(` const res = await fetch('/api/${lower}s', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });`);
899
+ lines.push(` return res.json();`);
900
+ lines.push(` },`);
901
+ lines.push(` update: async (id, data) => {`);
902
+ lines.push(` const res = await fetch(\`/api/${lower}s/\${id}\`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });`);
903
+ lines.push(` return res.json();`);
904
+ lines.push(` },`);
905
+ lines.push(` delete: async (id) => {`);
906
+ lines.push(` const res = await fetch(\`/api/${lower}s/\${id}\`, { method: 'DELETE' });`);
907
+ lines.push(` return res.json();`);
908
+ lines.push(` },`);
909
+ lines.push(`};`);
910
+ lines.push('');
911
+ // React hooks for model
912
+ lines.push(`// React hooks for ${name}`);
913
+ lines.push('');
914
+ lines.push(`function use${name}s(params = {}) {`);
915
+ lines.push(` const [data, setData] = useState([]);`);
916
+ lines.push(` const [loading, setLoading] = useState(true);`);
917
+ lines.push(` const [error, setError] = useState(null);`);
918
+ lines.push(` const fetch${name}s = useCallback(async () => {`);
919
+ lines.push(` setLoading(true);`);
920
+ lines.push(` try {`);
921
+ lines.push(` const result = await ${name}API.findAll(params);`);
922
+ lines.push(` setData(result);`);
923
+ lines.push(` setError(null);`);
924
+ lines.push(` } catch (e) {`);
925
+ lines.push(` setError(e.message);`);
926
+ lines.push(` } finally {`);
927
+ lines.push(` setLoading(false);`);
928
+ lines.push(` }`);
929
+ lines.push(` }, [JSON.stringify(params)]);`);
930
+ lines.push(` useEffect(() => { fetch${name}s(); }, [fetch${name}s]);`);
931
+ lines.push(` return { data, loading, error, refetch: fetch${name}s };`);
932
+ lines.push(`}`);
933
+ lines.push('');
934
+ lines.push(`function useCreate${name}() {`);
935
+ lines.push(` const [loading, setLoading] = useState(false);`);
936
+ lines.push(` const create = async (data) => {`);
937
+ lines.push(` setLoading(true);`);
938
+ lines.push(` try { return await ${name}API.create(data); }`);
939
+ lines.push(` finally { setLoading(false); }`);
940
+ lines.push(` };`);
941
+ lines.push(` return { create, loading };`);
942
+ lines.push(`}`);
943
+ return lines.join('\n');
944
+ }
945
+ function typeExprToJs(t) {
946
+ switch (t.kind) {
947
+ case 'primitive': {
948
+ const map = { int: 'number', float: 'number', str: 'string', bool: 'boolean', datetime: 'Date', date: 'Date', time: 'string' };
949
+ return map[t.name] || t.name;
950
+ }
951
+ case 'list': return `${typeExprToJs(t.itemType)}[]`;
952
+ case 'named': return t.name;
953
+ case 'nullable': return `${typeExprToJs(t.inner)} | null`;
954
+ default: return 'any';
955
+ }
956
+ }
957
+ function genExprStandalone(expr) {
958
+ const c = ctx();
959
+ return genExpr(expr, c);
960
+ }
961
+ // ── Phase 1 Advanced: Data Fetching ─────────────────
962
+ function genDataDecl(node, c) {
963
+ c.imports.add('useState');
964
+ c.imports.add('useEffect');
965
+ const query = genExpr(node.query, c);
966
+ const lines = [];
967
+ lines.push(`const [${node.name}, set${capitalize(node.name)}] = useState([]);`);
968
+ lines.push(`const [${node.name}Loading, set${capitalize(node.name)}Loading] = useState(true);`);
969
+ lines.push(`const [${node.name}Error, set${capitalize(node.name)}Error] = useState(null);`);
970
+ lines.push(`useEffect(() => {`);
971
+ lines.push(` let cancelled = false;`);
972
+ lines.push(` (async () => {`);
973
+ lines.push(` try {`);
974
+ lines.push(` const result = await ${query};`);
975
+ lines.push(` if (!cancelled) { set${capitalize(node.name)}(result); set${capitalize(node.name)}Error(null); }`);
976
+ lines.push(` } catch (e) {`);
977
+ lines.push(` if (!cancelled) set${capitalize(node.name)}Error(e.message);`);
978
+ lines.push(` } finally {`);
979
+ lines.push(` if (!cancelled) set${capitalize(node.name)}Loading(false);`);
980
+ lines.push(` }`);
981
+ lines.push(` })();`);
982
+ lines.push(` return () => { cancelled = true; };`);
983
+ lines.push(` }, []);`);
984
+ // Register as state so JSX can reference it
985
+ c.states.set(node.name, { type: 'StateDecl', name: node.name, valueType: { kind: 'primitive', name: 'any' }, initial: { kind: 'array', elements: [] }, loc: node.loc });
986
+ return lines.join('\n ');
987
+ }
988
+ // ── Phase 1 Advanced: Form ──────────────────────────
989
+ function genFormDecl(node, c) {
990
+ c.imports.add('useState');
991
+ c.imports.add('useCallback');
992
+ const lines = [];
993
+ // Form state: one state per field
994
+ const initialValues = [];
995
+ for (const f of node.fields) {
996
+ const defaultVal = getFieldDefault(f.fieldType);
997
+ initialValues.push(`${f.name}: ${defaultVal}`);
998
+ }
999
+ lines.push(`const [${node.name}, set${capitalize(node.name)}] = useState({ ${initialValues.join(', ')} });`);
1000
+ lines.push(`const [${node.name}Errors, set${capitalize(node.name)}Errors] = useState({});`);
1001
+ lines.push(`const [${node.name}Submitting, set${capitalize(node.name)}Submitting] = useState(false);`);
1002
+ // Update handler
1003
+ lines.push(`const update${capitalize(node.name)} = (field, value) => {`);
1004
+ lines.push(` set${capitalize(node.name)}(prev => ({ ...prev, [field]: value }));`);
1005
+ lines.push(` set${capitalize(node.name)}Errors(prev => ({ ...prev, [field]: null }));`);
1006
+ lines.push(` };`);
1007
+ // Validate function
1008
+ lines.push(`const validate${capitalize(node.name)} = useCallback(() => {`);
1009
+ lines.push(` const errors = {};`);
1010
+ for (const f of node.fields) {
1011
+ for (const v of f.validations) {
1012
+ switch (v.rule) {
1013
+ case 'required':
1014
+ lines.push(` if (!${node.name}.${f.name}) errors.${f.name} = '${v.message}';`);
1015
+ break;
1016
+ case 'min':
1017
+ lines.push(` if (${node.name}.${f.name}.length < ${genExprStandalone(v.value)}) errors.${f.name} = '${v.message}';`);
1018
+ break;
1019
+ case 'max':
1020
+ lines.push(` if (${node.name}.${f.name}.length > ${genExprStandalone(v.value)}) errors.${f.name} = '${v.message}';`);
1021
+ break;
1022
+ case 'format':
1023
+ if (v.value?.kind === 'string' && v.value.value === 'email') {
1024
+ lines.push(` if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(${node.name}.${f.name})) errors.${f.name} = '${v.message}';`);
1025
+ }
1026
+ break;
1027
+ case 'pattern':
1028
+ if (v.value?.kind === 'string') {
1029
+ lines.push(` if (!/${v.value.value}/.test(${node.name}.${f.name})) errors.${f.name} = '${v.message}';`);
1030
+ }
1031
+ break;
1032
+ }
1033
+ }
1034
+ }
1035
+ lines.push(` set${capitalize(node.name)}Errors(errors);`);
1036
+ lines.push(` return Object.keys(errors).length === 0;`);
1037
+ lines.push(` }, [${node.name}]);`);
1038
+ // Submit handler
1039
+ if (node.submit) {
1040
+ const action = genExpr(node.submit.action, c);
1041
+ lines.push(`const submit${capitalize(node.name)} = async () => {`);
1042
+ lines.push(` if (!validate${capitalize(node.name)}()) return;`);
1043
+ lines.push(` set${capitalize(node.name)}Submitting(true);`);
1044
+ lines.push(` try {`);
1045
+ lines.push(` await ${action};`);
1046
+ if (node.submit.success) {
1047
+ lines.push(` ${genExpr(node.submit.success, c)};`);
1048
+ }
1049
+ lines.push(` } catch (e) {`);
1050
+ if (node.submit.error) {
1051
+ lines.push(` ${genExpr(node.submit.error, c)};`);
1052
+ }
1053
+ lines.push(` } finally {`);
1054
+ lines.push(` set${capitalize(node.name)}Submitting(false);`);
1055
+ lines.push(` }`);
1056
+ lines.push(` };`);
1057
+ }
1058
+ return lines.join('\n ');
1059
+ }
1060
+ function getFieldDefault(t) {
1061
+ if (t.kind === 'primitive') {
1062
+ if (t.name === 'str')
1063
+ return "''";
1064
+ if (t.name === 'int' || t.name === 'float')
1065
+ return '0';
1066
+ if (t.name === 'bool')
1067
+ return 'false';
1068
+ }
1069
+ return "''";
1070
+ }
1071
+ // ── Phase 1 Advanced: Table ─────────────────────────
1072
+ function genTableUI(node, c) {
1073
+ const data = node.dataSource;
1074
+ const lines = [];
1075
+ // Sorting state
1076
+ const hasSortable = node.columns.some(col => col.sortable);
1077
+ if (hasSortable) {
1078
+ lines.push(`{(() => {`);
1079
+ }
1080
+ lines.push(`<table style={{ width: '100%', borderCollapse: 'collapse' }}>`);
1081
+ // Header
1082
+ lines.push(`<thead>`);
1083
+ lines.push(`<tr>`);
1084
+ for (const col of node.columns) {
1085
+ if (col.kind === 'select') {
1086
+ lines.push(`<th style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left' }}><input type="checkbox" /></th>`);
1087
+ }
1088
+ else if (col.kind === 'field') {
1089
+ const sortAttr = col.sortable ? ` style={{ cursor: 'pointer', padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left', fontWeight: 600 }}` : ` style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left', fontWeight: 600 }}`;
1090
+ lines.push(`<th${sortAttr}>${col.label || col.field}</th>`);
1091
+ }
1092
+ else if (col.kind === 'actions') {
1093
+ lines.push(`<th style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'right', fontWeight: 600 }}>Actions</th>`);
1094
+ }
1095
+ }
1096
+ lines.push(`</tr>`);
1097
+ lines.push(`</thead>`);
1098
+ // Body
1099
+ lines.push(`<tbody>`);
1100
+ lines.push(`{${data}.map((row, idx) => (`);
1101
+ lines.push(`<tr key={idx} style={{ borderBottom: '1px solid #e2e8f0' }}>`);
1102
+ for (const col of node.columns) {
1103
+ if (col.kind === 'select') {
1104
+ lines.push(`<td style={{ padding: '12px 8px' }}><input type="checkbox" /></td>`);
1105
+ }
1106
+ else if (col.kind === 'field') {
1107
+ const fieldAccess = col.field.includes('.') ? `row.${col.field}` : `row.${col.field}`;
1108
+ let content = `{${fieldAccess}}`;
1109
+ if (col.format) {
1110
+ if (col.format === 'date')
1111
+ content = `{new Date(${fieldAccess}).toLocaleDateString()}`;
1112
+ else if (col.format.startsWith('currency')) {
1113
+ const currency = col.format.match(/\((\w+)\)/)?.[1] || 'KRW';
1114
+ content = `{new Intl.NumberFormat('ko-KR', { style: 'currency', currency: '${currency}' }).format(${fieldAccess})}`;
1115
+ }
1116
+ }
1117
+ lines.push(`<td style={{ padding: '12px 8px' }}>${content}</td>`);
1118
+ }
1119
+ else if (col.kind === 'actions') {
1120
+ lines.push(`<td style={{ padding: '12px 8px', textAlign: 'right' }}>`);
1121
+ for (const action of (col.actions || [])) {
1122
+ if (action === 'edit') {
1123
+ lines.push(`<button onClick={() => onEdit(row)} style={{ marginRight: '4px', padding: '4px 8px', fontSize: '13px', cursor: 'pointer' }}>Edit</button>`);
1124
+ }
1125
+ else if (action === 'delete') {
1126
+ lines.push(`<button onClick={() => onDelete(row)} style={{ padding: '4px 8px', fontSize: '13px', cursor: 'pointer', color: '#e53e3e' }}>Delete</button>`);
1127
+ }
1128
+ }
1129
+ lines.push(`</td>`);
1130
+ }
1131
+ }
1132
+ lines.push(`</tr>`);
1133
+ lines.push(`))}`);
1134
+ lines.push(`</tbody>`);
1135
+ lines.push(`</table>`);
1136
+ // Pagination
1137
+ const paginationSize = node.features['pagination'];
1138
+ if (paginationSize) {
1139
+ const size = genExpr(paginationSize, c);
1140
+ lines.push(`{/* Pagination: ${size} per page */}`);
1141
+ }
1142
+ if (hasSortable) {
1143
+ lines.push(`})()}`);
1144
+ }
1145
+ return lines.join('\n');
1146
+ }
1147
+ // ── Phase 2 Advanced: Auth ──────────────────────────
1148
+ function genAuthCode(node) {
1149
+ const lines = [
1150
+ `// Generated by 0x — Auth: ${node.provider}`,
1151
+ `import React, { useState, useContext, createContext, useCallback } from 'react';`,
1152
+ '',
1153
+ ];
1154
+ // Auth context
1155
+ lines.push(`const AuthContext = createContext(null);`);
1156
+ lines.push('');
1157
+ lines.push(`export function useAuth() {`);
1158
+ lines.push(` const ctx = useContext(AuthContext);`);
1159
+ lines.push(` if (!ctx) throw new Error('useAuth must be inside AuthProvider');`);
1160
+ lines.push(` return ctx;`);
1161
+ lines.push(`}`);
1162
+ lines.push('');
1163
+ // Auth provider
1164
+ lines.push(`export function AuthProvider({ children }) {`);
1165
+ lines.push(` const [user, setUser] = useState(null);`);
1166
+ lines.push(` const [loading, setLoading] = useState(false);`);
1167
+ lines.push(` const [error, setError] = useState(null);`);
1168
+ lines.push('');
1169
+ // Login function
1170
+ if (node.loginFields.length > 0) {
1171
+ const params = node.loginFields.join(', ');
1172
+ lines.push(` const login = useCallback(async (${params}) => {`);
1173
+ lines.push(` setLoading(true); setError(null);`);
1174
+ lines.push(` try {`);
1175
+ lines.push(` const res = await fetch('/api/auth/login', {`);
1176
+ lines.push(` method: 'POST',`);
1177
+ lines.push(` headers: { 'Content-Type': 'application/json' },`);
1178
+ lines.push(` body: JSON.stringify({ ${params} }),`);
1179
+ lines.push(` });`);
1180
+ lines.push(` const data = await res.json();`);
1181
+ lines.push(` if (!res.ok) throw new Error(data.message || 'Login failed');`);
1182
+ lines.push(` setUser(data.user);`);
1183
+ lines.push(` return data;`);
1184
+ lines.push(` } catch (e) { setError(e.message); throw e; }`);
1185
+ lines.push(` finally { setLoading(false); }`);
1186
+ lines.push(` }, []);`);
1187
+ lines.push('');
1188
+ }
1189
+ // Signup function
1190
+ if (node.signupFields.length > 0) {
1191
+ const params = node.signupFields.join(', ');
1192
+ lines.push(` const signup = useCallback(async (${params}) => {`);
1193
+ lines.push(` setLoading(true); setError(null);`);
1194
+ lines.push(` try {`);
1195
+ lines.push(` const res = await fetch('/api/auth/signup', {`);
1196
+ lines.push(` method: 'POST',`);
1197
+ lines.push(` headers: { 'Content-Type': 'application/json' },`);
1198
+ lines.push(` body: JSON.stringify({ ${params} }),`);
1199
+ lines.push(` });`);
1200
+ lines.push(` const data = await res.json();`);
1201
+ lines.push(` if (!res.ok) throw new Error(data.message || 'Sign up failed');`);
1202
+ lines.push(` setUser(data.user);`);
1203
+ lines.push(` return data;`);
1204
+ lines.push(` } catch (e) { setError(e.message); throw e; }`);
1205
+ lines.push(` finally { setLoading(false); }`);
1206
+ lines.push(` }, []);`);
1207
+ lines.push('');
1208
+ }
1209
+ // Logout function
1210
+ lines.push(` const logout = useCallback(async () => {`);
1211
+ lines.push(` await fetch('/api/auth/logout', { method: 'POST' });`);
1212
+ lines.push(` setUser(null);`);
1213
+ lines.push(` }, []);`);
1214
+ lines.push('');
1215
+ lines.push(` const value = { user, loading, error, login, signup, logout };`);
1216
+ lines.push(` return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;`);
1217
+ lines.push(`}`);
1218
+ lines.push('');
1219
+ // Auth guard component
1220
+ for (const g of node.guards) {
1221
+ const guardName = `${capitalize(g.role)}Guard`;
1222
+ lines.push(`export function ${guardName}({ children }) {`);
1223
+ lines.push(` const { user } = useAuth();`);
1224
+ if (g.role === 'auth') {
1225
+ lines.push(` if (!user) {`);
1226
+ }
1227
+ else {
1228
+ lines.push(` if (!user || user.role !== '${g.role}') {`);
1229
+ }
1230
+ if (g.redirect) {
1231
+ lines.push(` window.location.href = '${g.redirect}';`);
1232
+ lines.push(` return null;`);
1233
+ }
1234
+ else {
1235
+ lines.push(` return <div>Access denied</div>;`);
1236
+ }
1237
+ lines.push(` }`);
1238
+ lines.push(` return children;`);
1239
+ lines.push(`}`);
1240
+ lines.push('');
1241
+ }
1242
+ return lines.join('\n');
1243
+ }
1244
+ // ── Phase 2 Advanced: Chart ─────────────────────────
1245
+ function genChartUI(node, c) {
1246
+ const lines = [];
1247
+ const data = node.props['data'] ? genExpr(node.props['data'], c) : '[]';
1248
+ const x = node.props['x'] ? genExpr(node.props['x'], c) : "'x'";
1249
+ const y = node.props['y'] ? genExpr(node.props['y'], c) : "'y'";
1250
+ const title = node.props['title'] ? genExpr(node.props['title'], c) : `'${node.name}'`;
1251
+ const color = node.props['color'] ? genExpr(node.props['color'], c) : null;
1252
+ const height = node.props['height'] ? genExpr(node.props['height'], c) : '300';
1253
+ lines.push(`<div className="chart-${node.name}" style={{ width: '100%', height: '${height}px' }}>`);
1254
+ lines.push(` {/* Chart: ${node.chartType} - ${node.name} */}`);
1255
+ lines.push(` {/* Data: ${data}, X: ${x}, Y: ${y} */}`);
1256
+ if (node.chartType === 'bar') {
1257
+ lines.push(` <div style={{ display: 'flex', alignItems: 'flex-end', gap: '4px', height: '100%', padding: '20px 0' }}>`);
1258
+ lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold', position: 'absolute', top: 0 }}>{${title}}</span>`);
1259
+ lines.push(` {${data}.map((item, i) => (`);
1260
+ lines.push(` <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>`);
1261
+ lines.push(` <span style={{ fontSize: '12px', marginBottom: '4px' }}>{item[${y}]}</span>`);
1262
+ lines.push(` <div style={{ width: '100%', backgroundColor: ${color ? `item[${color}] || '#3182ce'` : "'#3182ce'"}, height: \`\${(item[${y}] / Math.max(...${data}.map(d => d[${y}]))) * 100}%\`, borderRadius: '4px 4px 0 0', minHeight: '4px' }} />`);
1263
+ lines.push(` <span style={{ fontSize: '11px', marginTop: '4px', color: '#666' }}>{item[${x}]}</span>`);
1264
+ lines.push(` </div>`);
1265
+ lines.push(` ))}`);
1266
+ lines.push(` </div>`);
1267
+ }
1268
+ else if (node.chartType === 'pie') {
1269
+ lines.push(` <div style={{ position: 'relative', width: '200px', height: '200px', margin: '0 auto' }}>`);
1270
+ lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold' }}>{${title}}</span>`);
1271
+ lines.push(` {/* Pie chart - use recharts/chart.js for production */}`);
1272
+ lines.push(` {${data}.map((item, i) => (`);
1273
+ lines.push(` <div key={i} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>`);
1274
+ lines.push(` <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: ['#3182ce','#38a169','#d69e2e','#e53e3e','#805ad5'][i % 5] }} />`);
1275
+ lines.push(` <span>{item[${x}]}: {item[${y}]}</span>`);
1276
+ lines.push(` </div>`);
1277
+ lines.push(` ))}`);
1278
+ lines.push(` </div>`);
1279
+ }
1280
+ else {
1281
+ // line, area, doughnut, etc — render placeholder with data info
1282
+ lines.push(` <div style={{ padding: '20px' }}>`);
1283
+ lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold' }}>{${title}}</span>`);
1284
+ lines.push(` <div style={{ marginTop: '12px', color: '#666' }}>`);
1285
+ lines.push(` {${data}.map((item, i) => (`);
1286
+ lines.push(` <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderBottom: '1px solid #eee' }}>`);
1287
+ lines.push(` <span>{item[${x}]}</span>`);
1288
+ lines.push(` <span style={{ fontWeight: 600 }}>{item[${y}]}</span>`);
1289
+ lines.push(` </div>`);
1290
+ lines.push(` ))}`);
1291
+ lines.push(` </div>`);
1292
+ lines.push(` </div>`);
1293
+ }
1294
+ lines.push(`</div>`);
1295
+ return lines.join('\n');
1296
+ }
1297
+ // ── Phase 2 Advanced: Stat Card ─────────────────────
1298
+ function genStatUI(node, c) {
1299
+ const value = genExpr(node.value, c);
1300
+ const change = node.change ? genExpr(node.change, c) : null;
1301
+ const lines = [];
1302
+ lines.push(`<div style={{ padding: '20px', borderRadius: '12px', backgroundColor: '#f7fafc', border: '1px solid #e2e8f0', minWidth: '200px' }}>`);
1303
+ if (node.icon) {
1304
+ lines.push(` <div style={{ fontSize: '24px', marginBottom: '8px' }}>${node.icon}</div>`);
1305
+ }
1306
+ lines.push(` <div style={{ fontSize: '14px', color: '#718096', marginBottom: '4px' }}>${node.label}</div>`);
1307
+ lines.push(` <div style={{ fontSize: '28px', fontWeight: 'bold', color: '#1a202c' }}>{${value}}</div>`);
1308
+ if (change) {
1309
+ lines.push(` <div style={{ fontSize: '13px', marginTop: '4px', color: String(${change}).startsWith('+') ? '#38a169' : '#e53e3e' }}>{${change}}</div>`);
1310
+ }
1311
+ lines.push(`</div>`);
1312
+ return lines.join('\n');
1313
+ }
1314
+ // ── Phase 2 Advanced: Realtime ──────────────────────
1315
+ function genRealtimeDecl(node, c) {
1316
+ c.imports.add('useState');
1317
+ c.imports.add('useEffect');
1318
+ c.imports.add('useRef');
1319
+ const channel = genExpr(node.channel, c);
1320
+ const lines = [];
1321
+ lines.push(`const [${node.name}, set${capitalize(node.name)}] = useState([]);`);
1322
+ lines.push(`const ${node.name}Ref = useRef(null);`);
1323
+ lines.push(`useEffect(() => {`);
1324
+ lines.push(` const ws = new WebSocket(${channel});`);
1325
+ lines.push(` ${node.name}Ref.current = ws;`);
1326
+ for (const handler of node.handlers) {
1327
+ if (handler.event === 'message') {
1328
+ lines.push(` ws.onmessage = (event) => {`);
1329
+ lines.push(` const ${handler.event} = JSON.parse(event.data);`);
1330
+ const body = handler.body.map(s => genStatement(s, c)).join('\n ');
1331
+ lines.push(` ${body}`);
1332
+ lines.push(` };`);
1333
+ }
1334
+ else if (handler.event === 'error') {
1335
+ lines.push(` ws.onerror = (event) => {`);
1336
+ const body = handler.body.map(s => genStatement(s, c)).join('\n ');
1337
+ lines.push(` ${body}`);
1338
+ lines.push(` };`);
1339
+ }
1340
+ else if (handler.event === 'open') {
1341
+ lines.push(` ws.onopen = () => {`);
1342
+ const body = handler.body.map(s => genStatement(s, c)).join('\n ');
1343
+ lines.push(` ${body}`);
1344
+ lines.push(` };`);
1345
+ }
1346
+ else if (handler.event === 'close') {
1347
+ lines.push(` ws.onclose = () => {`);
1348
+ const body = handler.body.map(s => genStatement(s, c)).join('\n ');
1349
+ lines.push(` ${body}`);
1350
+ lines.push(` };`);
1351
+ }
1352
+ }
1353
+ lines.push(` return () => { ws.close(); };`);
1354
+ lines.push(` }, []);`);
1355
+ // Register as state
1356
+ c.states.set(node.name, {
1357
+ type: 'StateDecl', name: node.name,
1358
+ valueType: { kind: 'list', itemType: { kind: 'primitive', name: 'any' } },
1359
+ initial: { kind: 'array', elements: [] },
1360
+ loc: node.loc,
1361
+ });
1362
+ return lines.join('\n ');
1363
+ }
1364
+ // ── Phase 2 Advanced: Route ─────────────────────────
1365
+ function genRouteCode(node) {
1366
+ const lines = [];
1367
+ lines.push(`// Route: ${node.path} -> ${node.target}`);
1368
+ if (node.guard) {
1369
+ lines.push(`// Guard: ${node.guard}`);
1370
+ lines.push(`<Route path="${node.path}" element={<${capitalize(node.guard)}Guard><${node.target} /></${capitalize(node.guard)}Guard>} />`);
1371
+ }
1372
+ else {
1373
+ lines.push(`<Route path="${node.path}" element={<${node.target} />} />`);
1374
+ }
1375
+ return lines.join('\n');
1376
+ }
1377
+ // ── Phase 2 Advanced: Nav ───────────────────────────
1378
+ function genNavUI(node, c) {
1379
+ const lines = [];
1380
+ lines.push(`<nav style={{ display: 'flex', gap: '16px', padding: '12px 24px', backgroundColor: '#fff', borderBottom: '1px solid #e2e8f0', alignItems: 'center' }}>`);
1381
+ for (const item of node.items) {
1382
+ const iconPart = item.icon ? `<span style={{ marginRight: '6px' }}>${item.icon}</span>` : '';
1383
+ lines.push(` <a href="${item.href}" style={{ textDecoration: 'none', color: '#4a5568', fontWeight: 500, padding: '8px 12px', borderRadius: '6px', transition: 'background 0.2s' }}>${iconPart}${item.label}</a>`);
1384
+ }
1385
+ lines.push(`</nav>`);
1386
+ return lines.join('\n');
1387
+ }
1388
+ // ── Phase 2 Advanced: Upload ────────────────────────
1389
+ function genUploadUI(node, c) {
1390
+ c.imports.add('useState');
1391
+ c.imports.add('useRef');
1392
+ c.imports.add('useCallback');
1393
+ const lines = [];
1394
+ const statePrefix = node.name;
1395
+ lines.push(`{(() => {`);
1396
+ lines.push(` const [${statePrefix}File, set${capitalize(statePrefix)}File] = useState(null);`);
1397
+ lines.push(` const [${statePrefix}Preview, set${capitalize(statePrefix)}Preview] = useState(null);`);
1398
+ lines.push(` const [${statePrefix}Uploading, set${capitalize(statePrefix)}Uploading] = useState(false);`);
1399
+ lines.push(` const ${statePrefix}Ref = useRef(null);`);
1400
+ lines.push(` const handle${capitalize(statePrefix)}Change = (e) => {`);
1401
+ lines.push(` const file = e.target.files[0];`);
1402
+ lines.push(` if (!file) return;`);
1403
+ if (node.maxSize) {
1404
+ lines.push(` if (file.size > ${node.maxSize} * 1024 * 1024) { alert('File too large (max ${node.maxSize}MB)'); return; }`);
1405
+ }
1406
+ lines.push(` set${capitalize(statePrefix)}File(file);`);
1407
+ if (node.preview) {
1408
+ lines.push(` const reader = new FileReader();`);
1409
+ lines.push(` reader.onload = (ev) => set${capitalize(statePrefix)}Preview(ev.target.result);`);
1410
+ lines.push(` reader.readAsDataURL(file);`);
1411
+ }
1412
+ lines.push(` };`);
1413
+ if (node.action) {
1414
+ const action = genExpr(node.action, c);
1415
+ lines.push(` const upload${capitalize(statePrefix)} = async () => {`);
1416
+ lines.push(` if (!${statePrefix}File) return;`);
1417
+ lines.push(` set${capitalize(statePrefix)}Uploading(true);`);
1418
+ lines.push(` try {`);
1419
+ lines.push(` const formData = new FormData();`);
1420
+ lines.push(` formData.append('file', ${statePrefix}File);`);
1421
+ lines.push(` await ${action};`);
1422
+ lines.push(` } finally { set${capitalize(statePrefix)}Uploading(false); }`);
1423
+ lines.push(` };`);
1424
+ }
1425
+ lines.push(` return (`);
1426
+ lines.push(` <div style={{ border: '2px dashed #cbd5e0', borderRadius: '12px', padding: '24px', textAlign: 'center', cursor: 'pointer' }}`);
1427
+ lines.push(` onClick={() => ${statePrefix}Ref.current?.click()}>`);
1428
+ lines.push(` <input ref={${statePrefix}Ref} type="file"${node.accept ? ` accept="${node.accept}"` : ''} onChange={handle${capitalize(statePrefix)}Change} style={{ display: 'none' }} />`);
1429
+ if (node.preview) {
1430
+ lines.push(` {${statePrefix}Preview ? <img src={${statePrefix}Preview} style={{ maxWidth: '200px', maxHeight: '200px', borderRadius: '8px' }} /> : <span style={{ color: '#a0aec0' }}>Click to select file</span>}`);
1431
+ }
1432
+ else {
1433
+ lines.push(` {${statePrefix}File ? <span>{${statePrefix}File.name}</span> : <span style={{ color: '#a0aec0' }}>Click to select file</span>}`);
1434
+ }
1435
+ if (node.action) {
1436
+ lines.push(` {${statePrefix}File && <button onClick={upload${capitalize(statePrefix)}} disabled={${statePrefix}Uploading} style={{ marginTop: '12px', padding: '8px 16px', borderRadius: '6px', backgroundColor: '#3182ce', color: '#fff', border: 'none', cursor: 'pointer' }}>{${statePrefix}Uploading ? 'Uploading...' : 'Upload'}</button>}`);
1437
+ }
1438
+ lines.push(` </div>`);
1439
+ lines.push(` );`);
1440
+ lines.push(`})()}`);
1441
+ return lines.join('\n');
1442
+ }
1443
+ // ── Phase 2 Advanced: Modal ─────────────────────────
1444
+ function genModalUI(node, c) {
1445
+ c.imports.add('useState');
1446
+ const lines = [];
1447
+ const showVar = `show${capitalize(node.name)}`;
1448
+ lines.push(`{(() => {`);
1449
+ lines.push(` const [${showVar}, set${capitalize(showVar)}] = useState(false);`);
1450
+ lines.push(` return (`);
1451
+ lines.push(` <>`);
1452
+ // Trigger button
1453
+ if (node.trigger) {
1454
+ lines.push(` <button onClick={() => set${capitalize(showVar)}(true)} style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #e2e8f0', cursor: 'pointer' }}>${node.trigger}</button>`);
1455
+ }
1456
+ // Modal overlay
1457
+ lines.push(` {${showVar} && (`);
1458
+ lines.push(` <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}`);
1459
+ lines.push(` onClick={() => set${capitalize(showVar)}(false)}>`);
1460
+ lines.push(` <div style={{ backgroundColor: '#fff', borderRadius: '12px', padding: '24px', minWidth: '400px', maxWidth: '90vw', boxShadow: '0 20px 60px rgba(0,0,0,0.15)' }}`);
1461
+ lines.push(` onClick={e => e.stopPropagation()}>`);
1462
+ lines.push(` <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>`);
1463
+ lines.push(` <h2 style={{ margin: 0, fontSize: '20px' }}>${node.title}</h2>`);
1464
+ lines.push(` <button onClick={() => set${capitalize(showVar)}(false)} style={{ border: 'none', background: 'none', fontSize: '20px', cursor: 'pointer', padding: '4px' }}>&times;</button>`);
1465
+ lines.push(` </div>`);
1466
+ // Modal body
1467
+ const bodyJsx = node.body.map(ch => genUINode(ch, c)).join('\n');
1468
+ lines.push(` ${bodyJsx}`);
1469
+ lines.push(` </div>`);
1470
+ lines.push(` </div>`);
1471
+ lines.push(` )}`);
1472
+ lines.push(` </>`);
1473
+ lines.push(` );`);
1474
+ lines.push(`})()}`);
1475
+ return lines.join('\n');
1476
+ }
1477
+ // ── Phase 2 Advanced: Toast ─────────────────────────
1478
+ function genToastUI(node, c) {
1479
+ const message = genExpr(node.message, c);
1480
+ const colorMap = {
1481
+ success: '#38a169', error: '#e53e3e', warning: '#d69e2e', info: '#3182ce',
1482
+ };
1483
+ const bg = colorMap[node.toastType] || colorMap.info;
1484
+ const duration = node.duration || 3000;
1485
+ return `<div style={{ position: 'fixed', top: '20px', right: '20px', backgroundColor: '${bg}', color: '#fff', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', zIndex: 9999, animation: 'fadeIn 0.3s' }}>{${message}}</div>`;
1486
+ }
1487
+ // ── Phase 3: Top-level generators ───────────────────
1488
+ function genRoleCode(node) {
1489
+ const lines = [
1490
+ `// Generated by 0x — Roles & Permissions`,
1491
+ '',
1492
+ `const ROLES = {`,
1493
+ ];
1494
+ for (const role of node.roles) {
1495
+ const inherits = role.inherits.length > 0 ? `, inherits: [${role.inherits.map(r => `'${r}'`).join(', ')}]` : '';
1496
+ lines.push(` ${role.name}: { can: [${role.can.map(c => `'${c}'`).join(', ')}]${inherits} },`);
1497
+ }
1498
+ lines.push(`};`);
1499
+ lines.push('');
1500
+ lines.push(`function resolvePermissions(roleName) {`);
1501
+ lines.push(` const role = ROLES[roleName];`);
1502
+ lines.push(` if (!role) return [];`);
1503
+ lines.push(` const perms = new Set(role.can || []);`);
1504
+ lines.push(` for (const parent of (role.inherits || [])) {`);
1505
+ lines.push(` for (const p of resolvePermissions(parent)) perms.add(p);`);
1506
+ lines.push(` }`);
1507
+ lines.push(` return [...perms];`);
1508
+ lines.push(`}`);
1509
+ lines.push('');
1510
+ lines.push(`function hasPermission(userRole, action) {`);
1511
+ lines.push(` return resolvePermissions(userRole).includes(action);`);
1512
+ lines.push(`}`);
1513
+ return lines.join('\n');
1514
+ }
1515
+ function genAutomationCode(node) {
1516
+ const lines = [
1517
+ `// Generated by 0x — Automation`,
1518
+ '',
1519
+ ];
1520
+ for (const trigger of node.triggers) {
1521
+ const actions = trigger.actions.map(a => genExprStandalone(a)).join('; ');
1522
+ lines.push(`document.addEventListener('${trigger.event}', async () => { ${actions}; });`);
1523
+ }
1524
+ for (const schedule of node.schedules) {
1525
+ const actions = schedule.actions.map(a => genExprStandalone(a)).join('; ');
1526
+ lines.push(`// cron('${schedule.cron}', async () => { ${actions}; });`);
1527
+ }
1528
+ return lines.join('\n');
1529
+ }
1530
+ function genDevCode(node) {
1531
+ const c = ctx();
1532
+ const lines = [
1533
+ `// Generated by 0x — Dev Tools`,
1534
+ '',
1535
+ ];
1536
+ if (node.props['mock']) {
1537
+ lines.push(`const USE_MOCK = process.env.NODE_ENV === 'development';`);
1538
+ }
1539
+ if (node.props['seed']) {
1540
+ const seed = genExpr(node.props['seed'], c);
1541
+ lines.push(`// Seed data: ${seed}`);
1542
+ }
1543
+ return lines.join('\n');
1544
+ }
1545
+ // ── Phase 3: UI generators ──────────────────────────
1546
+ function genHeroUI(node, c) {
1547
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n ');
1548
+ return `<section style={{ textAlign: 'center', padding: '80px 20px', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: '#fff' }}>\n ${body}\n</section>`;
1549
+ }
1550
+ function genCrudUI(node, c) {
1551
+ return `<div className="crud-${node.model.toLowerCase()}">\n {/* CRUD: ${node.model} - auto-generated list + create + edit + delete */}\n <h2>${node.model} Management</h2>\n</div>`;
1552
+ }
1553
+ function genListUI(node, c) {
1554
+ const data = genExpr(node.dataSource, c);
1555
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1556
+ if (node.listType === 'grid') {
1557
+ return `<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '16px' }}>\n {${data}.map((item, i) => (\n <div key={i}>\n ${body}\n </div>\n ))}\n</div>`;
1558
+ }
1559
+ return `<div className="list-${node.listType}">\n {${data}.map((item, i) => (\n <div key={i}>${body}</div>\n ))}\n</div>`;
1560
+ }
1561
+ function genDrawerUI(node, c) {
1562
+ c.imports.add('useState');
1563
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1564
+ return `{(() => {\n const [open, setOpen] = useState(false);\n return (\n <>\n <button onClick={() => setOpen(true)}>Menu</button>\n {open && <div style={{ position: 'fixed', top: 0, left: 0, width: '300px', height: '100vh', backgroundColor: '#fff', boxShadow: '2px 0 8px rgba(0,0,0,0.1)', zIndex: 1000, padding: '20px' }}>\n <button onClick={() => setOpen(false)} style={{ float: 'right' }}>&times;</button>\n ${body}\n </div>}\n </>\n );\n})()}`;
1565
+ }
1566
+ function genCommandUI(node, c) {
1567
+ return `{/* Command Palette: ${node.shortcut} */}`;
1568
+ }
1569
+ function genConfirmUI(node, c) {
1570
+ const danger = node.danger ? ", color: '#e53e3e'" : '';
1571
+ return `<div style={{ padding: '20px', borderRadius: '12px', border: '1px solid #e2e8f0' }}>\n <p style={{ fontWeight: 600${danger} }}>${node.message}</p>\n ${node.description ? `<p style={{ color: '#718096' }}>${node.description}</p>` : ''}\n <div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>\n <button style={{ padding: '8px 16px', borderRadius: '6px', border: '1px solid #e2e8f0' }}>${node.cancelLabel}</button>\n <button style={{ padding: '8px 16px', borderRadius: '6px', backgroundColor: '${node.danger ? '#e53e3e' : '#3182ce'}', color: '#fff', border: 'none' }}>${node.confirmLabel}</button>\n </div>\n</div>`;
1572
+ }
1573
+ function genPayUI(node, c) {
1574
+ return `<div className="pay-${node.payType}" style={{ padding: '24px', borderRadius: '12px', border: '1px solid #e2e8f0' }}>\n {/* Payment: ${node.provider} ${node.payType} */}\n <button style={{ padding: '12px 24px', backgroundColor: '#635bff', color: '#fff', borderRadius: '8px', border: 'none', fontSize: '16px', cursor: 'pointer' }}>Pay</button>\n</div>`;
1575
+ }
1576
+ function genCartUI(node, c) {
1577
+ return `<div className="cart" style={{ padding: '20px' }}>\n {/* Shopping Cart */}\n <h3>Cart</h3>\n</div>`;
1578
+ }
1579
+ function genMediaUI(node, c) {
1580
+ const src = genExpr(node.src, c);
1581
+ if (node.mediaType === 'gallery') {
1582
+ const cols = node.props['cols'] ? genExpr(node.props['cols'], c) : '3';
1583
+ return `<div style={{ display: 'grid', gridTemplateColumns: \`repeat(\${${cols}}, 1fr)\`, gap: '8px' }}>\n {${src}.map((img, i) => <img key={i} src={img} style={{ width: '100%', borderRadius: '8px', objectFit: 'cover' }} />)}\n</div>`;
1584
+ }
1585
+ if (node.mediaType === 'video') {
1586
+ return `<video src={${src}} controls style={{ width: '100%', borderRadius: '12px' }} />`;
1587
+ }
1588
+ return `<div className="media-${node.mediaType}">{/* Media: ${node.mediaType} */}</div>`;
1589
+ }
1590
+ function genNotificationUI(node, c) {
1591
+ return `<div className="notifications">{/* Notification: ${node.notifType} */}</div>`;
1592
+ }
1593
+ function genSearchUI(node, c) {
1594
+ c.imports.add('useState');
1595
+ return `{(() => {\n const [query, setQuery] = useState('');\n return (\n <div style={{ position: 'relative' }}>\n <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." style={{ width: '100%', padding: '10px 16px', borderRadius: '8px', border: '1px solid #e2e8f0', fontSize: '14px' }} />\n </div>\n );\n})()}`;
1596
+ }
1597
+ function genFilterUI(node, c) {
1598
+ return `<div className="filter-panel">{/* Filter: ${node.target} */}</div>`;
1599
+ }
1600
+ function genSocialUI(node, c) {
1601
+ const target = genExpr(node.target, c);
1602
+ if (node.socialType === 'like') {
1603
+ return `<button style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 12px', borderRadius: '20px', border: '1px solid #e2e8f0', cursor: 'pointer' }}>❤️ Like</button>`;
1604
+ }
1605
+ return `<div className="social-${node.socialType}">{/* Social: ${node.socialType} */}</div>`;
1606
+ }
1607
+ function genProfileUI(node, c) {
1608
+ const user = genExpr(node.user, c);
1609
+ return `<div style={{ textAlign: 'center', padding: '24px' }}>\n <div style={{ width: '80px', height: '80px', borderRadius: '50%', backgroundColor: '#e2e8f0', margin: '0 auto 12px' }} />\n <h2>{${user}.name}</h2>\n</div>`;
1610
+ }
1611
+ function genFeaturesUI(node, c) {
1612
+ return `<section style={{ padding: '60px 20px' }}>\n <h2 style={{ textAlign: 'center', fontSize: '28px', marginBottom: '40px' }}>Features</h2>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '32px' }}>\n {/* Features items */}\n </div>\n</section>`;
1613
+ }
1614
+ function genPricingUI(node, c) {
1615
+ return `<section style={{ padding: '60px 20px' }}>\n <h2 style={{ textAlign: 'center', fontSize: '28px', marginBottom: '40px' }}>Pricing</h2>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px', maxWidth: '900px', margin: '0 auto' }}>\n {/* Pricing plans */}\n </div>\n</section>`;
1616
+ }
1617
+ function genFaqUI(node, c) {
1618
+ return `<section style={{ padding: '60px 20px', maxWidth: '700px', margin: '0 auto' }}>\n <h2 style={{ textAlign: 'center', fontSize: '28px', marginBottom: '40px' }}>FAQ</h2>\n {/* FAQ items */}\n</section>`;
1619
+ }
1620
+ function genTestimonialUI(node, c) {
1621
+ return `<section style={{ padding: '60px 20px' }}>\n <h2 style={{ textAlign: 'center', fontSize: '28px', marginBottom: '40px' }}>Testimonials</h2>\n {/* Testimonial items */}\n</section>`;
1622
+ }
1623
+ function genFooterUI(node, c) {
1624
+ return `<footer style={{ padding: '40px 20px', backgroundColor: '#1a202c', color: '#a0aec0' }}>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '32px', maxWidth: '1200px', margin: '0 auto' }}>\n {/* Footer columns */}\n </div>\n</footer>`;
1625
+ }
1626
+ function genAdminUI(node, c) {
1627
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1628
+ return `<div style={{ display: 'grid', gridTemplateColumns: '240px 1fr', minHeight: '100vh' }}>\n <aside style={{ backgroundColor: '#1a202c', color: '#fff', padding: '20px' }}>\n <h3>Admin</h3>\n </aside>\n <main style={{ padding: '24px' }}>\n ${body}\n </main>\n</div>`;
1629
+ }
1630
+ function genSeoUI(node, c) {
1631
+ const title = node.props['title'] ? genExpr(node.props['title'], c) : "''";
1632
+ const desc = node.props['description'] ? genExpr(node.props['description'], c) : "''";
1633
+ return `{/* SEO: title=${title}, description=${desc} */}`;
1634
+ }
1635
+ function genAnimateUI(node, c) {
1636
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1637
+ return `<div className="animate-${node.animType}" style={{ animation: '${node.animType === 'enter' ? 'fadeIn 0.3s ease-in' : 'fadeOut 0.3s ease-out'}' }}>\n ${body}\n</div>`;
1638
+ }
1639
+ function genGestureUI(node, c) {
1640
+ return `{/* Gesture: ${node.gestureType} */}`;
1641
+ }
1642
+ function genAiUI(node, c) {
1643
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1644
+ if (node.aiType === 'chat') {
1645
+ return `<div style={{ display: 'flex', flexDirection: 'column', height: '400px', border: '1px solid #e2e8f0', borderRadius: '12px', overflow: 'hidden' }}>\n <div style={{ flex: 1, padding: '16px', overflowY: 'auto' }}>\n ${body}\n </div>\n <div style={{ padding: '12px', borderTop: '1px solid #e2e8f0', display: 'flex', gap: '8px' }}>\n <input placeholder="Type a message..." style={{ flex: 1, padding: '8px 12px', borderRadius: '8px', border: '1px solid #e2e8f0' }} />\n <button style={{ padding: '8px 16px', backgroundColor: '#3182ce', color: '#fff', borderRadius: '8px', border: 'none' }}>Send</button>\n </div>\n</div>`;
1646
+ }
1647
+ return `<div className="ai-${node.aiType}">{/* AI: ${node.aiType} */}\n ${body}\n</div>`;
1648
+ }
1649
+ function genResponsiveUI(node, c) {
1650
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1651
+ const display = node.action === 'show' ? 'block' : 'none';
1652
+ const mediaQuery = node.breakpoint === 'mobile' ? '768' : node.breakpoint === 'tablet' ? '1024' : '1200';
1653
+ return `<div className="${node.breakpoint}-${node.action}" data-breakpoint="${node.breakpoint}">\n ${body}\n</div>`;
1654
+ }
1655
+ function genBreadcrumbUI(node, c) {
1656
+ return `<nav style={{ display: 'flex', gap: '8px', fontSize: '14px', color: '#718096', padding: '8px 0' }}>\n <a href="/" style={{ color: '#3182ce' }}>Home</a>\n <span>/</span>\n <span>Current page</span>\n</nav>`;
1657
+ }
1658
+ function genStatsGridUI(node, c) {
1659
+ const stats = node.stats.map(s => genStatUI(s, c)).join('\n');
1660
+ return `<div style={{ display: 'grid', gridTemplateColumns: 'repeat(${node.cols}, 1fr)', gap: '16px' }}>\n ${stats}\n</div>`;
1661
+ }
1662
+ // ── Phase 4: Infrastructure code generators ─────────
1663
+ function genDeployCode(node) {
1664
+ const c = ctx();
1665
+ const lines = [`// Generated by 0x — Deploy: ${node.provider}`, ''];
1666
+ if (node.provider === 'vercel') {
1667
+ lines.push(`// vercel.json`);
1668
+ lines.push(`const vercelConfig = {`);
1669
+ for (const [k, v] of Object.entries(node.props)) {
1670
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1671
+ }
1672
+ lines.push(`};`);
1673
+ lines.push(`// Run: npx vercel deploy`);
1674
+ }
1675
+ else if (node.provider === 'docker') {
1676
+ lines.push(`// Dockerfile generated — see docker.config.js`);
1677
+ }
1678
+ else if (node.provider === 'netlify') {
1679
+ lines.push(`// netlify.toml`);
1680
+ lines.push(`const netlifyConfig = {`);
1681
+ lines.push(` build: { command: 'npm run build', publish: 'dist' },`);
1682
+ for (const [k, v] of Object.entries(node.props)) {
1683
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1684
+ }
1685
+ lines.push(`};`);
1686
+ }
1687
+ else {
1688
+ lines.push(`const deployConfig = {`);
1689
+ lines.push(` provider: '${node.provider}',`);
1690
+ for (const [k, v] of Object.entries(node.props)) {
1691
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1692
+ }
1693
+ lines.push(`};`);
1694
+ }
1695
+ return lines.join('\n');
1696
+ }
1697
+ function genEnvCode(node) {
1698
+ const c = ctx();
1699
+ const lines = [`// Generated by 0x — Environment: ${node.envType}`, ''];
1700
+ lines.push(`const ENV = {`);
1701
+ for (const v of node.vars) {
1702
+ if (v.secret) {
1703
+ lines.push(` // [SECRET] ${v.name}`);
1704
+ lines.push(` ${v.name}: process.env.${v.name.toUpperCase()},`);
1705
+ }
1706
+ else {
1707
+ lines.push(` ${v.name}: ${genExpr(v.value, c)},`);
1708
+ }
1709
+ }
1710
+ lines.push(`};`);
1711
+ lines.push('');
1712
+ lines.push(`function getEnv(key) {`);
1713
+ lines.push(` const value = ENV[key] ?? process.env[key];`);
1714
+ lines.push(` if (value === undefined) throw new Error(\`Missing env: \${key}\`);`);
1715
+ lines.push(` return value;`);
1716
+ lines.push(`}`);
1717
+ return lines.join('\n');
1718
+ }
1719
+ function genDockerCode(node) {
1720
+ const c = ctx();
1721
+ const lines = [`// Generated by 0x — Docker`, ''];
1722
+ lines.push(`// Dockerfile`);
1723
+ lines.push(`const dockerfile = \``);
1724
+ lines.push(`FROM ${node.baseImage}`);
1725
+ lines.push(`WORKDIR /app`);
1726
+ lines.push(`COPY package*.json ./`);
1727
+ lines.push(`RUN npm ci --production`);
1728
+ lines.push(`COPY . .`);
1729
+ lines.push(`RUN npm run build`);
1730
+ const port = node.props['port'] ? genExpr(node.props['port'], c) : '3000';
1731
+ lines.push(`EXPOSE ${port}`);
1732
+ lines.push(`CMD ["npm", "start"]`);
1733
+ lines.push(`\`;`);
1734
+ return lines.join('\n');
1735
+ }
1736
+ function genCiCode(node) {
1737
+ const c = ctx();
1738
+ const lines = [`// Generated by 0x — CI/CD: ${node.provider}`, ''];
1739
+ if (node.provider === 'github') {
1740
+ lines.push(`// .github/workflows/ci.yml`);
1741
+ lines.push(`const ciConfig = {`);
1742
+ lines.push(` name: 'CI',`);
1743
+ lines.push(` on: { ${node.triggers.map(t => `${t}: { branches: ['main'] }`).join(', ')} },`);
1744
+ lines.push(` jobs: {`);
1745
+ lines.push(` build: {`);
1746
+ lines.push(` 'runs-on': 'ubuntu-latest',`);
1747
+ lines.push(` steps: [`);
1748
+ lines.push(` { uses: 'actions/checkout@v4' },`);
1749
+ lines.push(` { uses: 'actions/setup-node@v4', with: { 'node-version': '20' } },`);
1750
+ lines.push(` { run: 'npm ci' },`);
1751
+ for (const step of node.steps) {
1752
+ lines.push(` { name: '${step.name}', run: ${genExpr(step.command, c)} },`);
1753
+ }
1754
+ lines.push(` ],`);
1755
+ lines.push(` },`);
1756
+ lines.push(` },`);
1757
+ lines.push(`};`);
1758
+ }
1759
+ return lines.join('\n');
1760
+ }
1761
+ function genDomainCode(node) {
1762
+ const c = ctx();
1763
+ const lines = [`// Generated by 0x — Domain: ${node.domain}`, ''];
1764
+ lines.push(`const domainConfig = {`);
1765
+ lines.push(` domain: '${node.domain}',`);
1766
+ lines.push(` ssl: true,`);
1767
+ for (const [k, v] of Object.entries(node.props)) {
1768
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1769
+ }
1770
+ lines.push(`};`);
1771
+ return lines.join('\n');
1772
+ }
1773
+ function genCdnCode(node) {
1774
+ const c = ctx();
1775
+ const lines = [`// Generated by 0x — CDN: ${node.provider}`, ''];
1776
+ lines.push(`const cdnConfig = {`);
1777
+ lines.push(` provider: '${node.provider}',`);
1778
+ for (const [k, v] of Object.entries(node.props)) {
1779
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1780
+ }
1781
+ lines.push(`};`);
1782
+ return lines.join('\n');
1783
+ }
1784
+ function genMonitorCode(node) {
1785
+ const c = ctx();
1786
+ const lines = [`// Generated by 0x — Monitoring: ${node.provider}`, ''];
1787
+ if (node.provider === 'sentry') {
1788
+ const dsn = node.props['dsn'] ? genExpr(node.props['dsn'], c) : "process.env.SENTRY_DSN";
1789
+ lines.push(`import * as Sentry from '@sentry/react';`);
1790
+ lines.push('');
1791
+ lines.push(`Sentry.init({`);
1792
+ lines.push(` dsn: ${dsn},`);
1793
+ lines.push(` environment: process.env.NODE_ENV,`);
1794
+ lines.push(` tracesSampleRate: 1.0,`);
1795
+ lines.push(`});`);
1796
+ lines.push('');
1797
+ lines.push(`export const ErrorBoundary = Sentry.ErrorBoundary;`);
1798
+ }
1799
+ else {
1800
+ lines.push(`const monitorConfig = {`);
1801
+ lines.push(` provider: '${node.provider}',`);
1802
+ for (const [k, v] of Object.entries(node.props)) {
1803
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1804
+ }
1805
+ lines.push(`};`);
1806
+ }
1807
+ return lines.join('\n');
1808
+ }
1809
+ function genBackupCode(node) {
1810
+ const c = ctx();
1811
+ const lines = [`// Generated by 0x — Backup: ${node.strategy}`, ''];
1812
+ lines.push(`const backupConfig = {`);
1813
+ lines.push(` strategy: '${node.strategy}',`);
1814
+ for (const [k, v] of Object.entries(node.props)) {
1815
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1816
+ }
1817
+ lines.push(`};`);
1818
+ return lines.join('\n');
1819
+ }
1820
+ // ── Phase 4: Backend code generators ────────────────
1821
+ function genEndpointCode(node) {
1822
+ const c = ctx();
1823
+ const lines = [`// Generated by 0x — Endpoint: ${node.method} ${node.path}`, ''];
1824
+ lines.push(`export async function handle${node.method}${node.path.replace(/[^a-zA-Z]/g, '_')}(req, res) {`);
1825
+ if (node.middleware.length > 0) {
1826
+ lines.push(` // Middleware: ${node.middleware.join(', ')}`);
1827
+ }
1828
+ lines.push(` try {`);
1829
+ for (const stmt of node.handler) {
1830
+ lines.push(` ${genStatement(stmt, c)}`);
1831
+ }
1832
+ lines.push(` } catch (error) {`);
1833
+ lines.push(` res.status(500).json({ error: error.message });`);
1834
+ lines.push(` }`);
1835
+ lines.push(`}`);
1836
+ return lines.join('\n');
1837
+ }
1838
+ function genMiddlewareCode(node) {
1839
+ const c = ctx();
1840
+ const lines = [`// Generated by 0x — Middleware: ${node.name}`, ''];
1841
+ lines.push(`export function ${node.name}(req, res, next) {`);
1842
+ lines.push(` try {`);
1843
+ for (const stmt of node.handler) {
1844
+ lines.push(` ${genStatement(stmt, c)}`);
1845
+ }
1846
+ lines.push(` next();`);
1847
+ lines.push(` } catch (error) {`);
1848
+ lines.push(` res.status(403).json({ error: error.message });`);
1849
+ lines.push(` }`);
1850
+ lines.push(`}`);
1851
+ return lines.join('\n');
1852
+ }
1853
+ function genQueueCode(node) {
1854
+ const c = ctx();
1855
+ const lines = [`// Generated by 0x — Queue: ${node.name}`, ''];
1856
+ lines.push(`const ${node.name}Queue = {`);
1857
+ lines.push(` name: '${node.name}',`);
1858
+ lines.push(` process: async (job) => {`);
1859
+ for (const stmt of node.handler) {
1860
+ lines.push(` ${genStatement(stmt, c)}`);
1861
+ }
1862
+ lines.push(` },`);
1863
+ lines.push(` add: async (data, opts = {}) => {`);
1864
+ lines.push(` console.log('[Queue:${node.name}] Job added:', data);`);
1865
+ lines.push(` return ${node.name}Queue.process({ data });`);
1866
+ lines.push(` },`);
1867
+ lines.push(`};`);
1868
+ return lines.join('\n');
1869
+ }
1870
+ function genCronCode(node) {
1871
+ const c = ctx();
1872
+ const lines = [`// Generated by 0x — Cron: ${node.name} (${node.schedule})`, ''];
1873
+ lines.push(`// Schedule: ${node.schedule}`);
1874
+ lines.push(`async function ${node.name}Job() {`);
1875
+ for (const stmt of node.handler) {
1876
+ lines.push(` ${genStatement(stmt, c)}`);
1877
+ }
1878
+ lines.push(`}`);
1879
+ lines.push('');
1880
+ lines.push(`// Register: cron('${node.schedule}', ${node.name}Job);`);
1881
+ return lines.join('\n');
1882
+ }
1883
+ function genCacheCode(node) {
1884
+ const c = ctx();
1885
+ const lines = [`// Generated by 0x — Cache: ${node.name} (${node.strategy})`, ''];
1886
+ const ttl = node.ttl ? genExpr(node.ttl, c) : '300';
1887
+ if (node.strategy === 'memory') {
1888
+ lines.push(`const ${node.name}Cache = new Map();`);
1889
+ lines.push(`const ${node.name}Timestamps = new Map();`);
1890
+ lines.push('');
1891
+ lines.push(`function ${node.name}Get(key) {`);
1892
+ lines.push(` const ts = ${node.name}Timestamps.get(key);`);
1893
+ lines.push(` if (ts && Date.now() - ts > ${ttl} * 1000) {`);
1894
+ lines.push(` ${node.name}Cache.delete(key);`);
1895
+ lines.push(` ${node.name}Timestamps.delete(key);`);
1896
+ lines.push(` return null;`);
1897
+ lines.push(` }`);
1898
+ lines.push(` return ${node.name}Cache.get(key) ?? null;`);
1899
+ lines.push(`}`);
1900
+ lines.push('');
1901
+ lines.push(`function ${node.name}Set(key, value) {`);
1902
+ lines.push(` ${node.name}Cache.set(key, value);`);
1903
+ lines.push(` ${node.name}Timestamps.set(key, Date.now());`);
1904
+ lines.push(`}`);
1905
+ }
1906
+ else {
1907
+ lines.push(`const ${node.name}Config = {`);
1908
+ lines.push(` strategy: '${node.strategy}',`);
1909
+ lines.push(` ttl: ${ttl},`);
1910
+ for (const [k, v] of Object.entries(node.props)) {
1911
+ if (k !== 'ttl')
1912
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1913
+ }
1914
+ lines.push(`};`);
1915
+ }
1916
+ return lines.join('\n');
1917
+ }
1918
+ function genMigrateCode(node) {
1919
+ const c = ctx();
1920
+ const lines = [`// Generated by 0x — Migration: ${node.name}`, ''];
1921
+ lines.push(`export const migration_${node.name} = {`);
1922
+ lines.push(` name: '${node.name}',`);
1923
+ lines.push(` async up(db) {`);
1924
+ for (const stmt of node.up) {
1925
+ lines.push(` ${genStatement(stmt, c)}`);
1926
+ }
1927
+ lines.push(` },`);
1928
+ lines.push(` async down(db) {`);
1929
+ for (const stmt of node.down) {
1930
+ lines.push(` ${genStatement(stmt, c)}`);
1931
+ }
1932
+ lines.push(` },`);
1933
+ lines.push(`};`);
1934
+ return lines.join('\n');
1935
+ }
1936
+ function genSeedCode(node) {
1937
+ const c = ctx();
1938
+ const lines = [`// Generated by 0x — Seed: ${node.model}`, ''];
1939
+ const data = genExpr(node.data, c);
1940
+ const count = node.count ? genExpr(node.count, c) : null;
1941
+ lines.push(`export async function seed${capitalize(node.model)}(db) {`);
1942
+ if (count) {
1943
+ lines.push(` const seedData = Array.from({ length: ${count} }, (_, i) => ({`);
1944
+ lines.push(` ...${data},`);
1945
+ lines.push(` id: i + 1,`);
1946
+ lines.push(` }));`);
1947
+ }
1948
+ else {
1949
+ lines.push(` const seedData = ${data};`);
1950
+ }
1951
+ lines.push(` for (const item of ${count ? 'seedData' : `Array.isArray(${data}) ? ${data} : [${data}]`}) {`);
1952
+ lines.push(` await db.${node.model.toLowerCase()}.create(item);`);
1953
+ lines.push(` }`);
1954
+ lines.push(` console.log('Seeded ${node.model}:', ${count ? 'seedData.length' : '1'});`);
1955
+ lines.push(`}`);
1956
+ return lines.join('\n');
1957
+ }
1958
+ function genWebhookCode(node) {
1959
+ const c = ctx();
1960
+ const lines = [`// Generated by 0x — Webhook: ${node.name} (${node.path})`, ''];
1961
+ lines.push(`export async function webhook_${node.name}(req, res) {`);
1962
+ lines.push(` const payload = req.body;`);
1963
+ lines.push(` try {`);
1964
+ for (const stmt of node.handler) {
1965
+ lines.push(` ${genStatement(stmt, c)}`);
1966
+ }
1967
+ lines.push(` res.status(200).json({ ok: true });`);
1968
+ lines.push(` } catch (error) {`);
1969
+ lines.push(` console.error('[Webhook:${node.name}]', error);`);
1970
+ lines.push(` res.status(500).json({ error: error.message });`);
1971
+ lines.push(` }`);
1972
+ lines.push(`}`);
1973
+ return lines.join('\n');
1974
+ }
1975
+ function genStorageCode(node) {
1976
+ const c = ctx();
1977
+ const lines = [`// Generated by 0x — Storage: ${node.name} (${node.provider})`, ''];
1978
+ const bucket = node.props['bucket'] ? genExpr(node.props['bucket'], c) : `'${node.name}'`;
1979
+ if (node.provider === 's3') {
1980
+ lines.push(`import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';`);
1981
+ lines.push('');
1982
+ lines.push(`const ${node.name}Client = new S3Client({`);
1983
+ lines.push(` region: process.env.AWS_REGION || 'us-east-1',`);
1984
+ lines.push(`});`);
1985
+ lines.push(`const ${node.name}Bucket = ${bucket};`);
1986
+ }
1987
+ else {
1988
+ lines.push(`const ${node.name}Config = {`);
1989
+ lines.push(` provider: '${node.provider}',`);
1990
+ lines.push(` bucket: ${bucket},`);
1991
+ for (const [k, v] of Object.entries(node.props)) {
1992
+ if (k !== 'bucket')
1993
+ lines.push(` ${k}: ${genExpr(v, c)},`);
1994
+ }
1995
+ lines.push(`};`);
1996
+ }
1997
+ lines.push('');
1998
+ lines.push(`const ${node.name} = {`);
1999
+ lines.push(` async upload(key, data, contentType) {`);
2000
+ lines.push(` console.log('[Storage:${node.name}] upload:', key);`);
2001
+ lines.push(` // Implement based on provider`);
2002
+ lines.push(` },`);
2003
+ lines.push(` async download(key) {`);
2004
+ lines.push(` console.log('[Storage:${node.name}] download:', key);`);
2005
+ lines.push(` },`);
2006
+ lines.push(` async remove(key) {`);
2007
+ lines.push(` console.log('[Storage:${node.name}] delete:', key);`);
2008
+ lines.push(` },`);
2009
+ lines.push(` getUrl(key) {`);
2010
+ lines.push(` return \`/storage/${node.name}/\${key}\`;`);
2011
+ lines.push(` },`);
2012
+ lines.push(`};`);
2013
+ return lines.join('\n');
2014
+ }
2015
+ // ── Phase 4: Testing code generators ────────────────
2016
+ function genTestCode(node) {
2017
+ const c = ctx();
2018
+ const lines = [`// Generated by 0x — Test: ${node.name} (${node.testType})`, ''];
2019
+ lines.push(`import { describe, it, expect } from 'vitest';`);
2020
+ lines.push('');
2021
+ lines.push(`describe('${node.name}', () => {`);
2022
+ lines.push(` it('should pass', async () => {`);
2023
+ for (const stmt of node.body) {
2024
+ lines.push(` ${genStatement(stmt, c)}`);
2025
+ }
2026
+ lines.push(` });`);
2027
+ lines.push(`});`);
2028
+ return lines.join('\n');
2029
+ }
2030
+ function genE2eCode(node) {
2031
+ const c = ctx();
2032
+ const lines = [`// Generated by 0x — E2E Test: ${node.name}`, ''];
2033
+ lines.push(`import { test, expect } from '@playwright/test';`);
2034
+ lines.push('');
2035
+ lines.push(`test('${node.name}', async ({ page }) => {`);
2036
+ for (const step of node.steps) {
2037
+ const target = genExpr(step.target, c);
2038
+ const value = step.value ? genExpr(step.value, c) : null;
2039
+ switch (step.action) {
2040
+ case 'visit':
2041
+ case 'goto':
2042
+ lines.push(` await page.goto(${target});`);
2043
+ break;
2044
+ case 'click':
2045
+ lines.push(` await page.click(${target});`);
2046
+ break;
2047
+ case 'fill':
2048
+ case 'type':
2049
+ lines.push(` await page.fill(${target}, ${value || "''"});`);
2050
+ break;
2051
+ case 'see':
2052
+ case 'expect':
2053
+ lines.push(` await expect(page.locator(${target})).toBeVisible();`);
2054
+ break;
2055
+ case 'wait':
2056
+ lines.push(` await page.waitForSelector(${target});`);
2057
+ break;
2058
+ default:
2059
+ lines.push(` // ${step.action}: ${target}`);
2060
+ }
2061
+ }
2062
+ lines.push(`});`);
2063
+ return lines.join('\n');
2064
+ }
2065
+ function genMockCode(node) {
2066
+ const c = ctx();
2067
+ const lines = [`// Generated by 0x — Mock: ${node.target}`, ''];
2068
+ lines.push(`const ${node.target}Mock = {`);
2069
+ for (const resp of node.responses) {
2070
+ const response = genExpr(resp.response, c);
2071
+ lines.push(` '${resp.method} ${resp.path}': ${response},`);
2072
+ }
2073
+ lines.push(`};`);
2074
+ lines.push('');
2075
+ lines.push(`function mock${capitalize(node.target)}(method, path) {`);
2076
+ lines.push(` const key = \`\${method} \${path}\`;`);
2077
+ lines.push(` return ${node.target}Mock[key] ?? { status: 404, body: { error: 'Not found' } };`);
2078
+ lines.push(`}`);
2079
+ return lines.join('\n');
2080
+ }
2081
+ function genFixtureCode(node) {
2082
+ const c = ctx();
2083
+ const data = genExpr(node.data, c);
2084
+ const lines = [`// Generated by 0x — Fixture: ${node.name}`, ''];
2085
+ lines.push(`export const ${node.name}Fixture = ${data};`);
2086
+ return lines.join('\n');
2087
+ }
2088
+ // ── Phase 4: Error/Loading UI generators ────────────
2089
+ function genErrorUI(node, c) {
2090
+ const fallback = node.fallback.map(ch => genUINode(ch, c)).join('\n ');
2091
+ if (node.errorType === 'boundary') {
2092
+ return `<ErrorBoundary fallback={<div style={{ padding: '24px', textAlign: 'center', color: '#e53e3e' }}>\n ${fallback || '<p>An error occurred</p>'}\n </div>}>\n {children}\n</ErrorBoundary>`;
2093
+ }
2094
+ return `{/* Error handler: ${node.errorType} */}`;
2095
+ }
2096
+ function genLoadingUI(node, c) {
2097
+ const body = node.body.map(ch => genUINode(ch, c)).join('\n ');
2098
+ if (node.loadingType === 'skeleton') {
2099
+ return `<div style={{ animation: 'pulse 1.5s infinite', backgroundColor: '#e2e8f0', borderRadius: '8px' }}>\n ${body || '<div style={{ height: "20px", marginBottom: "8px" }} /><div style={{ height: "20px", width: "60%" }} />'}\n</div>`;
2100
+ }
2101
+ if (node.loadingType === 'spinner') {
2102
+ return `<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '40px' }}>\n <div style={{ width: '32px', height: '32px', border: '3px solid #e2e8f0', borderTop: '3px solid #3182ce', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />\n</div>`;
2103
+ }
2104
+ if (node.loadingType === 'shimmer') {
2105
+ return `<div style={{ background: 'linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)', backgroundSize: '200% 100%', animation: 'shimmer 1.5s infinite', borderRadius: '8px', height: '100px' }} />`;
2106
+ }
2107
+ return `<div style={{ textAlign: 'center', padding: '20px', color: '#718096' }}>Loading...</div>`;
2108
+ }
2109
+ function genOfflineUI(node, c) {
2110
+ const fallback = node.fallback.map(ch => genUINode(ch, c)).join('\n ');
2111
+ c.imports.add('useState');
2112
+ c.imports.add('useEffect');
2113
+ return `{(() => {\n const [online, setOnline] = useState(navigator.onLine);\n useEffect(() => {\n const on = () => setOnline(true);\n const off = () => setOnline(false);\n window.addEventListener('online', on);\n window.addEventListener('offline', off);\n return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); };\n }, []);\n if (!online) return (\n <div style={{ padding: '24px', textAlign: 'center', backgroundColor: '#fefcbf', borderRadius: '8px' }}>\n <p style={{ fontWeight: 600 }}>You are offline</p>\n ${fallback || "<p style={{ color: '#718096' }}>Please check your internet connection</p>"}\n </div>\n );\n return null;\n})()}`;
2114
+ }
2115
+ // ── Phase 4: i18n code generators ───────────────────
2116
+ function genI18nCode(node) {
2117
+ const lines = [`// Generated by 0x — i18n`, ''];
2118
+ lines.push(`const translations = {`);
2119
+ for (const t of node.translations) {
2120
+ lines.push(` '${t.locale}': {`);
2121
+ for (const e of t.entries) {
2122
+ lines.push(` '${e.key}': '${e.value}',`);
2123
+ }
2124
+ lines.push(` },`);
2125
+ }
2126
+ lines.push(`};`);
2127
+ lines.push('');
2128
+ lines.push(`let currentLocale = '${node.defaultLocale}';`);
2129
+ lines.push('');
2130
+ lines.push(`function t(key) {`);
2131
+ lines.push(` return translations[currentLocale]?.[key] ?? translations['${node.defaultLocale}']?.[key] ?? key;`);
2132
+ lines.push(`}`);
2133
+ lines.push('');
2134
+ lines.push(`function setLocale(locale) {`);
2135
+ lines.push(` if ([${node.locales.map(l => `'${l}'`).join(', ')}].includes(locale)) {`);
2136
+ lines.push(` currentLocale = locale;`);
2137
+ lines.push(` document.documentElement.lang = locale;`);
2138
+ lines.push(` }`);
2139
+ lines.push(`}`);
2140
+ lines.push('');
2141
+ lines.push(`import React, { createContext, useContext, useState } from 'react';`);
2142
+ lines.push('');
2143
+ lines.push(`const I18nContext = createContext({ locale: '${node.defaultLocale}', t, setLocale });`);
2144
+ lines.push('');
2145
+ lines.push(`export function I18nProvider({ children }) {`);
2146
+ lines.push(` const [locale, _setLocale] = useState('${node.defaultLocale}');`);
2147
+ lines.push(` const changeLocale = (l) => { _setLocale(l); setLocale(l); };`);
2148
+ lines.push(` return <I18nContext.Provider value={{ locale, t, setLocale: changeLocale }}>{children}</I18nContext.Provider>;`);
2149
+ lines.push(`}`);
2150
+ lines.push('');
2151
+ lines.push(`export function useI18n() { return useContext(I18nContext); }`);
2152
+ return lines.join('\n');
2153
+ }
2154
+ function genLocaleCode(node) {
2155
+ const c = ctx();
2156
+ const lines = [`// Generated by 0x — Locale`, ''];
2157
+ const dateFormat = node.props['date'] ? genExpr(node.props['date'], c) : "'ko-KR'";
2158
+ const numberFormat = node.props['number'] ? genExpr(node.props['number'], c) : "'ko-KR'";
2159
+ const currency = node.props['currency'] ? genExpr(node.props['currency'], c) : "'KRW'";
2160
+ lines.push(`const formatters = {`);
2161
+ lines.push(` date: (d) => new Intl.DateTimeFormat(${dateFormat}).format(new Date(d)),`);
2162
+ lines.push(` number: (n) => new Intl.NumberFormat(${numberFormat}).format(n),`);
2163
+ lines.push(` currency: (n) => new Intl.NumberFormat(${numberFormat}, { style: 'currency', currency: ${currency} }).format(n),`);
2164
+ lines.push(` relative: (d) => {`);
2165
+ lines.push(` const rtf = new Intl.RelativeTimeFormat(${dateFormat}, { numeric: 'auto' });`);
2166
+ lines.push(` const diff = (new Date(d).getTime() - Date.now()) / 1000;`);
2167
+ lines.push(` if (Math.abs(diff) < 60) return rtf.format(Math.round(diff), 'second');`);
2168
+ lines.push(` if (Math.abs(diff) < 3600) return rtf.format(Math.round(diff / 60), 'minute');`);
2169
+ lines.push(` if (Math.abs(diff) < 86400) return rtf.format(Math.round(diff / 3600), 'hour');`);
2170
+ lines.push(` return rtf.format(Math.round(diff / 86400), 'day');`);
2171
+ lines.push(` },`);
2172
+ lines.push(`};`);
2173
+ return lines.join('\n');
2174
+ }
2175
+ function genRtlCode(node) {
2176
+ const lines = [`// Generated by 0x — RTL Support`, ''];
2177
+ if (node.enabled) {
2178
+ lines.push(`document.documentElement.dir = 'rtl';`);
2179
+ lines.push(`document.documentElement.style.direction = 'rtl';`);
2180
+ }
2181
+ lines.push('');
2182
+ lines.push(`function setDirection(dir) {`);
2183
+ lines.push(` document.documentElement.dir = dir;`);
2184
+ lines.push(` document.documentElement.style.direction = dir;`);
2185
+ lines.push(`}`);
2186
+ return lines.join('\n');
2187
+ }
2188
+ // ── Helpers ─────────────────────────────────────────
2189
+ function extractStateName(expr) {
2190
+ if (expr.kind === 'identifier')
2191
+ return expr.name;
2192
+ if (expr.kind === 'member')
2193
+ return extractStateName(expr.object);
2194
+ return null;
2195
+ }
2196
+ function extractDeps(expr, c) {
2197
+ const deps = new Set();
2198
+ walkExpr(expr, e => {
2199
+ if (e.kind === 'identifier' && (c.states.has(e.name) || c.derivedNames.has(e.name))) {
2200
+ deps.add(e.name);
2201
+ }
2202
+ });
2203
+ return Array.from(deps);
2204
+ }
2205
+ function walkExpr(expr, fn) {
2206
+ fn(expr);
2207
+ switch (expr.kind) {
2208
+ case 'binary':
2209
+ walkExpr(expr.left, fn);
2210
+ walkExpr(expr.right, fn);
2211
+ break;
2212
+ case 'unary':
2213
+ walkExpr(expr.operand, fn);
2214
+ break;
2215
+ case 'call':
2216
+ walkExpr(expr.callee, fn);
2217
+ expr.args.forEach(a => walkExpr(a, fn));
2218
+ break;
2219
+ case 'member':
2220
+ walkExpr(expr.object, fn);
2221
+ break;
2222
+ case 'index':
2223
+ walkExpr(expr.object, fn);
2224
+ walkExpr(expr.index, fn);
2225
+ break;
2226
+ case 'ternary':
2227
+ walkExpr(expr.condition, fn);
2228
+ walkExpr(expr.consequent, fn);
2229
+ walkExpr(expr.alternate, fn);
2230
+ break;
2231
+ case 'array':
2232
+ expr.elements.forEach(e => walkExpr(e, fn));
2233
+ break;
2234
+ case 'object_expr':
2235
+ expr.properties.forEach(p => walkExpr(p.value, fn));
2236
+ break;
2237
+ case 'arrow':
2238
+ if (!Array.isArray(expr.body))
2239
+ walkExpr(expr.body, fn);
2240
+ break;
2241
+ case 'template':
2242
+ expr.parts.forEach(p => { if (typeof p !== 'string')
2243
+ walkExpr(p, fn); });
2244
+ break;
2245
+ case 'assignment':
2246
+ walkExpr(expr.target, fn);
2247
+ walkExpr(expr.value, fn);
2248
+ break;
2249
+ case 'await':
2250
+ walkExpr(expr.expression, fn);
2251
+ break;
2252
+ }
2253
+ }
2254
+ function genStyleObj(style) {
2255
+ if (Object.keys(style).length === 0)
2256
+ return '{}';
2257
+ const entries = Object.entries(style).map(([k, v]) => {
2258
+ // If value contains template literal, don't quote
2259
+ if (v.startsWith('${') || v.startsWith('`'))
2260
+ return `${k}: ${v}`;
2261
+ // If value is a number-like
2262
+ if (/^\d+$/.test(v))
2263
+ return `${k}: ${v}`;
2264
+ return `${k}: '${v}'`;
2265
+ });
2266
+ return `{ ${entries.join(', ')} }`;
2267
+ }
2268
+ function cssPropToJs(prop) {
2269
+ const map = {
2270
+ 'padding': 'padding',
2271
+ 'margin': 'margin',
2272
+ 'radius': 'borderRadius',
2273
+ 'shadow': 'boxShadow',
2274
+ 'bg': 'backgroundColor',
2275
+ 'color': 'color',
2276
+ };
2277
+ return map[prop] || prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2278
+ }
2279
+ function formatStyleValue(prop, val) {
2280
+ if (['padding', 'margin', 'radius', 'gap'].includes(prop)) {
2281
+ return `${val}px`;
2282
+ }
2283
+ if (prop === 'shadow') {
2284
+ if (val === 'sm')
2285
+ return '0 1px 2px rgba(0,0,0,0.1)';
2286
+ if (val === 'md')
2287
+ return '0 4px 6px rgba(0,0,0,0.1)';
2288
+ if (val === 'lg')
2289
+ return '0 10px 15px rgba(0,0,0,0.1)';
2290
+ }
2291
+ return val;
2292
+ }
2293
+ function quoteJsx(v) {
2294
+ // If the value is already a quoted string literal like 'xxx', extract it and use double quotes
2295
+ if (v.startsWith("'") && v.endsWith("'")) {
2296
+ const inner = v.slice(1, -1);
2297
+ return `"${inner}"`;
2298
+ }
2299
+ if (v.startsWith('"') || v.startsWith('`') || v.startsWith('{')) {
2300
+ return `{${v}}`;
2301
+ }
2302
+ return `"${v}"`;
2303
+ }
2304
+ //# sourceMappingURL=react.js.map