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