@lokascript/compilation-service 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3322 @@
1
+ // src/diff/diff.ts
2
+ function diffBehaviors(a, b) {
3
+ const triggerDiff = diffTrigger(a, b);
4
+ const opDiffs = diffOperations(a.operations, b.operations);
5
+ const identical = triggerDiff === null && opDiffs.every((d) => d.kind === "unchanged");
6
+ const summary = generateSummary(triggerDiff, opDiffs);
7
+ return { identical, trigger: triggerDiff, operations: opDiffs, summary };
8
+ }
9
+ function diffTrigger(a, b) {
10
+ const changes = [];
11
+ if (a.trigger.event !== b.trigger.event) {
12
+ changes.push(`event: ${a.trigger.event} \u2192 ${b.trigger.event}`);
13
+ }
14
+ const aModKeys = Object.keys(a.trigger.modifiers ?? {}).sort();
15
+ const bModKeys = Object.keys(b.trigger.modifiers ?? {}).sort();
16
+ const aMods = a.trigger.modifiers ?? {};
17
+ const bMods = b.trigger.modifiers ?? {};
18
+ for (const key of /* @__PURE__ */ new Set([...aModKeys, ...bModKeys])) {
19
+ const aVal = aMods[key];
20
+ const bVal = bMods[key];
21
+ if (aVal === void 0) {
22
+ changes.push(`added modifier: ${key}`);
23
+ } else if (bVal === void 0) {
24
+ changes.push(`removed modifier: ${key}`);
25
+ } else if (JSON.stringify(aVal) !== JSON.stringify(bVal)) {
26
+ changes.push(`modifier ${key}: ${JSON.stringify(aVal)} \u2192 ${JSON.stringify(bVal)}`);
27
+ }
28
+ }
29
+ if (changes.length === 0) return null;
30
+ return {
31
+ a: { event: a.trigger.event, modifiers: a.trigger.modifiers },
32
+ b: { event: b.trigger.event, modifiers: b.trigger.modifiers },
33
+ changes
34
+ };
35
+ }
36
+ function diffOperations(a, b) {
37
+ const aKeys = a.map(canonicalizeOp);
38
+ const bKeys = b.map(canonicalizeOp);
39
+ const lcs = computeLCS(aKeys, bKeys);
40
+ const rawDiffs = buildDiffFromLCS(a, b, aKeys, bKeys, lcs);
41
+ return detectReorderings(rawDiffs);
42
+ }
43
+ function computeLCS(a, b) {
44
+ const m = a.length;
45
+ const n = b.length;
46
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
47
+ for (let i = 1; i <= m; i++) {
48
+ for (let j = 1; j <= n; j++) {
49
+ if (a[i - 1] === b[j - 1]) {
50
+ dp[i][j] = dp[i - 1][j - 1] + 1;
51
+ } else {
52
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
53
+ }
54
+ }
55
+ }
56
+ return dp;
57
+ }
58
+ function buildDiffFromLCS(a, b, aKeys, bKeys, dp) {
59
+ const diffs = [];
60
+ let i = a.length;
61
+ let j = b.length;
62
+ while (i > 0 || j > 0) {
63
+ if (i > 0 && j > 0 && aKeys[i - 1] === bKeys[j - 1]) {
64
+ diffs.push({
65
+ kind: "unchanged",
66
+ index: { a: i - 1, b: j - 1 },
67
+ a: a[i - 1],
68
+ b: b[j - 1]
69
+ });
70
+ i--;
71
+ j--;
72
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
73
+ diffs.push({
74
+ kind: "added",
75
+ index: { b: j - 1 },
76
+ b: b[j - 1]
77
+ });
78
+ j--;
79
+ } else {
80
+ diffs.push({
81
+ kind: "removed",
82
+ index: { a: i - 1 },
83
+ a: a[i - 1]
84
+ });
85
+ i--;
86
+ }
87
+ }
88
+ return diffs.reverse();
89
+ }
90
+ function detectReorderings(diffs) {
91
+ const removed = [];
92
+ const added = [];
93
+ const result = [];
94
+ for (const d of diffs) {
95
+ if (d.kind === "removed") removed.push(d);
96
+ else if (d.kind === "added") added.push(d);
97
+ else result.push(d);
98
+ }
99
+ const usedRemoved = /* @__PURE__ */ new Set();
100
+ const usedAdded = /* @__PURE__ */ new Set();
101
+ for (let ri = 0; ri < removed.length; ri++) {
102
+ if (usedRemoved.has(ri)) continue;
103
+ const rKey = canonicalizeOp(removed[ri].a);
104
+ for (let ai = 0; ai < added.length; ai++) {
105
+ if (usedAdded.has(ai)) continue;
106
+ const aKey = canonicalizeOp(added[ai].b);
107
+ if (rKey === aKey) {
108
+ usedRemoved.add(ri);
109
+ usedAdded.add(ai);
110
+ result.push({
111
+ kind: "reordered",
112
+ index: { a: removed[ri].index.a, b: added[ai].index.b },
113
+ a: removed[ri].a,
114
+ b: added[ai].b
115
+ });
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ for (let ri = 0; ri < removed.length; ri++) {
121
+ if (usedRemoved.has(ri)) continue;
122
+ for (let ai = 0; ai < added.length; ai++) {
123
+ if (usedAdded.has(ai)) continue;
124
+ if (removed[ri].a.op === added[ai].b.op) {
125
+ usedRemoved.add(ri);
126
+ usedAdded.add(ai);
127
+ result.push({
128
+ kind: "changed",
129
+ index: { a: removed[ri].index.a, b: added[ai].index.b },
130
+ a: removed[ri].a,
131
+ b: added[ai].b,
132
+ changes: describeChanges(removed[ri].a, added[ai].b)
133
+ });
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ for (let ri = 0; ri < removed.length; ri++) {
139
+ if (!usedRemoved.has(ri)) result.push(removed[ri]);
140
+ }
141
+ for (let ai = 0; ai < added.length; ai++) {
142
+ if (!usedAdded.has(ai)) result.push(added[ai]);
143
+ }
144
+ result.sort((x, y) => {
145
+ const xPos = x.index.a ?? x.index.b ?? 0;
146
+ const yPos = y.index.a ?? y.index.b ?? 0;
147
+ return xPos - yPos;
148
+ });
149
+ return result;
150
+ }
151
+ function canonicalizeOp(op) {
152
+ const parts = [op.op];
153
+ switch (op.op) {
154
+ case "toggleClass":
155
+ case "addClass":
156
+ case "removeClass":
157
+ parts.push(op.className, canonicalizeTarget(op.target));
158
+ break;
159
+ case "setContent":
160
+ parts.push(op.content, canonicalizeTarget(op.target), op.position);
161
+ break;
162
+ case "appendContent":
163
+ parts.push(op.content, canonicalizeTarget(op.target));
164
+ break;
165
+ case "show":
166
+ case "hide":
167
+ case "focus":
168
+ case "blur":
169
+ parts.push(canonicalizeTarget(op.target));
170
+ break;
171
+ case "setVariable":
172
+ parts.push(op.name, op.value, op.scope);
173
+ break;
174
+ case "increment":
175
+ case "decrement":
176
+ parts.push(canonicalizeTarget(op.target), String(op.amount));
177
+ break;
178
+ case "navigate":
179
+ parts.push(op.url);
180
+ break;
181
+ case "historyBack":
182
+ case "historyForward":
183
+ break;
184
+ case "fetch":
185
+ parts.push(op.url, op.format, op.target ? canonicalizeTarget(op.target) : "");
186
+ break;
187
+ case "wait":
188
+ parts.push(String(op.durationMs));
189
+ break;
190
+ case "triggerEvent":
191
+ parts.push(op.eventName, canonicalizeTarget(op.target), op.detail ?? "");
192
+ break;
193
+ case "log":
194
+ parts.push(...op.values);
195
+ break;
196
+ }
197
+ return parts.join("|");
198
+ }
199
+ function canonicalizeTarget(t) {
200
+ return t.kind === "self" ? "self" : `${t.kind}:${t.value ?? ""}`;
201
+ }
202
+ function describeChanges(a, b) {
203
+ const changes = [];
204
+ const aObj = a;
205
+ const bObj = b;
206
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
207
+ for (const key of allKeys) {
208
+ if (key === "op") continue;
209
+ const aVal = JSON.stringify(aObj[key]);
210
+ const bVal = JSON.stringify(bObj[key]);
211
+ if (aVal !== bVal) {
212
+ changes.push(`${key}: ${formatValue(aObj[key])} \u2192 ${formatValue(bObj[key])}`);
213
+ }
214
+ }
215
+ return changes;
216
+ }
217
+ function formatValue(v) {
218
+ if (v === void 0) return "(none)";
219
+ if (typeof v === "object" && v !== null) {
220
+ const obj = v;
221
+ if ("kind" in obj) return obj.kind === "self" ? "self" : `${obj.kind}:${obj.value ?? ""}`;
222
+ return JSON.stringify(v);
223
+ }
224
+ return String(v);
225
+ }
226
+ function generateSummary(trigger, opDiffs) {
227
+ const parts = [];
228
+ if (!trigger && opDiffs.every((d) => d.kind === "unchanged")) {
229
+ return "No semantic change";
230
+ }
231
+ if (trigger) {
232
+ parts.push(`Trigger: ${trigger.changes.join(", ")}`);
233
+ }
234
+ const counts = {
235
+ added: 0,
236
+ removed: 0,
237
+ changed: 0,
238
+ reordered: 0,
239
+ unchanged: 0
240
+ };
241
+ for (const d of opDiffs) counts[d.kind]++;
242
+ const opParts = [];
243
+ if (counts.added > 0) opParts.push(`${counts.added} added`);
244
+ if (counts.removed > 0) opParts.push(`${counts.removed} removed`);
245
+ if (counts.changed > 0) opParts.push(`${counts.changed} changed`);
246
+ if (counts.reordered > 0) opParts.push(`${counts.reordered} reordered`);
247
+ if (opParts.length > 0) {
248
+ parts.push(`Operations: ${opParts.join(", ")}`);
249
+ }
250
+ return parts.join(". ");
251
+ }
252
+
253
+ // src/input/detect.ts
254
+ function detectFormat(input) {
255
+ const trimmed = input.trim();
256
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
257
+ return "explicit";
258
+ }
259
+ if (trimmed.startsWith("{")) {
260
+ try {
261
+ const parsed = JSON.parse(trimmed);
262
+ if (parsed && typeof parsed === "object" && typeof parsed.action === "string") {
263
+ return "json";
264
+ }
265
+ } catch {
266
+ }
267
+ }
268
+ return "natural";
269
+ }
270
+
271
+ // src/input/json-schema.ts
272
+ var VALID_VALUE_TYPES = /* @__PURE__ */ new Set(["selector", "literal", "reference", "expression"]);
273
+ function validateSemanticJSON(input) {
274
+ const diagnostics = [];
275
+ if (!input.action || typeof input.action !== "string") {
276
+ diagnostics.push({
277
+ severity: "error",
278
+ code: "INVALID_ACTION",
279
+ message: 'Field "action" is required and must be a string.',
280
+ suggestion: 'Provide a command name like "toggle", "add", "set", etc.'
281
+ });
282
+ return diagnostics;
283
+ }
284
+ if (input.roles && typeof input.roles === "object") {
285
+ for (const [role, value] of Object.entries(input.roles)) {
286
+ if (!value || typeof value !== "object") {
287
+ diagnostics.push({
288
+ severity: "error",
289
+ code: "INVALID_ROLE_VALUE",
290
+ message: `Role "${role}" must be an object with "type" and "value" fields.`,
291
+ suggestion: `Use: { "type": "selector", "value": ".active" }`
292
+ });
293
+ continue;
294
+ }
295
+ const v = value;
296
+ if (!VALID_VALUE_TYPES.has(v.type)) {
297
+ diagnostics.push({
298
+ severity: "error",
299
+ code: "INVALID_VALUE_TYPE",
300
+ message: `Role "${role}" has invalid type "${v.type}".`,
301
+ suggestion: `Valid types: selector, literal, reference, expression.`
302
+ });
303
+ }
304
+ if (v.value === void 0 || v.value === null) {
305
+ diagnostics.push({
306
+ severity: "error",
307
+ code: "MISSING_VALUE",
308
+ message: `Role "${role}" is missing the "value" field.`
309
+ });
310
+ }
311
+ }
312
+ }
313
+ if (input.trigger) {
314
+ if (!input.trigger.event || typeof input.trigger.event !== "string") {
315
+ diagnostics.push({
316
+ severity: "error",
317
+ code: "INVALID_TRIGGER",
318
+ message: 'Trigger "event" is required and must be a string.',
319
+ suggestion: 'Use an event name like "click", "mouseover", "keydown".'
320
+ });
321
+ }
322
+ }
323
+ return diagnostics;
324
+ }
325
+ function jsonToSemanticNode(input) {
326
+ const roles = /* @__PURE__ */ new Map();
327
+ if (input.roles) {
328
+ for (const [role, value] of Object.entries(input.roles)) {
329
+ roles.set(role, convertJSONValue(value));
330
+ }
331
+ }
332
+ if (input.trigger) {
333
+ roles.set("event", { type: "literal", value: input.trigger.event, dataType: "string" });
334
+ const bodyNode = {
335
+ kind: "command",
336
+ action: input.action,
337
+ roles: new Map(roles)
338
+ };
339
+ bodyNode.roles.delete("event");
340
+ return {
341
+ kind: "event-handler",
342
+ action: "on",
343
+ roles,
344
+ body: [bodyNode],
345
+ eventModifiers: input.trigger.modifiers ?? {}
346
+ };
347
+ }
348
+ return {
349
+ kind: "command",
350
+ action: input.action,
351
+ roles
352
+ };
353
+ }
354
+ function convertJSONValue(value) {
355
+ switch (value.type) {
356
+ case "selector":
357
+ return {
358
+ type: "selector",
359
+ value: String(value.value),
360
+ selectorKind: detectSelectorKind(String(value.value))
361
+ };
362
+ case "literal":
363
+ return {
364
+ type: "literal",
365
+ value: value.value,
366
+ dataType: typeof value.value === "number" ? "number" : typeof value.value === "boolean" ? "boolean" : "string"
367
+ };
368
+ case "reference":
369
+ return { type: "reference", value: String(value.value) };
370
+ case "expression":
371
+ return { type: "expression", raw: String(value.value) };
372
+ default:
373
+ return { type: "literal", value: String(value.value), dataType: "string" };
374
+ }
375
+ }
376
+ function detectSelectorKind(selector) {
377
+ if (selector.startsWith("#")) return "id";
378
+ if (selector.startsWith(".")) return "class";
379
+ if (selector.startsWith("[")) return "attribute";
380
+ return "complex";
381
+ }
382
+
383
+ // src/compile/cache.ts
384
+ var SemanticCache = class {
385
+ constructor(maxSize = 500) {
386
+ /** Cache hit/miss statistics */
387
+ this.hits = 0;
388
+ this.misses = 0;
389
+ this.maxSize = maxSize;
390
+ this.cache = /* @__PURE__ */ new Map();
391
+ }
392
+ /**
393
+ * Get a cached response, or undefined if not cached.
394
+ */
395
+ get(key) {
396
+ const entry = this.cache.get(key);
397
+ if (entry) {
398
+ this.hits++;
399
+ this.cache.delete(key);
400
+ this.cache.set(key, entry);
401
+ return entry;
402
+ }
403
+ this.misses++;
404
+ return void 0;
405
+ }
406
+ /**
407
+ * Store a response in the cache.
408
+ */
409
+ set(key, value) {
410
+ this.cache.delete(key);
411
+ if (this.cache.size >= this.maxSize) {
412
+ const oldest = this.cache.keys().next().value;
413
+ if (oldest !== void 0) {
414
+ this.cache.delete(oldest);
415
+ }
416
+ }
417
+ this.cache.set(key, value);
418
+ }
419
+ /** Current cache size */
420
+ get size() {
421
+ return this.cache.size;
422
+ }
423
+ /** Clear all entries */
424
+ clear() {
425
+ this.cache.clear();
426
+ this.hits = 0;
427
+ this.misses = 0;
428
+ }
429
+ };
430
+ function generateCacheKey(node, options) {
431
+ const canonical = canonicalize(node);
432
+ const optKey = `${options.optimization ?? 2}:${options.target ?? "esm"}:${options.minify ?? false}`;
433
+ return `${canonical}|${optKey}`;
434
+ }
435
+ function canonicalize(node) {
436
+ if (!node || typeof node !== "object") return String(node);
437
+ const n = node;
438
+ const parts = [];
439
+ if (n.action) parts.push(`a:${n.action}`);
440
+ if (n.kind) parts.push(`k:${n.kind}`);
441
+ if (n.roles && typeof n.roles.entries === "function") {
442
+ const roles = n.roles;
443
+ const sortedRoles = [...roles.entries()].sort(([a], [b]) => a.localeCompare(b));
444
+ for (const [role, value] of sortedRoles) {
445
+ parts.push(`r:${role}=${canonicalizeValue(value)}`);
446
+ }
447
+ }
448
+ if (Array.isArray(n.body)) {
449
+ parts.push(`b:[${n.body.map(canonicalize).join(",")}]`);
450
+ }
451
+ if (n.eventModifiers && typeof n.eventModifiers === "object") {
452
+ const mods = Object.entries(n.eventModifiers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
453
+ if (mods) parts.push(`m:${mods}`);
454
+ }
455
+ return parts.join(";");
456
+ }
457
+ function canonicalizeValue(value) {
458
+ if (!value || typeof value !== "object") return String(value);
459
+ const v = value;
460
+ return `${v.type}:${v.value ?? v.raw ?? ""}`;
461
+ }
462
+
463
+ // src/operations/extract.ts
464
+ function extractOperations(node) {
465
+ const n = node;
466
+ if (!n || typeof n !== "object") {
467
+ return emptySpec();
468
+ }
469
+ if (n.kind === "event-handler" || n.action === "on") {
470
+ return extractEventHandler(n);
471
+ }
472
+ const ops = extractCommand(n);
473
+ return {
474
+ trigger: { event: "click" },
475
+ triggerTarget: { kind: "self" },
476
+ operations: ops,
477
+ async: ops.some(isAsyncOp),
478
+ source: n.metadata?.sourceText
479
+ };
480
+ }
481
+ function extractEventHandler(n) {
482
+ const eventRole = n.roles?.get("event");
483
+ const eventName = String(eventRole?.value ?? "click");
484
+ const operations = [];
485
+ if (n.body && Array.isArray(n.body)) {
486
+ for (const child of n.body) {
487
+ operations.push(...extractCommand(child));
488
+ }
489
+ }
490
+ return {
491
+ trigger: {
492
+ event: eventName,
493
+ modifiers: n.eventModifiers
494
+ },
495
+ triggerTarget: { kind: "self" },
496
+ operations,
497
+ async: operations.some(isAsyncOp),
498
+ source: n.metadata?.sourceText
499
+ };
500
+ }
501
+ function extractCommand(n) {
502
+ const action = n.action;
503
+ if (!action || action === "on") return [];
504
+ const roles = n.roles;
505
+ switch (action) {
506
+ // --- DOM Class ---
507
+ case "toggle":
508
+ return extractClassOp("toggleClass", roles);
509
+ case "add":
510
+ return extractClassOp("addClass", roles);
511
+ case "remove":
512
+ return extractClassOp("removeClass", roles);
513
+ // --- DOM Content ---
514
+ case "put":
515
+ return extractPut(roles);
516
+ case "append":
517
+ return extractAppend(roles);
518
+ // --- DOM Visibility ---
519
+ case "show":
520
+ return extractVisibility("show", roles);
521
+ case "hide":
522
+ return extractVisibility("hide", roles);
523
+ // --- Variables ---
524
+ case "set":
525
+ return extractSet(roles);
526
+ case "increment":
527
+ return extractIncrDecr("increment", roles);
528
+ case "decrement":
529
+ return extractIncrDecr("decrement", roles);
530
+ // --- Navigation ---
531
+ case "go":
532
+ return extractGo(roles);
533
+ // --- Async ---
534
+ case "fetch":
535
+ return extractFetch(roles);
536
+ case "wait":
537
+ return extractWait(roles);
538
+ // --- Events ---
539
+ case "send":
540
+ case "trigger":
541
+ return extractTriggerEvent(roles);
542
+ // --- Focus ---
543
+ case "focus":
544
+ return [{ op: "focus", target: resolveTarget(roles, "patient") }];
545
+ case "blur":
546
+ return [{ op: "blur", target: resolveTarget(roles, "patient") }];
547
+ // --- Utility ---
548
+ case "log":
549
+ return extractLog(roles);
550
+ default:
551
+ return [];
552
+ }
553
+ }
554
+ function extractClassOp(op, roles) {
555
+ const patient = roles?.get("patient");
556
+ if (!patient) return [];
557
+ const value = String(patient.value ?? "");
558
+ const className = value.startsWith(".") ? value.slice(1) : value;
559
+ if (!className) return [];
560
+ const target = resolveTarget(roles, op === "removeClass" ? "source" : "destination");
561
+ return [{ op, className, target }];
562
+ }
563
+ function extractPut(roles) {
564
+ const patient = roles?.get("patient");
565
+ if (!patient) return [];
566
+ const content = String(patient.value ?? patient.raw ?? "");
567
+ const target = resolveTarget(roles, "destination");
568
+ const method = roles?.get("method");
569
+ let position = "into";
570
+ if (method?.value) {
571
+ const m = String(method.value);
572
+ if (m === "before") position = "before";
573
+ else if (m === "after") position = "after";
574
+ else if (m === "start" || m === "at start of") position = "start";
575
+ else if (m === "end" || m === "at end of") position = "end";
576
+ }
577
+ return [{ op: "setContent", content, target, position }];
578
+ }
579
+ function extractAppend(roles) {
580
+ const patient = roles?.get("patient");
581
+ if (!patient) return [];
582
+ const content = String(patient.value ?? patient.raw ?? "");
583
+ const target = resolveTarget(roles, "destination");
584
+ return [{ op: "appendContent", content, target }];
585
+ }
586
+ function extractVisibility(op, roles) {
587
+ const target = resolveTarget(roles, "patient");
588
+ return [{ op, target }];
589
+ }
590
+ function extractSet(roles) {
591
+ const destination = roles?.get("destination");
592
+ const patient = roles?.get("patient");
593
+ if (!destination && !patient) return [];
594
+ const targetRole = destination ?? patient;
595
+ const valueRole = destination ? patient : void 0;
596
+ const name = String(targetRole?.value ?? "");
597
+ const value = valueRole ? String(valueRole.value ?? "") : "";
598
+ const scope = targetRole?.scope ?? "local";
599
+ return [{ op: "setVariable", name, value, scope }];
600
+ }
601
+ function extractIncrDecr(op, roles) {
602
+ const target = resolveTarget(roles, "patient");
603
+ const quantity = roles?.get("quantity");
604
+ const amount = quantity?.value ? Number(quantity.value) : 1;
605
+ return [{ op, target, amount }];
606
+ }
607
+ function extractGo(roles) {
608
+ const destination = roles?.get("destination");
609
+ if (!destination) return [];
610
+ const url = String(destination.value ?? "");
611
+ if (url === "back") return [{ op: "historyBack" }];
612
+ if (url === "forward") return [{ op: "historyForward" }];
613
+ return [{ op: "navigate", url }];
614
+ }
615
+ function extractFetch(roles) {
616
+ const source = roles?.get("source");
617
+ if (!source) return [];
618
+ const url = String(source.value ?? source.raw ?? "");
619
+ const responseType = roles?.get("responseType");
620
+ let format = "text";
621
+ if (responseType?.value) {
622
+ const f = String(responseType.value);
623
+ if (f === "json") format = "json";
624
+ else if (f === "html") format = "html";
625
+ }
626
+ const destRole = roles?.get("destination");
627
+ const target = destRole ? resolveTargetFromRole(destRole) : void 0;
628
+ return [{ op: "fetch", url, format, target }];
629
+ }
630
+ function extractWait(roles) {
631
+ const duration = roles?.get("duration") ?? roles?.get("patient");
632
+ if (!duration) return [];
633
+ let ms = 0;
634
+ const val = duration.value;
635
+ if (typeof val === "number") {
636
+ ms = val;
637
+ } else if (typeof val === "string") {
638
+ ms = parseDurationMs(val);
639
+ }
640
+ return [{ op: "wait", durationMs: ms }];
641
+ }
642
+ function extractTriggerEvent(roles) {
643
+ const patient = roles?.get("patient");
644
+ if (!patient) return [];
645
+ const eventName = String(patient.value ?? "");
646
+ const target = resolveTarget(roles, "destination");
647
+ return [{ op: "triggerEvent", eventName, target }];
648
+ }
649
+ function extractLog(roles) {
650
+ const patient = roles?.get("patient");
651
+ const values = [];
652
+ if (patient) {
653
+ values.push(String(patient.value ?? patient.raw ?? ""));
654
+ }
655
+ return [{ op: "log", values }];
656
+ }
657
+ function resolveTarget(roles, roleName) {
658
+ const role = roles?.get(roleName);
659
+ if (!role) return { kind: "self" };
660
+ return resolveTargetFromRole(role);
661
+ }
662
+ function resolveTargetFromRole(role) {
663
+ const value = String(role.value ?? "");
664
+ if (role.type === "reference" && (value === "me" || value === "it")) {
665
+ return { kind: "self" };
666
+ }
667
+ if (value.startsWith(":") || role.scope) {
668
+ return { kind: "variable", value };
669
+ }
670
+ if (role.type === "selector" || value.startsWith("#") || value.startsWith(".") || value.startsWith("[")) {
671
+ return { kind: "selector", value };
672
+ }
673
+ if (value) {
674
+ return { kind: "selector", value };
675
+ }
676
+ return { kind: "self" };
677
+ }
678
+ function isAsyncOp(op) {
679
+ return op.op === "fetch" || op.op === "wait";
680
+ }
681
+ function parseDurationMs(value) {
682
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/);
683
+ if (!match) return 0;
684
+ const num = parseFloat(match[1]);
685
+ const unit = match[2] ?? "ms";
686
+ switch (unit) {
687
+ case "ms":
688
+ return num;
689
+ case "s":
690
+ return num * 1e3;
691
+ case "m":
692
+ return num * 6e4;
693
+ case "h":
694
+ return num * 36e5;
695
+ default:
696
+ return num;
697
+ }
698
+ }
699
+ function emptySpec() {
700
+ return {
701
+ trigger: { event: "click" },
702
+ triggerTarget: { kind: "self" },
703
+ operations: [],
704
+ async: false
705
+ };
706
+ }
707
+
708
+ // src/renderers/html-fixture.ts
709
+ function generateFixture(spec, hyperscript) {
710
+ const elements = collectElements(spec);
711
+ const triggerSelector = targetToSelector(spec.triggerTarget);
712
+ const triggerIsSeparate = triggerSelector !== null && !elements.some((e) => e.selector === triggerSelector);
713
+ let hyperscriptPlaced = false;
714
+ const lines = [];
715
+ if (needsInitialStyles(spec.operations)) {
716
+ lines.push("<style>");
717
+ for (const op of spec.operations) {
718
+ if (op.op === "hide") {
719
+ const sel = targetToSelector(op.target);
720
+ if (sel) lines.push(` ${sel} { display: block; }`);
721
+ }
722
+ if (op.op === "show") {
723
+ const sel = targetToSelector(op.target);
724
+ if (sel) lines.push(` ${sel} { display: none; }`);
725
+ }
726
+ }
727
+ lines.push("</style>");
728
+ }
729
+ if (triggerIsSeparate) {
730
+ const triggerEl = createTriggerElement(triggerSelector, hyperscript);
731
+ lines.push(triggerEl);
732
+ hyperscriptPlaced = true;
733
+ }
734
+ for (const el of elements) {
735
+ const isTrigger = !triggerIsSeparate && el.selector === triggerSelector || triggerSelector === null && !hyperscriptPlaced;
736
+ const attrs = isTrigger && hyperscript ? ` _="${escapeAttr(hyperscript)}"` : "";
737
+ if (isTrigger && hyperscript) hyperscriptPlaced = true;
738
+ const extraClasses = getInitialClasses(el.selector, spec.operations);
739
+ let tag = el.tag;
740
+ let className = el.className;
741
+ if (extraClasses.length > 0) {
742
+ className = className ? `${className} ${extraClasses.join(" ")}` : extraClasses.join(" ");
743
+ }
744
+ const classAttr = className ? ` class="${className}"` : "";
745
+ const idAttr = el.id ? ` id="${el.id}"` : "";
746
+ if (tag === "input") {
747
+ lines.push(`<${tag}${idAttr}${classAttr}${attrs} />`);
748
+ } else {
749
+ const content = el.defaultContent ?? "";
750
+ lines.push(`<${tag}${idAttr}${classAttr}${attrs}>${content}</${tag}>`);
751
+ }
752
+ }
753
+ if (elements.length === 0 && spec.triggerTarget.kind === "self") {
754
+ const attr = hyperscript ? ` _="${escapeAttr(hyperscript)}"` : "";
755
+ lines.push(`<button id="trigger"${attr}>Click me</button>`);
756
+ }
757
+ return lines.join("\n");
758
+ }
759
+ function collectElements(spec) {
760
+ const seen = /* @__PURE__ */ new Set();
761
+ const elements = [];
762
+ for (const op of spec.operations) {
763
+ for (const ref of getTargetRefs(op)) {
764
+ if (ref.kind !== "selector") continue;
765
+ if (seen.has(ref.value)) continue;
766
+ seen.add(ref.value);
767
+ elements.push(selectorToElement(ref.value));
768
+ }
769
+ }
770
+ return elements;
771
+ }
772
+ function getTargetRefs(op) {
773
+ const refs = [];
774
+ if ("target" in op && op.target) {
775
+ refs.push(op.target);
776
+ }
777
+ return refs;
778
+ }
779
+ function selectorToElement(selector) {
780
+ if (selector.startsWith("#")) {
781
+ const id = selector.slice(1);
782
+ return {
783
+ selector,
784
+ id,
785
+ tag: inferTagFromId(id),
786
+ defaultContent: inferContent(id)
787
+ };
788
+ }
789
+ if (selector.startsWith(".")) {
790
+ const className = selector.slice(1);
791
+ return {
792
+ selector,
793
+ className,
794
+ tag: "div",
795
+ defaultContent: className
796
+ };
797
+ }
798
+ return {
799
+ selector,
800
+ tag: "div",
801
+ defaultContent: ""
802
+ };
803
+ }
804
+ var BUTTON_IDS = /* @__PURE__ */ new Set(["btn", "button", "submit", "trigger", "action"]);
805
+ var INPUT_IDS = /* @__PURE__ */ new Set(["input", "search", "email", "text", "field", "query"]);
806
+ var FORM_IDS = /* @__PURE__ */ new Set(["form", "signup", "login"]);
807
+ var DIALOG_IDS = /* @__PURE__ */ new Set(["modal", "dialog", "popup", "overlay"]);
808
+ var LIST_IDS = /* @__PURE__ */ new Set(["list", "items", "results", "options"]);
809
+ function inferTagFromId(id) {
810
+ const lower = id.toLowerCase();
811
+ if (BUTTON_IDS.has(lower)) return "button";
812
+ if (INPUT_IDS.has(lower)) return "input";
813
+ if (FORM_IDS.has(lower)) return "form";
814
+ if (DIALOG_IDS.has(lower)) return "div";
815
+ if (LIST_IDS.has(lower)) return "ul";
816
+ return "div";
817
+ }
818
+ function inferContent(id) {
819
+ const lower = id.toLowerCase();
820
+ if (BUTTON_IDS.has(lower)) return "Click";
821
+ if (INPUT_IDS.has(lower)) return "";
822
+ if (LIST_IDS.has(lower)) return "";
823
+ return id.charAt(0).toUpperCase() + id.slice(1);
824
+ }
825
+ function createTriggerElement(selector, hyperscript) {
826
+ const attr = hyperscript ? ` _="${escapeAttr(hyperscript)}"` : "";
827
+ if (selector.startsWith("#")) {
828
+ const id = selector.slice(1);
829
+ return `<button id="${id}"${attr}>Click</button>`;
830
+ }
831
+ return `<button id="trigger"${attr}>Click</button>`;
832
+ }
833
+ function targetToSelector(ref) {
834
+ if (ref.kind === "selector") return ref.value;
835
+ if (ref.kind === "self") return null;
836
+ return null;
837
+ }
838
+ function needsInitialStyles(ops) {
839
+ return ops.some((op) => op.op === "hide" || op.op === "show");
840
+ }
841
+ function getInitialClasses(selector, ops) {
842
+ const classes = [];
843
+ for (const op of ops) {
844
+ if (op.op === "removeClass" && "target" in op) {
845
+ const targetSel = targetToSelector(op.target);
846
+ if (targetSel === selector || op.target.kind === "self" && selector === null) {
847
+ classes.push(op.className);
848
+ }
849
+ }
850
+ }
851
+ return classes;
852
+ }
853
+ function escapeAttr(s) {
854
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
855
+ }
856
+
857
+ // src/renderers/playwright.ts
858
+ var PlaywrightRenderer = class {
859
+ constructor() {
860
+ this.framework = "playwright";
861
+ }
862
+ render(spec, options = {}) {
863
+ const testName = options.testName ?? generateTestName(spec);
864
+ const html = generateFixture(spec, options.hyperscript);
865
+ const lines = [];
866
+ lines.push("import { test, expect } from '@playwright/test';");
867
+ lines.push("");
868
+ lines.push(`test('${escapeString(testName)}', async ({ page }) => {`);
869
+ lines.push(` await page.setContent(\`${escapeTemplate(wrapHtml(html))}\`);`);
870
+ if (options.executionMode === "compiled" && options.compiledJs) {
871
+ lines.push(
872
+ ` await page.addScriptTag({ content: \`${escapeTemplate(options.compiledJs)}\` });`
873
+ );
874
+ } else {
875
+ const bundlePath = options.bundlePath ?? "./node_modules/@hyperfixi/core/dist/lokascript-browser.js";
876
+ lines.push(` await page.addScriptTag({ path: '${escapeString(bundlePath)}' });`);
877
+ lines.push(" await page.waitForFunction(() => document.querySelector('[_]') !== null);");
878
+ lines.push(" await page.waitForTimeout(100);");
879
+ }
880
+ lines.push("");
881
+ const triggerLocator = getTriggerLocator(spec);
882
+ for (const op of spec.operations) {
883
+ const assertions = generateAssertions(op, spec, triggerLocator);
884
+ lines.push(...assertions.map((l) => ` ${l}`));
885
+ }
886
+ lines.push("});");
887
+ lines.push("");
888
+ return {
889
+ name: testName,
890
+ code: lines.join("\n"),
891
+ html,
892
+ framework: "playwright",
893
+ operations: spec.operations
894
+ };
895
+ }
896
+ };
897
+ function generateAssertions(op, spec, triggerLocator) {
898
+ switch (op.op) {
899
+ case "toggleClass":
900
+ return generateToggleClassAssertions(op, spec, triggerLocator);
901
+ case "addClass":
902
+ return generateAddClassAssertions(op, spec, triggerLocator);
903
+ case "removeClass":
904
+ return generateRemoveClassAssertions(op, spec, triggerLocator);
905
+ case "setContent":
906
+ return generateSetContentAssertions(op, spec, triggerLocator);
907
+ case "appendContent":
908
+ return generateAppendContentAssertions(op, spec, triggerLocator);
909
+ case "show":
910
+ return generateShowAssertions(op, spec, triggerLocator);
911
+ case "hide":
912
+ return generateHideAssertions(op, spec, triggerLocator);
913
+ case "navigate":
914
+ return generateNavigateAssertions(op, spec, triggerLocator);
915
+ case "focus":
916
+ return generateFocusAssertions(op, spec, triggerLocator);
917
+ case "blur":
918
+ return generateBlurAssertions(op, spec, triggerLocator);
919
+ case "triggerEvent":
920
+ return generateTriggerEventAssertions(op, spec, triggerLocator);
921
+ case "fetch":
922
+ return generateFetchAssertions(op, spec, triggerLocator);
923
+ case "wait":
924
+ return generateWaitAssertions(op);
925
+ case "setVariable":
926
+ case "increment":
927
+ case "decrement":
928
+ return generateVariableAssertions(op, spec, triggerLocator);
929
+ case "log":
930
+ return generateLogAssertions(op, spec, triggerLocator);
931
+ default:
932
+ return [`// Unsupported operation: ${op.op}`];
933
+ }
934
+ }
935
+ function generateToggleClassAssertions(op, spec, triggerLocator) {
936
+ const locator = targetToLocator(op.target, spec);
937
+ const lines = [];
938
+ lines.push(`// Toggle .${op.className}`);
939
+ lines.push(`await ${triggerLocator}.${triggerAction(spec)};`);
940
+ if (spec.async) lines.push("await page.waitForTimeout(100);");
941
+ lines.push(`await expect(${locator}).toHaveClass(/${op.className}/);`);
942
+ lines.push("");
943
+ lines.push(`// Toggle back`);
944
+ lines.push(`await ${triggerLocator}.${triggerAction(spec)};`);
945
+ if (spec.async) lines.push("await page.waitForTimeout(100);");
946
+ lines.push(`await expect(${locator}).not.toHaveClass(/${op.className}/);`);
947
+ return lines;
948
+ }
949
+ function generateAddClassAssertions(op, spec, triggerLocator) {
950
+ const locator = targetToLocator(op.target, spec);
951
+ return [
952
+ `// Add .${op.className}`,
953
+ `await ${triggerLocator}.${triggerAction(spec)};`,
954
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
955
+ `await expect(${locator}).toHaveClass(/${op.className}/);`
956
+ ];
957
+ }
958
+ function generateRemoveClassAssertions(op, spec, triggerLocator) {
959
+ const locator = targetToLocator(op.target, spec);
960
+ return [
961
+ `// Remove .${op.className} (should have it initially)`,
962
+ `await expect(${locator}).toHaveClass(/${op.className}/);`,
963
+ `await ${triggerLocator}.${triggerAction(spec)};`,
964
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
965
+ `await expect(${locator}).not.toHaveClass(/${op.className}/);`
966
+ ];
967
+ }
968
+ function generateSetContentAssertions(op, spec, triggerLocator) {
969
+ const locator = targetToLocator(op.target, spec);
970
+ return [
971
+ `// Put "${op.content}" into element`,
972
+ `await ${triggerLocator}.${triggerAction(spec)};`,
973
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
974
+ `await expect(${locator}).toContainText('${escapeString(op.content)}');`
975
+ ];
976
+ }
977
+ function generateAppendContentAssertions(op, spec, triggerLocator) {
978
+ const locator = targetToLocator(op.target, spec);
979
+ return [
980
+ `// Append "${op.content}"`,
981
+ `await ${triggerLocator}.${triggerAction(spec)};`,
982
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
983
+ `await expect(${locator}).toContainText('${escapeString(op.content)}');`
984
+ ];
985
+ }
986
+ function generateShowAssertions(op, spec, triggerLocator) {
987
+ const locator = targetToLocator(op.target, spec);
988
+ return [
989
+ "// Show element (initially hidden)",
990
+ `await expect(${locator}).not.toBeVisible();`,
991
+ `await ${triggerLocator}.${triggerAction(spec)};`,
992
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
993
+ `await expect(${locator}).toBeVisible();`
994
+ ];
995
+ }
996
+ function generateHideAssertions(op, spec, triggerLocator) {
997
+ const locator = targetToLocator(op.target, spec);
998
+ return [
999
+ "// Hide element (initially visible)",
1000
+ `await expect(${locator}).toBeVisible();`,
1001
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1002
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
1003
+ `await expect(${locator}).not.toBeVisible();`
1004
+ ];
1005
+ }
1006
+ function generateNavigateAssertions(op, spec, triggerLocator) {
1007
+ return [
1008
+ `// Navigate to ${op.url}`,
1009
+ `// Note: Navigation will change the page URL`,
1010
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1011
+ `await page.waitForURL('**${op.url}**');`
1012
+ ];
1013
+ }
1014
+ function generateFocusAssertions(op, spec, triggerLocator) {
1015
+ const locator = targetToLocator(op.target, spec);
1016
+ return [
1017
+ "// Focus element",
1018
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1019
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
1020
+ `await expect(${locator}).toBeFocused();`
1021
+ ];
1022
+ }
1023
+ function generateBlurAssertions(op, spec, triggerLocator) {
1024
+ const locator = targetToLocator(op.target, spec);
1025
+ return [
1026
+ "// Blur element",
1027
+ `await ${locator}.focus();`,
1028
+ `await expect(${locator}).toBeFocused();`,
1029
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1030
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
1031
+ `await expect(${locator}).not.toBeFocused();`
1032
+ ];
1033
+ }
1034
+ function generateTriggerEventAssertions(op, spec, triggerLocator) {
1035
+ return [
1036
+ `// Trigger '${op.eventName}' event`,
1037
+ `const eventReceived = page.evaluate((sel) => {`,
1038
+ ` return new Promise<boolean>((resolve) => {`,
1039
+ ` const el = document.querySelector(sel) ?? document.body;`,
1040
+ ` el.addEventListener('${escapeString(op.eventName)}', () => resolve(true), { once: true });`,
1041
+ ` setTimeout(() => resolve(false), 2000);`,
1042
+ ` });`,
1043
+ `}, '${escapeString(targetToCssSelector(op.target))}');`,
1044
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1045
+ `expect(await eventReceived).toBe(true);`
1046
+ ];
1047
+ }
1048
+ function generateFetchAssertions(op, spec, triggerLocator) {
1049
+ const lines = [];
1050
+ lines.push(`// Fetch ${op.url} as ${op.format}`);
1051
+ if (op.format === "json") {
1052
+ lines.push(`await page.route('**${op.url}**', route => {`);
1053
+ lines.push(` route.fulfill({`);
1054
+ lines.push(` contentType: 'application/json',`);
1055
+ lines.push(` body: JSON.stringify({ data: 'mock' }),`);
1056
+ lines.push(` });`);
1057
+ lines.push(`});`);
1058
+ } else if (op.format === "html") {
1059
+ lines.push(`await page.route('**${op.url}**', route => {`);
1060
+ lines.push(` route.fulfill({`);
1061
+ lines.push(` contentType: 'text/html',`);
1062
+ lines.push(` body: '<div>Mock HTML</div>',`);
1063
+ lines.push(` });`);
1064
+ lines.push(`});`);
1065
+ } else {
1066
+ lines.push(`await page.route('**${op.url}**', route => {`);
1067
+ lines.push(` route.fulfill({`);
1068
+ lines.push(` contentType: 'text/plain',`);
1069
+ lines.push(` body: 'Mock response',`);
1070
+ lines.push(` });`);
1071
+ lines.push(`});`);
1072
+ }
1073
+ lines.push(`await ${triggerLocator}.${triggerAction(spec)};`);
1074
+ lines.push("await page.waitForTimeout(500);");
1075
+ if (op.target) {
1076
+ const locator = targetToLocator(op.target, spec);
1077
+ lines.push(`await expect(${locator}).not.toBeEmpty();`);
1078
+ }
1079
+ return lines;
1080
+ }
1081
+ function generateWaitAssertions(op) {
1082
+ return [`// Wait ${op.durationMs}ms`, `// (no assertion \u2014 wait is a timing operation)`];
1083
+ }
1084
+ function generateVariableAssertions(op, spec, triggerLocator) {
1085
+ return [
1086
+ `// ${op.op} (variable operation \u2014 verify no errors)`,
1087
+ "const errors: string[] = [];",
1088
+ "page.on('pageerror', (err) => errors.push(err.message));",
1089
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1090
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
1091
+ "expect(errors).toHaveLength(0);"
1092
+ ];
1093
+ }
1094
+ function generateLogAssertions(op, spec, triggerLocator) {
1095
+ return [
1096
+ `// Log: ${op.values.join(", ")}`,
1097
+ "const logs: string[] = [];",
1098
+ "page.on('console', msg => { if (msg.type() === 'log') logs.push(msg.text()); });",
1099
+ `await ${triggerLocator}.${triggerAction(spec)};`,
1100
+ ...spec.async ? ["await page.waitForTimeout(100);"] : [],
1101
+ "expect(logs.length).toBeGreaterThan(0);"
1102
+ ];
1103
+ }
1104
+ function generateTestName(spec) {
1105
+ if (spec.source) return spec.source;
1106
+ const parts = [];
1107
+ parts.push(`on ${spec.trigger.event}`);
1108
+ for (const op of spec.operations) {
1109
+ parts.push(describeOp(op));
1110
+ }
1111
+ return parts.join(" ");
1112
+ }
1113
+ function describeOp(op) {
1114
+ switch (op.op) {
1115
+ case "toggleClass":
1116
+ return `toggle .${op.className}${describeTarget(op.target)}`;
1117
+ case "addClass":
1118
+ return `add .${op.className}${describeTarget(op.target)}`;
1119
+ case "removeClass":
1120
+ return `remove .${op.className}${describeTarget(op.target)}`;
1121
+ case "setContent":
1122
+ return `put "${op.content}"${describeTarget(op.target)}`;
1123
+ case "appendContent":
1124
+ return `append "${op.content}"${describeTarget(op.target)}`;
1125
+ case "show":
1126
+ return `show${describeTarget(op.target)}`;
1127
+ case "hide":
1128
+ return `hide${describeTarget(op.target)}`;
1129
+ case "navigate":
1130
+ return `go to ${op.url}`;
1131
+ case "focus":
1132
+ return `focus${describeTarget(op.target)}`;
1133
+ case "blur":
1134
+ return `blur${describeTarget(op.target)}`;
1135
+ case "fetch":
1136
+ return `fetch ${op.url} as ${op.format}`;
1137
+ case "wait":
1138
+ return `wait ${op.durationMs}ms`;
1139
+ case "setVariable":
1140
+ return `set ${op.name} to ${op.value}`;
1141
+ case "increment":
1142
+ return `increment${describeTarget(op.target)}`;
1143
+ case "decrement":
1144
+ return `decrement${describeTarget(op.target)}`;
1145
+ case "triggerEvent":
1146
+ return `trigger ${op.eventName}${describeTarget(op.target)}`;
1147
+ case "log":
1148
+ return `log ${op.values.join(", ")}`;
1149
+ case "historyBack":
1150
+ return "go back";
1151
+ case "historyForward":
1152
+ return "go forward";
1153
+ default:
1154
+ return op.op;
1155
+ }
1156
+ }
1157
+ function describeTarget(target) {
1158
+ if (target.kind === "self") return "";
1159
+ if (target.kind === "selector") return ` on ${target.value}`;
1160
+ if (target.kind === "variable") return ` on ${target.value}`;
1161
+ return "";
1162
+ }
1163
+ function getTriggerLocator(spec) {
1164
+ if (spec.triggerTarget.kind === "selector") {
1165
+ return `page.locator('${escapeString(spec.triggerTarget.value)}')`;
1166
+ }
1167
+ for (const op of spec.operations) {
1168
+ if ("target" in op && op.target.kind === "selector") {
1169
+ const t = op.target;
1170
+ if (t.kind === "selector") {
1171
+ return `page.locator('${escapeString(t.value)}')`;
1172
+ }
1173
+ }
1174
+ }
1175
+ return "page.locator('[_]')";
1176
+ }
1177
+ function targetToLocator(target, spec) {
1178
+ if (target.kind === "selector") {
1179
+ return `page.locator('${escapeString(target.value)}')`;
1180
+ }
1181
+ if (target.kind === "self") {
1182
+ return getTriggerLocator(spec);
1183
+ }
1184
+ return getTriggerLocator(spec);
1185
+ }
1186
+ function targetToCssSelector(target) {
1187
+ if (target.kind === "selector") return target.value;
1188
+ if (target.kind === "self") return "[_]";
1189
+ return "body";
1190
+ }
1191
+ function triggerAction(spec) {
1192
+ const event = spec.trigger.event;
1193
+ switch (event) {
1194
+ case "click":
1195
+ return "click()";
1196
+ case "dblclick":
1197
+ return "dblclick()";
1198
+ case "mouseenter":
1199
+ return "hover()";
1200
+ case "mouseover":
1201
+ return "hover()";
1202
+ case "focus":
1203
+ return "focus()";
1204
+ case "blur":
1205
+ return "blur()";
1206
+ case "keydown":
1207
+ case "keyup":
1208
+ case "keypress":
1209
+ return "press('Enter')";
1210
+ case "input":
1211
+ case "change":
1212
+ return "fill('test')";
1213
+ case "submit":
1214
+ return "click()";
1215
+ default:
1216
+ return `dispatchEvent(new Event('${escapeString(event)}'))`;
1217
+ }
1218
+ }
1219
+ function wrapHtml(body) {
1220
+ return `<!DOCTYPE html>
1221
+ <html>
1222
+ <body>
1223
+ ${body}
1224
+ </body>
1225
+ </html>`;
1226
+ }
1227
+ function escapeString(s) {
1228
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1229
+ }
1230
+ function escapeTemplate(s) {
1231
+ return s.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1232
+ }
1233
+
1234
+ // src/renderers/renderer-helpers.ts
1235
+ function capitalize(s) {
1236
+ if (!s) return s;
1237
+ return s.charAt(0).toUpperCase() + s.slice(1);
1238
+ }
1239
+ function escapeString2(s) {
1240
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1241
+ }
1242
+ function isNumeric(s) {
1243
+ return /^-?\d+(\.\d+)?$/.test(s);
1244
+ }
1245
+ function cleanVarName(name) {
1246
+ return name.replace(/^:/, "").replace(/[^a-zA-Z0-9]/g, "");
1247
+ }
1248
+ function targetKey(target) {
1249
+ if (target.kind === "self") return "self";
1250
+ return target.value;
1251
+ }
1252
+ function stateKey(prefix, name, target) {
1253
+ return `${prefix}:${name}:${targetKey(target)}`;
1254
+ }
1255
+ function targetStateName(target, suffix) {
1256
+ if (target.kind === "selector") {
1257
+ const clean = target.value.replace(/^[#.]/, "");
1258
+ return clean + suffix;
1259
+ }
1260
+ return suffix.toLowerCase();
1261
+ }
1262
+ function targetRefName(target) {
1263
+ if (target.kind === "selector") {
1264
+ const clean = target.value.replace(/^[#.]/, "");
1265
+ return clean + "Ref";
1266
+ }
1267
+ return "elementRef";
1268
+ }
1269
+ function inferType(value) {
1270
+ if (isNumeric(value)) return "<number>";
1271
+ if (value === "true" || value === "false") return "<boolean>";
1272
+ return "<string>";
1273
+ }
1274
+ function inferInitial(value, typeAnn) {
1275
+ if (typeAnn === "<number>") return isNumeric(value) ? value : "0";
1276
+ if (typeAnn === "<boolean>") return value === "true" ? "true" : "false";
1277
+ return `'${escapeString2(value)}'`;
1278
+ }
1279
+ function selectorToTag(selector) {
1280
+ if (selector.startsWith("#")) {
1281
+ const id = selector.slice(1).toLowerCase();
1282
+ if (["btn", "button", "submit", "trigger"].includes(id)) return "button";
1283
+ if (["input", "search", "email", "text", "field"].includes(id)) return "input";
1284
+ if (["form", "signup", "login"].includes(id)) return "form";
1285
+ return "div";
1286
+ }
1287
+ return "div";
1288
+ }
1289
+ function inferTriggerTag(spec) {
1290
+ if (spec.triggerTarget.kind === "selector") {
1291
+ return selectorToTag(spec.triggerTarget.value);
1292
+ }
1293
+ if (spec.trigger.event === "submit") return "form";
1294
+ if (spec.trigger.event === "input" || spec.trigger.event === "change") return "input";
1295
+ return "button";
1296
+ }
1297
+ function inferTriggerContent(spec) {
1298
+ const tag = inferTriggerTag(spec);
1299
+ if (tag === "input" || tag === "form") return "";
1300
+ if (spec.triggerTarget.kind === "selector") {
1301
+ const id = spec.triggerTarget.value.replace(/^[#.]/, "");
1302
+ return capitalize(id);
1303
+ }
1304
+ return "Click";
1305
+ }
1306
+ function generateComponentName(spec) {
1307
+ if (spec.operations.length === 0) return "Component";
1308
+ const parts = [];
1309
+ const firstOp = spec.operations[0];
1310
+ switch (firstOp.op) {
1311
+ case "toggleClass":
1312
+ parts.push("Toggle", capitalize(firstOp.className));
1313
+ break;
1314
+ case "addClass":
1315
+ parts.push("Add", capitalize(firstOp.className));
1316
+ break;
1317
+ case "removeClass":
1318
+ parts.push("Remove", capitalize(firstOp.className));
1319
+ break;
1320
+ case "show":
1321
+ parts.push("Show");
1322
+ appendTargetName(parts, firstOp.target);
1323
+ break;
1324
+ case "hide":
1325
+ parts.push("Hide");
1326
+ appendTargetName(parts, firstOp.target);
1327
+ break;
1328
+ case "setContent":
1329
+ parts.push("SetContent");
1330
+ appendTargetName(parts, firstOp.target);
1331
+ break;
1332
+ case "navigate":
1333
+ parts.push("NavigateTo");
1334
+ parts.push(capitalize(firstOp.url.replace(/[^a-zA-Z0-9]/g, "")));
1335
+ break;
1336
+ case "fetch":
1337
+ parts.push("FetchData");
1338
+ break;
1339
+ case "increment":
1340
+ parts.push("Increment");
1341
+ appendTargetName(parts, firstOp.target);
1342
+ break;
1343
+ case "decrement":
1344
+ parts.push("Decrement");
1345
+ appendTargetName(parts, firstOp.target);
1346
+ break;
1347
+ case "focus":
1348
+ parts.push("Focus");
1349
+ appendTargetName(parts, firstOp.target);
1350
+ break;
1351
+ default:
1352
+ parts.push("Component");
1353
+ }
1354
+ if (spec.trigger.event !== "click") {
1355
+ parts.push("On", capitalize(spec.trigger.event));
1356
+ }
1357
+ return parts.join("") || "Component";
1358
+ }
1359
+ function appendTargetName(parts, target) {
1360
+ if (target.kind === "selector") {
1361
+ const clean = target.value.replace(/^[#.]/, "");
1362
+ parts.push(capitalize(clean));
1363
+ }
1364
+ }
1365
+ function getSelfClassStates(spec, stateMap) {
1366
+ const result = [];
1367
+ for (const op of spec.operations) {
1368
+ if ((op.op === "toggleClass" || op.op === "addClass" || op.op === "removeClass") && op.target.kind === "self") {
1369
+ const stateVar = stateMap.get("self:class:" + op.className);
1370
+ if (stateVar) {
1371
+ result.push({ stateName: stateVar, className: op.className });
1372
+ }
1373
+ }
1374
+ }
1375
+ return result;
1376
+ }
1377
+
1378
+ // src/renderers/react.ts
1379
+ var ReactRenderer = class {
1380
+ constructor() {
1381
+ this.framework = "react";
1382
+ }
1383
+ render(spec, options = {}) {
1384
+ const componentName = options.componentName ?? generateComponentName(spec);
1385
+ const ts = options.typescript !== false;
1386
+ const analysis = analyzeOperations(spec);
1387
+ const lines = [];
1388
+ const hooksList = [...analysis.hooks];
1389
+ if (hooksList.length > 0) {
1390
+ lines.push(`import { ${hooksList.join(", ")} } from 'react';`);
1391
+ }
1392
+ lines.push("");
1393
+ const propsType = ts ? "()" : "()";
1394
+ lines.push(`export function ${componentName}${propsType} {`);
1395
+ for (const state of analysis.states) {
1396
+ const typeAnn = ts ? state.typeAnnotation : "";
1397
+ lines.push(
1398
+ ` const [${state.name}, ${state.setter}] = useState${typeAnn}(${state.initialValue});`
1399
+ );
1400
+ }
1401
+ for (const ref of analysis.refs) {
1402
+ const typeAnn = ts ? ref.typeAnnotation : "";
1403
+ lines.push(` const ${ref.name} = useRef${typeAnn}(null);`);
1404
+ }
1405
+ if (analysis.states.length > 0 || analysis.refs.length > 0) {
1406
+ lines.push("");
1407
+ }
1408
+ const handlerName = `handle${capitalize(spec.trigger.event)}`;
1409
+ const asyncPrefix = spec.async ? "async " : "";
1410
+ lines.push(` const ${handlerName} = useCallback(${asyncPrefix}() => {`);
1411
+ for (const op of spec.operations) {
1412
+ const opLines = generateOperationCode(op, analysis);
1413
+ for (const l of opLines) {
1414
+ lines.push(` ${l}`);
1415
+ }
1416
+ }
1417
+ lines.push(" }, []);");
1418
+ lines.push("");
1419
+ lines.push(" return (");
1420
+ const jsx = generateJSX(spec, analysis, handlerName);
1421
+ for (const l of jsx) {
1422
+ lines.push(` ${l}`);
1423
+ }
1424
+ lines.push(" );");
1425
+ lines.push("}");
1426
+ lines.push("");
1427
+ return {
1428
+ name: componentName,
1429
+ code: lines.join("\n"),
1430
+ framework: "react",
1431
+ operations: spec.operations,
1432
+ hooks: hooksList
1433
+ };
1434
+ }
1435
+ };
1436
+ function analyzeOperations(spec) {
1437
+ const hooks = /* @__PURE__ */ new Set();
1438
+ const states = [];
1439
+ const refs = [];
1440
+ const stateMap = /* @__PURE__ */ new Map();
1441
+ const refMap = /* @__PURE__ */ new Map();
1442
+ const seenStates = /* @__PURE__ */ new Set();
1443
+ hooks.add("useCallback");
1444
+ for (const op of spec.operations) {
1445
+ switch (op.op) {
1446
+ case "toggleClass": {
1447
+ const key = stateKey("has", op.className, op.target);
1448
+ if (!seenStates.has(key)) {
1449
+ seenStates.add(key);
1450
+ hooks.add("useState");
1451
+ const name = `has${capitalize(op.className)}`;
1452
+ states.push({
1453
+ name,
1454
+ setter: `set${capitalize(name)}`,
1455
+ typeAnnotation: "<boolean>",
1456
+ initialValue: "false",
1457
+ targetKey: targetKey(op.target)
1458
+ });
1459
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
1460
+ }
1461
+ break;
1462
+ }
1463
+ case "addClass":
1464
+ case "removeClass": {
1465
+ const key = stateKey("has", op.className, op.target);
1466
+ if (!seenStates.has(key)) {
1467
+ seenStates.add(key);
1468
+ hooks.add("useState");
1469
+ const name = `has${capitalize(op.className)}`;
1470
+ const initial = op.op === "removeClass" ? "true" : "false";
1471
+ states.push({
1472
+ name,
1473
+ setter: `set${capitalize(name)}`,
1474
+ typeAnnotation: "<boolean>",
1475
+ initialValue: initial,
1476
+ targetKey: targetKey(op.target)
1477
+ });
1478
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
1479
+ }
1480
+ break;
1481
+ }
1482
+ case "show":
1483
+ case "hide": {
1484
+ const tk = targetKey(op.target);
1485
+ const key = "visible:" + tk;
1486
+ if (!seenStates.has(key)) {
1487
+ seenStates.add(key);
1488
+ hooks.add("useState");
1489
+ const name = targetStateName(op.target, "Visible");
1490
+ states.push({
1491
+ name,
1492
+ setter: `set${capitalize(name)}`,
1493
+ typeAnnotation: "<boolean>",
1494
+ initialValue: op.op === "hide" ? "true" : "false",
1495
+ targetKey: tk
1496
+ });
1497
+ stateMap.set(tk + ":visible", name);
1498
+ }
1499
+ break;
1500
+ }
1501
+ case "setContent":
1502
+ case "appendContent": {
1503
+ const tk = targetKey(op.target);
1504
+ const key = "content:" + tk;
1505
+ if (!seenStates.has(key)) {
1506
+ seenStates.add(key);
1507
+ hooks.add("useState");
1508
+ const name = targetStateName(op.target, "Content");
1509
+ states.push({
1510
+ name,
1511
+ setter: `set${capitalize(name)}`,
1512
+ typeAnnotation: "<string>",
1513
+ initialValue: "''",
1514
+ targetKey: tk
1515
+ });
1516
+ stateMap.set(tk + ":content", name);
1517
+ }
1518
+ break;
1519
+ }
1520
+ case "setVariable": {
1521
+ const key = "var:" + op.name;
1522
+ if (!seenStates.has(key)) {
1523
+ seenStates.add(key);
1524
+ hooks.add("useState");
1525
+ const name = cleanVarName(op.name);
1526
+ const typeAnn = inferType(op.value);
1527
+ states.push({
1528
+ name,
1529
+ setter: `set${capitalize(name)}`,
1530
+ typeAnnotation: typeAnn,
1531
+ initialValue: inferInitial(op.value, typeAnn),
1532
+ targetKey: "var:" + op.name
1533
+ });
1534
+ stateMap.set("var:" + op.name, name);
1535
+ }
1536
+ break;
1537
+ }
1538
+ case "increment":
1539
+ case "decrement": {
1540
+ const tk = targetKey(op.target);
1541
+ const key = "num:" + tk;
1542
+ if (!seenStates.has(key)) {
1543
+ seenStates.add(key);
1544
+ hooks.add("useState");
1545
+ const name = targetStateName(op.target, "Count");
1546
+ states.push({
1547
+ name,
1548
+ setter: `set${capitalize(name)}`,
1549
+ typeAnnotation: "<number>",
1550
+ initialValue: "0",
1551
+ targetKey: tk
1552
+ });
1553
+ stateMap.set(tk + ":num", name);
1554
+ }
1555
+ break;
1556
+ }
1557
+ case "fetch": {
1558
+ if (op.target) {
1559
+ const tk = targetKey(op.target);
1560
+ const key = "content:" + tk;
1561
+ if (!seenStates.has(key)) {
1562
+ seenStates.add(key);
1563
+ hooks.add("useState");
1564
+ const name = targetStateName(op.target, "Content");
1565
+ states.push({
1566
+ name,
1567
+ setter: `set${capitalize(name)}`,
1568
+ typeAnnotation: "<string>",
1569
+ initialValue: "''",
1570
+ targetKey: tk
1571
+ });
1572
+ stateMap.set(tk + ":content", name);
1573
+ }
1574
+ }
1575
+ break;
1576
+ }
1577
+ case "focus":
1578
+ case "blur": {
1579
+ const tk = targetKey(op.target);
1580
+ if (!refMap.has(tk)) {
1581
+ hooks.add("useRef");
1582
+ const name = targetRefName(op.target);
1583
+ refs.push({
1584
+ name,
1585
+ typeAnnotation: "<HTMLElement>",
1586
+ targetKey: tk
1587
+ });
1588
+ refMap.set(tk, name);
1589
+ }
1590
+ break;
1591
+ }
1592
+ case "triggerEvent": {
1593
+ const tk = targetKey(op.target);
1594
+ if (!refMap.has(tk)) {
1595
+ hooks.add("useRef");
1596
+ const name = targetRefName(op.target);
1597
+ refs.push({
1598
+ name,
1599
+ typeAnnotation: "<HTMLElement>",
1600
+ targetKey: tk
1601
+ });
1602
+ refMap.set(tk, name);
1603
+ }
1604
+ break;
1605
+ }
1606
+ }
1607
+ }
1608
+ return { hooks, states, refs, stateMap, refMap };
1609
+ }
1610
+ function generateOperationCode(op, analysis) {
1611
+ switch (op.op) {
1612
+ case "toggleClass": {
1613
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
1614
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setState";
1615
+ return [`${setter}(prev => !prev);`];
1616
+ }
1617
+ case "addClass": {
1618
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
1619
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setState";
1620
+ return [`${setter}(true);`];
1621
+ }
1622
+ case "removeClass": {
1623
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
1624
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setState";
1625
+ return [`${setter}(false);`];
1626
+ }
1627
+ case "show": {
1628
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
1629
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setVisible";
1630
+ return [`${setter}(true);`];
1631
+ }
1632
+ case "hide": {
1633
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
1634
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setVisible";
1635
+ return [`${setter}(false);`];
1636
+ }
1637
+ case "setContent": {
1638
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
1639
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setContent";
1640
+ return [`${setter}('${escapeString2(op.content)}');`];
1641
+ }
1642
+ case "appendContent": {
1643
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
1644
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setContent";
1645
+ return [`${setter}(prev => prev + '${escapeString2(op.content)}');`];
1646
+ }
1647
+ case "setVariable": {
1648
+ const stateVar = analysis.stateMap.get("var:" + op.name);
1649
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setValue";
1650
+ const val = isNumeric(op.value) ? op.value : `'${escapeString2(op.value)}'`;
1651
+ return [`${setter}(${val});`];
1652
+ }
1653
+ case "increment": {
1654
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
1655
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setCount";
1656
+ return [`${setter}(prev => prev + ${op.amount});`];
1657
+ }
1658
+ case "decrement": {
1659
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
1660
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setCount";
1661
+ return [`${setter}(prev => prev - ${op.amount});`];
1662
+ }
1663
+ case "navigate":
1664
+ return [`window.location.href = '${escapeString2(op.url)}';`];
1665
+ case "historyBack":
1666
+ return ["window.history.back();"];
1667
+ case "historyForward":
1668
+ return ["window.history.forward();"];
1669
+ case "fetch": {
1670
+ const lines = [];
1671
+ lines.push(`const response = await fetch('${escapeString2(op.url)}');`);
1672
+ if (op.format === "json") {
1673
+ lines.push("const data = await response.json();");
1674
+ } else if (op.format === "html") {
1675
+ lines.push("const data = await response.text();");
1676
+ } else {
1677
+ lines.push("const data = await response.text();");
1678
+ }
1679
+ if (op.target) {
1680
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
1681
+ const setter = stateVar ? `set${capitalize(stateVar)}` : "setContent";
1682
+ if (op.format === "json") {
1683
+ lines.push(`${setter}(JSON.stringify(data));`);
1684
+ } else {
1685
+ lines.push(`${setter}(data);`);
1686
+ }
1687
+ }
1688
+ return lines;
1689
+ }
1690
+ case "wait":
1691
+ return [`await new Promise(resolve => setTimeout(resolve, ${op.durationMs}));`];
1692
+ case "focus": {
1693
+ const refName = analysis.refMap.get(targetKey(op.target));
1694
+ return [`${refName ?? "ref"}?.current?.focus();`];
1695
+ }
1696
+ case "blur": {
1697
+ const refName = analysis.refMap.get(targetKey(op.target));
1698
+ return [`${refName ?? "ref"}?.current?.blur();`];
1699
+ }
1700
+ case "triggerEvent": {
1701
+ const refName = analysis.refMap.get(targetKey(op.target));
1702
+ return [
1703
+ `${refName ?? "ref"}?.current?.dispatchEvent(new CustomEvent('${escapeString2(op.eventName)}', { bubbles: true }));`
1704
+ ];
1705
+ }
1706
+ case "log":
1707
+ return [`console.log(${op.values.map((v) => `'${escapeString2(v)}'`).join(", ")});`];
1708
+ default:
1709
+ return [`// Unsupported: ${op.op}`];
1710
+ }
1711
+ }
1712
+ function generateJSX(spec, analysis, handlerName) {
1713
+ const lines = [];
1714
+ const elements = collectJSXElements(spec, analysis);
1715
+ const eventProp = eventToReactProp(spec.trigger.event);
1716
+ const needsFragment = elements.length > 1 || elements.length === 0;
1717
+ if (needsFragment) lines.push("<>");
1718
+ const triggerTag = inferTriggerTag(spec);
1719
+ const triggerAttrs = [`${eventProp}={${handlerName}}`];
1720
+ const selfClassStates = getSelfClassStates(spec, analysis.stateMap);
1721
+ if (selfClassStates.length > 0) {
1722
+ const classExpr = selfClassStates.map((s) => `\${${s.stateName} ? '${s.className}' : ''}`).join(" ").trim();
1723
+ triggerAttrs.push(`className={\`${classExpr}\`.trim()}`);
1724
+ }
1725
+ const indent = needsFragment ? " " : "";
1726
+ lines.push(
1727
+ `${indent}<${triggerTag} ${triggerAttrs.join(" ")}>${inferTriggerContent(spec)}</${triggerTag}>`
1728
+ );
1729
+ for (const el of elements) {
1730
+ const elLines = renderJSXElement(el, analysis);
1731
+ for (const l of elLines) {
1732
+ lines.push(`${indent}${l}`);
1733
+ }
1734
+ }
1735
+ if (needsFragment) lines.push("</>");
1736
+ return lines;
1737
+ }
1738
+ function collectJSXElements(spec, _analysis) {
1739
+ const seen = /* @__PURE__ */ new Set();
1740
+ const elements = [];
1741
+ for (const op of spec.operations) {
1742
+ if (!("target" in op)) continue;
1743
+ const target = op.target;
1744
+ if (target.kind !== "selector") continue;
1745
+ if (spec.triggerTarget.kind === "selector" && spec.triggerTarget.value === target.value)
1746
+ continue;
1747
+ const sel = target.value;
1748
+ if (seen.has(sel)) {
1749
+ const existing = elements.find((e) => e.selector === sel);
1750
+ if (existing) existing.ops.push(op);
1751
+ continue;
1752
+ }
1753
+ seen.add(sel);
1754
+ const tag = selectorToTag(sel);
1755
+ const id = sel.startsWith("#") ? sel.slice(1) : void 0;
1756
+ const cls = sel.startsWith(".") ? sel.slice(1) : void 0;
1757
+ elements.push({
1758
+ selector: sel,
1759
+ tag,
1760
+ id,
1761
+ className: cls,
1762
+ ops: [op]
1763
+ });
1764
+ }
1765
+ return elements;
1766
+ }
1767
+ function renderJSXElement(el, analysis) {
1768
+ const lines = [];
1769
+ const attrs = [];
1770
+ if (el.id) attrs.push(`id="${el.id}"`);
1771
+ const classStates = [];
1772
+ for (const op of el.ops) {
1773
+ if (op.op === "toggleClass" || op.op === "addClass" || op.op === "removeClass") {
1774
+ const stateVar = analysis.stateMap.get(el.selector + ":class:" + op.className);
1775
+ if (stateVar) {
1776
+ classStates.push({ stateName: stateVar, className: op.className });
1777
+ }
1778
+ }
1779
+ }
1780
+ if (classStates.length > 0) {
1781
+ const parts = classStates.map((s) => `\${${s.stateName} ? '${s.className}' : ''}`);
1782
+ if (el.className) {
1783
+ attrs.push(`className={\`${el.className} ${parts.join(" ")}\`.trim()}`);
1784
+ } else {
1785
+ attrs.push(`className={\`${parts.join(" ")}\`.trim()}`);
1786
+ }
1787
+ } else if (el.className) {
1788
+ attrs.push(`className="${el.className}"`);
1789
+ }
1790
+ const refName = analysis.refMap.get(el.selector);
1791
+ if (refName) {
1792
+ attrs.push(`ref={${refName}}`);
1793
+ }
1794
+ const visState = analysis.stateMap.get(el.selector + ":visible");
1795
+ const contentState = analysis.stateMap.get(el.selector + ":content");
1796
+ const numState = analysis.stateMap.get(el.selector + ":num");
1797
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1798
+ const content = contentState ? `{${contentState}}` : numState ? `{${numState}}` : el.content ?? el.id ?? "";
1799
+ const elementLine = `<${el.tag}${attrStr}>${content}</${el.tag}>`;
1800
+ if (visState) {
1801
+ lines.push(`{${visState} && ${elementLine}}`);
1802
+ } else {
1803
+ lines.push(elementLine);
1804
+ }
1805
+ return lines;
1806
+ }
1807
+ function eventToReactProp(event) {
1808
+ switch (event) {
1809
+ case "click":
1810
+ return "onClick";
1811
+ case "dblclick":
1812
+ return "onDoubleClick";
1813
+ case "mouseenter":
1814
+ return "onMouseEnter";
1815
+ case "mouseover":
1816
+ return "onMouseOver";
1817
+ case "mouseleave":
1818
+ return "onMouseLeave";
1819
+ case "focus":
1820
+ return "onFocus";
1821
+ case "blur":
1822
+ return "onBlur";
1823
+ case "keydown":
1824
+ return "onKeyDown";
1825
+ case "keyup":
1826
+ return "onKeyUp";
1827
+ case "keypress":
1828
+ return "onKeyPress";
1829
+ case "input":
1830
+ return "onInput";
1831
+ case "change":
1832
+ return "onChange";
1833
+ case "submit":
1834
+ return "onSubmit";
1835
+ default:
1836
+ return `on${capitalize(event)}`;
1837
+ }
1838
+ }
1839
+
1840
+ // src/renderers/vue.ts
1841
+ var VueRenderer = class {
1842
+ constructor() {
1843
+ this.framework = "vue";
1844
+ }
1845
+ render(spec, options = {}) {
1846
+ const componentName = options.componentName ?? generateComponentName(spec);
1847
+ const ts = options.typescript !== false;
1848
+ const analysis = analyzeOperations2(spec);
1849
+ const scriptLines = [];
1850
+ const vueImports = [];
1851
+ if (analysis.states.length > 0 || analysis.refs.length > 0) {
1852
+ vueImports.push("ref");
1853
+ }
1854
+ if (vueImports.length > 0) {
1855
+ scriptLines.push(`import { ${vueImports.join(", ")} } from 'vue'`);
1856
+ scriptLines.push("");
1857
+ }
1858
+ for (const state of analysis.states) {
1859
+ if (ts) {
1860
+ scriptLines.push(`const ${state.name} = ref${state.typeAnnotation}(${state.initialValue})`);
1861
+ } else {
1862
+ scriptLines.push(`const ${state.name} = ref(${state.initialValue})`);
1863
+ }
1864
+ }
1865
+ for (const r of analysis.refs) {
1866
+ if (ts) {
1867
+ scriptLines.push(`const ${r.name} = ref<HTMLElement | null>(null)`);
1868
+ } else {
1869
+ scriptLines.push(`const ${r.name} = ref(null)`);
1870
+ }
1871
+ }
1872
+ if (analysis.states.length > 0 || analysis.refs.length > 0) {
1873
+ scriptLines.push("");
1874
+ }
1875
+ const handlerName = `handle${capitalize(spec.trigger.event)}`;
1876
+ const asyncPrefix = spec.async ? "async " : "";
1877
+ scriptLines.push(`${asyncPrefix}function ${handlerName}() {`);
1878
+ for (const op of spec.operations) {
1879
+ const opLines = generateOperationCode2(op, analysis);
1880
+ for (const l of opLines) {
1881
+ scriptLines.push(` ${l}`);
1882
+ }
1883
+ }
1884
+ scriptLines.push("}");
1885
+ const templateLines = generateTemplate(spec, analysis, handlerName);
1886
+ const langAttr = ts ? ' lang="ts"' : "";
1887
+ const lines = [];
1888
+ lines.push(`<script setup${langAttr}>`);
1889
+ for (const l of scriptLines) {
1890
+ lines.push(l);
1891
+ }
1892
+ lines.push("</script>");
1893
+ lines.push("");
1894
+ lines.push("<template>");
1895
+ for (const l of templateLines) {
1896
+ lines.push(` ${l}`);
1897
+ }
1898
+ lines.push("</template>");
1899
+ lines.push("");
1900
+ const hooks = vueImports.length > 0 ? [...vueImports] : [];
1901
+ return {
1902
+ name: componentName,
1903
+ code: lines.join("\n"),
1904
+ framework: "vue",
1905
+ operations: spec.operations,
1906
+ hooks
1907
+ };
1908
+ }
1909
+ };
1910
+ function analyzeOperations2(spec) {
1911
+ const states = [];
1912
+ const refs = [];
1913
+ const stateMap = /* @__PURE__ */ new Map();
1914
+ const refMap = /* @__PURE__ */ new Map();
1915
+ const seenStates = /* @__PURE__ */ new Set();
1916
+ for (const op of spec.operations) {
1917
+ switch (op.op) {
1918
+ case "toggleClass": {
1919
+ const key = stateKey("has", op.className, op.target);
1920
+ if (!seenStates.has(key)) {
1921
+ seenStates.add(key);
1922
+ const name = `has${capitalize(op.className)}`;
1923
+ states.push({
1924
+ name,
1925
+ typeAnnotation: "<boolean>",
1926
+ initialValue: "false",
1927
+ targetKey: targetKey(op.target)
1928
+ });
1929
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
1930
+ }
1931
+ break;
1932
+ }
1933
+ case "addClass":
1934
+ case "removeClass": {
1935
+ const key = stateKey("has", op.className, op.target);
1936
+ if (!seenStates.has(key)) {
1937
+ seenStates.add(key);
1938
+ const name = `has${capitalize(op.className)}`;
1939
+ const initial = op.op === "removeClass" ? "true" : "false";
1940
+ states.push({
1941
+ name,
1942
+ typeAnnotation: "<boolean>",
1943
+ initialValue: initial,
1944
+ targetKey: targetKey(op.target)
1945
+ });
1946
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
1947
+ }
1948
+ break;
1949
+ }
1950
+ case "show":
1951
+ case "hide": {
1952
+ const tk = targetKey(op.target);
1953
+ const key = "visible:" + tk;
1954
+ if (!seenStates.has(key)) {
1955
+ seenStates.add(key);
1956
+ const name = targetStateName(op.target, "Visible");
1957
+ states.push({
1958
+ name,
1959
+ typeAnnotation: "<boolean>",
1960
+ initialValue: op.op === "hide" ? "true" : "false",
1961
+ targetKey: tk
1962
+ });
1963
+ stateMap.set(tk + ":visible", name);
1964
+ }
1965
+ break;
1966
+ }
1967
+ case "setContent":
1968
+ case "appendContent": {
1969
+ const tk = targetKey(op.target);
1970
+ const key = "content:" + tk;
1971
+ if (!seenStates.has(key)) {
1972
+ seenStates.add(key);
1973
+ const name = targetStateName(op.target, "Content");
1974
+ states.push({
1975
+ name,
1976
+ typeAnnotation: "<string>",
1977
+ initialValue: "''",
1978
+ targetKey: tk
1979
+ });
1980
+ stateMap.set(tk + ":content", name);
1981
+ }
1982
+ break;
1983
+ }
1984
+ case "setVariable": {
1985
+ const key = "var:" + op.name;
1986
+ if (!seenStates.has(key)) {
1987
+ seenStates.add(key);
1988
+ const name = cleanVarName(op.name);
1989
+ const typeAnn = inferType(op.value);
1990
+ states.push({
1991
+ name,
1992
+ typeAnnotation: typeAnn,
1993
+ initialValue: inferInitial(op.value, typeAnn),
1994
+ targetKey: "var:" + op.name
1995
+ });
1996
+ stateMap.set("var:" + op.name, name);
1997
+ }
1998
+ break;
1999
+ }
2000
+ case "increment":
2001
+ case "decrement": {
2002
+ const tk = targetKey(op.target);
2003
+ const key = "num:" + tk;
2004
+ if (!seenStates.has(key)) {
2005
+ seenStates.add(key);
2006
+ const name = targetStateName(op.target, "Count");
2007
+ states.push({
2008
+ name,
2009
+ typeAnnotation: "<number>",
2010
+ initialValue: "0",
2011
+ targetKey: tk
2012
+ });
2013
+ stateMap.set(tk + ":num", name);
2014
+ }
2015
+ break;
2016
+ }
2017
+ case "fetch": {
2018
+ if (op.target) {
2019
+ const tk = targetKey(op.target);
2020
+ const key = "content:" + tk;
2021
+ if (!seenStates.has(key)) {
2022
+ seenStates.add(key);
2023
+ const name = targetStateName(op.target, "Content");
2024
+ states.push({
2025
+ name,
2026
+ typeAnnotation: "<string>",
2027
+ initialValue: "''",
2028
+ targetKey: tk
2029
+ });
2030
+ stateMap.set(tk + ":content", name);
2031
+ }
2032
+ }
2033
+ break;
2034
+ }
2035
+ case "focus":
2036
+ case "blur": {
2037
+ const tk = targetKey(op.target);
2038
+ if (!refMap.has(tk)) {
2039
+ const name = targetRefName(op.target);
2040
+ refs.push({ name, targetKey: tk });
2041
+ refMap.set(tk, name);
2042
+ }
2043
+ break;
2044
+ }
2045
+ case "triggerEvent": {
2046
+ const tk = targetKey(op.target);
2047
+ if (!refMap.has(tk)) {
2048
+ const name = targetRefName(op.target);
2049
+ refs.push({ name, targetKey: tk });
2050
+ refMap.set(tk, name);
2051
+ }
2052
+ break;
2053
+ }
2054
+ }
2055
+ }
2056
+ return { states, refs, stateMap, refMap };
2057
+ }
2058
+ function generateOperationCode2(op, analysis) {
2059
+ switch (op.op) {
2060
+ case "toggleClass": {
2061
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2062
+ return [`${stateVar ?? "state"}.value = !${stateVar ?? "state"}.value`];
2063
+ }
2064
+ case "addClass": {
2065
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2066
+ return [`${stateVar ?? "state"}.value = true`];
2067
+ }
2068
+ case "removeClass": {
2069
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2070
+ return [`${stateVar ?? "state"}.value = false`];
2071
+ }
2072
+ case "show": {
2073
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
2074
+ return [`${stateVar ?? "visible"}.value = true`];
2075
+ }
2076
+ case "hide": {
2077
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
2078
+ return [`${stateVar ?? "visible"}.value = false`];
2079
+ }
2080
+ case "setContent": {
2081
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2082
+ return [`${stateVar ?? "content"}.value = '${escapeString2(op.content)}'`];
2083
+ }
2084
+ case "appendContent": {
2085
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2086
+ return [`${stateVar ?? "content"}.value += '${escapeString2(op.content)}'`];
2087
+ }
2088
+ case "setVariable": {
2089
+ const stateVar = analysis.stateMap.get("var:" + op.name);
2090
+ const val = isNumeric(op.value) ? op.value : `'${escapeString2(op.value)}'`;
2091
+ return [`${stateVar ?? "value"}.value = ${val}`];
2092
+ }
2093
+ case "increment": {
2094
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
2095
+ return [`${stateVar ?? "count"}.value += ${op.amount}`];
2096
+ }
2097
+ case "decrement": {
2098
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
2099
+ return [`${stateVar ?? "count"}.value -= ${op.amount}`];
2100
+ }
2101
+ case "navigate":
2102
+ return [`window.location.href = '${escapeString2(op.url)}'`];
2103
+ case "historyBack":
2104
+ return ["window.history.back()"];
2105
+ case "historyForward":
2106
+ return ["window.history.forward()"];
2107
+ case "fetch": {
2108
+ const lines = [];
2109
+ lines.push(`const response = await fetch('${escapeString2(op.url)}')`);
2110
+ if (op.format === "json") {
2111
+ lines.push("const data = await response.json()");
2112
+ } else {
2113
+ lines.push("const data = await response.text()");
2114
+ }
2115
+ if (op.target) {
2116
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2117
+ if (op.format === "json") {
2118
+ lines.push(`${stateVar ?? "content"}.value = JSON.stringify(data)`);
2119
+ } else {
2120
+ lines.push(`${stateVar ?? "content"}.value = data`);
2121
+ }
2122
+ }
2123
+ return lines;
2124
+ }
2125
+ case "wait":
2126
+ return [`await new Promise(resolve => setTimeout(resolve, ${op.durationMs}))`];
2127
+ case "focus": {
2128
+ const refName = analysis.refMap.get(targetKey(op.target));
2129
+ return [`${refName ?? "ref"}.value?.focus()`];
2130
+ }
2131
+ case "blur": {
2132
+ const refName = analysis.refMap.get(targetKey(op.target));
2133
+ return [`${refName ?? "ref"}.value?.blur()`];
2134
+ }
2135
+ case "triggerEvent": {
2136
+ const refName = analysis.refMap.get(targetKey(op.target));
2137
+ return [
2138
+ `${refName ?? "ref"}.value?.dispatchEvent(new CustomEvent('${escapeString2(op.eventName)}', { bubbles: true }))`
2139
+ ];
2140
+ }
2141
+ case "log":
2142
+ return [`console.log(${op.values.map((v) => `'${escapeString2(v)}'`).join(", ")})`];
2143
+ default:
2144
+ return [`// Unsupported: ${op.op}`];
2145
+ }
2146
+ }
2147
+ function generateTemplate(spec, analysis, handlerName) {
2148
+ const lines = [];
2149
+ const elements = collectTargetElements(spec);
2150
+ const eventDirective = `@${spec.trigger.event}`;
2151
+ const triggerTag = inferTriggerTag(spec);
2152
+ const triggerAttrs = [`${eventDirective}="${handlerName}"`];
2153
+ const selfClassStates = getSelfClassStates(spec, analysis.stateMap);
2154
+ if (selfClassStates.length > 0) {
2155
+ const classObj = selfClassStates.map((s) => `${s.className}: ${s.stateName}`).join(", ");
2156
+ triggerAttrs.push(`:class="{ ${classObj} }"`);
2157
+ }
2158
+ const triggerContent = inferTriggerContent(spec);
2159
+ lines.push(`<${triggerTag} ${triggerAttrs.join(" ")}>${triggerContent}</${triggerTag}>`);
2160
+ for (const el of elements) {
2161
+ const elLines = renderVueElement(el, analysis);
2162
+ for (const l of elLines) {
2163
+ lines.push(l);
2164
+ }
2165
+ }
2166
+ return lines;
2167
+ }
2168
+ function collectTargetElements(spec) {
2169
+ const seen = /* @__PURE__ */ new Set();
2170
+ const elements = [];
2171
+ for (const op of spec.operations) {
2172
+ if (!("target" in op)) continue;
2173
+ const target = op.target;
2174
+ if (target.kind !== "selector") continue;
2175
+ if (spec.triggerTarget.kind === "selector" && spec.triggerTarget.value === target.value)
2176
+ continue;
2177
+ const sel = target.value;
2178
+ if (seen.has(sel)) {
2179
+ const existing = elements.find((e) => e.selector === sel);
2180
+ if (existing) existing.ops.push(op);
2181
+ continue;
2182
+ }
2183
+ seen.add(sel);
2184
+ const tag = selectorToTag(sel);
2185
+ const id = sel.startsWith("#") ? sel.slice(1) : void 0;
2186
+ const cls = sel.startsWith(".") ? sel.slice(1) : void 0;
2187
+ elements.push({ selector: sel, tag, id, className: cls, ops: [op] });
2188
+ }
2189
+ return elements;
2190
+ }
2191
+ function renderVueElement(el, analysis) {
2192
+ const lines = [];
2193
+ const attrs = [];
2194
+ if (el.id) attrs.push(`id="${el.id}"`);
2195
+ const classStates = [];
2196
+ for (const op of el.ops) {
2197
+ if (op.op === "toggleClass" || op.op === "addClass" || op.op === "removeClass") {
2198
+ const stateVar = analysis.stateMap.get(el.selector + ":class:" + op.className);
2199
+ if (stateVar) {
2200
+ classStates.push({ stateName: stateVar, className: op.className });
2201
+ }
2202
+ }
2203
+ }
2204
+ if (classStates.length > 0) {
2205
+ const classObj = classStates.map((s) => `${s.className}: ${s.stateName}`).join(", ");
2206
+ if (el.className) {
2207
+ attrs.push(`class="${el.className}"`);
2208
+ attrs.push(`:class="{ ${classObj} }"`);
2209
+ } else {
2210
+ attrs.push(`:class="{ ${classObj} }"`);
2211
+ }
2212
+ } else if (el.className) {
2213
+ attrs.push(`class="${el.className}"`);
2214
+ }
2215
+ const refName = analysis.refMap.get(el.selector);
2216
+ if (refName) {
2217
+ attrs.push(`ref="${refName}"`);
2218
+ }
2219
+ const visState = analysis.stateMap.get(el.selector + ":visible");
2220
+ const contentState = analysis.stateMap.get(el.selector + ":content");
2221
+ const numState = analysis.stateMap.get(el.selector + ":num");
2222
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
2223
+ const content = contentState ? `{{ ${contentState} }}` : numState ? `{{ ${numState} }}` : el.id ?? "";
2224
+ if (visState) {
2225
+ lines.push(`<${el.tag}${attrStr} v-if="${visState}">${content}</${el.tag}>`);
2226
+ } else {
2227
+ lines.push(`<${el.tag}${attrStr}>${content}</${el.tag}>`);
2228
+ }
2229
+ return lines;
2230
+ }
2231
+
2232
+ // src/renderers/svelte.ts
2233
+ var SvelteRenderer = class {
2234
+ constructor() {
2235
+ this.framework = "svelte";
2236
+ }
2237
+ render(spec, options = {}) {
2238
+ const componentName = options.componentName ?? generateComponentName(spec);
2239
+ const ts = options.typescript !== false;
2240
+ const analysis = analyzeOperations3(spec, ts);
2241
+ const scriptLines = [];
2242
+ for (const state of analysis.states) {
2243
+ scriptLines.push(`let ${state.name} = $state(${state.initialValue})`);
2244
+ }
2245
+ for (const r of analysis.refs) {
2246
+ if (ts) {
2247
+ scriptLines.push(`let ${r.name}: HTMLElement`);
2248
+ } else {
2249
+ scriptLines.push(`let ${r.name}`);
2250
+ }
2251
+ }
2252
+ if (analysis.states.length > 0 || analysis.refs.length > 0) {
2253
+ scriptLines.push("");
2254
+ }
2255
+ const handlerName = `handle${capitalize(spec.trigger.event)}`;
2256
+ const asyncPrefix = spec.async ? "async " : "";
2257
+ scriptLines.push(`${asyncPrefix}function ${handlerName}() {`);
2258
+ for (const op of spec.operations) {
2259
+ const opLines = generateOperationCode3(op, analysis);
2260
+ for (const l of opLines) {
2261
+ scriptLines.push(` ${l}`);
2262
+ }
2263
+ }
2264
+ scriptLines.push("}");
2265
+ const templateLines = generateTemplate2(spec, analysis, handlerName);
2266
+ const langAttr = ts ? ' lang="ts"' : "";
2267
+ const lines = [];
2268
+ lines.push(`<script${langAttr}>`);
2269
+ for (const l of scriptLines) {
2270
+ lines.push(` ${l}`);
2271
+ }
2272
+ lines.push("</script>");
2273
+ lines.push("");
2274
+ for (const l of templateLines) {
2275
+ lines.push(l);
2276
+ }
2277
+ lines.push("");
2278
+ const hooks = [];
2279
+ if (analysis.states.length > 0) hooks.push("$state");
2280
+ return {
2281
+ name: componentName,
2282
+ code: lines.join("\n"),
2283
+ framework: "svelte",
2284
+ operations: spec.operations,
2285
+ hooks
2286
+ };
2287
+ }
2288
+ };
2289
+ function analyzeOperations3(spec, ts) {
2290
+ const states = [];
2291
+ const refs = [];
2292
+ const stateMap = /* @__PURE__ */ new Map();
2293
+ const refMap = /* @__PURE__ */ new Map();
2294
+ const seenStates = /* @__PURE__ */ new Set();
2295
+ for (const op of spec.operations) {
2296
+ switch (op.op) {
2297
+ case "toggleClass": {
2298
+ const key = stateKey("has", op.className, op.target);
2299
+ if (!seenStates.has(key)) {
2300
+ seenStates.add(key);
2301
+ const name = `has${capitalize(op.className)}`;
2302
+ states.push({ name, initialValue: "false", targetKey: targetKey(op.target) });
2303
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
2304
+ }
2305
+ break;
2306
+ }
2307
+ case "addClass":
2308
+ case "removeClass": {
2309
+ const key = stateKey("has", op.className, op.target);
2310
+ if (!seenStates.has(key)) {
2311
+ seenStates.add(key);
2312
+ const name = `has${capitalize(op.className)}`;
2313
+ const initial = op.op === "removeClass" ? "true" : "false";
2314
+ states.push({ name, initialValue: initial, targetKey: targetKey(op.target) });
2315
+ stateMap.set(targetKey(op.target) + ":class:" + op.className, name);
2316
+ }
2317
+ break;
2318
+ }
2319
+ case "show":
2320
+ case "hide": {
2321
+ const tk = targetKey(op.target);
2322
+ const key = "visible:" + tk;
2323
+ if (!seenStates.has(key)) {
2324
+ seenStates.add(key);
2325
+ const name = targetStateName(op.target, "Visible");
2326
+ states.push({
2327
+ name,
2328
+ initialValue: op.op === "hide" ? "true" : "false",
2329
+ targetKey: tk
2330
+ });
2331
+ stateMap.set(tk + ":visible", name);
2332
+ }
2333
+ break;
2334
+ }
2335
+ case "setContent":
2336
+ case "appendContent": {
2337
+ const tk = targetKey(op.target);
2338
+ const key = "content:" + tk;
2339
+ if (!seenStates.has(key)) {
2340
+ seenStates.add(key);
2341
+ const name = targetStateName(op.target, "Content");
2342
+ states.push({ name, initialValue: "''", targetKey: tk });
2343
+ stateMap.set(tk + ":content", name);
2344
+ }
2345
+ break;
2346
+ }
2347
+ case "setVariable": {
2348
+ const key = "var:" + op.name;
2349
+ if (!seenStates.has(key)) {
2350
+ seenStates.add(key);
2351
+ const name = cleanVarName(op.name);
2352
+ const typeAnn = inferType(op.value);
2353
+ states.push({
2354
+ name,
2355
+ initialValue: inferInitial(op.value, typeAnn),
2356
+ targetKey: "var:" + op.name
2357
+ });
2358
+ stateMap.set("var:" + op.name, name);
2359
+ }
2360
+ break;
2361
+ }
2362
+ case "increment":
2363
+ case "decrement": {
2364
+ const tk = targetKey(op.target);
2365
+ const key = "num:" + tk;
2366
+ if (!seenStates.has(key)) {
2367
+ seenStates.add(key);
2368
+ const name = targetStateName(op.target, "Count");
2369
+ states.push({ name, initialValue: "0", targetKey: tk });
2370
+ stateMap.set(tk + ":num", name);
2371
+ }
2372
+ break;
2373
+ }
2374
+ case "fetch": {
2375
+ if (op.target) {
2376
+ const tk = targetKey(op.target);
2377
+ const key = "content:" + tk;
2378
+ if (!seenStates.has(key)) {
2379
+ seenStates.add(key);
2380
+ const name = targetStateName(op.target, "Content");
2381
+ states.push({ name, initialValue: "''", targetKey: tk });
2382
+ stateMap.set(tk + ":content", name);
2383
+ }
2384
+ }
2385
+ break;
2386
+ }
2387
+ case "focus":
2388
+ case "blur": {
2389
+ const tk = targetKey(op.target);
2390
+ if (!refMap.has(tk)) {
2391
+ const name = targetRefName(op.target);
2392
+ refs.push({ name, targetKey: tk });
2393
+ refMap.set(tk, name);
2394
+ }
2395
+ break;
2396
+ }
2397
+ case "triggerEvent": {
2398
+ const tk = targetKey(op.target);
2399
+ if (!refMap.has(tk)) {
2400
+ const name = targetRefName(op.target);
2401
+ refs.push({ name, targetKey: tk });
2402
+ refMap.set(tk, name);
2403
+ }
2404
+ break;
2405
+ }
2406
+ }
2407
+ }
2408
+ return { states, refs, stateMap, refMap, ts };
2409
+ }
2410
+ function generateOperationCode3(op, analysis) {
2411
+ switch (op.op) {
2412
+ case "toggleClass": {
2413
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2414
+ return [`${stateVar ?? "state"} = !${stateVar ?? "state"}`];
2415
+ }
2416
+ case "addClass": {
2417
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2418
+ return [`${stateVar ?? "state"} = true`];
2419
+ }
2420
+ case "removeClass": {
2421
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":class:" + op.className);
2422
+ return [`${stateVar ?? "state"} = false`];
2423
+ }
2424
+ case "show": {
2425
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
2426
+ return [`${stateVar ?? "visible"} = true`];
2427
+ }
2428
+ case "hide": {
2429
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":visible");
2430
+ return [`${stateVar ?? "visible"} = false`];
2431
+ }
2432
+ case "setContent": {
2433
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2434
+ return [`${stateVar ?? "content"} = '${escapeString2(op.content)}'`];
2435
+ }
2436
+ case "appendContent": {
2437
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2438
+ return [`${stateVar ?? "content"} += '${escapeString2(op.content)}'`];
2439
+ }
2440
+ case "setVariable": {
2441
+ const stateVar = analysis.stateMap.get("var:" + op.name);
2442
+ const val = isNumeric(op.value) ? op.value : `'${escapeString2(op.value)}'`;
2443
+ return [`${stateVar ?? "value"} = ${val}`];
2444
+ }
2445
+ case "increment": {
2446
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
2447
+ return [`${stateVar ?? "count"} += ${op.amount}`];
2448
+ }
2449
+ case "decrement": {
2450
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":num");
2451
+ return [`${stateVar ?? "count"} -= ${op.amount}`];
2452
+ }
2453
+ case "navigate":
2454
+ return [`window.location.href = '${escapeString2(op.url)}'`];
2455
+ case "historyBack":
2456
+ return ["window.history.back()"];
2457
+ case "historyForward":
2458
+ return ["window.history.forward()"];
2459
+ case "fetch": {
2460
+ const lines = [];
2461
+ lines.push(`const response = await fetch('${escapeString2(op.url)}')`);
2462
+ if (op.format === "json") {
2463
+ lines.push("const data = await response.json()");
2464
+ } else {
2465
+ lines.push("const data = await response.text()");
2466
+ }
2467
+ if (op.target) {
2468
+ const stateVar = analysis.stateMap.get(targetKey(op.target) + ":content");
2469
+ if (op.format === "json") {
2470
+ lines.push(`${stateVar ?? "content"} = JSON.stringify(data)`);
2471
+ } else {
2472
+ lines.push(`${stateVar ?? "content"} = data`);
2473
+ }
2474
+ }
2475
+ return lines;
2476
+ }
2477
+ case "wait":
2478
+ return [`await new Promise(resolve => setTimeout(resolve, ${op.durationMs}))`];
2479
+ case "focus": {
2480
+ const refName = analysis.refMap.get(targetKey(op.target));
2481
+ return [`${refName ?? "ref"}?.focus()`];
2482
+ }
2483
+ case "blur": {
2484
+ const refName = analysis.refMap.get(targetKey(op.target));
2485
+ return [`${refName ?? "ref"}?.blur()`];
2486
+ }
2487
+ case "triggerEvent": {
2488
+ const refName = analysis.refMap.get(targetKey(op.target));
2489
+ return [
2490
+ `${refName ?? "ref"}?.dispatchEvent(new CustomEvent('${escapeString2(op.eventName)}', { bubbles: true }))`
2491
+ ];
2492
+ }
2493
+ case "log":
2494
+ return [`console.log(${op.values.map((v) => `'${escapeString2(v)}'`).join(", ")})`];
2495
+ default:
2496
+ return [`// Unsupported: ${op.op}`];
2497
+ }
2498
+ }
2499
+ function generateTemplate2(spec, analysis, handlerName) {
2500
+ const lines = [];
2501
+ const elements = collectTargetElements2(spec);
2502
+ const eventAttr = `on${spec.trigger.event}`;
2503
+ const triggerTag = inferTriggerTag(spec);
2504
+ const triggerAttrs = [`${eventAttr}={${handlerName}}`];
2505
+ const selfClassStates = getSelfClassStates(spec, analysis.stateMap);
2506
+ for (const s of selfClassStates) {
2507
+ triggerAttrs.push(`class:${s.className}={${s.stateName}}`);
2508
+ }
2509
+ const triggerContent = inferTriggerContent(spec);
2510
+ lines.push(`<${triggerTag} ${triggerAttrs.join(" ")}>${triggerContent}</${triggerTag}>`);
2511
+ for (const el of elements) {
2512
+ const elLines = renderSvelteElement(el, analysis);
2513
+ for (const l of elLines) {
2514
+ lines.push(l);
2515
+ }
2516
+ }
2517
+ return lines;
2518
+ }
2519
+ function collectTargetElements2(spec) {
2520
+ const seen = /* @__PURE__ */ new Set();
2521
+ const elements = [];
2522
+ for (const op of spec.operations) {
2523
+ if (!("target" in op)) continue;
2524
+ const target = op.target;
2525
+ if (target.kind !== "selector") continue;
2526
+ if (spec.triggerTarget.kind === "selector" && spec.triggerTarget.value === target.value)
2527
+ continue;
2528
+ const sel = target.value;
2529
+ if (seen.has(sel)) {
2530
+ const existing = elements.find((e) => e.selector === sel);
2531
+ if (existing) existing.ops.push(op);
2532
+ continue;
2533
+ }
2534
+ seen.add(sel);
2535
+ const tag = selectorToTag(sel);
2536
+ const id = sel.startsWith("#") ? sel.slice(1) : void 0;
2537
+ const cls = sel.startsWith(".") ? sel.slice(1) : void 0;
2538
+ elements.push({ selector: sel, tag, id, className: cls, ops: [op] });
2539
+ }
2540
+ return elements;
2541
+ }
2542
+ function renderSvelteElement(el, analysis) {
2543
+ const lines = [];
2544
+ const attrs = [];
2545
+ if (el.id) attrs.push(`id="${el.id}"`);
2546
+ const classStates = [];
2547
+ for (const op of el.ops) {
2548
+ if (op.op === "toggleClass" || op.op === "addClass" || op.op === "removeClass") {
2549
+ const stateVar = analysis.stateMap.get(el.selector + ":class:" + op.className);
2550
+ if (stateVar) {
2551
+ classStates.push({ stateName: stateVar, className: op.className });
2552
+ }
2553
+ }
2554
+ }
2555
+ if (el.className) {
2556
+ attrs.push(`class="${el.className}"`);
2557
+ }
2558
+ for (const s of classStates) {
2559
+ attrs.push(`class:${s.className}={${s.stateName}}`);
2560
+ }
2561
+ const refName = analysis.refMap.get(el.selector);
2562
+ if (refName) {
2563
+ attrs.push(`bind:this={${refName}}`);
2564
+ }
2565
+ const visState = analysis.stateMap.get(el.selector + ":visible");
2566
+ const contentState = analysis.stateMap.get(el.selector + ":content");
2567
+ const numState = analysis.stateMap.get(el.selector + ":num");
2568
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
2569
+ const content = contentState ? `{${contentState}}` : numState ? `{${numState}}` : el.id ?? "";
2570
+ const elementLine = `<${el.tag}${attrStr}>${content}</${el.tag}>`;
2571
+ if (visState) {
2572
+ lines.push(`{#if ${visState}}`);
2573
+ lines.push(` ${elementLine}`);
2574
+ lines.push("{/if}");
2575
+ } else {
2576
+ lines.push(elementLine);
2577
+ }
2578
+ return lines;
2579
+ }
2580
+
2581
+ // src/input/normalize.ts
2582
+ var _semantic = null;
2583
+ function initNormalizer(semantic) {
2584
+ _semantic = semantic;
2585
+ }
2586
+ function normalize(request) {
2587
+ if (!_semantic) {
2588
+ throw new Error("Normalizer not initialized. Call initNormalizer() first.");
2589
+ }
2590
+ if (request.semantic) {
2591
+ return normalizeJSON(request.semantic);
2592
+ }
2593
+ const input = request.code ?? request.explicit;
2594
+ if (!input) {
2595
+ return {
2596
+ node: null,
2597
+ confidence: 0,
2598
+ format: "natural",
2599
+ diagnostics: [
2600
+ {
2601
+ severity: "error",
2602
+ code: "NO_INPUT",
2603
+ message: "No input provided. Supply one of: code, explicit, or semantic."
2604
+ }
2605
+ ]
2606
+ };
2607
+ }
2608
+ const format = request.explicit ? "explicit" : detectFormat(input);
2609
+ switch (format) {
2610
+ case "explicit":
2611
+ return normalizeExplicit(input);
2612
+ case "json":
2613
+ return normalizeJSON(JSON.parse(input.trim()));
2614
+ case "natural":
2615
+ return normalizeNatural(input, request.language ?? "en");
2616
+ }
2617
+ }
2618
+ function normalizeNatural(code, language) {
2619
+ const diagnostics = [];
2620
+ try {
2621
+ const result = _semantic.parseSemantic(code, language);
2622
+ if (result.error) {
2623
+ diagnostics.push({
2624
+ severity: "error",
2625
+ code: "PARSE_ERROR",
2626
+ message: result.error
2627
+ });
2628
+ }
2629
+ if (!result.node) {
2630
+ diagnostics.push({
2631
+ severity: "error",
2632
+ code: "PARSE_FAILED",
2633
+ message: `Failed to parse "${code}" as ${language} hyperscript.`,
2634
+ suggestion: "Check syntax or try explicit syntax: [command role:value ...]"
2635
+ });
2636
+ return { node: null, confidence: result.confidence, format: "natural", diagnostics };
2637
+ }
2638
+ return {
2639
+ node: result.node,
2640
+ confidence: result.confidence,
2641
+ format: "natural",
2642
+ diagnostics
2643
+ };
2644
+ } catch (error) {
2645
+ diagnostics.push({
2646
+ severity: "error",
2647
+ code: "PARSE_EXCEPTION",
2648
+ message: error instanceof Error ? error.message : String(error)
2649
+ });
2650
+ return { node: null, confidence: 0, format: "natural", diagnostics };
2651
+ }
2652
+ }
2653
+ function normalizeExplicit(input) {
2654
+ const diagnostics = [];
2655
+ try {
2656
+ const node = _semantic.parseExplicit(input);
2657
+ return {
2658
+ node,
2659
+ confidence: 1,
2660
+ format: "explicit",
2661
+ diagnostics
2662
+ };
2663
+ } catch (error) {
2664
+ const message = error instanceof Error ? error.message : String(error);
2665
+ const hasRoleGuidance = message.includes("Valid roles") || message.includes("Missing required role");
2666
+ diagnostics.push({
2667
+ severity: "error",
2668
+ code: "EXPLICIT_PARSE_ERROR",
2669
+ message,
2670
+ suggestion: hasRoleGuidance ? void 0 : "Use format: [command role:value ...] e.g. [toggle patient:.active]. Use get_command_docs(command) to see valid roles."
2671
+ });
2672
+ return { node: null, confidence: 0, format: "explicit", diagnostics };
2673
+ }
2674
+ }
2675
+ function normalizeJSON(input) {
2676
+ const validationErrors = validateSemanticJSON(input);
2677
+ if (validationErrors.some((d) => d.severity === "error")) {
2678
+ return {
2679
+ node: null,
2680
+ confidence: 0,
2681
+ format: "json",
2682
+ diagnostics: validationErrors
2683
+ };
2684
+ }
2685
+ const node = jsonToSemanticNode(input);
2686
+ return {
2687
+ node,
2688
+ confidence: 1,
2689
+ format: "json",
2690
+ diagnostics: validationErrors
2691
+ // May contain warnings
2692
+ };
2693
+ }
2694
+
2695
+ // src/validation/gates.ts
2696
+ var _validators = null;
2697
+ function initValidation(validators) {
2698
+ _validators = validators;
2699
+ }
2700
+ function runValidationGates(node, confidence, confidenceThreshold) {
2701
+ const diagnostics = [];
2702
+ let adjustedConfidence = confidence;
2703
+ if (!node) {
2704
+ diagnostics.push({
2705
+ severity: "error",
2706
+ code: "PARSE_FAILED",
2707
+ message: "No semantic node produced from input."
2708
+ });
2709
+ return { pass: false, diagnostics, adjustedConfidence: 0 };
2710
+ }
2711
+ if (confidence < confidenceThreshold) {
2712
+ diagnostics.push({
2713
+ severity: "error",
2714
+ code: "LOW_CONFIDENCE",
2715
+ message: `Parse confidence ${confidence.toFixed(2)} is below threshold ${confidenceThreshold.toFixed(2)}.`,
2716
+ suggestion: "Try explicit syntax [command role:value ...] for unambiguous input."
2717
+ });
2718
+ return { pass: false, diagnostics, adjustedConfidence: confidence };
2719
+ }
2720
+ if (_validators) {
2721
+ const semanticResult = nodeToParseResult(node);
2722
+ if (semanticResult) {
2723
+ const validation = _validators.validateSemanticResult(semanticResult);
2724
+ for (const err of validation.errors) {
2725
+ diagnostics.push({
2726
+ severity: "error",
2727
+ code: err.code,
2728
+ message: err.message
2729
+ });
2730
+ }
2731
+ for (const warn of validation.warnings) {
2732
+ diagnostics.push({
2733
+ severity: "warning",
2734
+ code: warn.code,
2735
+ message: warn.message
2736
+ });
2737
+ }
2738
+ for (const suggestion of validation.suggestions) {
2739
+ diagnostics.push({
2740
+ severity: "info",
2741
+ code: "SUGGESTION",
2742
+ message: suggestion
2743
+ });
2744
+ }
2745
+ adjustedConfidence = Math.max(0, Math.min(1, confidence + validation.confidenceAdjustment));
2746
+ if (!validation.valid) {
2747
+ return { pass: false, diagnostics, adjustedConfidence };
2748
+ }
2749
+ }
2750
+ }
2751
+ return { pass: true, diagnostics, adjustedConfidence };
2752
+ }
2753
+ function nodeToParseResult(node) {
2754
+ const n = node;
2755
+ if (!n.action) return null;
2756
+ const action = n.action === "on" ? null : n.action;
2757
+ if (!action) return null;
2758
+ const args = [];
2759
+ if (n.roles && typeof n.roles.entries === "function") {
2760
+ for (const [role, value] of n.roles.entries()) {
2761
+ args.push({ role, ...value });
2762
+ }
2763
+ }
2764
+ return {
2765
+ action,
2766
+ arguments: args,
2767
+ confidence: 1
2768
+ };
2769
+ }
2770
+
2771
+ // src/compile/bridge.ts
2772
+ var _bridge = null;
2773
+ var _compiler = null;
2774
+ function initBridge(bridge, compiler) {
2775
+ _bridge = bridge;
2776
+ _compiler = compiler;
2777
+ }
2778
+ function compileSemanticNode(node, options = {}) {
2779
+ if (!_bridge || !_compiler) {
2780
+ throw new Error("Bridge not initialized. Call initBridge() first.");
2781
+ }
2782
+ const errors = [];
2783
+ const warnings = [];
2784
+ let semanticAST;
2785
+ try {
2786
+ const builder = new _bridge.ASTBuilder();
2787
+ semanticAST = builder.build(node);
2788
+ warnings.push(...builder.warnings);
2789
+ } catch (error) {
2790
+ errors.push(`AST build failed: ${error instanceof Error ? error.message : String(error)}`);
2791
+ return { success: false, helpers: [], warnings, errors };
2792
+ }
2793
+ let interchangeAST;
2794
+ try {
2795
+ interchangeAST = _bridge.fromSemanticAST(semanticAST);
2796
+ } catch (error) {
2797
+ errors.push(
2798
+ `Interchange conversion failed: ${error instanceof Error ? error.message : String(error)}`
2799
+ );
2800
+ return { success: false, helpers: [], warnings, errors };
2801
+ }
2802
+ try {
2803
+ const result = _compiler.compileAST(interchangeAST, {
2804
+ language: options.language ?? "en",
2805
+ optimizationLevel: options.optimization ?? 2,
2806
+ codegen: {
2807
+ mode: options.target ?? "esm",
2808
+ minify: options.minify ?? false
2809
+ }
2810
+ });
2811
+ if (!result.success) {
2812
+ return {
2813
+ success: false,
2814
+ helpers: [],
2815
+ warnings: [...warnings, ...result.warnings],
2816
+ errors: [...errors, ...result.errors ?? ["Compilation failed"]]
2817
+ };
2818
+ }
2819
+ return {
2820
+ success: true,
2821
+ code: result.code,
2822
+ helpers: result.metadata.runtimeHelpers,
2823
+ warnings: [...warnings, ...result.warnings],
2824
+ errors
2825
+ };
2826
+ } catch (error) {
2827
+ errors.push(
2828
+ `AOT compilation failed: ${error instanceof Error ? error.message : String(error)}`
2829
+ );
2830
+ return { success: false, helpers: [], warnings, errors };
2831
+ }
2832
+ }
2833
+
2834
+ // src/service.ts
2835
+ var CompilationService = class _CompilationService {
2836
+ constructor(options = {}) {
2837
+ this.translateFn = null;
2838
+ this.confidenceThreshold = options.confidenceThreshold ?? 0.7;
2839
+ this.cache = new SemanticCache(options.cacheSize ?? 500);
2840
+ this.testRenderers = new Map(
2841
+ Object.entries(options.testRenderers ?? { playwright: new PlaywrightRenderer() })
2842
+ );
2843
+ this.componentRenderers = new Map(
2844
+ Object.entries(
2845
+ options.componentRenderers ?? {
2846
+ react: new ReactRenderer(),
2847
+ vue: new VueRenderer(),
2848
+ svelte: new SvelteRenderer()
2849
+ }
2850
+ )
2851
+ );
2852
+ }
2853
+ /**
2854
+ * Register a test renderer for a given framework.
2855
+ */
2856
+ registerTestRenderer(framework, renderer) {
2857
+ this.testRenderers.set(framework, renderer);
2858
+ }
2859
+ /**
2860
+ * Register a component renderer for a given framework.
2861
+ */
2862
+ registerComponentRenderer(framework, renderer) {
2863
+ this.componentRenderers.set(framework, renderer);
2864
+ }
2865
+ /**
2866
+ * Create a CompilationService by dynamically importing dependencies.
2867
+ *
2868
+ * This async factory resolves @lokascript/semantic and @hyperfixi/aot-compiler
2869
+ * at runtime, following the same pattern as createMultilingualCompiler().
2870
+ */
2871
+ static async create(options = {}) {
2872
+ const service = new _CompilationService(options);
2873
+ const semantic = await import("@lokascript/semantic");
2874
+ initNormalizer({
2875
+ parseSemantic: semantic.parseSemantic,
2876
+ parseExplicit: semantic.parseExplicit,
2877
+ isExplicitSyntax: semantic.isExplicitSyntax
2878
+ });
2879
+ initValidation({
2880
+ validateSemanticResult: semantic.validateSemanticResult
2881
+ });
2882
+ const aot = await import("./dist-ICUX26U7.js");
2883
+ const compiler = aot.createCompiler();
2884
+ initBridge(
2885
+ {
2886
+ ASTBuilder: semantic.ASTBuilder,
2887
+ fromSemanticAST: semantic.fromSemanticAST
2888
+ },
2889
+ compiler
2890
+ );
2891
+ service.translateFn = semantic.translate;
2892
+ return service;
2893
+ }
2894
+ // ===========================================================================
2895
+ // Public API
2896
+ // ===========================================================================
2897
+ /**
2898
+ * Compile hyperscript to JavaScript.
2899
+ *
2900
+ * Accepts any of three input formats:
2901
+ * - `code` + `language`: Natural language hyperscript
2902
+ * - `explicit`: Bracket syntax [command role:value ...]
2903
+ * - `semantic`: LLM JSON { action, roles, trigger }
2904
+ *
2905
+ * Returns compiled JS, semantic representation, and diagnostics.
2906
+ */
2907
+ compile(request) {
2908
+ const diagnostics = [];
2909
+ const normalized = normalize(request);
2910
+ diagnostics.push(...normalized.diagnostics);
2911
+ if (!normalized.node) {
2912
+ return { ok: false, diagnostics };
2913
+ }
2914
+ const threshold = request.confidence ?? this.confidenceThreshold;
2915
+ const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
2916
+ diagnostics.push(...gateResult.diagnostics);
2917
+ if (!gateResult.pass) {
2918
+ return {
2919
+ ok: false,
2920
+ confidence: gateResult.adjustedConfidence,
2921
+ diagnostics
2922
+ };
2923
+ }
2924
+ const cacheKey = generateCacheKey(normalized.node, {
2925
+ optimization: request.optimization,
2926
+ target: request.target,
2927
+ minify: request.minify
2928
+ });
2929
+ const cached = this.cache.get(cacheKey);
2930
+ if (cached) {
2931
+ return {
2932
+ ...cached,
2933
+ confidence: gateResult.adjustedConfidence,
2934
+ diagnostics: [...diagnostics, ...cached.diagnostics.filter((d) => d.severity !== "error")]
2935
+ };
2936
+ }
2937
+ const result = compileSemanticNode(normalized.node, {
2938
+ language: request.language,
2939
+ optimization: request.optimization,
2940
+ target: request.target,
2941
+ minify: request.minify
2942
+ });
2943
+ for (const warning of result.warnings) {
2944
+ diagnostics.push({ severity: "warning", code: "COMPILE_WARNING", message: warning });
2945
+ }
2946
+ for (const error of result.errors) {
2947
+ diagnostics.push({ severity: "error", code: "COMPILE_ERROR", message: error });
2948
+ }
2949
+ if (!result.success) {
2950
+ return { ok: false, diagnostics };
2951
+ }
2952
+ const semanticJSON = nodeToSemanticJSON(normalized.node);
2953
+ const response = {
2954
+ ok: true,
2955
+ js: result.code,
2956
+ helpers: result.helpers,
2957
+ size: result.code ? new TextEncoder().encode(result.code).length : 0,
2958
+ semantic: semanticJSON,
2959
+ confidence: gateResult.adjustedConfidence,
2960
+ diagnostics
2961
+ };
2962
+ this.cache.set(cacheKey, response);
2963
+ return response;
2964
+ }
2965
+ /**
2966
+ * Validate input without compiling.
2967
+ * Returns semantic representation and diagnostics.
2968
+ */
2969
+ validate(request) {
2970
+ const diagnostics = [];
2971
+ const normalized = normalize(request);
2972
+ diagnostics.push(...normalized.diagnostics);
2973
+ if (!normalized.node) {
2974
+ return { ok: false, diagnostics };
2975
+ }
2976
+ const threshold = request.confidence ?? this.confidenceThreshold;
2977
+ const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
2978
+ diagnostics.push(...gateResult.diagnostics);
2979
+ const semanticJSON = gateResult.pass ? nodeToSemanticJSON(normalized.node) : void 0;
2980
+ return {
2981
+ ok: gateResult.pass,
2982
+ semantic: semanticJSON,
2983
+ confidence: gateResult.adjustedConfidence,
2984
+ diagnostics
2985
+ };
2986
+ }
2987
+ /**
2988
+ * Translate hyperscript between languages.
2989
+ */
2990
+ translate(request) {
2991
+ if (!this.translateFn) {
2992
+ return {
2993
+ ok: false,
2994
+ diagnostics: [
2995
+ {
2996
+ severity: "error",
2997
+ code: "NOT_INITIALIZED",
2998
+ message: "Translation not available."
2999
+ }
3000
+ ]
3001
+ };
3002
+ }
3003
+ try {
3004
+ const result = this.translateFn(request.code, request.from, request.to);
3005
+ return {
3006
+ ok: true,
3007
+ code: result,
3008
+ diagnostics: []
3009
+ };
3010
+ } catch (error) {
3011
+ return {
3012
+ ok: false,
3013
+ diagnostics: [
3014
+ {
3015
+ severity: "error",
3016
+ code: "TRANSLATE_ERROR",
3017
+ message: error instanceof Error ? error.message : String(error)
3018
+ }
3019
+ ]
3020
+ };
3021
+ }
3022
+ }
3023
+ /**
3024
+ * Generate behavior-level tests from hyperscript.
3025
+ *
3026
+ * Parses the input, extracts abstract operations (what the behavior does),
3027
+ * and renders them as test code in the requested framework.
3028
+ */
3029
+ generateTests(request) {
3030
+ const diagnostics = [];
3031
+ const normalized = normalize({
3032
+ code: request.code,
3033
+ explicit: request.explicit,
3034
+ semantic: request.semantic,
3035
+ language: request.language,
3036
+ confidence: request.confidence
3037
+ });
3038
+ diagnostics.push(...normalized.diagnostics);
3039
+ if (!normalized.node) {
3040
+ return { ok: false, tests: [], operations: [], diagnostics };
3041
+ }
3042
+ const threshold = request.confidence ?? this.confidenceThreshold;
3043
+ const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
3044
+ diagnostics.push(...gateResult.diagnostics);
3045
+ if (!gateResult.pass) {
3046
+ return { ok: false, tests: [], operations: [], diagnostics };
3047
+ }
3048
+ const spec = extractOperations(normalized.node);
3049
+ if (spec.operations.length === 0) {
3050
+ diagnostics.push({
3051
+ severity: "warning",
3052
+ code: "NO_OPERATIONS",
3053
+ message: "No testable operations extracted from the input."
3054
+ });
3055
+ return { ok: false, tests: [], operations: [], diagnostics };
3056
+ }
3057
+ if (!spec.source && request.code) {
3058
+ spec.source = request.code;
3059
+ }
3060
+ const frameworkName = request.framework ?? "playwright";
3061
+ const renderer = this.testRenderers.get(frameworkName);
3062
+ if (!renderer) {
3063
+ diagnostics.push({
3064
+ severity: "error",
3065
+ code: "UNKNOWN_FRAMEWORK",
3066
+ message: `No test renderer registered for framework '${frameworkName}'. Available: ${[...this.testRenderers.keys()].join(", ")}`
3067
+ });
3068
+ return { ok: false, tests: [], operations: [], diagnostics };
3069
+ }
3070
+ let compiledJs;
3071
+ if (request.executionMode === "compiled") {
3072
+ const compileResult = this.compile({
3073
+ code: request.code,
3074
+ explicit: request.explicit,
3075
+ semantic: request.semantic,
3076
+ language: request.language
3077
+ });
3078
+ if (compileResult.ok && compileResult.js) {
3079
+ compiledJs = compileResult.js;
3080
+ }
3081
+ }
3082
+ const generated = renderer.render(spec, {
3083
+ testName: request.testName,
3084
+ executionMode: request.executionMode,
3085
+ bundlePath: request.bundlePath,
3086
+ hyperscript: request.code ?? request.explicit,
3087
+ compiledJs
3088
+ });
3089
+ const semanticJSON = nodeToSemanticJSON(normalized.node);
3090
+ return {
3091
+ ok: true,
3092
+ tests: [
3093
+ {
3094
+ name: generated.name,
3095
+ code: generated.code,
3096
+ html: generated.html,
3097
+ framework: generated.framework
3098
+ }
3099
+ ],
3100
+ operations: spec.operations,
3101
+ semantic: semanticJSON,
3102
+ diagnostics
3103
+ };
3104
+ }
3105
+ /**
3106
+ * Generate a framework component from hyperscript.
3107
+ *
3108
+ * Parses the input, extracts abstract operations, and renders
3109
+ * them as a framework-specific component (React, Vue, or Svelte).
3110
+ */
3111
+ generateComponent(request) {
3112
+ const diagnostics = [];
3113
+ const normalized = normalize({
3114
+ code: request.code,
3115
+ explicit: request.explicit,
3116
+ semantic: request.semantic,
3117
+ language: request.language,
3118
+ confidence: request.confidence
3119
+ });
3120
+ diagnostics.push(...normalized.diagnostics);
3121
+ if (!normalized.node) {
3122
+ return { ok: false, operations: [], diagnostics };
3123
+ }
3124
+ const threshold = request.confidence ?? this.confidenceThreshold;
3125
+ const gateResult = runValidationGates(normalized.node, normalized.confidence, threshold);
3126
+ diagnostics.push(...gateResult.diagnostics);
3127
+ if (!gateResult.pass) {
3128
+ return { ok: false, operations: [], diagnostics };
3129
+ }
3130
+ const spec = extractOperations(normalized.node);
3131
+ if (spec.operations.length === 0) {
3132
+ diagnostics.push({
3133
+ severity: "warning",
3134
+ code: "NO_OPERATIONS",
3135
+ message: "No operations extracted from the input."
3136
+ });
3137
+ return { ok: false, operations: [], diagnostics };
3138
+ }
3139
+ if (!spec.source && request.code) {
3140
+ spec.source = request.code;
3141
+ }
3142
+ const frameworkName = request.framework ?? "react";
3143
+ const renderer = this.componentRenderers.get(frameworkName);
3144
+ if (!renderer) {
3145
+ diagnostics.push({
3146
+ severity: "error",
3147
+ code: "UNKNOWN_FRAMEWORK",
3148
+ message: `No component renderer registered for framework '${frameworkName}'. Available: ${[...this.componentRenderers.keys()].join(", ")}`
3149
+ });
3150
+ return { ok: false, operations: [], diagnostics };
3151
+ }
3152
+ const generated = renderer.render(spec, {
3153
+ componentName: request.componentName,
3154
+ typescript: request.typescript
3155
+ });
3156
+ const semanticJSON = nodeToSemanticJSON(normalized.node);
3157
+ return {
3158
+ ok: true,
3159
+ component: {
3160
+ name: generated.name,
3161
+ code: generated.code,
3162
+ hooks: generated.hooks,
3163
+ framework: generated.framework
3164
+ },
3165
+ operations: spec.operations,
3166
+ semantic: semanticJSON,
3167
+ diagnostics
3168
+ };
3169
+ }
3170
+ /**
3171
+ * Compare two hyperscript inputs at the behavior level.
3172
+ *
3173
+ * Both sides are independently normalized and validated, then compared
3174
+ * as abstract operations. Same behavior in different languages → identical.
3175
+ */
3176
+ diff(request) {
3177
+ const diagnostics = [];
3178
+ const threshold = request.confidence ?? this.confidenceThreshold;
3179
+ const normA = normalize({
3180
+ code: request.a.code,
3181
+ explicit: request.a.explicit,
3182
+ semantic: request.a.semantic,
3183
+ language: request.a.language,
3184
+ confidence: request.confidence
3185
+ });
3186
+ diagnostics.push(...normA.diagnostics.map((d) => ({ ...d, message: `[a] ${d.message}` })));
3187
+ const normB = normalize({
3188
+ code: request.b.code,
3189
+ explicit: request.b.explicit,
3190
+ semantic: request.b.semantic,
3191
+ language: request.b.language,
3192
+ confidence: request.confidence
3193
+ });
3194
+ diagnostics.push(...normB.diagnostics.map((d) => ({ ...d, message: `[b] ${d.message}` })));
3195
+ if (!normA.node || !normB.node) {
3196
+ return {
3197
+ ok: false,
3198
+ identical: false,
3199
+ trigger: null,
3200
+ operations: [],
3201
+ summary: "Parse failed",
3202
+ diagnostics
3203
+ };
3204
+ }
3205
+ const gateA = runValidationGates(normA.node, normA.confidence, threshold);
3206
+ diagnostics.push(...gateA.diagnostics.map((d) => ({ ...d, message: `[a] ${d.message}` })));
3207
+ const gateB = runValidationGates(normB.node, normB.confidence, threshold);
3208
+ diagnostics.push(...gateB.diagnostics.map((d) => ({ ...d, message: `[b] ${d.message}` })));
3209
+ if (!gateA.pass || !gateB.pass) {
3210
+ return {
3211
+ ok: false,
3212
+ identical: false,
3213
+ trigger: null,
3214
+ operations: [],
3215
+ summary: "Validation failed",
3216
+ diagnostics
3217
+ };
3218
+ }
3219
+ const specA = extractOperations(normA.node);
3220
+ const specB = extractOperations(normB.node);
3221
+ const result = diffBehaviors(specA, specB);
3222
+ return {
3223
+ ok: true,
3224
+ identical: result.identical,
3225
+ trigger: result.trigger,
3226
+ operations: result.operations,
3227
+ summary: result.summary,
3228
+ diagnostics
3229
+ };
3230
+ }
3231
+ /**
3232
+ * Get cache statistics.
3233
+ */
3234
+ getCacheStats() {
3235
+ const total = this.cache.hits + this.cache.misses;
3236
+ return {
3237
+ size: this.cache.size,
3238
+ hits: this.cache.hits,
3239
+ misses: this.cache.misses,
3240
+ hitRate: total > 0 ? this.cache.hits / total : 0
3241
+ };
3242
+ }
3243
+ /**
3244
+ * Clear the compilation cache.
3245
+ */
3246
+ clearCache() {
3247
+ this.cache.clear();
3248
+ }
3249
+ };
3250
+ function extractSemanticValueString(v) {
3251
+ if (!v || typeof v !== "object") return "";
3252
+ const sv = v;
3253
+ if (sv.type === "property-path") {
3254
+ return `${extractSemanticValueString(sv.object)}.${sv.property ?? ""}`;
3255
+ }
3256
+ return String(sv.value ?? sv.raw ?? "");
3257
+ }
3258
+ function nodeToSemanticJSON(node) {
3259
+ if (!node || typeof node !== "object") return void 0;
3260
+ const n = node;
3261
+ if (!n.action) return void 0;
3262
+ const roles = {};
3263
+ if (n.roles && typeof n.roles.entries === "function") {
3264
+ for (const [role, value] of n.roles.entries()) {
3265
+ if (role === "event") continue;
3266
+ const v = value;
3267
+ if (v && v.type) {
3268
+ if (v.type === "property-path") {
3269
+ roles[role] = {
3270
+ type: "property-path",
3271
+ value: extractSemanticValueString(v)
3272
+ };
3273
+ } else {
3274
+ const valueType = v.type;
3275
+ roles[role] = {
3276
+ type: ["selector", "literal", "reference", "expression"].includes(valueType) ? valueType : "literal",
3277
+ value: v.value ?? v.raw ?? ""
3278
+ };
3279
+ }
3280
+ }
3281
+ }
3282
+ }
3283
+ if (n.kind === "event-handler" || n.action === "on") {
3284
+ const eventRole = n.roles && typeof n.roles.get === "function" ? n.roles.get("event") : void 0;
3285
+ if (n.body && n.body.length > 0) {
3286
+ const bodyJSON = nodeToSemanticJSON(n.body[0]);
3287
+ if (bodyJSON) {
3288
+ bodyJSON.trigger = {
3289
+ event: eventRole?.value ?? "click",
3290
+ modifiers: n.eventModifiers
3291
+ };
3292
+ return bodyJSON;
3293
+ }
3294
+ }
3295
+ return {
3296
+ action: n.action,
3297
+ roles,
3298
+ trigger: {
3299
+ event: eventRole?.value ?? "click",
3300
+ modifiers: n.eventModifiers
3301
+ }
3302
+ };
3303
+ }
3304
+ return { action: n.action, roles };
3305
+ }
3306
+
3307
+ export {
3308
+ diffBehaviors,
3309
+ canonicalizeOp,
3310
+ detectFormat,
3311
+ validateSemanticJSON,
3312
+ jsonToSemanticNode,
3313
+ SemanticCache,
3314
+ generateCacheKey,
3315
+ extractOperations,
3316
+ PlaywrightRenderer,
3317
+ ReactRenderer,
3318
+ VueRenderer,
3319
+ SvelteRenderer,
3320
+ CompilationService
3321
+ };
3322
+ //# sourceMappingURL=chunk-ODJIDVMN.js.map