@qontinui/ui-bridge 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/control/index.d.mts +134 -0
  2. package/dist/control/index.d.ts +134 -0
  3. package/dist/control/index.js +924 -0
  4. package/dist/control/index.js.map +1 -0
  5. package/dist/control/index.mjs +919 -0
  6. package/dist/control/index.mjs.map +1 -0
  7. package/dist/core/index.d.mts +52 -0
  8. package/dist/core/index.d.ts +52 -0
  9. package/dist/core/index.js +1424 -0
  10. package/dist/core/index.js.map +1 -0
  11. package/dist/core/index.mjs +1409 -0
  12. package/dist/core/index.mjs.map +1 -0
  13. package/dist/debug/index.d.mts +93 -0
  14. package/dist/debug/index.d.ts +93 -0
  15. package/dist/debug/index.js +673 -0
  16. package/dist/debug/index.js.map +1 -0
  17. package/dist/debug/index.mjs +664 -0
  18. package/dist/debug/index.mjs.map +1 -0
  19. package/dist/index.d.mts +12 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.js +4719 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/index.mjs +4665 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/metrics-BCG7z7Aq.d.mts +147 -0
  26. package/dist/metrics-QCnK0EFw.d.ts +147 -0
  27. package/dist/react/index.d.mts +786 -0
  28. package/dist/react/index.d.ts +786 -0
  29. package/dist/react/index.js +4312 -0
  30. package/dist/react/index.js.map +1 -0
  31. package/dist/react/index.mjs +4290 -0
  32. package/dist/react/index.mjs.map +1 -0
  33. package/dist/registry-CT6BVVKr.d.mts +253 -0
  34. package/dist/registry-D4mQ01B3.d.ts +253 -0
  35. package/dist/render-log/index.d.mts +340 -0
  36. package/dist/render-log/index.d.ts +340 -0
  37. package/dist/render-log/index.js +702 -0
  38. package/dist/render-log/index.js.map +1 -0
  39. package/dist/render-log/index.mjs +695 -0
  40. package/dist/render-log/index.mjs.map +1 -0
  41. package/dist/types-BDkXy5si.d.ts +354 -0
  42. package/dist/types-BpvpStn3.d.mts +802 -0
  43. package/dist/types-BpvpStn3.d.ts +802 -0
  44. package/dist/types-DdJD9yw5.d.mts +354 -0
  45. package/dist/websocket-client-B2LC9CYc.d.mts +124 -0
  46. package/dist/websocket-client-DupH0X7B.d.ts +124 -0
  47. package/package.json +83 -0
