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