@@ -0,0 +1,4312 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/react/UIBridgeProvider.tsx
7
+
8
+ // src/core/element-identifier.ts
9
+ function generateXPath(element) {
10
+ if (element.id) {
11
+ return `//*[@id="${element.id}"]`;
12
+ }
13
+ const parts = [];
14
+ let current = element;
15
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
16
+ let selector = current.nodeName.toLowerCase();
17
+ const uiId = current.getAttribute("data-ui-id");
18
+ if (uiId) {
19
+ selector += `[@data-ui-id="${uiId}"]`;
20
+ parts.unshift(selector);
21
+ break;
22
+ }
23
+ const testId = current.getAttribute("data-testid");
24
+ if (testId) {
25
+ selector += `[@data-testid="${testId}"]`;
26
+ parts.unshift(selector);
27
+ break;
28
+ }
29
+ const id = current.id;
30
+ if (id) {
31
+ selector += `[@id="${id}"]`;
32
+ parts.unshift(selector);
33
+ break;
34
+ }
35
+ const parentEl = current.parentElement;
36
+ if (parentEl) {
37
+ const currentEl = current;
38
+ const siblings = Array.from(parentEl.children).filter(
39
+ (child) => child.nodeName === currentEl.nodeName
40
+ );
41
+ if (siblings.length > 1) {
42
+ const index = siblings.indexOf(currentEl) + 1;
43
+ selector += `[${index}]`;
44
+ }
45
+ }
46
+ parts.unshift(selector);
47
+ current = parentEl;
48
+ }
49
+ return "/" + parts.join("/");
50
+ }
51
+ function generateCSSSelector(element) {
52
+ const uiId = element.getAttribute("data-ui-id");
53
+ if (uiId) {
54
+ return `[data-ui-id="${uiId}"]`;
55
+ }
56
+ const testId = element.getAttribute("data-testid");
57
+ if (testId) {
58
+ return `[data-testid="${testId}"]`;
59
+ }
60
+ const awasId = element.getAttribute("data-awas-element");
61
+ if (awasId) {
62
+ return `[data-awas-element="${awasId}"]`;
63
+ }
64
+ if (element.id) {
65
+ return `#${CSS.escape(element.id)}`;
66
+ }
67
+ const path = [];
68
+ let current = element;
69
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
70
+ let selector = current.nodeName.toLowerCase();
71
+ const parentUiId = current.getAttribute("data-ui-id");
72
+ if (parentUiId && current !== element) {
73
+ path.unshift(`[data-ui-id="${parentUiId}"]`);
74
+ break;
75
+ }
76
+ const parentTestId = current.getAttribute("data-testid");
77
+ if (parentTestId && current !== element) {
78
+ path.unshift(`[data-testid="${parentTestId}"]`);
79
+ break;
80
+ }
81
+ if (current.id) {
82
+ path.unshift(`#${CSS.escape(current.id)}`);
83
+ break;
84
+ }
85
+ const parentEl = current.parentElement;
86
+ if (parentEl) {
87
+ const currentEl = current;
88
+ const siblings = Array.from(parentEl.children);
89
+ const sameTagSiblings = siblings.filter(
90
+ (s) => s.nodeName === currentEl.nodeName
91
+ );
92
+ if (sameTagSiblings.length > 1) {
93
+ const index = siblings.indexOf(currentEl) + 1;
94
+ selector += `:nth-child(${index})`;
95
+ }
96
+ }
97
+ path.unshift(selector);
98
+ current = current.parentElement;
99
+ }
100
+ return path.join(" > ");
101
+ }
102
+ function getBestIdentifier(element) {
103
+ const uiId = element.getAttribute("data-ui-id");
104
+ if (uiId) return uiId;
105
+ const testId = element.getAttribute("data-testid");
106
+ if (testId) return testId;
107
+ const awasId = element.getAttribute("data-awas-element");
108
+ if (awasId) return awasId;
109
+ if (element.id) return element.id;
110
+ return generateCSSSelector(element);
111
+ }
112
+ function createElementIdentifier(element) {
113
+ return {
114
+ uiId: element.getAttribute("data-ui-id") || void 0,
115
+ testId: element.getAttribute("data-testid") || void 0,
116
+ awasId: element.getAttribute("data-awas-element") || void 0,
117
+ htmlId: element.id || void 0,
118
+ xpath: generateXPath(element),
119
+ selector: generateCSSSelector(element)
120
+ };
121
+ }
122
+ function findElementByIdentifier(identifier, root = document) {
123
+ if (typeof identifier === "string") {
124
+ const byUiId = root.querySelector(`[data-ui-id="${identifier}"]`);
125
+ if (byUiId) return byUiId;
126
+ const byTestId = root.querySelector(`[data-testid="${identifier}"]`);
127
+ if (byTestId) return byTestId;
128
+ const byAwasId = root.querySelector(`[data-awas-element="${identifier}"]`);
129
+ if (byAwasId) return byAwasId;
130
+ const byId = root.querySelector(`#${CSS.escape(identifier)}`);
131
+ if (byId) return byId;
132
+ try {
133
+ const bySelector = root.querySelector(identifier);
134
+ if (bySelector) return bySelector;
135
+ } catch {
136
+ }
137
+ try {
138
+ const result = document.evaluate(
139
+ identifier,
140
+ root,
141
+ null,
142
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
143
+ null
144
+ );
145
+ if (result.singleNodeValue instanceof HTMLElement) {
146
+ return result.singleNodeValue;
147
+ }
148
+ } catch {
149
+ }
150
+ return null;
151
+ }
152
+ if (identifier.uiId) {
153
+ const el = root.querySelector(`[data-ui-id="${identifier.uiId}"]`);
154
+ if (el) return el;
155
+ }
156
+ if (identifier.testId) {
157
+ const el = root.querySelector(`[data-testid="${identifier.testId}"]`);
158
+ if (el) return el;
159
+ }
160
+ if (identifier.awasId) {
161
+ const el = root.querySelector(`[data-awas-element="${identifier.awasId}"]`);
162
+ if (el) return el;
163
+ }
164
+ if (identifier.htmlId) {
165
+ const el = root.querySelector(`#${CSS.escape(identifier.htmlId)}`);
166
+ if (el) return el;
167
+ }
168
+ if (identifier.selector) {
169
+ try {
170
+ const el = root.querySelector(identifier.selector);
171
+ if (el) return el;
172
+ } catch {
173
+ }
174
+ }
175
+ if (identifier.xpath) {
176
+ try {
177
+ const result = document.evaluate(
178
+ identifier.xpath,
179
+ root,
180
+ null,
181
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
182
+ null
183
+ );
184
+ if (result.singleNodeValue instanceof HTMLElement) {
185
+ return result.singleNodeValue;
186
+ }
187
+ } catch {
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+
193
+ // src/core/registry.ts
194
+ function getElementState(element) {
195
+ const rect = element.getBoundingClientRect();
196
+ const computedStyle = window.getComputedStyle(element);
197
+ const state = {
198
+ visible: isElementVisible(element, rect, computedStyle),
199
+ enabled: !isElementDisabled(element),
200
+ focused: document.activeElement === element,
201
+ rect: {
202
+ x: rect.x,
203
+ y: rect.y,
204
+ width: rect.width,
205
+ height: rect.height,
206
+ top: rect.top,
207
+ right: rect.right,
208
+ bottom: rect.bottom,
209
+ left: rect.left
210
+ },
211
+ textContent: element.textContent?.trim() || void 0,
212
+ computedStyles: {
213
+ display: computedStyle.display,
214
+ visibility: computedStyle.visibility,
215
+ opacity: computedStyle.opacity,
216
+ pointerEvents: computedStyle.pointerEvents
217
+ }
218
+ };
219
+ if (element instanceof HTMLInputElement) {
220
+ state.value = element.value;
221
+ if (element.type === "checkbox" || element.type === "radio") {
222
+ state.checked = element.checked;
223
+ }
224
+ } else if (element instanceof HTMLTextAreaElement) {
225
+ state.value = element.value;
226
+ } else if (element instanceof HTMLSelectElement) {
227
+ state.value = element.value;
228
+ state.selectedOptions = Array.from(element.selectedOptions).map((opt) => opt.value);
229
+ }
230
+ return state;
231
+ }
232
+ function isElementVisible(element, rect, style) {
233
+ if (rect.width === 0 || rect.height === 0) return false;
234
+ if (style.display === "none") return false;
235
+ if (style.visibility === "hidden") return false;
236
+ if (parseFloat(style.opacity) === 0) return false;
237
+ const inViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
238
+ return inViewport;
239
+ }
240
+ function isElementDisabled(element) {
241
+ if ("disabled" in element && element.disabled) {
242
+ return true;
243
+ }
244
+ if (element.getAttribute("aria-disabled") === "true") {
245
+ return true;
246
+ }
247
+ return false;
248
+ }
249
+ function inferActions(type) {
250
+ const baseActions = ["focus", "blur", "hover"];
251
+ switch (type) {
252
+ case "button":
253
+ return [...baseActions, "click", "doubleClick", "rightClick"];
254
+ case "input":
255
+ return [...baseActions, "click", "type", "clear"];
256
+ case "textarea":
257
+ return [...baseActions, "click", "type", "clear"];
258
+ case "select":
259
+ return [...baseActions, "click", "select"];
260
+ case "checkbox":
261
+ return [...baseActions, "click", "check", "uncheck", "toggle"];
262
+ case "radio":
263
+ return [...baseActions, "click", "check"];
264
+ case "link":
265
+ return [...baseActions, "click"];
266
+ case "form":
267
+ return ["focus", "blur"];
268
+ case "menu":
269
+ case "menuitem":
270
+ return [...baseActions, "click"];
271
+ case "tab":
272
+ return [...baseActions, "click"];
273
+ case "dialog":
274
+ return ["focus", "blur"];
275
+ case "custom":
276
+ default:
277
+ return [...baseActions, "click"];
278
+ }
279
+ }
280
+ function inferElementType(element) {
281
+ const tagName = element.tagName.toLowerCase();
282
+ const role = element.getAttribute("role");
283
+ if (role) {
284
+ switch (role) {
285
+ case "button":
286
+ return "button";
287
+ case "textbox":
288
+ return "input";
289
+ case "checkbox":
290
+ return "checkbox";
291
+ case "radio":
292
+ return "radio";
293
+ case "link":
294
+ return "link";
295
+ case "listbox":
296
+ case "combobox":
297
+ return "select";
298
+ case "menu":
299
+ return "menu";
300
+ case "menuitem":
301
+ return "menuitem";
302
+ case "tab":
303
+ return "tab";
304
+ case "dialog":
305
+ return "dialog";
306
+ }
307
+ }
308
+ switch (tagName) {
309
+ case "button":
310
+ return "button";
311
+ case "input": {
312
+ const inputType = element.type;
313
+ if (inputType === "checkbox") return "checkbox";
314
+ if (inputType === "radio") return "radio";
315
+ if (inputType === "submit" || inputType === "button") return "button";
316
+ return "input";
317
+ }
318
+ case "textarea":
319
+ return "textarea";
320
+ case "select":
321
+ return "select";
322
+ case "a":
323
+ return "link";
324
+ case "form":
325
+ return "form";
326
+ default:
327
+ return "custom";
328
+ }
329
+ }
330
+ var UIBridgeRegistry = class {
331
+ constructor(options = {}) {
332
+ this.elements = /* @__PURE__ */ new Map();
333
+ this.components = /* @__PURE__ */ new Map();
334
+ this.workflows = /* @__PURE__ */ new Map();
335
+ this.eventListeners = /* @__PURE__ */ new Map();
336
+ // State management
337
+ this.states = /* @__PURE__ */ new Map();
338
+ this.stateGroups = /* @__PURE__ */ new Map();
339
+ this.transitions = /* @__PURE__ */ new Map();
340
+ this.activeStates = /* @__PURE__ */ new Set();
341
+ this.options = options;
342
+ }
343
+ /**
344
+ * Emit an event
345
+ */
346
+ emit(type, data) {
347
+ const event = {
348
+ type,
349
+ timestamp: Date.now(),
350
+ data
351
+ };
352
+ this.options.onEvent?.(event);
353
+ const listeners = this.eventListeners.get(type);
354
+ if (listeners) {
355
+ for (const listener of listeners) {
356
+ try {
357
+ listener(event);
358
+ } catch (error) {
359
+ console.error(`Error in event listener for ${type}:`, error);
360
+ }
361
+ }
362
+ }
363
+ if (this.options.verbose) {
364
+ console.log("[UIBridge]", type, data);
365
+ }
366
+ }
367
+ /**
368
+ * Register an event listener
369
+ */
370
+ on(type, listener) {
371
+ if (!this.eventListeners.has(type)) {
372
+ this.eventListeners.set(type, /* @__PURE__ */ new Set());
373
+ }
374
+ this.eventListeners.get(type).add(listener);
375
+ return () => {
376
+ this.eventListeners.get(type)?.delete(listener);
377
+ };
378
+ }
379
+ /**
380
+ * Remove an event listener
381
+ */
382
+ off(type, listener) {
383
+ this.eventListeners.get(type)?.delete(listener);
384
+ }
385
+ /**
386
+ * Register an element
387
+ */
388
+ registerElement(id, element, options = {}) {
389
+ const type = options.type ?? inferElementType(element);
390
+ const actions = options.actions ?? inferActions(type);
391
+ element.setAttribute("data-ui-id", id);
392
+ const registered = {
393
+ id,
394
+ element,
395
+ type,
396
+ label: options.label,
397
+ actions,
398
+ customActions: options.customActions,
399
+ getState: () => getElementState(element),
400
+ getIdentifier: () => createElementIdentifier(element),
401
+ registeredAt: Date.now(),
402
+ mounted: true
403
+ };
404
+ this.elements.set(id, registered);
405
+ this.emit("element:registered", { id, type, label: options.label });
406
+ return registered;
407
+ }
408
+ /**
409
+ * Unregister an element
410
+ */
411
+ unregisterElement(id) {
412
+ const registered = this.elements.get(id);
413
+ if (registered) {
414
+ registered.mounted = false;
415
+ registered.element.removeAttribute("data-ui-id");
416
+ this.elements.delete(id);
417
+ this.emit("element:unregistered", { id });
418
+ return true;
419
+ }
420
+ return false;
421
+ }
422
+ /**
423
+ * Get a registered element
424
+ */
425
+ getElement(id) {
426
+ return this.elements.get(id);
427
+ }
428
+ /**
429
+ * Get all registered elements
430
+ */
431
+ getAllElements() {
432
+ return Array.from(this.elements.values());
433
+ }
434
+ /**
435
+ * Find element by DOM element reference
436
+ */
437
+ findByDOMElement(element) {
438
+ for (const registered of this.elements.values()) {
439
+ if (registered.element === element) {
440
+ return registered;
441
+ }
442
+ }
443
+ return void 0;
444
+ }
445
+ /**
446
+ * Register a component
447
+ */
448
+ registerComponent(id, options) {
449
+ const registered = {
450
+ id,
451
+ name: options.name,
452
+ description: options.description,
453
+ actions: options.actions?.map((a) => ({
454
+ id: a.id,
455
+ label: a.label,
456
+ description: a.description,
457
+ handler: a.handler
458
+ })) ?? [],
459
+ elementIds: options.elementIds,
460
+ registeredAt: Date.now(),
461
+ mounted: true
462
+ };
463
+ this.components.set(id, registered);
464
+ this.emit("component:registered", { id, name: options.name });
465
+ return registered;
466
+ }
467
+ /**
468
+ * Unregister a component
469
+ */
470
+ unregisterComponent(id) {
471
+ const component = this.components.get(id);
472
+ if (component) {
473
+ component.mounted = false;
474
+ this.components.delete(id);
475
+ this.emit("component:unregistered", { id });
476
+ return true;
477
+ }
478
+ return false;
479
+ }
480
+ /**
481
+ * Get a registered component
482
+ */
483
+ getComponent(id) {
484
+ return this.components.get(id);
485
+ }
486
+ /**
487
+ * Get all registered components
488
+ */
489
+ getAllComponents() {
490
+ return Array.from(this.components.values());
491
+ }
492
+ /**
493
+ * Register a workflow
494
+ */
495
+ registerWorkflow(workflow) {
496
+ this.workflows.set(workflow.id, workflow);
497
+ return workflow;
498
+ }
499
+ /**
500
+ * Unregister a workflow
501
+ */
502
+ unregisterWorkflow(id) {
503
+ return this.workflows.delete(id);
504
+ }
505
+ /**
506
+ * Get a workflow
507
+ */
508
+ getWorkflow(id) {
509
+ return this.workflows.get(id);
510
+ }
511
+ /**
512
+ * Get all workflows
513
+ */
514
+ getAllWorkflows() {
515
+ return Array.from(this.workflows.values());
516
+ }
517
+ // ==========================================================================
518
+ // State Management
519
+ // ==========================================================================
520
+ /**
521
+ * Register a state
522
+ */
523
+ registerState(state) {
524
+ this.states.set(state.id, state);
525
+ this.emit("element:registered", { id: state.id, type: "state", name: state.name });
526
+ return state;
527
+ }
528
+ /**
529
+ * Unregister a state
530
+ */
531
+ unregisterState(id) {
532
+ const state = this.states.get(id);
533
+ if (state) {
534
+ this.activeStates.delete(id);
535
+ this.states.delete(id);
536
+ this.emit("element:unregistered", { id, type: "state" });
537
+ return true;
538
+ }
539
+ return false;
540
+ }
541
+ /**
542
+ * Get a registered state
543
+ */
544
+ getState(id) {
545
+ return this.states.get(id);
546
+ }
547
+ /**
548
+ * Get all registered states
549
+ */
550
+ getAllStates() {
551
+ return Array.from(this.states.values());
552
+ }
553
+ /**
554
+ * Register a state group
555
+ */
556
+ registerStateGroup(group) {
557
+ this.stateGroups.set(group.id, group);
558
+ return group;
559
+ }
560
+ /**
561
+ * Unregister a state group
562
+ */
563
+ unregisterStateGroup(id) {
564
+ return this.stateGroups.delete(id);
565
+ }
566
+ /**
567
+ * Get a state group
568
+ */
569
+ getStateGroup(id) {
570
+ return this.stateGroups.get(id);
571
+ }
572
+ /**
573
+ * Get all state groups
574
+ */
575
+ getAllStateGroups() {
576
+ return Array.from(this.stateGroups.values());
577
+ }
578
+ /**
579
+ * Register a transition
580
+ */
581
+ registerTransition(transition) {
582
+ this.transitions.set(transition.id, transition);
583
+ return transition;
584
+ }
585
+ /**
586
+ * Unregister a transition
587
+ */
588
+ unregisterTransition(id) {
589
+ return this.transitions.delete(id);
590
+ }
591
+ /**
592
+ * Get a transition
593
+ */
594
+ getTransition(id) {
595
+ return this.transitions.get(id);
596
+ }
597
+ /**
598
+ * Get all transitions
599
+ */
600
+ getAllTransitions() {
601
+ return Array.from(this.transitions.values());
602
+ }
603
+ /**
604
+ * Get currently active states
605
+ */
606
+ getActiveStates() {
607
+ return Array.from(this.activeStates);
608
+ }
609
+ /**
610
+ * Check if a state is active
611
+ */
612
+ isStateActive(id) {
613
+ return this.activeStates.has(id);
614
+ }
615
+ /**
616
+ * Activate a state
617
+ */
618
+ activateState(id) {
619
+ const state = this.states.get(id);
620
+ if (!state) {
621
+ return false;
622
+ }
623
+ for (const activeId of this.activeStates) {
624
+ const activeState = this.states.get(activeId);
625
+ if (activeState?.blocking && activeState.id !== id) {
626
+ return false;
627
+ }
628
+ if (activeState?.blocks?.includes(id)) {
629
+ return false;
630
+ }
631
+ }
632
+ const wasActive = this.activeStates.has(id);
633
+ this.activeStates.add(id);
634
+ if (!wasActive) {
635
+ this.emit("element:stateChanged", {
636
+ stateId: id,
637
+ active: true,
638
+ activeStates: this.getActiveStates()
639
+ });
640
+ }
641
+ return true;
642
+ }
643
+ /**
644
+ * Deactivate a state
645
+ */
646
+ deactivateState(id) {
647
+ const wasActive = this.activeStates.has(id);
648
+ this.activeStates.delete(id);
649
+ if (wasActive) {
650
+ this.emit("element:stateChanged", {
651
+ stateId: id,
652
+ active: false,
653
+ activeStates: this.getActiveStates()
654
+ });
655
+ }
656
+ return wasActive;
657
+ }
658
+ /**
659
+ * Activate multiple states
660
+ */
661
+ activateStates(ids) {
662
+ const activated = [];
663
+ for (const id of ids) {
664
+ if (this.activateState(id)) {
665
+ activated.push(id);
666
+ }
667
+ }
668
+ return activated;
669
+ }
670
+ /**
671
+ * Deactivate multiple states
672
+ */
673
+ deactivateStates(ids) {
674
+ const deactivated = [];
675
+ for (const id of ids) {
676
+ if (this.deactivateState(id)) {
677
+ deactivated.push(id);
678
+ }
679
+ }
680
+ return deactivated;
681
+ }
682
+ /**
683
+ * Activate a state group (all states in the group)
684
+ */
685
+ activateStateGroup(groupId) {
686
+ const group = this.stateGroups.get(groupId);
687
+ if (!group) return [];
688
+ return this.activateStates(group.states);
689
+ }
690
+ /**
691
+ * Deactivate a state group (all states in the group)
692
+ */
693
+ deactivateStateGroup(groupId) {
694
+ const group = this.stateGroups.get(groupId);
695
+ if (!group) return [];
696
+ return this.deactivateStates(group.states);
697
+ }
698
+ /**
699
+ * Check if a transition can be executed from current state
700
+ */
701
+ canExecuteTransition(transitionId) {
702
+ const transition = this.transitions.get(transitionId);
703
+ if (!transition) return false;
704
+ return transition.fromStates.some((stateId) => this.activeStates.has(stateId));
705
+ }
706
+ /**
707
+ * Execute a transition
708
+ */
709
+ async executeTransition(transitionId) {
710
+ const startTime = performance.now();
711
+ const transition = this.transitions.get(transitionId);
712
+ if (!transition) {
713
+ return {
714
+ success: false,
715
+ activatedStates: [],
716
+ deactivatedStates: [],
717
+ error: `Transition not found: ${transitionId}`,
718
+ durationMs: performance.now() - startTime
719
+ };
720
+ }
721
+ if (!this.canExecuteTransition(transitionId)) {
722
+ return {
723
+ success: false,
724
+ activatedStates: [],
725
+ deactivatedStates: [],
726
+ error: "Precondition not met: none of the fromStates are active",
727
+ failedPhase: "precondition",
728
+ durationMs: performance.now() - startTime
729
+ };
730
+ }
731
+ try {
732
+ const deactivated = this.deactivateStates(transition.exitStates);
733
+ if (transition.exitGroups) {
734
+ for (const groupId of transition.exitGroups) {
735
+ deactivated.push(...this.deactivateStateGroup(groupId));
736
+ }
737
+ }
738
+ const activated = this.activateStates(transition.activateStates);
739
+ if (transition.activateGroups) {
740
+ for (const groupId of transition.activateGroups) {
741
+ activated.push(...this.activateStateGroup(groupId));
742
+ }
743
+ }
744
+ return {
745
+ success: true,
746
+ activatedStates: activated,
747
+ deactivatedStates: deactivated,
748
+ durationMs: performance.now() - startTime
749
+ };
750
+ } catch (error) {
751
+ return {
752
+ success: false,
753
+ activatedStates: [],
754
+ deactivatedStates: [],
755
+ error: error instanceof Error ? error.message : String(error),
756
+ failedPhase: "execution",
757
+ durationMs: performance.now() - startTime
758
+ };
759
+ }
760
+ }
761
+ /**
762
+ * Find a path from current state to target states
763
+ *
764
+ * Uses a simple BFS algorithm for pathfinding.
765
+ * For more advanced pathfinding (Dijkstra, A*), use the Python state manager service.
766
+ */
767
+ findPath(targetStates) {
768
+ if (targetStates.every((t) => this.activeStates.has(t))) {
769
+ return {
770
+ found: true,
771
+ transitions: [],
772
+ totalCost: 0,
773
+ targetStates,
774
+ estimatedSteps: 0
775
+ };
776
+ }
777
+ const queue = [
778
+ { activeStates: new Set(this.activeStates), path: [], cost: 0 }
779
+ ];
780
+ const visited = /* @__PURE__ */ new Set();
781
+ while (queue.length > 0) {
782
+ const current = queue.shift();
783
+ const stateKey = Array.from(current.activeStates).sort().join(",");
784
+ if (visited.has(stateKey)) continue;
785
+ visited.add(stateKey);
786
+ if (targetStates.every((t) => current.activeStates.has(t))) {
787
+ return {
788
+ found: true,
789
+ transitions: current.path,
790
+ totalCost: current.cost,
791
+ targetStates,
792
+ estimatedSteps: current.path.length
793
+ };
794
+ }
795
+ for (const transition of this.transitions.values()) {
796
+ const canExecute = transition.fromStates.some((s) => current.activeStates.has(s));
797
+ if (!canExecute) continue;
798
+ const newActive = new Set(current.activeStates);
799
+ for (const s of transition.exitStates) newActive.delete(s);
800
+ for (const s of transition.activateStates) newActive.add(s);
801
+ const newCost = current.cost + (transition.pathCost ?? 1);
802
+ queue.push({
803
+ activeStates: newActive,
804
+ path: [...current.path, transition.id],
805
+ cost: newCost
806
+ });
807
+ }
808
+ }
809
+ return {
810
+ found: false,
811
+ transitions: [],
812
+ totalCost: 0,
813
+ targetStates,
814
+ estimatedSteps: 0
815
+ };
816
+ }
817
+ /**
818
+ * Navigate to target states using pathfinding
819
+ */
820
+ async navigateTo(targetStates) {
821
+ const startTime = performance.now();
822
+ const path = this.findPath(targetStates);
823
+ if (!path.found) {
824
+ return {
825
+ success: false,
826
+ path,
827
+ executedTransitions: [],
828
+ finalActiveStates: this.getActiveStates(),
829
+ error: `No path found to target states: ${targetStates.join(", ")}`,
830
+ durationMs: performance.now() - startTime
831
+ };
832
+ }
833
+ const executedTransitions = [];
834
+ for (const transitionId of path.transitions) {
835
+ const result = await this.executeTransition(transitionId);
836
+ if (!result.success) {
837
+ return {
838
+ success: false,
839
+ path,
840
+ executedTransitions,
841
+ finalActiveStates: this.getActiveStates(),
842
+ error: result.error,
843
+ durationMs: performance.now() - startTime
844
+ };
845
+ }
846
+ executedTransitions.push(transitionId);
847
+ }
848
+ return {
849
+ success: true,
850
+ path,
851
+ executedTransitions,
852
+ finalActiveStates: this.getActiveStates(),
853
+ durationMs: performance.now() - startTime
854
+ };
855
+ }
856
+ /**
857
+ * Create a state snapshot
858
+ */
859
+ createStateSnapshot() {
860
+ return {
861
+ timestamp: Date.now(),
862
+ activeStates: this.getActiveStates(),
863
+ states: this.getAllStates(),
864
+ groups: this.getAllStateGroups(),
865
+ transitions: this.getAllTransitions()
866
+ };
867
+ }
868
+ /**
869
+ * Create a snapshot of the current state
870
+ */
871
+ createSnapshot() {
872
+ return {
873
+ timestamp: Date.now(),
874
+ elements: this.getAllElements().map((el) => ({
875
+ id: el.id,
876
+ type: el.type,
877
+ label: el.label,
878
+ identifier: el.getIdentifier(),
879
+ state: el.getState(),
880
+ actions: el.actions,
881
+ customActions: el.customActions ? Object.keys(el.customActions) : void 0
882
+ })),
883
+ components: this.getAllComponents().map((comp) => ({
884
+ id: comp.id,
885
+ name: comp.name,
886
+ description: comp.description,
887
+ actions: comp.actions.map((a) => a.id),
888
+ elementIds: comp.elementIds
889
+ })),
890
+ workflows: this.getAllWorkflows().map((wf) => ({
891
+ id: wf.id,
892
+ name: wf.name,
893
+ description: wf.description,
894
+ stepCount: wf.steps.length
895
+ }))
896
+ };
897
+ }
898
+ /**
899
+ * Clear all registrations
900
+ */
901
+ clear() {
902
+ this.elements.clear();
903
+ this.components.clear();
904
+ this.workflows.clear();
905
+ this.eventListeners.clear();
906
+ this.states.clear();
907
+ this.stateGroups.clear();
908
+ this.transitions.clear();
909
+ this.activeStates.clear();
910
+ }
911
+ /**
912
+ * Get registry statistics
913
+ */
914
+ getStats() {
915
+ const elements = this.getAllElements();
916
+ const components = this.getAllComponents();
917
+ return {
918
+ elementCount: elements.length,
919
+ componentCount: components.length,
920
+ workflowCount: this.workflows.size,
921
+ mountedElementCount: elements.filter((e) => e.mounted).length,
922
+ mountedComponentCount: components.filter((c) => c.mounted).length,
923
+ stateCount: this.states.size,
924
+ stateGroupCount: this.stateGroups.size,
925
+ transitionCount: this.transitions.size,
926
+ activeStateCount: this.activeStates.size
927
+ };
928
+ }
929
+ };
930
+ var globalRegistry = null;
931
+ function setGlobalRegistry(registry) {
932
+ globalRegistry = registry;
933
+ }
934
+ function resetGlobalRegistry() {
935
+ globalRegistry?.clear();
936
+ globalRegistry = null;
937
+ }
938
+
939
+ // src/core/websocket-client.ts
940
+ function generateId() {
941
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
942
+ }
943
+ var UIBridgeWSClient = class {
944
+ constructor(config) {
945
+ this.ws = null;
946
+ this.state = "disconnected";
947
+ this.clientId = null;
948
+ this.reconnectAttempts = 0;
949
+ this.reconnectTimer = null;
950
+ this.pingTimer = null;
951
+ this.pendingRequests = /* @__PURE__ */ new Map();
952
+ // Event listeners
953
+ this.connectionListeners = /* @__PURE__ */ new Set();
954
+ this.eventListeners = /* @__PURE__ */ new Map();
955
+ this.errorListeners = /* @__PURE__ */ new Set();
956
+ // Current subscriptions
957
+ this.subscriptions = {};
958
+ this.config = {
959
+ url: config.url,
960
+ autoReconnect: config.autoReconnect ?? true,
961
+ reconnectDelay: config.reconnectDelay ?? 1e3,
962
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
963
+ pingInterval: config.pingInterval ?? 3e4,
964
+ connectionTimeout: config.connectionTimeout ?? 1e4
965
+ };
966
+ }
967
+ /**
968
+ * Get current connection state
969
+ */
970
+ get connectionState() {
971
+ return this.state;
972
+ }
973
+ /**
974
+ * Get assigned client ID
975
+ */
976
+ get id() {
977
+ return this.clientId;
978
+ }
979
+ /**
980
+ * Connect to the WebSocket server
981
+ */
982
+ connect() {
983
+ return new Promise((resolve, reject) => {
984
+ if (this.ws && this.state === "connected") {
985
+ resolve();
986
+ return;
987
+ }
988
+ this.setState("connecting");
989
+ try {
990
+ this.ws = new WebSocket(this.config.url);
991
+ } catch (error) {
992
+ this.setState("disconnected");
993
+ reject(error);
994
+ return;
995
+ }
996
+ const connectionTimeout = setTimeout(() => {
997
+ if (this.state === "connecting") {
998
+ this.ws?.close();
999
+ this.setState("disconnected");
1000
+ reject(new Error("Connection timeout"));
1001
+ }
1002
+ }, this.config.connectionTimeout);
1003
+ this.ws.onopen = () => {
1004
+ clearTimeout(connectionTimeout);
1005
+ };
1006
+ this.ws.onmessage = (event) => {
1007
+ try {
1008
+ const message = JSON.parse(event.data);
1009
+ this.handleMessage(message);
1010
+ if (message.type === "welcome") {
1011
+ clearTimeout(connectionTimeout);
1012
+ this.reconnectAttempts = 0;
1013
+ this.setState("connected");
1014
+ this.startPingInterval();
1015
+ if (this.subscriptions.events?.length || this.subscriptions.elementIds?.length || this.subscriptions.componentIds?.length) {
1016
+ this.subscribe(this.subscriptions);
1017
+ }
1018
+ resolve();
1019
+ }
1020
+ } catch (error) {
1021
+ console.error("Failed to parse WebSocket message:", error);
1022
+ }
1023
+ };
1024
+ this.ws.onerror = (_event) => {
1025
+ clearTimeout(connectionTimeout);
1026
+ const error = new Error("WebSocket error");
1027
+ this.notifyError(error);
1028
+ if (this.state === "connecting") {
1029
+ reject(error);
1030
+ }
1031
+ };
1032
+ this.ws.onclose = () => {
1033
+ clearTimeout(connectionTimeout);
1034
+ this.stopPingInterval();
1035
+ this.clientId = null;
1036
+ const wasConnected = this.state === "connected";
1037
+ this.setState("disconnected");
1038
+ for (const [_id, pending] of this.pendingRequests) {
1039
+ clearTimeout(pending.timeout);
1040
+ pending.reject(new Error("Connection closed"));
1041
+ }
1042
+ this.pendingRequests.clear();
1043
+ if (wasConnected && this.config.autoReconnect && (this.config.maxReconnectAttempts === 0 || this.reconnectAttempts < this.config.maxReconnectAttempts)) {
1044
+ this.scheduleReconnect();
1045
+ }
1046
+ };
1047
+ });
1048
+ }
1049
+ /**
1050
+ * Disconnect from the server
1051
+ */
1052
+ disconnect() {
1053
+ if (this.reconnectTimer) {
1054
+ clearTimeout(this.reconnectTimer);
1055
+ this.reconnectTimer = null;
1056
+ }
1057
+ this.stopPingInterval();
1058
+ if (this.ws) {
1059
+ this.ws.close();
1060
+ this.ws = null;
1061
+ }
1062
+ this.setState("disconnected");
1063
+ }
1064
+ /**
1065
+ * Subscribe to events
1066
+ */
1067
+ async subscribe(options) {
1068
+ this.subscriptions = { ...this.subscriptions, ...options };
1069
+ const response = await this.sendRequest({
1070
+ id: generateId(),
1071
+ type: "subscribe",
1072
+ timestamp: Date.now(),
1073
+ payload: options
1074
+ });
1075
+ return response.events;
1076
+ }
1077
+ /**
1078
+ * Unsubscribe from events
1079
+ */
1080
+ async unsubscribe(events) {
1081
+ if (events) {
1082
+ this.subscriptions.events = this.subscriptions.events?.filter((e) => !events.includes(e));
1083
+ } else {
1084
+ this.subscriptions = {};
1085
+ }
1086
+ const response = await this.sendRequest({
1087
+ id: generateId(),
1088
+ type: "unsubscribe",
1089
+ timestamp: Date.now(),
1090
+ payload: { events }
1091
+ });
1092
+ return response.events;
1093
+ }
1094
+ /**
1095
+ * Find elements
1096
+ */
1097
+ async find(options) {
1098
+ const response = await this.sendRequest({
1099
+ id: generateId(),
1100
+ type: "find",
1101
+ timestamp: Date.now(),
1102
+ payload: options
1103
+ });
1104
+ return response.elements;
1105
+ }
1106
+ /**
1107
+ * Discover elements
1108
+ * @deprecated Use find() instead
1109
+ */
1110
+ async discover(options) {
1111
+ return this.find(options);
1112
+ }
1113
+ /**
1114
+ * Get element details
1115
+ */
1116
+ async getElement(elementId, includeState = true) {
1117
+ const response = await this.sendRequest({
1118
+ id: generateId(),
1119
+ type: "getElement",
1120
+ timestamp: Date.now(),
1121
+ payload: { elementId, includeState }
1122
+ });
1123
+ return response.element;
1124
+ }
1125
+ /**
1126
+ * Get full snapshot
1127
+ */
1128
+ async getSnapshot() {
1129
+ const response = await this.sendRequest({
1130
+ id: generateId(),
1131
+ type: "getSnapshot",
1132
+ timestamp: Date.now()
1133
+ });
1134
+ return response;
1135
+ }
1136
+ /**
1137
+ * Execute action on an element
1138
+ */
1139
+ async executeAction(elementId, action) {
1140
+ const response = await this.sendRequest({
1141
+ id: generateId(),
1142
+ type: "executeAction",
1143
+ timestamp: Date.now(),
1144
+ payload: { elementId, action }
1145
+ });
1146
+ return response;
1147
+ }
1148
+ /**
1149
+ * Execute component action
1150
+ */
1151
+ async executeComponentAction(componentId, action, params) {
1152
+ const response = await this.sendRequest({
1153
+ id: generateId(),
1154
+ type: "executeComponentAction",
1155
+ timestamp: Date.now(),
1156
+ payload: { componentId, action, params }
1157
+ });
1158
+ return response;
1159
+ }
1160
+ /**
1161
+ * Execute workflow with optional progress streaming
1162
+ */
1163
+ async executeWorkflow(workflowId, params, onProgress) {
1164
+ const id = generateId();
1165
+ const progressHandler = onProgress ? (message) => {
1166
+ if (message.type === "workflowProgress" && message.requestId === id) {
1167
+ onProgress({
1168
+ currentStep: message.payload.currentStep,
1169
+ totalSteps: message.payload.totalSteps,
1170
+ step: {
1171
+ id: message.payload.step.id,
1172
+ status: message.payload.step.status
1173
+ }
1174
+ });
1175
+ }
1176
+ } : void 0;
1177
+ const response = await this.sendRequest(
1178
+ {
1179
+ id,
1180
+ type: "executeWorkflow",
1181
+ timestamp: Date.now(),
1182
+ payload: { workflowId, params, streamProgress: !!onProgress }
1183
+ },
1184
+ progressHandler
1185
+ );
1186
+ return response;
1187
+ }
1188
+ /**
1189
+ * Add connection state listener
1190
+ */
1191
+ onConnectionChange(listener) {
1192
+ this.connectionListeners.add(listener);
1193
+ return () => this.connectionListeners.delete(listener);
1194
+ }
1195
+ /**
1196
+ * Add event listener
1197
+ */
1198
+ onEvent(eventType, listener) {
1199
+ if (!this.eventListeners.has(eventType)) {
1200
+ this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
1201
+ }
1202
+ this.eventListeners.get(eventType).add(listener);
1203
+ return () => this.eventListeners.get(eventType)?.delete(listener);
1204
+ }
1205
+ /**
1206
+ * Add error listener
1207
+ */
1208
+ onError(listener) {
1209
+ this.errorListeners.add(listener);
1210
+ return () => this.errorListeners.delete(listener);
1211
+ }
1212
+ // Private methods
1213
+ setState(state) {
1214
+ this.state = state;
1215
+ for (const listener of this.connectionListeners) {
1216
+ try {
1217
+ listener(state);
1218
+ } catch (error) {
1219
+ console.error("Connection listener error:", error);
1220
+ }
1221
+ }
1222
+ }
1223
+ handleMessage(message) {
1224
+ switch (message.type) {
1225
+ case "welcome":
1226
+ this.clientId = message.payload.clientId;
1227
+ break;
1228
+ case "pong":
1229
+ break;
1230
+ case "subscribed":
1231
+ case "unsubscribed":
1232
+ break;
1233
+ case "event":
1234
+ this.notifyEvent(message.payload);
1235
+ break;
1236
+ case "response":
1237
+ this.handleResponse(message);
1238
+ break;
1239
+ case "error":
1240
+ if (message.requestId) {
1241
+ this.handleResponse({
1242
+ ...message,
1243
+ type: "response",
1244
+ requestId: message.requestId,
1245
+ payload: {
1246
+ success: false,
1247
+ error: message.payload.message
1248
+ }
1249
+ });
1250
+ } else {
1251
+ this.notifyError(new Error(message.payload.message));
1252
+ }
1253
+ break;
1254
+ }
1255
+ }
1256
+ handleResponse(message) {
1257
+ const pending = this.pendingRequests.get(message.requestId);
1258
+ if (!pending) return;
1259
+ clearTimeout(pending.timeout);
1260
+ this.pendingRequests.delete(message.requestId);
1261
+ if (message.type === "response") {
1262
+ if (message.payload.success) {
1263
+ pending.resolve(message.payload.data);
1264
+ } else {
1265
+ pending.reject(new Error(message.payload.error || "Request failed"));
1266
+ }
1267
+ }
1268
+ }
1269
+ notifyEvent(event) {
1270
+ const typeListeners = this.eventListeners.get(event.type);
1271
+ if (typeListeners) {
1272
+ for (const listener of typeListeners) {
1273
+ try {
1274
+ listener(event);
1275
+ } catch (error) {
1276
+ console.error("Event listener error:", error);
1277
+ }
1278
+ }
1279
+ }
1280
+ const wildcardListeners = this.eventListeners.get("*");
1281
+ if (wildcardListeners) {
1282
+ for (const listener of wildcardListeners) {
1283
+ try {
1284
+ listener(event);
1285
+ } catch (error) {
1286
+ console.error("Event listener error:", error);
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+ notifyError(error) {
1292
+ for (const listener of this.errorListeners) {
1293
+ try {
1294
+ listener(error);
1295
+ } catch (e) {
1296
+ console.error("Error listener error:", e);
1297
+ }
1298
+ }
1299
+ }
1300
+ sendRequest(message, progressHandler) {
1301
+ return new Promise((resolve, reject) => {
1302
+ if (!this.ws || this.state !== "connected") {
1303
+ reject(new Error("Not connected"));
1304
+ return;
1305
+ }
1306
+ const timeout = setTimeout(() => {
1307
+ this.pendingRequests.delete(message.id);
1308
+ reject(new Error("Request timeout"));
1309
+ }, 3e4);
1310
+ this.pendingRequests.set(message.id, {
1311
+ resolve,
1312
+ reject,
1313
+ timeout
1314
+ });
1315
+ if (progressHandler && this.ws) {
1316
+ const originalHandler = this.ws.onmessage;
1317
+ const wsRef = this.ws;
1318
+ const wrappedHandler = (event) => {
1319
+ try {
1320
+ const msg = JSON.parse(event.data);
1321
+ if (msg.type === "workflowProgress") {
1322
+ progressHandler(msg);
1323
+ }
1324
+ } catch {
1325
+ }
1326
+ if (originalHandler) {
1327
+ originalHandler.call(wsRef, event);
1328
+ }
1329
+ };
1330
+ this.ws.onmessage = wrappedHandler;
1331
+ }
1332
+ this.ws.send(JSON.stringify(message));
1333
+ });
1334
+ }
1335
+ scheduleReconnect() {
1336
+ if (this.reconnectTimer) return;
1337
+ this.setState("reconnecting");
1338
+ this.reconnectAttempts++;
1339
+ const delay = Math.min(
1340
+ this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
1341
+ 3e4
1342
+ );
1343
+ this.reconnectTimer = setTimeout(() => {
1344
+ this.reconnectTimer = null;
1345
+ this.connect().catch(() => {
1346
+ });
1347
+ }, delay);
1348
+ }
1349
+ startPingInterval() {
1350
+ if (this.config.pingInterval <= 0) return;
1351
+ this.pingTimer = setInterval(() => {
1352
+ if (this.ws && this.state === "connected") {
1353
+ this.ws.send(
1354
+ JSON.stringify({
1355
+ id: generateId(),
1356
+ type: "ping",
1357
+ timestamp: Date.now()
1358
+ })
1359
+ );
1360
+ }
1361
+ }, this.config.pingInterval);
1362
+ }
1363
+ stopPingInterval() {
1364
+ if (this.pingTimer) {
1365
+ clearInterval(this.pingTimer);
1366
+ this.pingTimer = null;
1367
+ }
1368
+ }
1369
+ };
1370
+ function createWSClient(config) {
1371
+ return new UIBridgeWSClient(config);
1372
+ }
1373
+
1374
+ // src/control/action-executor.ts
1375
+ var DEFAULT_WAIT_OPTIONS = {
1376
+ visible: true,
1377
+ enabled: true,
1378
+ focused: false,
1379
+ state: {},
1380
+ timeout: 1e4,
1381
+ interval: 100
1382
+ };
1383
+ function getElementState2(element) {
1384
+ const rect = element.getBoundingClientRect();
1385
+ const style = window.getComputedStyle(element);
1386
+ const state = {
1387
+ visible: isVisible(element, rect, style),
1388
+ enabled: !isDisabled(element),
1389
+ focused: document.activeElement === element,
1390
+ rect: {
1391
+ x: rect.x,
1392
+ y: rect.y,
1393
+ width: rect.width,
1394
+ height: rect.height,
1395
+ top: rect.top,
1396
+ right: rect.right,
1397
+ bottom: rect.bottom,
1398
+ left: rect.left
1399
+ },
1400
+ computedStyles: {
1401
+ display: style.display,
1402
+ visibility: style.visibility,
1403
+ opacity: style.opacity,
1404
+ pointerEvents: style.pointerEvents
1405
+ }
1406
+ };
1407
+ if (element instanceof HTMLInputElement) {
1408
+ state.value = element.value;
1409
+ if (element.type === "checkbox" || element.type === "radio") {
1410
+ state.checked = element.checked;
1411
+ }
1412
+ } else if (element instanceof HTMLTextAreaElement) {
1413
+ state.value = element.value;
1414
+ } else if (element instanceof HTMLSelectElement) {
1415
+ state.value = element.value;
1416
+ state.selectedOptions = Array.from(element.selectedOptions).map((opt) => opt.value);
1417
+ }
1418
+ return state;
1419
+ }
1420
+ function isVisible(element, rect, style) {
1421
+ if (rect.width === 0 || rect.height === 0) return false;
1422
+ if (style.display === "none") return false;
1423
+ if (style.visibility === "hidden") return false;
1424
+ if (parseFloat(style.opacity) === 0) return false;
1425
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
1426
+ }
1427
+ function isDisabled(element) {
1428
+ if ("disabled" in element && element.disabled) return true;
1429
+ if (element.getAttribute("aria-disabled") === "true") return true;
1430
+ return false;
1431
+ }
1432
+ function sleep(ms) {
1433
+ return new Promise((resolve) => setTimeout(resolve, ms));
1434
+ }
1435
+ function createMouseEvent(type, element, options) {
1436
+ const rect = element.getBoundingClientRect();
1437
+ const x = options?.position?.x ?? rect.width / 2;
1438
+ const y = options?.position?.y ?? rect.height / 2;
1439
+ return new MouseEvent(type, {
1440
+ bubbles: true,
1441
+ cancelable: true,
1442
+ view: window,
1443
+ button: options?.button === "right" ? 2 : options?.button === "middle" ? 1 : 0,
1444
+ clientX: rect.left + x,
1445
+ clientY: rect.top + y
1446
+ });
1447
+ }
1448
+ var DefaultActionExecutor = class {
1449
+ constructor(registry) {
1450
+ this.registry = registry;
1451
+ }
1452
+ /**
1453
+ * Execute an action on an element
1454
+ */
1455
+ async executeAction(elementId, request) {
1456
+ const startTime = performance.now();
1457
+ let waitDurationMs = 0;
1458
+ try {
1459
+ const registered = this.registry.getElement(elementId);
1460
+ let element = registered?.element ?? null;
1461
+ if (!element) {
1462
+ element = findElementByIdentifier(elementId);
1463
+ }
1464
+ if (!element) {
1465
+ return {
1466
+ success: false,
1467
+ error: `Element not found: ${elementId}`,
1468
+ durationMs: performance.now() - startTime,
1469
+ timestamp: Date.now(),
1470
+ requestId: request.requestId
1471
+ };
1472
+ }
1473
+ if (request.waitOptions) {
1474
+ const waitResult = await this.waitForElement(element, request.waitOptions);
1475
+ waitDurationMs = waitResult.waitedMs;
1476
+ if (!waitResult.met) {
1477
+ return {
1478
+ success: false,
1479
+ error: waitResult.error || "Wait condition not met",
1480
+ durationMs: performance.now() - startTime,
1481
+ timestamp: Date.now(),
1482
+ requestId: request.requestId,
1483
+ waitDurationMs
1484
+ };
1485
+ }
1486
+ }
1487
+ const result = await this.performAction(element, request.action, request.params);
1488
+ return {
1489
+ success: true,
1490
+ elementState: getElementState2(element),
1491
+ result,
1492
+ durationMs: performance.now() - startTime,
1493
+ timestamp: Date.now(),
1494
+ requestId: request.requestId,
1495
+ waitDurationMs
1496
+ };
1497
+ } catch (error) {
1498
+ return {
1499
+ success: false,
1500
+ error: error instanceof Error ? error.message : String(error),
1501
+ stack: error instanceof Error ? error.stack : void 0,
1502
+ durationMs: performance.now() - startTime,
1503
+ timestamp: Date.now(),
1504
+ requestId: request.requestId,
1505
+ waitDurationMs
1506
+ };
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Execute an action on a component
1511
+ */
1512
+ async executeComponentAction(componentId, request) {
1513
+ const startTime = performance.now();
1514
+ try {
1515
+ const component = this.registry.getComponent(componentId);
1516
+ if (!component) {
1517
+ return {
1518
+ success: false,
1519
+ error: `Component not found: ${componentId}`,
1520
+ durationMs: performance.now() - startTime,
1521
+ timestamp: Date.now(),
1522
+ requestId: request.requestId
1523
+ };
1524
+ }
1525
+ const action = component.actions.find((a) => a.id === request.action);
1526
+ if (!action) {
1527
+ return {
1528
+ success: false,
1529
+ error: `Action not found: ${request.action}`,
1530
+ durationMs: performance.now() - startTime,
1531
+ timestamp: Date.now(),
1532
+ requestId: request.requestId
1533
+ };
1534
+ }
1535
+ const result = await action.handler(request.params);
1536
+ return {
1537
+ success: true,
1538
+ result,
1539
+ durationMs: performance.now() - startTime,
1540
+ timestamp: Date.now(),
1541
+ requestId: request.requestId
1542
+ };
1543
+ } catch (error) {
1544
+ return {
1545
+ success: false,
1546
+ error: error instanceof Error ? error.message : String(error),
1547
+ stack: error instanceof Error ? error.stack : void 0,
1548
+ durationMs: performance.now() - startTime,
1549
+ timestamp: Date.now(),
1550
+ requestId: request.requestId
1551
+ };
1552
+ }
1553
+ }
1554
+ /**
1555
+ * Wait for a condition on an element
1556
+ */
1557
+ async waitFor(elementId, options) {
1558
+ const registered = this.registry.getElement(elementId);
1559
+ let element = registered?.element ?? null;
1560
+ if (!element) {
1561
+ element = findElementByIdentifier(elementId);
1562
+ }
1563
+ if (!element) {
1564
+ return {
1565
+ met: false,
1566
+ waitedMs: 0,
1567
+ error: `Element not found: ${elementId}`
1568
+ };
1569
+ }
1570
+ return this.waitForElement(element, options);
1571
+ }
1572
+ /**
1573
+ * Find controllable elements
1574
+ */
1575
+ async find(options) {
1576
+ const startTime = performance.now();
1577
+ const elements = [];
1578
+ let root = document.body;
1579
+ if (options?.root) {
1580
+ const rootEl = document.querySelector(options.root);
1581
+ if (rootEl) root = rootEl;
1582
+ }
1583
+ const interactiveSelectors = [
1584
+ "a[href]",
1585
+ "button",
1586
+ "input",
1587
+ "select",
1588
+ "textarea",
1589
+ "[onclick]",
1590
+ '[role="button"]',
1591
+ '[role="link"]',
1592
+ '[role="checkbox"]',
1593
+ '[role="radio"]',
1594
+ '[role="menuitem"]',
1595
+ '[role="tab"]',
1596
+ '[role="switch"]',
1597
+ '[tabindex]:not([tabindex="-1"])',
1598
+ '[contenteditable="true"]',
1599
+ "[data-ui-id]",
1600
+ "[data-testid]"
1601
+ ];
1602
+ const selector = options?.selector || interactiveSelectors.join(", ");
1603
+ const foundElements = root.querySelectorAll(selector);
1604
+ for (const el of foundElements) {
1605
+ if (options?.limit && elements.length >= options.limit) break;
1606
+ const state = getElementState2(el);
1607
+ if (!options?.includeHidden && !state.visible) continue;
1608
+ if (options?.types) {
1609
+ const type = this.inferElementType(el);
1610
+ if (!options.types.includes(type)) continue;
1611
+ }
1612
+ const registered = this.registry.findByDOMElement(el);
1613
+ elements.push({
1614
+ id: registered?.id || this.getElementId(el),
1615
+ type: registered?.type || this.inferElementType(el),
1616
+ label: registered?.label || this.getElementLabel(el),
1617
+ tagName: el.tagName.toLowerCase(),
1618
+ role: el.getAttribute("role") || void 0,
1619
+ accessibleName: this.getAccessibleName(el),
1620
+ actions: registered?.actions || this.inferActions(el),
1621
+ state,
1622
+ registered: !!registered
1623
+ });
1624
+ }
1625
+ return {
1626
+ elements,
1627
+ total: elements.length,
1628
+ durationMs: performance.now() - startTime,
1629
+ timestamp: Date.now()
1630
+ };
1631
+ }
1632
+ /**
1633
+ * Discover controllable elements
1634
+ * @deprecated Use find() instead
1635
+ */
1636
+ async discover(options) {
1637
+ return this.find(options);
1638
+ }
1639
+ /**
1640
+ * Get control snapshot
1641
+ */
1642
+ async getSnapshot() {
1643
+ const elements = this.registry.getAllElements();
1644
+ const components = this.registry.getAllComponents();
1645
+ const workflows = this.registry.getAllWorkflows();
1646
+ return {
1647
+ timestamp: Date.now(),
1648
+ elements: elements.map((el) => ({
1649
+ id: el.id,
1650
+ type: el.type,
1651
+ label: el.label,
1652
+ actions: [...el.actions, ...el.customActions ? Object.keys(el.customActions) : []],
1653
+ state: el.getState()
1654
+ })),
1655
+ components: components.map((comp) => ({
1656
+ id: comp.id,
1657
+ name: comp.name,
1658
+ actions: comp.actions.map((a) => a.id)
1659
+ })),
1660
+ workflows: workflows.map((wf) => ({
1661
+ id: wf.id,
1662
+ name: wf.name,
1663
+ stepCount: wf.steps.length
1664
+ })),
1665
+ activeRuns: []
1666
+ // Workflow engine manages this
1667
+ };
1668
+ }
1669
+ /**
1670
+ * Wait for element conditions
1671
+ */
1672
+ async waitForElement(element, options) {
1673
+ const opts = { ...DEFAULT_WAIT_OPTIONS, ...options };
1674
+ const startTime = performance.now();
1675
+ const deadline = startTime + opts.timeout;
1676
+ while (Date.now() < deadline) {
1677
+ const state = getElementState2(element);
1678
+ let allMet = true;
1679
+ if (opts.visible && !state.visible) allMet = false;
1680
+ if (opts.enabled && !state.enabled) allMet = false;
1681
+ if (opts.focused && !state.focused) allMet = false;
1682
+ if (opts.state) {
1683
+ for (const [key, value] of Object.entries(opts.state)) {
1684
+ if (state[key] !== value) {
1685
+ allMet = false;
1686
+ break;
1687
+ }
1688
+ }
1689
+ }
1690
+ if (allMet) {
1691
+ return {
1692
+ met: true,
1693
+ waitedMs: performance.now() - startTime,
1694
+ state
1695
+ };
1696
+ }
1697
+ await sleep(opts.interval);
1698
+ }
1699
+ return {
1700
+ met: false,
1701
+ waitedMs: performance.now() - startTime,
1702
+ state: getElementState2(element),
1703
+ error: `Timeout waiting for conditions after ${opts.timeout}ms`
1704
+ };
1705
+ }
1706
+ /**
1707
+ * Perform an action on an element
1708
+ */
1709
+ async performAction(element, action, params) {
1710
+ switch (action) {
1711
+ case "click":
1712
+ return this.performClick(element, params);
1713
+ case "doubleClick":
1714
+ return this.performDoubleClick(element, params);
1715
+ case "rightClick":
1716
+ return this.performRightClick(element, params);
1717
+ case "type":
1718
+ return this.performType(element, params);
1719
+ case "clear":
1720
+ return this.performClear(element);
1721
+ case "select":
1722
+ return this.performSelect(element, params);
1723
+ case "focus":
1724
+ return this.performFocus(element);
1725
+ case "blur":
1726
+ return this.performBlur(element);
1727
+ case "hover":
1728
+ return this.performHover(element);
1729
+ case "scroll":
1730
+ return this.performScroll(element, params);
1731
+ case "check":
1732
+ return this.performCheck(element, true);
1733
+ case "uncheck":
1734
+ return this.performCheck(element, false);
1735
+ case "toggle":
1736
+ return this.performToggle(element);
1737
+ default: {
1738
+ const registered = this.registry.findByDOMElement(element);
1739
+ if (registered?.customActions?.[action]) {
1740
+ return registered.customActions[action].handler(params);
1741
+ }
1742
+ throw new Error(`Unknown action: ${action}`);
1743
+ }
1744
+ }
1745
+ }
1746
+ performClick(element, options) {
1747
+ element.dispatchEvent(createMouseEvent("mousedown", element, options));
1748
+ element.dispatchEvent(createMouseEvent("mouseup", element, options));
1749
+ element.dispatchEvent(createMouseEvent("click", element, options));
1750
+ }
1751
+ performDoubleClick(element, options) {
1752
+ this.performClick(element, options);
1753
+ this.performClick(element, options);
1754
+ element.dispatchEvent(createMouseEvent("dblclick", element, options));
1755
+ }
1756
+ performRightClick(element, options) {
1757
+ const opts = { ...options, button: "right" };
1758
+ element.dispatchEvent(createMouseEvent("mousedown", element, opts));
1759
+ element.dispatchEvent(createMouseEvent("mouseup", element, opts));
1760
+ element.dispatchEvent(createMouseEvent("contextmenu", element, opts));
1761
+ }
1762
+ async performType(element, options) {
1763
+ if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) {
1764
+ throw new Error("Type action requires an input or textarea element");
1765
+ }
1766
+ element.focus();
1767
+ if (options?.clear) {
1768
+ element.value = "";
1769
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1770
+ }
1771
+ const text = options?.text || "";
1772
+ const delay = options?.delay || 0;
1773
+ for (const char of text) {
1774
+ element.value += char;
1775
+ if (options?.triggerEvents !== false) {
1776
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1777
+ }
1778
+ if (delay > 0) {
1779
+ await sleep(delay);
1780
+ }
1781
+ }
1782
+ if (options?.triggerEvents !== false) {
1783
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1784
+ }
1785
+ }
1786
+ performClear(element) {
1787
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
1788
+ element.value = "";
1789
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1790
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1791
+ }
1792
+ }
1793
+ performSelect(element, options) {
1794
+ if (!(element instanceof HTMLSelectElement)) {
1795
+ throw new Error("Select action requires a select element");
1796
+ }
1797
+ const values = Array.isArray(options?.value) ? options.value : [options?.value];
1798
+ if (!options?.additive) {
1799
+ for (const option of element.options) {
1800
+ option.selected = false;
1801
+ }
1802
+ }
1803
+ for (const option of element.options) {
1804
+ const matchValue = options?.byLabel ? option.text : option.value;
1805
+ if (values.includes(matchValue)) {
1806
+ option.selected = true;
1807
+ }
1808
+ }
1809
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1810
+ }
1811
+ performFocus(element) {
1812
+ element.focus();
1813
+ element.dispatchEvent(new FocusEvent("focus", { bubbles: true }));
1814
+ }
1815
+ performBlur(element) {
1816
+ element.blur();
1817
+ element.dispatchEvent(new FocusEvent("blur", { bubbles: true }));
1818
+ }
1819
+ performHover(element) {
1820
+ element.dispatchEvent(createMouseEvent("mouseenter", element));
1821
+ element.dispatchEvent(createMouseEvent("mouseover", element));
1822
+ }
1823
+ performScroll(element, options) {
1824
+ if (options?.toElement) {
1825
+ const target = document.querySelector(options.toElement);
1826
+ if (target) {
1827
+ target.scrollIntoView({ behavior: options.smooth ? "smooth" : "auto" });
1828
+ return;
1829
+ }
1830
+ }
1831
+ if (options?.position) {
1832
+ element.scrollTo({
1833
+ left: options.position.x,
1834
+ top: options.position.y,
1835
+ behavior: options.smooth ? "smooth" : "auto"
1836
+ });
1837
+ return;
1838
+ }
1839
+ const amount = options?.amount || 100;
1840
+ const direction = options?.direction || "down";
1841
+ switch (direction) {
1842
+ case "up":
1843
+ element.scrollBy({ top: -amount, behavior: options?.smooth ? "smooth" : "auto" });
1844
+ break;
1845
+ case "down":
1846
+ element.scrollBy({ top: amount, behavior: options?.smooth ? "smooth" : "auto" });
1847
+ break;
1848
+ case "left":
1849
+ element.scrollBy({ left: -amount, behavior: options?.smooth ? "smooth" : "auto" });
1850
+ break;
1851
+ case "right":
1852
+ element.scrollBy({ left: amount, behavior: options?.smooth ? "smooth" : "auto" });
1853
+ break;
1854
+ }
1855
+ }
1856
+ performCheck(element, checked) {
1857
+ if (element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio")) {
1858
+ if (element.checked !== checked) {
1859
+ element.checked = checked;
1860
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1861
+ }
1862
+ }
1863
+ }
1864
+ performToggle(element) {
1865
+ if (element instanceof HTMLInputElement && element.type === "checkbox") {
1866
+ element.checked = !element.checked;
1867
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1868
+ }
1869
+ }
1870
+ getElementId(element) {
1871
+ return element.getAttribute("data-ui-id") || element.getAttribute("data-testid") || element.id || `${element.tagName.toLowerCase()}-${Math.random().toString(36).substr(2, 8)}`;
1872
+ }
1873
+ getElementLabel(element) {
1874
+ return element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent?.trim().substring(0, 50) || void 0;
1875
+ }
1876
+ getAccessibleName(element) {
1877
+ const ariaLabel = element.getAttribute("aria-label");
1878
+ if (ariaLabel) return ariaLabel;
1879
+ const labelledBy = element.getAttribute("aria-labelledby");
1880
+ if (labelledBy) {
1881
+ const labels = labelledBy.split(" ").map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean);
1882
+ if (labels.length > 0) return labels.join(" ");
1883
+ }
1884
+ if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
1885
+ if (element.id) {
1886
+ const label = document.querySelector(`label[for="${element.id}"]`);
1887
+ if (label) return label.textContent?.trim();
1888
+ }
1889
+ }
1890
+ return element.getAttribute("title") || element.textContent?.trim().substring(0, 50) || void 0;
1891
+ }
1892
+ inferElementType(element) {
1893
+ const tagName = element.tagName.toLowerCase();
1894
+ const role = element.getAttribute("role");
1895
+ if (role) {
1896
+ switch (role) {
1897
+ case "button":
1898
+ return "button";
1899
+ case "textbox":
1900
+ return "input";
1901
+ case "checkbox":
1902
+ return "checkbox";
1903
+ case "radio":
1904
+ return "radio";
1905
+ case "link":
1906
+ return "link";
1907
+ case "listbox":
1908
+ case "combobox":
1909
+ return "select";
1910
+ case "menu":
1911
+ return "menu";
1912
+ case "menuitem":
1913
+ return "menuitem";
1914
+ case "tab":
1915
+ return "tab";
1916
+ case "dialog":
1917
+ return "dialog";
1918
+ }
1919
+ }
1920
+ switch (tagName) {
1921
+ case "button":
1922
+ return "button";
1923
+ case "input": {
1924
+ const type = element.type;
1925
+ if (type === "checkbox") return "checkbox";
1926
+ if (type === "radio") return "radio";
1927
+ if (type === "submit" || type === "button") return "button";
1928
+ return "input";
1929
+ }
1930
+ case "textarea":
1931
+ return "textarea";
1932
+ case "select":
1933
+ return "select";
1934
+ case "a":
1935
+ return "link";
1936
+ case "form":
1937
+ return "form";
1938
+ default:
1939
+ return "custom";
1940
+ }
1941
+ }
1942
+ inferActions(element) {
1943
+ const type = this.inferElementType(element);
1944
+ const baseActions = ["focus", "blur", "hover"];
1945
+ switch (type) {
1946
+ case "button":
1947
+ return [...baseActions, "click", "doubleClick", "rightClick"];
1948
+ case "input":
1949
+ return [...baseActions, "click", "type", "clear"];
1950
+ case "textarea":
1951
+ return [...baseActions, "click", "type", "clear"];
1952
+ case "select":
1953
+ return [...baseActions, "click", "select"];
1954
+ case "checkbox":
1955
+ return [...baseActions, "click", "check", "uncheck", "toggle"];
1956
+ case "radio":
1957
+ return [...baseActions, "click", "check"];
1958
+ case "link":
1959
+ return [...baseActions, "click"];
1960
+ default:
1961
+ return [...baseActions, "click"];
1962
+ }
1963
+ }
1964
+ };
1965
+ function createActionExecutor(registry) {
1966
+ return new DefaultActionExecutor(registry);
1967
+ }
1968
+
1969
+ // src/control/workflow-engine.ts
1970
+ function generateRunId() {
1971
+ return `run-${Date.now()}-${Math.random().toString(36).substr(2, 8)}`;
1972
+ }
1973
+ var DefaultWorkflowEngine = class {
1974
+ constructor(registry, executor) {
1975
+ this.registry = registry;
1976
+ this.executor = executor;
1977
+ this.activeRuns = /* @__PURE__ */ new Map();
1978
+ }
1979
+ /**
1980
+ * Run a workflow
1981
+ */
1982
+ async run(workflowId, request) {
1983
+ const workflow = this.registry.getWorkflow(workflowId);
1984
+ if (!workflow) {
1985
+ return {
1986
+ workflowId,
1987
+ runId: generateRunId(),
1988
+ status: "failed",
1989
+ steps: [],
1990
+ totalSteps: 0,
1991
+ success: false,
1992
+ error: `Workflow not found: ${workflowId}`,
1993
+ startedAt: Date.now(),
1994
+ completedAt: Date.now(),
1995
+ durationMs: 0
1996
+ };
1997
+ }
1998
+ const runId = generateRunId();
1999
+ const state = {
2000
+ workflowId,
2001
+ runId,
2002
+ workflow,
2003
+ request,
2004
+ status: "running",
2005
+ steps: [],
2006
+ currentStep: 0,
2007
+ startedAt: Date.now()
2008
+ };
2009
+ this.activeRuns.set(runId, state);
2010
+ try {
2011
+ await this.executeWorkflow(state);
2012
+ } catch (error) {
2013
+ state.status = "failed";
2014
+ state.error = error instanceof Error ? error.message : String(error);
2015
+ }
2016
+ state.completedAt = Date.now();
2017
+ state.durationMs = state.completedAt - state.startedAt;
2018
+ state.success = state.status === "completed" && state.steps.every((s) => s.success);
2019
+ setTimeout(() => {
2020
+ this.activeRuns.delete(runId);
2021
+ }, 6e4);
2022
+ return this.buildResponse(state);
2023
+ }
2024
+ /**
2025
+ * Get workflow run status
2026
+ */
2027
+ async getRunStatus(runId) {
2028
+ const state = this.activeRuns.get(runId);
2029
+ if (!state) return null;
2030
+ return this.buildResponse(state);
2031
+ }
2032
+ /**
2033
+ * Cancel a running workflow
2034
+ */
2035
+ async cancel(runId) {
2036
+ const state = this.activeRuns.get(runId);
2037
+ if (!state || state.status !== "running") return false;
2038
+ state.status = "cancelled";
2039
+ state.completedAt = Date.now();
2040
+ state.durationMs = state.completedAt - state.startedAt;
2041
+ state.error = "Workflow cancelled by user";
2042
+ return true;
2043
+ }
2044
+ /**
2045
+ * List active runs
2046
+ */
2047
+ async listActiveRuns() {
2048
+ return Array.from(this.activeRuns.values()).filter((state) => state.status === "running").map((state) => this.buildResponse(state));
2049
+ }
2050
+ /**
2051
+ * Execute a workflow
2052
+ */
2053
+ async executeWorkflow(state) {
2054
+ const { workflow, request } = state;
2055
+ const params = { ...workflow.defaultParams, ...request?.params };
2056
+ let startIndex = 0;
2057
+ if (request?.startStep) {
2058
+ const idx = workflow.steps.findIndex((s) => s.id === request.startStep);
2059
+ if (idx >= 0) startIndex = idx;
2060
+ }
2061
+ let stopIndex = workflow.steps.length;
2062
+ if (request?.stopStep) {
2063
+ const idx = workflow.steps.findIndex((s) => s.id === request.stopStep);
2064
+ if (idx >= 0) stopIndex = idx + 1;
2065
+ }
2066
+ for (let i = startIndex; i < stopIndex; i++) {
2067
+ if (state.status === "cancelled") break;
2068
+ state.currentStep = i;
2069
+ const step = workflow.steps[i];
2070
+ const stepResult = await this.executeStep(step, params, request?.stepTimeout);
2071
+ state.steps.push(stepResult);
2072
+ if (!stepResult.success) {
2073
+ state.status = "failed";
2074
+ state.error = stepResult.error;
2075
+ return;
2076
+ }
2077
+ }
2078
+ state.status = "completed";
2079
+ }
2080
+ /**
2081
+ * Execute a single step
2082
+ */
2083
+ async executeStep(step, params, timeout) {
2084
+ const startTime = performance.now();
2085
+ try {
2086
+ const timeoutPromise = timeout ? new Promise(
2087
+ (_, reject) => setTimeout(() => reject(new Error("Step timeout")), timeout)
2088
+ ) : null;
2089
+ const executePromise = this.executeStepInternal(step, params);
2090
+ const result = timeoutPromise ? await Promise.race([executePromise, timeoutPromise]) : await executePromise;
2091
+ return {
2092
+ stepId: step.id,
2093
+ stepType: step.type,
2094
+ success: true,
2095
+ result,
2096
+ durationMs: performance.now() - startTime,
2097
+ timestamp: Date.now()
2098
+ };
2099
+ } catch (error) {
2100
+ return {
2101
+ stepId: step.id,
2102
+ stepType: step.type,
2103
+ success: false,
2104
+ error: error instanceof Error ? error.message : String(error),
2105
+ durationMs: performance.now() - startTime,
2106
+ timestamp: Date.now()
2107
+ };
2108
+ }
2109
+ }
2110
+ /**
2111
+ * Execute step internal logic
2112
+ */
2113
+ async executeStepInternal(step, params) {
2114
+ const resolvedParams = this.interpolateParams(step.params || {}, params);
2115
+ switch (step.type) {
2116
+ case "element-action":
2117
+ if (!step.target || !step.action) {
2118
+ throw new Error("Element action requires target and action");
2119
+ }
2120
+ return this.executor.executeAction(step.target, {
2121
+ action: step.action,
2122
+ params: resolvedParams,
2123
+ waitOptions: step.waitOptions
2124
+ });
2125
+ case "component-action":
2126
+ if (!step.target || !step.action) {
2127
+ throw new Error("Component action requires target and action");
2128
+ }
2129
+ return this.executor.executeComponentAction(step.target, {
2130
+ action: step.action,
2131
+ params: resolvedParams
2132
+ });
2133
+ case "wait": {
2134
+ if (!step.target) {
2135
+ throw new Error("Wait step requires target");
2136
+ }
2137
+ const waitResult = await this.executor.waitFor(step.target, step.waitOptions || {});
2138
+ if (!waitResult.met) {
2139
+ throw new Error(waitResult.error || "Wait condition not met");
2140
+ }
2141
+ return waitResult;
2142
+ }
2143
+ case "assert":
2144
+ if (!step.target || !step.expectedState) {
2145
+ throw new Error("Assert step requires target and expectedState");
2146
+ }
2147
+ return this.performAssertion(step.target, step.expectedState);
2148
+ case "custom":
2149
+ if (!step.handler) {
2150
+ throw new Error("Custom step requires handler");
2151
+ }
2152
+ return step.handler();
2153
+ default:
2154
+ throw new Error(`Unknown step type: ${step.type}`);
2155
+ }
2156
+ }
2157
+ /**
2158
+ * Perform state assertion
2159
+ */
2160
+ async performAssertion(target, expectedState) {
2161
+ const snapshot = await this.executor.getSnapshot();
2162
+ const element = snapshot.elements.find((e) => e.id === target);
2163
+ if (!element) {
2164
+ throw new Error(`Element not found for assertion: ${target}`);
2165
+ }
2166
+ const differences = [];
2167
+ for (const [key, expected] of Object.entries(expectedState)) {
2168
+ const actual = element.state[key];
2169
+ if (actual !== expected) {
2170
+ differences.push(`${key}: expected ${expected}, got ${actual}`);
2171
+ }
2172
+ }
2173
+ if (differences.length > 0) {
2174
+ throw new Error(`Assertion failed:
2175
+ ${differences.join("\n")}`);
2176
+ }
2177
+ return { passed: true, differences };
2178
+ }
2179
+ /**
2180
+ * Interpolate parameters with {{param}} syntax
2181
+ */
2182
+ interpolateParams(stepParams, workflowParams) {
2183
+ const result = {};
2184
+ for (const [key, value] of Object.entries(stepParams)) {
2185
+ if (typeof value === "string") {
2186
+ result[key] = value.replace(/\{\{(\w+)\}\}/g, (_, name) => {
2187
+ return String(workflowParams[name] ?? "");
2188
+ });
2189
+ } else {
2190
+ result[key] = value;
2191
+ }
2192
+ }
2193
+ return result;
2194
+ }
2195
+ /**
2196
+ * Build response from state
2197
+ */
2198
+ buildResponse(state) {
2199
+ return {
2200
+ workflowId: state.workflowId,
2201
+ runId: state.runId,
2202
+ status: state.status,
2203
+ steps: [...state.steps],
2204
+ currentStep: state.currentStep,
2205
+ totalSteps: state.workflow.steps.length,
2206
+ success: state.success,
2207
+ error: state.error,
2208
+ startedAt: state.startedAt,
2209
+ completedAt: state.completedAt,
2210
+ durationMs: state.durationMs
2211
+ };
2212
+ }
2213
+ };
2214
+ function createWorkflowEngine(registry, executor) {
2215
+ return new DefaultWorkflowEngine(registry, executor);
2216
+ }
2217
+
2218
+ // src/render-log/dom-capture.ts
2219
+ var CAPTURE_ATTRIBUTES = [
2220
+ "data-ui-id",
2221
+ "data-testid",
2222
+ "data-awas-element",
2223
+ "id",
2224
+ "name",
2225
+ "type",
2226
+ "href",
2227
+ "src",
2228
+ "alt",
2229
+ "title",
2230
+ "placeholder",
2231
+ "value",
2232
+ "aria-label",
2233
+ "aria-labelledby",
2234
+ "aria-describedby",
2235
+ "aria-expanded",
2236
+ "aria-selected",
2237
+ "aria-checked",
2238
+ "aria-disabled",
2239
+ "aria-hidden",
2240
+ "role",
2241
+ "tabindex",
2242
+ "disabled",
2243
+ "readonly",
2244
+ "required",
2245
+ "checked"
2246
+ ];
2247
+ var INTERACTIVE_SELECTORS = [
2248
+ "a[href]",
2249
+ "button",
2250
+ "input",
2251
+ "select",
2252
+ "textarea",
2253
+ "[onclick]",
2254
+ '[role="button"]',
2255
+ '[role="link"]',
2256
+ '[role="checkbox"]',
2257
+ '[role="radio"]',
2258
+ '[role="menuitem"]',
2259
+ '[role="tab"]',
2260
+ '[role="switch"]',
2261
+ '[tabindex]:not([tabindex="-1"])',
2262
+ '[contenteditable="true"]'
2263
+ ];
2264
+ function isInteractive(element) {
2265
+ return INTERACTIVE_SELECTORS.some((selector) => {
2266
+ try {
2267
+ return element.matches(selector);
2268
+ } catch {
2269
+ return false;
2270
+ }
2271
+ });
2272
+ }
2273
+ function getAccessibleName(element) {
2274
+ const ariaLabel = element.getAttribute("aria-label");
2275
+ if (ariaLabel) return ariaLabel;
2276
+ const labelledBy = element.getAttribute("aria-labelledby");
2277
+ if (labelledBy) {
2278
+ const labels = labelledBy.split(" ").map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean);
2279
+ if (labels.length > 0) return labels.join(" ");
2280
+ }
2281
+ if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
2282
+ const id = element.id;
2283
+ if (id) {
2284
+ const label = document.querySelector(`label[for="${id}"]`);
2285
+ if (label) return label.textContent?.trim();
2286
+ }
2287
+ }
2288
+ const title = element.getAttribute("title");
2289
+ if (title) return title;
2290
+ if (element instanceof HTMLImageElement) {
2291
+ return element.alt || void 0;
2292
+ }
2293
+ if (element.matches('button, a, [role="button"], [role="link"]')) {
2294
+ return element.textContent?.trim() || void 0;
2295
+ }
2296
+ return void 0;
2297
+ }
2298
+ function getElementState3(element) {
2299
+ const rect = element.getBoundingClientRect();
2300
+ const style = window.getComputedStyle(element);
2301
+ const state = {
2302
+ visible: isVisible2(element, rect, style),
2303
+ enabled: !isDisabled2(element),
2304
+ focused: document.activeElement === element,
2305
+ rect: {
2306
+ x: rect.x,
2307
+ y: rect.y,
2308
+ width: rect.width,
2309
+ height: rect.height,
2310
+ top: rect.top,
2311
+ right: rect.right,
2312
+ bottom: rect.bottom,
2313
+ left: rect.left
2314
+ },
2315
+ computedStyles: {
2316
+ display: style.display,
2317
+ visibility: style.visibility,
2318
+ opacity: style.opacity,
2319
+ pointerEvents: style.pointerEvents
2320
+ }
2321
+ };
2322
+ if (element instanceof HTMLInputElement) {
2323
+ state.value = element.value;
2324
+ if (element.type === "checkbox" || element.type === "radio") {
2325
+ state.checked = element.checked;
2326
+ }
2327
+ } else if (element instanceof HTMLTextAreaElement) {
2328
+ state.value = element.value;
2329
+ } else if (element instanceof HTMLSelectElement) {
2330
+ state.value = element.value;
2331
+ state.selectedOptions = Array.from(element.selectedOptions).map((opt) => opt.value);
2332
+ }
2333
+ return state;
2334
+ }
2335
+ function isVisible2(element, rect, style) {
2336
+ if (rect.width === 0 || rect.height === 0) return false;
2337
+ if (style.display === "none") return false;
2338
+ if (style.visibility === "hidden") return false;
2339
+ if (parseFloat(style.opacity) === 0) return false;
2340
+ if (element.getAttribute("aria-hidden") === "true") return false;
2341
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
2342
+ }
2343
+ function isDisabled2(element) {
2344
+ if ("disabled" in element && element.disabled) return true;
2345
+ if (element.getAttribute("aria-disabled") === "true") return true;
2346
+ return false;
2347
+ }
2348
+ function captureAttributes(element) {
2349
+ const attrs = {};
2350
+ for (const attr of CAPTURE_ATTRIBUTES) {
2351
+ const value = element.getAttribute(attr);
2352
+ if (value !== null) {
2353
+ attrs[attr] = value;
2354
+ }
2355
+ }
2356
+ return attrs;
2357
+ }
2358
+ function captureElement(element, depth, maxTextLength) {
2359
+ const identifier = createElementIdentifier(element);
2360
+ let textContent = element.textContent?.trim();
2361
+ if (textContent && textContent.length > maxTextLength) {
2362
+ textContent = textContent.substring(0, maxTextLength) + "...";
2363
+ }
2364
+ return {
2365
+ identifier,
2366
+ bestId: getBestIdentifier(element),
2367
+ tagName: element.tagName.toLowerCase(),
2368
+ role: element.getAttribute("role") || void 0,
2369
+ accessibleName: getAccessibleName(element),
2370
+ textContent,
2371
+ state: getElementState3(element),
2372
+ attributes: captureAttributes(element),
2373
+ childCount: element.children.length,
2374
+ depth
2375
+ };
2376
+ }
2377
+ function captureDOMSnapshot(options = {}) {
2378
+ const startTime = performance.now();
2379
+ const {
2380
+ root = document.body,
2381
+ maxDepth = 50,
2382
+ maxElements = 5e3,
2383
+ interactiveOnly = false,
2384
+ includeHidden = false,
2385
+ includeSelectors,
2386
+ excludeSelectors,
2387
+ filter,
2388
+ maxTextLength = 200
2389
+ } = options;
2390
+ const elements = [];
2391
+ let totalNodeCount = 0;
2392
+ function shouldCapture(element) {
2393
+ if (filter && !filter(element)) return false;
2394
+ if (excludeSelectors) {
2395
+ for (const selector of excludeSelectors) {
2396
+ try {
2397
+ if (element.matches(selector)) return false;
2398
+ } catch {
2399
+ }
2400
+ }
2401
+ }
2402
+ if (includeSelectors && includeSelectors.length > 0) {
2403
+ let matches = false;
2404
+ for (const selector of includeSelectors) {
2405
+ try {
2406
+ if (element.matches(selector)) {
2407
+ matches = true;
2408
+ break;
2409
+ }
2410
+ } catch {
2411
+ }
2412
+ }
2413
+ if (!matches) return false;
2414
+ }
2415
+ if (interactiveOnly && !isInteractive(element)) return false;
2416
+ if (!includeHidden) {
2417
+ const rect = element.getBoundingClientRect();
2418
+ const style = window.getComputedStyle(element);
2419
+ if (!isVisible2(element, rect, style)) return false;
2420
+ }
2421
+ return true;
2422
+ }
2423
+ function traverse(element, depth) {
2424
+ if (depth > maxDepth || elements.length >= maxElements) return;
2425
+ totalNodeCount++;
2426
+ if (shouldCapture(element)) {
2427
+ elements.push(captureElement(element, depth, maxTextLength));
2428
+ }
2429
+ for (const child of element.children) {
2430
+ if (child instanceof HTMLElement) {
2431
+ traverse(child, depth + 1);
2432
+ }
2433
+ }
2434
+ }
2435
+ traverse(root, 0);
2436
+ const endTime = performance.now();
2437
+ return {
2438
+ timestamp: Date.now(),
2439
+ url: window.location.href,
2440
+ title: document.title,
2441
+ viewport: {
2442
+ width: window.innerWidth,
2443
+ height: window.innerHeight,
2444
+ scrollX: window.scrollX,
2445
+ scrollY: window.scrollY
2446
+ },
2447
+ elements,
2448
+ totalNodeCount,
2449
+ captureDurationMs: endTime - startTime
2450
+ };
2451
+ }
2452
+ var DOMChangeObserver = class {
2453
+ constructor(options = {}) {
2454
+ this.observer = null;
2455
+ this.changes = [];
2456
+ this.maxChanges = options.maxChanges ?? 1e3;
2457
+ this.callback = options.callback;
2458
+ }
2459
+ start(root = document.body) {
2460
+ if (this.observer) return;
2461
+ this.observer = new MutationObserver((mutations) => {
2462
+ for (const mutation of mutations) {
2463
+ const change = this.processMutation(mutation);
2464
+ if (change) {
2465
+ this.addChange(change);
2466
+ }
2467
+ }
2468
+ });
2469
+ this.observer.observe(root, {
2470
+ childList: true,
2471
+ attributes: true,
2472
+ characterData: true,
2473
+ subtree: true,
2474
+ attributeOldValue: true
2475
+ });
2476
+ }
2477
+ stop() {
2478
+ this.observer?.disconnect();
2479
+ this.observer = null;
2480
+ }
2481
+ processMutation(mutation) {
2482
+ const target = mutation.target;
2483
+ if (!(target instanceof HTMLElement)) return null;
2484
+ const elementId = getBestIdentifier(target);
2485
+ if (mutation.type === "attributes") {
2486
+ return {
2487
+ timestamp: Date.now(),
2488
+ type: "attribute",
2489
+ elementId,
2490
+ tagName: target.tagName.toLowerCase(),
2491
+ details: {
2492
+ attributeName: mutation.attributeName || void 0,
2493
+ oldValue: mutation.oldValue || void 0,
2494
+ newValue: mutation.attributeName ? target.getAttribute(mutation.attributeName) || void 0 : void 0
2495
+ }
2496
+ };
2497
+ }
2498
+ if (mutation.type === "childList") {
2499
+ if (mutation.addedNodes.length > 0) {
2500
+ return {
2501
+ timestamp: Date.now(),
2502
+ type: "added",
2503
+ elementId,
2504
+ tagName: target.tagName.toLowerCase(),
2505
+ details: {
2506
+ addedNodes: mutation.addedNodes.length
2507
+ }
2508
+ };
2509
+ }
2510
+ if (mutation.removedNodes.length > 0) {
2511
+ return {
2512
+ timestamp: Date.now(),
2513
+ type: "removed",
2514
+ elementId,
2515
+ tagName: target.tagName.toLowerCase(),
2516
+ details: {
2517
+ removedNodes: mutation.removedNodes.length
2518
+ }
2519
+ };
2520
+ }
2521
+ }
2522
+ return null;
2523
+ }
2524
+ addChange(change) {
2525
+ this.changes.push(change);
2526
+ if (this.changes.length > this.maxChanges) {
2527
+ this.changes.shift();
2528
+ }
2529
+ this.callback?.(change);
2530
+ }
2531
+ getChanges() {
2532
+ return [...this.changes];
2533
+ }
2534
+ clearChanges() {
2535
+ this.changes = [];
2536
+ }
2537
+ };
2538
+
2539
+ // src/render-log/snapshot.ts
2540
+ var InMemoryRenderLogStorage = class {
2541
+ constructor(maxEntries = 1e3) {
2542
+ this.entries = [];
2543
+ this.maxEntries = maxEntries;
2544
+ }
2545
+ async append(entry) {
2546
+ this.entries.push(entry);
2547
+ while (this.entries.length > this.maxEntries) {
2548
+ this.entries.shift();
2549
+ }
2550
+ }
2551
+ async getEntries(options) {
2552
+ let results = [...this.entries];
2553
+ if (options?.type) {
2554
+ results = results.filter((e) => e.type === options.type);
2555
+ }
2556
+ if (options?.since) {
2557
+ results = results.filter((e) => e.timestamp >= options.since);
2558
+ }
2559
+ if (options?.until) {
2560
+ results = results.filter((e) => e.timestamp <= options.until);
2561
+ }
2562
+ if (options?.limit) {
2563
+ results = results.slice(-options.limit);
2564
+ }
2565
+ return results;
2566
+ }
2567
+ async clear() {
2568
+ this.entries = [];
2569
+ }
2570
+ async count() {
2571
+ return this.entries.length;
2572
+ }
2573
+ /** Get entries synchronously (for in-memory only) */
2574
+ getEntriesSync() {
2575
+ return [...this.entries];
2576
+ }
2577
+ };
2578
+ function generateId2() {
2579
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2580
+ }
2581
+ var RenderLogManager = class {
2582
+ constructor(options = {}) {
2583
+ this.changeObserver = null;
2584
+ this.snapshotTimer = null;
2585
+ this.pendingChanges = [];
2586
+ this.started = false;
2587
+ this.options = options;
2588
+ this.storage = options.storage ?? new InMemoryRenderLogStorage(options.maxEntries);
2589
+ }
2590
+ /**
2591
+ * Start capturing
2592
+ */
2593
+ start() {
2594
+ if (this.started) return;
2595
+ this.started = true;
2596
+ if (this.options.captureChanges !== false) {
2597
+ this.changeObserver = new DOMChangeObserver({
2598
+ callback: (change) => {
2599
+ this.pendingChanges.push(change);
2600
+ }
2601
+ });
2602
+ this.changeObserver.start();
2603
+ }
2604
+ if (this.options.captureOnNavigation !== false) {
2605
+ this.setupNavigationObserver();
2606
+ }
2607
+ if (this.options.snapshotInterval) {
2608
+ this.snapshotTimer = setInterval(() => {
2609
+ this.captureSnapshot();
2610
+ }, this.options.snapshotInterval);
2611
+ }
2612
+ this.captureSnapshot();
2613
+ }
2614
+ /**
2615
+ * Stop capturing
2616
+ */
2617
+ stop() {
2618
+ if (!this.started) return;
2619
+ this.started = false;
2620
+ this.changeObserver?.stop();
2621
+ this.changeObserver = null;
2622
+ if (this.snapshotTimer) {
2623
+ clearInterval(this.snapshotTimer);
2624
+ this.snapshotTimer = null;
2625
+ }
2626
+ }
2627
+ /**
2628
+ * Capture a DOM snapshot
2629
+ */
2630
+ async captureSnapshot(metadata) {
2631
+ if (this.pendingChanges.length > 0) {
2632
+ await this.flushChanges();
2633
+ }
2634
+ const snapshot = captureDOMSnapshot(this.options.captureOptions);
2635
+ const entry = {
2636
+ id: generateId2(),
2637
+ type: "snapshot",
2638
+ timestamp: snapshot.timestamp,
2639
+ data: snapshot,
2640
+ metadata
2641
+ };
2642
+ await this.addEntry(entry);
2643
+ return entry;
2644
+ }
2645
+ /**
2646
+ * Flush pending DOM changes
2647
+ */
2648
+ async flushChanges() {
2649
+ if (this.pendingChanges.length === 0) return null;
2650
+ const changes = [...this.pendingChanges];
2651
+ this.pendingChanges = [];
2652
+ const entry = {
2653
+ id: generateId2(),
2654
+ type: "change",
2655
+ timestamp: Date.now(),
2656
+ data: changes
2657
+ };
2658
+ await this.addEntry(entry);
2659
+ return entry;
2660
+ }
2661
+ /**
2662
+ * Log an interaction
2663
+ */
2664
+ async logInteraction(eventType, details) {
2665
+ const entry = {
2666
+ id: generateId2(),
2667
+ type: "interaction",
2668
+ timestamp: Date.now(),
2669
+ data: {
2670
+ eventType,
2671
+ ...details
2672
+ }
2673
+ };
2674
+ await this.addEntry(entry);
2675
+ return entry;
2676
+ }
2677
+ /**
2678
+ * Log an error
2679
+ */
2680
+ async logError(message, details) {
2681
+ const entry = {
2682
+ id: generateId2(),
2683
+ type: "error",
2684
+ timestamp: Date.now(),
2685
+ data: {
2686
+ message,
2687
+ ...details
2688
+ }
2689
+ };
2690
+ await this.addEntry(entry);
2691
+ return entry;
2692
+ }
2693
+ /**
2694
+ * Log a navigation
2695
+ */
2696
+ async logNavigation(from, to, navigationType) {
2697
+ const entry = {
2698
+ id: generateId2(),
2699
+ type: "navigation",
2700
+ timestamp: Date.now(),
2701
+ data: {
2702
+ from,
2703
+ to,
2704
+ navigationType
2705
+ }
2706
+ };
2707
+ await this.addEntry(entry);
2708
+ return entry;
2709
+ }
2710
+ /**
2711
+ * Add a custom entry
2712
+ */
2713
+ async logCustom(data, metadata) {
2714
+ const entry = {
2715
+ id: generateId2(),
2716
+ type: "custom",
2717
+ timestamp: Date.now(),
2718
+ data,
2719
+ metadata
2720
+ };
2721
+ await this.addEntry(entry);
2722
+ return entry;
2723
+ }
2724
+ /**
2725
+ * Get log entries
2726
+ */
2727
+ async getEntries(options) {
2728
+ return this.storage.getEntries(options);
2729
+ }
2730
+ /**
2731
+ * Clear the log
2732
+ */
2733
+ async clear() {
2734
+ this.pendingChanges = [];
2735
+ await this.storage.clear();
2736
+ }
2737
+ /**
2738
+ * Get entry count
2739
+ */
2740
+ async count() {
2741
+ return this.storage.count();
2742
+ }
2743
+ /**
2744
+ * Get the latest snapshot
2745
+ */
2746
+ async getLatestSnapshot() {
2747
+ const snapshots = await this.storage.getEntries({ type: "snapshot", limit: 1 });
2748
+ return snapshots[0] || null;
2749
+ }
2750
+ async addEntry(entry) {
2751
+ await this.storage.append(entry);
2752
+ this.options.onEntry?.(entry);
2753
+ }
2754
+ setupNavigationObserver() {
2755
+ let lastUrl = window.location.href;
2756
+ const originalPushState = history.pushState;
2757
+ const originalReplaceState = history.replaceState;
2758
+ history.pushState = (...args) => {
2759
+ const result = originalPushState.apply(history, args);
2760
+ const newUrl = window.location.href;
2761
+ if (newUrl !== lastUrl) {
2762
+ this.logNavigation(lastUrl, newUrl, "push");
2763
+ this.captureSnapshot({ trigger: "navigation" });
2764
+ lastUrl = newUrl;
2765
+ }
2766
+ return result;
2767
+ };
2768
+ history.replaceState = (...args) => {
2769
+ const result = originalReplaceState.apply(history, args);
2770
+ const newUrl = window.location.href;
2771
+ if (newUrl !== lastUrl) {
2772
+ this.logNavigation(lastUrl, newUrl, "replace");
2773
+ this.captureSnapshot({ trigger: "navigation" });
2774
+ lastUrl = newUrl;
2775
+ }
2776
+ return result;
2777
+ };
2778
+ window.addEventListener("popstate", () => {
2779
+ const newUrl = window.location.href;
2780
+ if (newUrl !== lastUrl) {
2781
+ this.logNavigation(lastUrl, newUrl, "pop");
2782
+ this.captureSnapshot({ trigger: "navigation" });
2783
+ lastUrl = newUrl;
2784
+ }
2785
+ });
2786
+ }
2787
+ };
2788
+ function createRenderLogManager(options) {
2789
+ return new RenderLogManager(options);
2790
+ }
2791
+
2792
+ // src/debug/metrics.ts
2793
+ function generateId3() {
2794
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 8)}`;
2795
+ }
2796
+ var MetricsCollector = class {
2797
+ constructor(options = {}) {
2798
+ this.history = [];
2799
+ this.maxHistoryEntries = options.maxHistoryEntries ?? 1e3;
2800
+ this.rateWindow = options.rateWindow ?? 6e4;
2801
+ }
2802
+ /**
2803
+ * Record an element action
2804
+ */
2805
+ recordElementAction(target, action, response, params) {
2806
+ const entry = {
2807
+ id: generateId3(),
2808
+ timestamp: response.timestamp,
2809
+ type: "element",
2810
+ target,
2811
+ action,
2812
+ success: response.success,
2813
+ durationMs: response.durationMs,
2814
+ error: response.error,
2815
+ params,
2816
+ response: response.elementState
2817
+ };
2818
+ this.addEntry(entry);
2819
+ return entry;
2820
+ }
2821
+ /**
2822
+ * Record a component action
2823
+ */
2824
+ recordComponentAction(target, action, response, params) {
2825
+ const entry = {
2826
+ id: generateId3(),
2827
+ timestamp: response.timestamp,
2828
+ type: "component",
2829
+ target,
2830
+ action,
2831
+ success: response.success,
2832
+ durationMs: response.durationMs,
2833
+ error: response.error,
2834
+ params,
2835
+ response: response.result
2836
+ };
2837
+ this.addEntry(entry);
2838
+ return entry;
2839
+ }
2840
+ /**
2841
+ * Record a workflow step
2842
+ */
2843
+ recordWorkflowStep(workflowId, result) {
2844
+ const entry = {
2845
+ id: generateId3(),
2846
+ timestamp: result.timestamp,
2847
+ type: "workflow-step",
2848
+ target: workflowId,
2849
+ action: result.stepId,
2850
+ success: result.success,
2851
+ durationMs: result.durationMs,
2852
+ error: result.error,
2853
+ response: result.result
2854
+ };
2855
+ this.addEntry(entry);
2856
+ return entry;
2857
+ }
2858
+ /**
2859
+ * Record from a bridge event
2860
+ */
2861
+ recordEvent(event) {
2862
+ if (event.type === "action:completed" || event.type === "action:failed") {
2863
+ const data = event.data;
2864
+ if (data.elementId) {
2865
+ this.recordElementAction(
2866
+ data.elementId,
2867
+ data.action,
2868
+ data.response,
2869
+ data.params
2870
+ );
2871
+ } else if (data.componentId) {
2872
+ this.recordComponentAction(
2873
+ data.componentId,
2874
+ data.action,
2875
+ data.response,
2876
+ data.params
2877
+ );
2878
+ }
2879
+ }
2880
+ }
2881
+ /**
2882
+ * Get action history
2883
+ */
2884
+ getHistory(options) {
2885
+ let results = [...this.history];
2886
+ if (options?.type) {
2887
+ results = results.filter((e) => e.type === options.type);
2888
+ }
2889
+ if (options?.target) {
2890
+ results = results.filter((e) => e.target === options.target);
2891
+ }
2892
+ if (options?.action) {
2893
+ results = results.filter((e) => e.action === options.action);
2894
+ }
2895
+ if (options?.success !== void 0) {
2896
+ results = results.filter((e) => e.success === options.success);
2897
+ }
2898
+ if (options?.since) {
2899
+ results = results.filter((e) => e.timestamp >= options.since);
2900
+ }
2901
+ if (options?.limit) {
2902
+ results = results.slice(-options.limit);
2903
+ }
2904
+ return results;
2905
+ }
2906
+ /**
2907
+ * Get performance metrics
2908
+ */
2909
+ getMetrics(since) {
2910
+ const entries = since ? this.history.filter((e) => e.timestamp >= since) : this.history;
2911
+ if (entries.length === 0) {
2912
+ return {
2913
+ totalActions: 0,
2914
+ successfulActions: 0,
2915
+ failedActions: 0,
2916
+ successRate: 0,
2917
+ avgDurationMs: 0,
2918
+ minDurationMs: 0,
2919
+ maxDurationMs: 0,
2920
+ p95DurationMs: 0,
2921
+ actionsPerSecond: 0,
2922
+ errorsByType: {},
2923
+ actionsByType: {}
2924
+ };
2925
+ }
2926
+ const successful = entries.filter((e) => e.success);
2927
+ const failed = entries.filter((e) => !e.success);
2928
+ const durations = entries.map((e) => e.durationMs).sort((a, b) => a - b);
2929
+ const now = Date.now();
2930
+ const windowStart = now - this.rateWindow;
2931
+ const recentActions = this.history.filter((e) => e.timestamp >= windowStart);
2932
+ const windowSeconds = this.rateWindow / 1e3;
2933
+ const errorsByType = {};
2934
+ for (const entry of failed) {
2935
+ const errorType = entry.error?.split(":")[0] || "Unknown";
2936
+ errorsByType[errorType] = (errorsByType[errorType] || 0) + 1;
2937
+ }
2938
+ const actionsByType = {};
2939
+ for (const entry of entries) {
2940
+ const key = `${entry.type}:${entry.action}`;
2941
+ actionsByType[key] = (actionsByType[key] || 0) + 1;
2942
+ }
2943
+ return {
2944
+ totalActions: entries.length,
2945
+ successfulActions: successful.length,
2946
+ failedActions: failed.length,
2947
+ successRate: successful.length / entries.length,
2948
+ avgDurationMs: durations.reduce((a, b) => a + b, 0) / durations.length,
2949
+ minDurationMs: durations[0],
2950
+ maxDurationMs: durations[durations.length - 1],
2951
+ p95DurationMs: durations[Math.floor(durations.length * 0.95)],
2952
+ actionsPerSecond: recentActions.length / windowSeconds,
2953
+ errorsByType,
2954
+ actionsByType
2955
+ };
2956
+ }
2957
+ /**
2958
+ * Get recent errors
2959
+ */
2960
+ getRecentErrors(limit = 10) {
2961
+ return this.history.filter((e) => !e.success).slice(-limit);
2962
+ }
2963
+ /**
2964
+ * Get slowest actions
2965
+ */
2966
+ getSlowestActions(limit = 10) {
2967
+ return [...this.history].sort((a, b) => b.durationMs - a.durationMs).slice(0, limit);
2968
+ }
2969
+ /**
2970
+ * Clear history
2971
+ */
2972
+ clearHistory() {
2973
+ this.history = [];
2974
+ }
2975
+ /**
2976
+ * Export history as JSON
2977
+ */
2978
+ exportHistory() {
2979
+ return JSON.stringify(this.history, null, 2);
2980
+ }
2981
+ /**
2982
+ * Import history from JSON
2983
+ */
2984
+ importHistory(json) {
2985
+ const entries = JSON.parse(json);
2986
+ this.history = entries.slice(-this.maxHistoryEntries);
2987
+ }
2988
+ addEntry(entry) {
2989
+ this.history.push(entry);
2990
+ while (this.history.length > this.maxHistoryEntries) {
2991
+ this.history.shift();
2992
+ }
2993
+ }
2994
+ };
2995
+ function createMetricsCollector(options) {
2996
+ return new MetricsCollector(options);
2997
+ }
2998
+ var UIBridgeContext = react.createContext(null);
2999
+ function UIBridgeProvider({
3000
+ children,
3001
+ features = {},
3002
+ config = {},
3003
+ onEvent
3004
+ }) {
3005
+ const registryRef = react.useRef(null);
3006
+ const renderLogRef = react.useRef(null);
3007
+ const metricsRef = react.useRef(null);
3008
+ const wsClientRef = react.useRef(null);
3009
+ const [wsConnectionState, setWsConnectionState] = react.useState("disconnected");
3010
+ if (!registryRef.current) {
3011
+ registryRef.current = new UIBridgeRegistry({
3012
+ verbose: config.verbose,
3013
+ onEvent
3014
+ });
3015
+ setGlobalRegistry(registryRef.current);
3016
+ if (features.renderLog) {
3017
+ renderLogRef.current = createRenderLogManager({
3018
+ maxEntries: config.maxLogEntries
3019
+ });
3020
+ }
3021
+ if (features.debug) {
3022
+ metricsRef.current = createMetricsCollector();
3023
+ }
3024
+ if (config.websocket) {
3025
+ const wsPort = config.websocketPort || config.serverPort || 9876;
3026
+ const wsUrl = `ws://localhost:${wsPort}`;
3027
+ wsClientRef.current = createWSClient({
3028
+ url: wsUrl,
3029
+ autoReconnect: true,
3030
+ reconnectDelay: 1e3,
3031
+ maxReconnectAttempts: 10,
3032
+ pingInterval: 3e4
3033
+ });
3034
+ }
3035
+ }
3036
+ const registry = registryRef.current;
3037
+ const renderLog = renderLogRef.current || void 0;
3038
+ const metrics = metricsRef.current || void 0;
3039
+ const wsClient = wsClientRef.current || void 0;
3040
+ const executor = react.useMemo(() => createActionExecutor(registry), [registry]);
3041
+ const workflowEngine = react.useMemo(
3042
+ () => createWorkflowEngine(registry, executor),
3043
+ [registry, executor]
3044
+ );
3045
+ react.useEffect(() => {
3046
+ if (features.renderLog && renderLog) {
3047
+ renderLog.start();
3048
+ return () => renderLog.stop();
3049
+ }
3050
+ }, [features.renderLog, renderLog]);
3051
+ react.useEffect(() => {
3052
+ if (!metrics) return;
3053
+ const unsubCompleted = registry.on("action:completed", (event) => {
3054
+ metrics.recordEvent(event);
3055
+ });
3056
+ const unsubFailed = registry.on("action:failed", (event) => {
3057
+ metrics.recordEvent(event);
3058
+ });
3059
+ return () => {
3060
+ unsubCompleted();
3061
+ unsubFailed();
3062
+ };
3063
+ }, [registry, metrics]);
3064
+ react.useEffect(() => {
3065
+ if (!wsClient) return;
3066
+ const unsubscribe = wsClient.onConnectionChange((state) => {
3067
+ setWsConnectionState(state);
3068
+ });
3069
+ return unsubscribe;
3070
+ }, [wsClient]);
3071
+ react.useEffect(() => {
3072
+ return () => {
3073
+ renderLog?.stop();
3074
+ wsClient?.disconnect();
3075
+ resetGlobalRegistry();
3076
+ };
3077
+ }, [renderLog, wsClient]);
3078
+ const wsConnect = react.useCallback(async () => {
3079
+ if (wsClient) {
3080
+ await wsClient.connect();
3081
+ }
3082
+ }, [wsClient]);
3083
+ const wsDisconnect = react.useCallback(() => {
3084
+ wsClient?.disconnect();
3085
+ }, [wsClient]);
3086
+ const wsSubscribe = react.useCallback(
3087
+ async (options) => {
3088
+ if (!wsClient) {
3089
+ return [];
3090
+ }
3091
+ return wsClient.subscribe(options);
3092
+ },
3093
+ [wsClient]
3094
+ );
3095
+ const onWsEvent = react.useCallback(
3096
+ (eventType, listener) => {
3097
+ if (!wsClient) {
3098
+ return () => {
3099
+ };
3100
+ }
3101
+ return wsClient.onEvent(eventType, listener);
3102
+ },
3103
+ [wsClient]
3104
+ );
3105
+ const getElements = react.useCallback(() => registry.getAllElements(), [registry]);
3106
+ const getComponents = react.useCallback(() => registry.getAllComponents(), [registry]);
3107
+ const createSnapshot = react.useCallback(() => registry.createSnapshot(), [registry]);
3108
+ const on = react.useCallback(
3109
+ (type, listener) => registry.on(type, listener),
3110
+ [registry]
3111
+ );
3112
+ const off = react.useCallback(
3113
+ (type, listener) => registry.off(type, listener),
3114
+ [registry]
3115
+ );
3116
+ const contextValue = react.useMemo(
3117
+ () => ({
3118
+ features,
3119
+ config,
3120
+ registry,
3121
+ executor,
3122
+ workflowEngine,
3123
+ renderLog,
3124
+ metrics,
3125
+ wsClient,
3126
+ wsConnectionState,
3127
+ getElements,
3128
+ getComponents,
3129
+ createSnapshot,
3130
+ on,
3131
+ off,
3132
+ initialized: true,
3133
+ wsConnect,
3134
+ wsDisconnect,
3135
+ wsSubscribe,
3136
+ onWsEvent
3137
+ }),
3138
+ [
3139
+ features,
3140
+ config,
3141
+ registry,
3142
+ executor,
3143
+ workflowEngine,
3144
+ renderLog,
3145
+ metrics,
3146
+ wsClient,
3147
+ wsConnectionState,
3148
+ getElements,
3149
+ getComponents,
3150
+ createSnapshot,
3151
+ on,
3152
+ off,
3153
+ wsConnect,
3154
+ wsDisconnect,
3155
+ wsSubscribe,
3156
+ onWsEvent
3157
+ ]
3158
+ );
3159
+ return /* @__PURE__ */ jsxRuntime.jsx(UIBridgeContext.Provider, { value: contextValue, children });
3160
+ }
3161
+ function useUIBridgeContext() {
3162
+ const context = react.useContext(UIBridgeContext);
3163
+ if (!context) {
3164
+ throw new Error("useUIBridgeContext must be used within a UIBridgeProvider");
3165
+ }
3166
+ return context;
3167
+ }
3168
+ function useUIBridgeOptional() {
3169
+ return react.useContext(UIBridgeContext);
3170
+ }
3171
+ function useUIElement(options) {
3172
+ const bridge = useUIBridgeOptional();
3173
+ const elementRef = react.useRef(null);
3174
+ const registeredRef = react.useRef(false);
3175
+ const { id, type, label, actions, customActions, autoRegister = true } = options;
3176
+ const register = react.useCallback(() => {
3177
+ if (!bridge || !elementRef.current || registeredRef.current) return;
3178
+ bridge.registry.registerElement(id, elementRef.current, {
3179
+ type,
3180
+ label,
3181
+ actions,
3182
+ customActions
3183
+ });
3184
+ registeredRef.current = true;
3185
+ }, [bridge, id, type, label, actions, customActions]);
3186
+ const unregister = react.useCallback(() => {
3187
+ if (!bridge || !registeredRef.current) return;
3188
+ bridge.registry.unregisterElement(id);
3189
+ registeredRef.current = false;
3190
+ }, [bridge, id]);
3191
+ const ref = react.useCallback(
3192
+ (node) => {
3193
+ if (elementRef.current && elementRef.current !== node) {
3194
+ unregister();
3195
+ }
3196
+ elementRef.current = node;
3197
+ if (node && autoRegister) {
3198
+ register();
3199
+ }
3200
+ },
3201
+ [autoRegister, register, unregister]
3202
+ );
3203
+ react.useEffect(() => {
3204
+ return () => {
3205
+ unregister();
3206
+ };
3207
+ }, [unregister]);
3208
+ const getState = react.useCallback(() => {
3209
+ if (!bridge) return null;
3210
+ const registered = bridge.registry.getElement(id);
3211
+ return registered?.getState() || null;
3212
+ }, [bridge, id]);
3213
+ const getIdentifier = react.useCallback(() => {
3214
+ if (!bridge) return null;
3215
+ const registered = bridge.registry.getElement(id);
3216
+ return registered?.getIdentifier() || null;
3217
+ }, [bridge, id]);
3218
+ const trigger = react.useCallback(
3219
+ async (action, params) => {
3220
+ if (!bridge) {
3221
+ throw new Error("UI Bridge not available");
3222
+ }
3223
+ const response = await bridge.executor.executeAction(id, {
3224
+ action,
3225
+ params
3226
+ });
3227
+ if (!response.success) {
3228
+ throw new Error(response.error || "Action failed");
3229
+ }
3230
+ },
3231
+ [bridge, id]
3232
+ );
3233
+ const registeredElement = react.useMemo(() => {
3234
+ if (!bridge) return null;
3235
+ return bridge.registry.getElement(id) || null;
3236
+ }, [bridge, id]);
3237
+ return {
3238
+ ref,
3239
+ element: elementRef.current,
3240
+ registered: registeredRef.current,
3241
+ getState,
3242
+ getIdentifier,
3243
+ trigger,
3244
+ register,
3245
+ unregister,
3246
+ registeredElement
3247
+ };
3248
+ }
3249
+ function useUIElementRef(id) {
3250
+ return react.useCallback(
3251
+ (node) => {
3252
+ if (node) {
3253
+ node.setAttribute("data-ui-id", id);
3254
+ }
3255
+ },
3256
+ [id]
3257
+ );
3258
+ }
3259
+ function useUIComponent(options) {
3260
+ const bridge = useUIBridgeOptional();
3261
+ const registeredRef = react.useRef(false);
3262
+ const actionsRef = react.useRef(options.actions || []);
3263
+ const elementIdsRef = react.useRef(options.elementIds || []);
3264
+ const { id, name, description, autoRegister = true } = options;
3265
+ react.useEffect(() => {
3266
+ actionsRef.current = options.actions || [];
3267
+ elementIdsRef.current = options.elementIds || [];
3268
+ }, [options.actions, options.elementIds]);
3269
+ const register = react.useCallback(() => {
3270
+ if (!bridge || registeredRef.current) return;
3271
+ bridge.registry.registerComponent(id, {
3272
+ name,
3273
+ description,
3274
+ actions: actionsRef.current.map((a) => ({
3275
+ id: a.id,
3276
+ label: a.label,
3277
+ description: a.description,
3278
+ handler: a.handler
3279
+ })),
3280
+ elementIds: elementIdsRef.current
3281
+ });
3282
+ registeredRef.current = true;
3283
+ }, [bridge, id, name, description]);
3284
+ const unregister = react.useCallback(() => {
3285
+ if (!bridge || !registeredRef.current) return;
3286
+ bridge.registry.unregisterComponent(id);
3287
+ registeredRef.current = false;
3288
+ }, [bridge, id]);
3289
+ const executeAction = react.useCallback(
3290
+ async (actionId, params) => {
3291
+ if (!bridge) {
3292
+ throw new Error("UI Bridge not available");
3293
+ }
3294
+ const response = await bridge.executor.executeComponentAction(id, {
3295
+ action: actionId,
3296
+ params
3297
+ });
3298
+ if (!response.success) {
3299
+ throw new Error(response.error || "Action failed");
3300
+ }
3301
+ return response.result;
3302
+ },
3303
+ [bridge, id]
3304
+ );
3305
+ const updateActions = react.useCallback(
3306
+ (actions) => {
3307
+ actionsRef.current = actions;
3308
+ if (registeredRef.current && bridge) {
3309
+ bridge.registry.unregisterComponent(id);
3310
+ registeredRef.current = false;
3311
+ register();
3312
+ }
3313
+ },
3314
+ [bridge, id, register]
3315
+ );
3316
+ const addElement = react.useCallback((elementId) => {
3317
+ if (!elementIdsRef.current.includes(elementId)) {
3318
+ elementIdsRef.current = [...elementIdsRef.current, elementId];
3319
+ }
3320
+ }, []);
3321
+ const removeElement = react.useCallback((elementId) => {
3322
+ elementIdsRef.current = elementIdsRef.current.filter((id2) => id2 !== elementId);
3323
+ }, []);
3324
+ react.useEffect(() => {
3325
+ if (autoRegister) {
3326
+ register();
3327
+ }
3328
+ return () => {
3329
+ unregister();
3330
+ };
3331
+ }, [autoRegister, register, unregister]);
3332
+ const registeredComponent = react.useMemo(() => {
3333
+ if (!bridge) return null;
3334
+ return bridge.registry.getComponent(id) || null;
3335
+ }, [bridge, id]);
3336
+ return {
3337
+ registered: registeredRef.current,
3338
+ executeAction,
3339
+ register,
3340
+ unregister,
3341
+ updateActions,
3342
+ addElement,
3343
+ removeElement,
3344
+ registeredComponent
3345
+ };
3346
+ }
3347
+ function useUIComponentAction(handler, deps) {
3348
+ return react.useCallback(handler, deps);
3349
+ }
3350
+ function useUIBridge() {
3351
+ const context = useUIBridgeOptional();
3352
+ const available = !!context;
3353
+ const initialized = context?.initialized ?? false;
3354
+ const elements = react.useMemo(() => context?.getElements() ?? [], [context]);
3355
+ const components = react.useMemo(() => context?.getComponents() ?? [], [context]);
3356
+ const workflows = react.useMemo(() => context?.registry.getAllWorkflows() ?? [], [context]);
3357
+ const createSnapshot = react.useCallback(() => {
3358
+ if (!context) {
3359
+ return {
3360
+ timestamp: Date.now(),
3361
+ elements: [],
3362
+ components: [],
3363
+ workflows: []
3364
+ };
3365
+ }
3366
+ return context.createSnapshot();
3367
+ }, [context]);
3368
+ const executeAction = react.useCallback(
3369
+ async (elementId, request) => {
3370
+ if (!context) {
3371
+ return {
3372
+ success: false,
3373
+ error: "UI Bridge not available",
3374
+ durationMs: 0,
3375
+ timestamp: Date.now()
3376
+ };
3377
+ }
3378
+ return context.executor.executeAction(elementId, request);
3379
+ },
3380
+ [context]
3381
+ );
3382
+ const executeComponentAction = react.useCallback(
3383
+ async (componentId, request) => {
3384
+ if (!context) {
3385
+ return {
3386
+ success: false,
3387
+ error: "UI Bridge not available",
3388
+ durationMs: 0,
3389
+ timestamp: Date.now()
3390
+ };
3391
+ }
3392
+ return context.executor.executeComponentAction(componentId, request);
3393
+ },
3394
+ [context]
3395
+ );
3396
+ const find = react.useCallback(
3397
+ async (options) => {
3398
+ if (!context) {
3399
+ return {
3400
+ elements: [],
3401
+ total: 0,
3402
+ durationMs: 0,
3403
+ timestamp: Date.now()
3404
+ };
3405
+ }
3406
+ return context.executor.find(options);
3407
+ },
3408
+ [context]
3409
+ );
3410
+ const discover = react.useCallback(
3411
+ async (options) => {
3412
+ return find(options);
3413
+ },
3414
+ [find]
3415
+ );
3416
+ const runWorkflow = react.useCallback(
3417
+ async (workflowId, request) => {
3418
+ if (!context) {
3419
+ return {
3420
+ workflowId,
3421
+ runId: "",
3422
+ status: "failed",
3423
+ steps: [],
3424
+ totalSteps: 0,
3425
+ success: false,
3426
+ error: "UI Bridge not available",
3427
+ startedAt: Date.now(),
3428
+ completedAt: Date.now(),
3429
+ durationMs: 0
3430
+ };
3431
+ }
3432
+ return context.workflowEngine.run(workflowId, request);
3433
+ },
3434
+ [context]
3435
+ );
3436
+ const getElement = react.useCallback(
3437
+ (id) => {
3438
+ return context?.registry.getElement(id);
3439
+ },
3440
+ [context]
3441
+ );
3442
+ const getComponent = react.useCallback(
3443
+ (id) => {
3444
+ return context?.registry.getComponent(id);
3445
+ },
3446
+ [context]
3447
+ );
3448
+ const getElementState4 = react.useCallback(
3449
+ (id) => {
3450
+ const element = context?.registry.getElement(id);
3451
+ return element?.getState();
3452
+ },
3453
+ [context]
3454
+ );
3455
+ const registerWorkflow = react.useCallback(
3456
+ (workflow) => {
3457
+ context?.registry.registerWorkflow(workflow);
3458
+ },
3459
+ [context]
3460
+ );
3461
+ const unregisterWorkflow = react.useCallback(
3462
+ (id) => {
3463
+ context?.registry.unregisterWorkflow(id);
3464
+ },
3465
+ [context]
3466
+ );
3467
+ const captureRenderLog = react.useCallback(async () => {
3468
+ await context?.renderLog?.captureSnapshot();
3469
+ }, [context]);
3470
+ const getRenderLogEntries = react.useCallback(async () => {
3471
+ return await context?.renderLog?.getEntries() ?? [];
3472
+ }, [context]);
3473
+ const clearRenderLog = react.useCallback(async () => {
3474
+ await context?.renderLog?.clear();
3475
+ }, [context]);
3476
+ const getMetrics = react.useCallback(() => {
3477
+ return context?.metrics?.getMetrics();
3478
+ }, [context]);
3479
+ const getActionHistory = react.useCallback(() => {
3480
+ return context?.metrics?.getHistory();
3481
+ }, [context]);
3482
+ return {
3483
+ available,
3484
+ initialized,
3485
+ elements,
3486
+ components,
3487
+ workflows,
3488
+ createSnapshot,
3489
+ executeAction,
3490
+ executeComponentAction,
3491
+ find,
3492
+ discover,
3493
+ // deprecated - use find
3494
+ runWorkflow,
3495
+ getElement,
3496
+ getComponent,
3497
+ getElementState: getElementState4,
3498
+ registerWorkflow,
3499
+ unregisterWorkflow,
3500
+ captureRenderLog,
3501
+ getRenderLogEntries,
3502
+ clearRenderLog,
3503
+ getMetrics,
3504
+ getActionHistory
3505
+ };
3506
+ }
3507
+ function useUIBridgeRequired() {
3508
+ useUIBridgeContext();
3509
+ return useUIBridge();
3510
+ }
3511
+ function useUIState(options) {
3512
+ const bridge = useUIBridgeOptional();
3513
+ const [registered, setRegistered] = react.useState(false);
3514
+ const [isActive, setIsActive] = react.useState(options.initialActive ?? false);
3515
+ const [activeStates, setActiveStates] = react.useState([]);
3516
+ const {
3517
+ id,
3518
+ name,
3519
+ elements = [],
3520
+ activeWhen,
3521
+ blocking,
3522
+ blocks,
3523
+ group,
3524
+ pathCost,
3525
+ metadata,
3526
+ autoRegister = true,
3527
+ initialActive = false
3528
+ } = options;
3529
+ const register = react.useCallback(() => {
3530
+ if (!bridge || registered) return;
3531
+ const state2 = {
3532
+ id,
3533
+ name,
3534
+ elements,
3535
+ activeWhen,
3536
+ blocking,
3537
+ blocks,
3538
+ group,
3539
+ pathCost,
3540
+ metadata
3541
+ };
3542
+ bridge.registry.registerState(state2);
3543
+ setRegistered(true);
3544
+ if (initialActive) {
3545
+ bridge.registry.activateState(id);
3546
+ setIsActive(true);
3547
+ }
3548
+ setActiveStates(bridge.registry.getActiveStates());
3549
+ }, [
3550
+ bridge,
3551
+ registered,
3552
+ id,
3553
+ name,
3554
+ elements,
3555
+ activeWhen,
3556
+ blocking,
3557
+ blocks,
3558
+ group,
3559
+ pathCost,
3560
+ metadata,
3561
+ initialActive
3562
+ ]);
3563
+ const unregister = react.useCallback(() => {
3564
+ if (!bridge || !registered) return;
3565
+ bridge.registry.unregisterState(id);
3566
+ setRegistered(false);
3567
+ setIsActive(false);
3568
+ }, [bridge, registered, id]);
3569
+ react.useEffect(() => {
3570
+ if (autoRegister && bridge) {
3571
+ register();
3572
+ }
3573
+ return () => {
3574
+ if (registered) {
3575
+ unregister();
3576
+ }
3577
+ };
3578
+ }, [autoRegister, bridge, register, unregister, registered]);
3579
+ react.useEffect(() => {
3580
+ if (!bridge) return;
3581
+ const unsubscribe = bridge.registry.on("element:stateChanged", (event) => {
3582
+ const data = event.data;
3583
+ if (data.stateId === id) {
3584
+ setIsActive(data.active);
3585
+ }
3586
+ setActiveStates(data.activeStates);
3587
+ });
3588
+ return unsubscribe;
3589
+ }, [bridge, id]);
3590
+ const activate = react.useCallback(() => {
3591
+ if (!bridge) return false;
3592
+ const success = bridge.registry.activateState(id);
3593
+ if (success) {
3594
+ setIsActive(true);
3595
+ setActiveStates(bridge.registry.getActiveStates());
3596
+ }
3597
+ return success;
3598
+ }, [bridge, id]);
3599
+ const deactivate = react.useCallback(() => {
3600
+ if (!bridge) return false;
3601
+ const success = bridge.registry.deactivateState(id);
3602
+ if (success) {
3603
+ setIsActive(false);
3604
+ setActiveStates(bridge.registry.getActiveStates());
3605
+ }
3606
+ return success;
3607
+ }, [bridge, id]);
3608
+ const toggle = react.useCallback(() => {
3609
+ return isActive ? deactivate() : activate();
3610
+ }, [isActive, activate, deactivate]);
3611
+ const state = react.useMemo(() => {
3612
+ if (!bridge) return void 0;
3613
+ return bridge.registry.getState(id);
3614
+ }, [bridge, id, registered]);
3615
+ return {
3616
+ registered,
3617
+ isActive,
3618
+ activate,
3619
+ deactivate,
3620
+ toggle,
3621
+ activeStates,
3622
+ register,
3623
+ unregister,
3624
+ state
3625
+ };
3626
+ }
3627
+ function useUIStateGroup(options) {
3628
+ const bridge = useUIBridgeOptional();
3629
+ const [registered, setRegistered] = react.useState(false);
3630
+ const { id, name, states, autoRegister = true } = options;
3631
+ const register = react.useCallback(() => {
3632
+ if (!bridge || registered) return;
3633
+ const group2 = { id, name, states };
3634
+ bridge.registry.registerStateGroup(group2);
3635
+ setRegistered(true);
3636
+ }, [bridge, registered, id, name, states]);
3637
+ const unregister = react.useCallback(() => {
3638
+ if (!bridge || !registered) return;
3639
+ bridge.registry.unregisterStateGroup(id);
3640
+ setRegistered(false);
3641
+ }, [bridge, registered, id]);
3642
+ react.useEffect(() => {
3643
+ if (autoRegister && bridge) {
3644
+ register();
3645
+ }
3646
+ return () => {
3647
+ if (registered) {
3648
+ unregister();
3649
+ }
3650
+ };
3651
+ }, [autoRegister, bridge, register, unregister, registered]);
3652
+ const activate = react.useCallback(() => {
3653
+ if (!bridge) return [];
3654
+ return bridge.registry.activateStateGroup(id);
3655
+ }, [bridge, id]);
3656
+ const deactivate = react.useCallback(() => {
3657
+ if (!bridge) return [];
3658
+ return bridge.registry.deactivateStateGroup(id);
3659
+ }, [bridge, id]);
3660
+ const group = react.useMemo(() => {
3661
+ if (!bridge) return void 0;
3662
+ return bridge.registry.getStateGroup(id);
3663
+ }, [bridge, id, registered]);
3664
+ return {
3665
+ registered,
3666
+ activate,
3667
+ deactivate,
3668
+ register,
3669
+ unregister,
3670
+ group
3671
+ };
3672
+ }
3673
+ function useActiveStates() {
3674
+ const bridge = useUIBridgeOptional();
3675
+ const [activeStates, setActiveStates] = react.useState([]);
3676
+ react.useEffect(() => {
3677
+ if (!bridge) return;
3678
+ setActiveStates(bridge.registry.getActiveStates());
3679
+ const unsubscribe = bridge.registry.on("element:stateChanged", (event) => {
3680
+ const data = event.data;
3681
+ setActiveStates(data.activeStates);
3682
+ });
3683
+ return unsubscribe;
3684
+ }, [bridge]);
3685
+ return activeStates;
3686
+ }
3687
+ function useStateSnapshot() {
3688
+ const bridge = useUIBridgeOptional();
3689
+ return react.useMemo(() => {
3690
+ if (!bridge) return null;
3691
+ return bridge.registry.createStateSnapshot();
3692
+ }, [bridge]);
3693
+ }
3694
+ function useUITransition(options) {
3695
+ const bridge = useUIBridgeOptional();
3696
+ const [registered, setRegistered] = react.useState(false);
3697
+ const [canExecute, setCanExecute] = react.useState(false);
3698
+ const {
3699
+ id,
3700
+ name,
3701
+ fromStates,
3702
+ activateStates,
3703
+ exitStates,
3704
+ activateGroups,
3705
+ exitGroups,
3706
+ actions,
3707
+ pathCost,
3708
+ staysVisible,
3709
+ autoRegister = true
3710
+ } = options;
3711
+ const register = react.useCallback(() => {
3712
+ if (!bridge || registered) return;
3713
+ const transition2 = {
3714
+ id,
3715
+ name,
3716
+ fromStates,
3717
+ activateStates,
3718
+ exitStates,
3719
+ activateGroups,
3720
+ exitGroups,
3721
+ actions,
3722
+ pathCost,
3723
+ staysVisible
3724
+ };
3725
+ bridge.registry.registerTransition(transition2);
3726
+ setRegistered(true);
3727
+ setCanExecute(bridge.registry.canExecuteTransition(id));
3728
+ }, [
3729
+ bridge,
3730
+ registered,
3731
+ id,
3732
+ name,
3733
+ fromStates,
3734
+ activateStates,
3735
+ exitStates,
3736
+ activateGroups,
3737
+ exitGroups,
3738
+ actions,
3739
+ pathCost,
3740
+ staysVisible
3741
+ ]);
3742
+ const unregister = react.useCallback(() => {
3743
+ if (!bridge || !registered) return;
3744
+ bridge.registry.unregisterTransition(id);
3745
+ setRegistered(false);
3746
+ setCanExecute(false);
3747
+ }, [bridge, registered, id]);
3748
+ react.useEffect(() => {
3749
+ if (autoRegister && bridge) {
3750
+ register();
3751
+ }
3752
+ return () => {
3753
+ if (registered) {
3754
+ unregister();
3755
+ }
3756
+ };
3757
+ }, [autoRegister, bridge, register, unregister, registered]);
3758
+ react.useEffect(() => {
3759
+ if (!bridge || !registered) return;
3760
+ const unsubscribe = bridge.registry.on("element:stateChanged", () => {
3761
+ setCanExecute(bridge.registry.canExecuteTransition(id));
3762
+ });
3763
+ return unsubscribe;
3764
+ }, [bridge, id, registered]);
3765
+ const execute = react.useCallback(async () => {
3766
+ if (!bridge) {
3767
+ return {
3768
+ success: false,
3769
+ activatedStates: [],
3770
+ deactivatedStates: [],
3771
+ error: "UI Bridge not available",
3772
+ durationMs: 0
3773
+ };
3774
+ }
3775
+ const result = await bridge.registry.executeTransition(id);
3776
+ setCanExecute(bridge.registry.canExecuteTransition(id));
3777
+ return result;
3778
+ }, [bridge, id]);
3779
+ const transition = react.useMemo(() => {
3780
+ if (!bridge) return void 0;
3781
+ return bridge.registry.getTransition(id);
3782
+ }, [bridge, id, registered]);
3783
+ return {
3784
+ registered,
3785
+ canExecute,
3786
+ execute,
3787
+ register,
3788
+ unregister,
3789
+ transition
3790
+ };
3791
+ }
3792
+ function useTransitions() {
3793
+ const bridge = useUIBridgeOptional();
3794
+ return react.useMemo(() => {
3795
+ if (!bridge) return [];
3796
+ return bridge.registry.getAllTransitions();
3797
+ }, [bridge]);
3798
+ }
3799
+ function useAvailableTransitions() {
3800
+ const bridge = useUIBridgeOptional();
3801
+ const [available, setAvailable] = react.useState([]);
3802
+ react.useEffect(() => {
3803
+ if (!bridge) return;
3804
+ const updateAvailable = () => {
3805
+ const transitions = bridge.registry.getAllTransitions();
3806
+ const availableTransitions = transitions.filter(
3807
+ (t) => bridge.registry.canExecuteTransition(t.id)
3808
+ );
3809
+ setAvailable(availableTransitions);
3810
+ };
3811
+ updateAvailable();
3812
+ const unsubscribe = bridge.registry.on("element:stateChanged", updateAvailable);
3813
+ return unsubscribe;
3814
+ }, [bridge]);
3815
+ return available;
3816
+ }
3817
+ function useUINavigation() {
3818
+ const bridge = useUIBridgeOptional();
3819
+ const [isNavigating, setIsNavigating] = react.useState(false);
3820
+ const [lastResult, setLastResult] = react.useState(null);
3821
+ const available = !!bridge;
3822
+ const activeStates = react.useMemo(() => {
3823
+ if (!bridge) return [];
3824
+ return bridge.registry.getActiveStates();
3825
+ }, [bridge]);
3826
+ const findPath = react.useCallback(
3827
+ (targetStates) => {
3828
+ if (!bridge) {
3829
+ return {
3830
+ found: false,
3831
+ transitions: [],
3832
+ totalCost: 0,
3833
+ targetStates,
3834
+ estimatedSteps: 0
3835
+ };
3836
+ }
3837
+ return bridge.registry.findPath(targetStates);
3838
+ },
3839
+ [bridge]
3840
+ );
3841
+ const navigateTo = react.useCallback(
3842
+ async (targetStates) => {
3843
+ if (!bridge) {
3844
+ const result = {
3845
+ success: false,
3846
+ path: {
3847
+ found: false,
3848
+ transitions: [],
3849
+ totalCost: 0,
3850
+ targetStates,
3851
+ estimatedSteps: 0
3852
+ },
3853
+ executedTransitions: [],
3854
+ finalActiveStates: [],
3855
+ error: "UI Bridge not available",
3856
+ durationMs: 0
3857
+ };
3858
+ setLastResult(result);
3859
+ return result;
3860
+ }
3861
+ setIsNavigating(true);
3862
+ try {
3863
+ const result = await bridge.registry.navigateTo(targetStates);
3864
+ setLastResult(result);
3865
+ return result;
3866
+ } finally {
3867
+ setIsNavigating(false);
3868
+ }
3869
+ },
3870
+ [bridge]
3871
+ );
3872
+ return {
3873
+ available,
3874
+ isNavigating,
3875
+ lastResult,
3876
+ findPath,
3877
+ navigateTo,
3878
+ activeStates
3879
+ };
3880
+ }
3881
+ function useCanNavigateTo(targetStates) {
3882
+ const bridge = useUIBridgeOptional();
3883
+ return react.useMemo(() => {
3884
+ if (!bridge) return false;
3885
+ const path = bridge.registry.findPath(targetStates);
3886
+ return path.found;
3887
+ }, [bridge, targetStates]);
3888
+ }
3889
+ function useNavigationPath(targetStates) {
3890
+ const bridge = useUIBridgeOptional();
3891
+ return react.useMemo(() => {
3892
+ if (!bridge) {
3893
+ return {
3894
+ found: false,
3895
+ transitions: [],
3896
+ totalCost: 0,
3897
+ targetStates,
3898
+ estimatedSteps: 0
3899
+ };
3900
+ }
3901
+ return bridge.registry.findPath(targetStates);
3902
+ }, [bridge, targetStates]);
3903
+ }
3904
+ var INTERACTIVE_SELECTORS2 = [
3905
+ "a[href]",
3906
+ "button",
3907
+ "input",
3908
+ "select",
3909
+ "textarea",
3910
+ '[role="button"]',
3911
+ '[role="link"]',
3912
+ '[role="checkbox"]',
3913
+ '[role="radio"]',
3914
+ '[role="menuitem"]',
3915
+ '[role="tab"]',
3916
+ '[role="switch"]',
3917
+ '[role="slider"]',
3918
+ '[role="spinbutton"]',
3919
+ '[role="combobox"]',
3920
+ '[role="listbox"]',
3921
+ '[role="option"]',
3922
+ '[role="textbox"]',
3923
+ '[tabindex]:not([tabindex="-1"])',
3924
+ '[contenteditable="true"]',
3925
+ "[data-ui-element]",
3926
+ // Explicitly marked for registration
3927
+ "[data-testid]"
3928
+ // Testing library convention
3929
+ ];
3930
+ function inferElementType2(element) {
3931
+ const role = element.getAttribute("role");
3932
+ if (role) {
3933
+ const roleMap = {
3934
+ button: "button",
3935
+ link: "link",
3936
+ checkbox: "checkbox",
3937
+ radio: "radio",
3938
+ menuitem: "menuitem",
3939
+ tab: "tab",
3940
+ switch: "switch",
3941
+ slider: "slider",
3942
+ combobox: "combobox",
3943
+ listbox: "listbox",
3944
+ option: "option",
3945
+ textbox: "textbox"
3946
+ };
3947
+ if (role in roleMap) {
3948
+ return roleMap[role];
3949
+ }
3950
+ }
3951
+ const tagName = element.tagName.toLowerCase();
3952
+ switch (tagName) {
3953
+ case "a":
3954
+ return "link";
3955
+ case "button":
3956
+ return "button";
3957
+ case "input": {
3958
+ const type = element.type?.toLowerCase() || "text";
3959
+ switch (type) {
3960
+ case "checkbox":
3961
+ return "checkbox";
3962
+ case "radio":
3963
+ return "radio";
3964
+ case "range":
3965
+ return "slider";
3966
+ case "submit":
3967
+ case "button":
3968
+ return "button";
3969
+ default:
3970
+ return "input";
3971
+ }
3972
+ }
3973
+ case "select":
3974
+ return "select";
3975
+ case "textarea":
3976
+ return "textarea";
3977
+ case "option":
3978
+ return "option";
3979
+ default:
3980
+ return "generic";
3981
+ }
3982
+ }
3983
+ function inferActions2(type) {
3984
+ const baseActions = ["focus", "blur"];
3985
+ const typeActions = {
3986
+ button: [...baseActions, "click", "hover"],
3987
+ link: [...baseActions, "click", "hover"],
3988
+ input: [...baseActions, "type", "clear", "click"],
3989
+ textarea: [...baseActions, "type", "clear", "click"],
3990
+ textbox: [...baseActions, "type", "clear", "click"],
3991
+ checkbox: [...baseActions, "check", "uncheck", "toggle", "click"],
3992
+ radio: [...baseActions, "click", "select"],
3993
+ select: [...baseActions, "select", "click"],
3994
+ combobox: [...baseActions, "select", "type", "click"],
3995
+ listbox: [...baseActions, "select", "click"],
3996
+ option: [...baseActions, "select", "click"],
3997
+ switch: [...baseActions, "toggle", "click"],
3998
+ slider: [...baseActions, "setValue", "click", "drag"],
3999
+ tab: [...baseActions, "click", "select"],
4000
+ menuitem: [...baseActions, "click"],
4001
+ dialog: [...baseActions],
4002
+ menu: [...baseActions],
4003
+ form: [...baseActions, "submit", "reset"],
4004
+ custom: [...baseActions, "click"],
4005
+ generic: [...baseActions, "click"]
4006
+ };
4007
+ return typeActions[type] || baseActions;
4008
+ }
4009
+ function getAccessibleLabel(element) {
4010
+ const ariaLabel = element.getAttribute("aria-label");
4011
+ if (ariaLabel) return ariaLabel;
4012
+ const labelledBy = element.getAttribute("aria-labelledby");
4013
+ if (labelledBy) {
4014
+ const labelEl = document.getElementById(labelledBy);
4015
+ if (labelEl) return labelEl.textContent?.trim();
4016
+ }
4017
+ if (element.id) {
4018
+ const label = document.querySelector(`label[for="${element.id}"]`);
4019
+ if (label) return label.textContent?.trim();
4020
+ }
4021
+ const title = element.getAttribute("title");
4022
+ if (title) return title;
4023
+ const text = element.textContent?.trim();
4024
+ if (text && text.length <= 50) return text;
4025
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
4026
+ const placeholder = element.placeholder;
4027
+ if (placeholder) return placeholder;
4028
+ }
4029
+ return void 0;
4030
+ }
4031
+ function isElementVisible2(element) {
4032
+ const style = window.getComputedStyle(element);
4033
+ if (style.display === "none" || style.visibility === "hidden") {
4034
+ return false;
4035
+ }
4036
+ const rect = element.getBoundingClientRect();
4037
+ return rect.width > 0 && rect.height > 0;
4038
+ }
4039
+ function generateSemanticId(element) {
4040
+ const type = inferElementType2(element);
4041
+ const label = getAccessibleLabel(element);
4042
+ if (label) {
4043
+ const sanitized = label.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 30);
4044
+ return `${type}-${sanitized}`;
4045
+ }
4046
+ const parent = element.parentElement;
4047
+ if (parent) {
4048
+ const siblings = Array.from(parent.querySelectorAll(element.tagName));
4049
+ const index = siblings.indexOf(element);
4050
+ return `${element.tagName.toLowerCase()}-${index}`;
4051
+ }
4052
+ return `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
4053
+ }
4054
+ function generateAutoId(element) {
4055
+ const type = inferElementType2(element);
4056
+ return `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4057
+ }
4058
+ function generateIdForElement(element, strategy, customGenerator) {
4059
+ if (customGenerator) {
4060
+ return customGenerator(element);
4061
+ }
4062
+ switch (strategy) {
4063
+ case "data-testid": {
4064
+ const testId = element.getAttribute("data-testid");
4065
+ return testId || generateAutoId(element);
4066
+ }
4067
+ case "data-ui-id": {
4068
+ const uiId = element.getAttribute("data-ui-id");
4069
+ return uiId || generateAutoId(element);
4070
+ }
4071
+ case "semantic":
4072
+ return generateSemanticId(element);
4073
+ case "auto":
4074
+ return generateAutoId(element);
4075
+ case "prefer-existing":
4076
+ default: {
4077
+ const uiId = element.getAttribute("data-ui-id");
4078
+ if (uiId) return uiId;
4079
+ const testId = element.getAttribute("data-testid");
4080
+ if (testId) return testId;
4081
+ const htmlId = element.id;
4082
+ if (htmlId) return htmlId;
4083
+ return generateSemanticId(element);
4084
+ }
4085
+ }
4086
+ }
4087
+ function useAutoRegister(options = {}) {
4088
+ const {
4089
+ enabled = process.env.NODE_ENV === "development",
4090
+ root = null,
4091
+ idStrategy = "prefer-existing",
4092
+ debounceMs = 100,
4093
+ includeHidden = false,
4094
+ includeSelectors = [],
4095
+ excludeSelectors = [],
4096
+ generateId: customGenerateId,
4097
+ onRegister,
4098
+ onUnregister
4099
+ } = options;
4100
+ const bridge = useUIBridgeOptional();
4101
+ const registeredElementsRef = react.useRef(/* @__PURE__ */ new Map());
4102
+ const pendingRegistrationsRef = react.useRef(/* @__PURE__ */ new Set());
4103
+ const debounceTimeoutRef = react.useRef(null);
4104
+ const shouldRegister = react.useCallback(
4105
+ (element) => {
4106
+ if (!includeHidden && !isElementVisible2(element)) {
4107
+ return false;
4108
+ }
4109
+ for (const selector of excludeSelectors) {
4110
+ if (element.matches(selector)) {
4111
+ return false;
4112
+ }
4113
+ }
4114
+ if (registeredElementsRef.current.has(element)) {
4115
+ return false;
4116
+ }
4117
+ const allSelectors = [...INTERACTIVE_SELECTORS2, ...includeSelectors];
4118
+ for (const selector of allSelectors) {
4119
+ if (element.matches(selector)) {
4120
+ return true;
4121
+ }
4122
+ }
4123
+ return false;
4124
+ },
4125
+ [includeHidden, includeSelectors, excludeSelectors]
4126
+ );
4127
+ const registerElement = react.useCallback(
4128
+ (element) => {
4129
+ if (!bridge?.registry || registeredElementsRef.current.has(element)) {
4130
+ return;
4131
+ }
4132
+ const id = generateIdForElement(element, idStrategy, customGenerateId);
4133
+ const existing = bridge.registry.getElement(id);
4134
+ if (existing) {
4135
+ const uniqueId = `${id}-${Date.now().toString(36)}`;
4136
+ const type = inferElementType2(element);
4137
+ const actions = inferActions2(type);
4138
+ const label = getAccessibleLabel(element);
4139
+ bridge.registry.registerElement(uniqueId, element, {
4140
+ type,
4141
+ actions,
4142
+ label
4143
+ });
4144
+ registeredElementsRef.current.set(element, uniqueId);
4145
+ onRegister?.(uniqueId, element);
4146
+ } else {
4147
+ const type = inferElementType2(element);
4148
+ const actions = inferActions2(type);
4149
+ const label = getAccessibleLabel(element);
4150
+ bridge.registry.registerElement(id, element, {
4151
+ type,
4152
+ actions,
4153
+ label
4154
+ });
4155
+ registeredElementsRef.current.set(element, id);
4156
+ onRegister?.(id, element);
4157
+ }
4158
+ },
4159
+ [bridge, idStrategy, customGenerateId, onRegister]
4160
+ );
4161
+ const unregisterElement = react.useCallback(
4162
+ (element) => {
4163
+ const id = registeredElementsRef.current.get(element);
4164
+ if (!id || !bridge?.registry) return;
4165
+ bridge.registry.unregisterElement(id);
4166
+ registeredElementsRef.current.delete(element);
4167
+ onUnregister?.(id);
4168
+ },
4169
+ [bridge, onUnregister]
4170
+ );
4171
+ const processPendingRegistrations = react.useCallback(() => {
4172
+ pendingRegistrationsRef.current.forEach((element) => {
4173
+ if (shouldRegister(element)) {
4174
+ registerElement(element);
4175
+ }
4176
+ });
4177
+ pendingRegistrationsRef.current.clear();
4178
+ }, [shouldRegister, registerElement]);
4179
+ const queueRegistration = react.useCallback(
4180
+ (element) => {
4181
+ pendingRegistrationsRef.current.add(element);
4182
+ if (debounceTimeoutRef.current) {
4183
+ clearTimeout(debounceTimeoutRef.current);
4184
+ }
4185
+ debounceTimeoutRef.current = setTimeout(processPendingRegistrations, debounceMs);
4186
+ },
4187
+ [debounceMs, processPendingRegistrations]
4188
+ );
4189
+ const scanAndRegister = react.useCallback(
4190
+ (rootElement) => {
4191
+ const allSelectors = [...INTERACTIVE_SELECTORS2, ...includeSelectors].join(", ");
4192
+ const elements = rootElement.querySelectorAll(allSelectors);
4193
+ elements.forEach((element) => {
4194
+ if (shouldRegister(element)) {
4195
+ queueRegistration(element);
4196
+ }
4197
+ });
4198
+ },
4199
+ [includeSelectors, shouldRegister, queueRegistration]
4200
+ );
4201
+ const handleMutations = react.useCallback(
4202
+ (mutations) => {
4203
+ mutations.forEach((mutation) => {
4204
+ mutation.addedNodes.forEach((node) => {
4205
+ if (node.nodeType === Node.ELEMENT_NODE) {
4206
+ const element = node;
4207
+ if (shouldRegister(element)) {
4208
+ queueRegistration(element);
4209
+ }
4210
+ const allSelectors = [...INTERACTIVE_SELECTORS2, ...includeSelectors].join(", ");
4211
+ const descendants = element.querySelectorAll(allSelectors);
4212
+ descendants.forEach((descendant) => {
4213
+ if (shouldRegister(descendant)) {
4214
+ queueRegistration(descendant);
4215
+ }
4216
+ });
4217
+ }
4218
+ });
4219
+ mutation.removedNodes.forEach((node) => {
4220
+ if (node.nodeType === Node.ELEMENT_NODE) {
4221
+ const element = node;
4222
+ if (registeredElementsRef.current.has(element)) {
4223
+ unregisterElement(element);
4224
+ }
4225
+ const descendants = element.querySelectorAll("*");
4226
+ descendants.forEach((descendant) => {
4227
+ if (registeredElementsRef.current.has(descendant)) {
4228
+ unregisterElement(descendant);
4229
+ }
4230
+ });
4231
+ }
4232
+ });
4233
+ });
4234
+ },
4235
+ [shouldRegister, queueRegistration, unregisterElement, includeSelectors]
4236
+ );
4237
+ react.useEffect(() => {
4238
+ if (!enabled || !bridge?.registry) return;
4239
+ const rootElement = root || document.body;
4240
+ scanAndRegister(rootElement);
4241
+ const observer = new MutationObserver(handleMutations);
4242
+ observer.observe(rootElement, {
4243
+ childList: true,
4244
+ subtree: true
4245
+ });
4246
+ return () => {
4247
+ observer.disconnect();
4248
+ if (debounceTimeoutRef.current) {
4249
+ clearTimeout(debounceTimeoutRef.current);
4250
+ }
4251
+ registeredElementsRef.current.forEach((id, _element) => {
4252
+ bridge.registry.unregisterElement(id);
4253
+ });
4254
+ registeredElementsRef.current.clear();
4255
+ };
4256
+ }, [enabled, bridge, root, scanAndRegister, handleMutations]);
4257
+ }
4258
+ function AutoRegisterProvider({
4259
+ children,
4260
+ scopeToChildren = false,
4261
+ enabled = process.env.NODE_ENV === "development",
4262
+ idStrategy = "prefer-existing",
4263
+ debounceMs = 100,
4264
+ includeHidden = false,
4265
+ includeSelectors = [],
4266
+ excludeSelectors = [],
4267
+ generateId: generateId4,
4268
+ onRegister,
4269
+ onUnregister
4270
+ }) {
4271
+ const containerRef = react.useRef(null);
4272
+ useAutoRegister({
4273
+ enabled,
4274
+ root: scopeToChildren ? containerRef.current : null,
4275
+ idStrategy,
4276
+ debounceMs,
4277
+ includeHidden,
4278
+ includeSelectors,
4279
+ excludeSelectors,
4280
+ generateId: generateId4,
4281
+ onRegister,
4282
+ onUnregister
4283
+ });
4284
+ if (scopeToChildren) {
4285
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { display: "contents" }, children });
4286
+ }
4287
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
4288
+ }
4289
+
4290
+ exports.AutoRegisterProvider = AutoRegisterProvider;
4291
+ exports.UIBridgeProvider = UIBridgeProvider;
4292
+ exports.useActiveStates = useActiveStates;
4293
+ exports.useAutoRegister = useAutoRegister;
4294
+ exports.useAvailableTransitions = useAvailableTransitions;
4295
+ exports.useCanNavigateTo = useCanNavigateTo;
4296
+ exports.useNavigationPath = useNavigationPath;
4297
+ exports.useStateSnapshot = useStateSnapshot;
4298
+ exports.useTransitions = useTransitions;
4299
+ exports.useUIBridge = useUIBridge;
4300
+ exports.useUIBridgeContext = useUIBridgeContext;
4301
+ exports.useUIBridgeOptional = useUIBridgeOptional;
4302
+ exports.useUIBridgeRequired = useUIBridgeRequired;
4303
+ exports.useUIComponent = useUIComponent;
4304
+ exports.useUIComponentAction = useUIComponentAction;
4305
+ exports.useUIElement = useUIElement;
4306
+ exports.useUIElementRef = useUIElementRef;
4307
+ exports.useUINavigation = useUINavigation;
4308
+ exports.useUIState = useUIState;
4309
+ exports.useUIStateGroup = useUIStateGroup;
4310
+ exports.useUITransition = useUITransition;
4311
+ //# sourceMappingURL=index.js.map
4312
+ //# sourceMappingURL=index.js.map