@qontinui/ui-bridge-native 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/control/index.d.mts +9 -5
  2. package/dist/control/index.d.ts +9 -5
  3. package/dist/control/index.js +75 -12
  4. package/dist/control/index.js.map +1 -1
  5. package/dist/control/index.mjs +75 -12
  6. package/dist/control/index.mjs.map +1 -1
  7. package/dist/core/index.d.mts +3 -3
  8. package/dist/core/index.d.ts +3 -3
  9. package/dist/core/index.js +69 -1
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/core/index.mjs +69 -1
  12. package/dist/core/index.mjs.map +1 -1
  13. package/dist/debug/index.js.map +1 -1
  14. package/dist/debug/index.mjs.map +1 -1
  15. package/dist/design/index.d.mts +1 -1
  16. package/dist/design/index.d.ts +1 -1
  17. package/dist/{http-server-D7uVANBU.d.mts → http-server-DCdR8WlL.d.mts} +3 -3
  18. package/dist/{http-server-B7KudcYJ.d.ts → http-server-Dj3grQW4.d.ts} +3 -3
  19. package/dist/index.d.mts +5 -5
  20. package/dist/index.d.ts +5 -5
  21. package/dist/index.js +498 -16
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +493 -17
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/react/index.d.mts +5 -5
  26. package/dist/react/index.d.ts +5 -5
  27. package/dist/react/index.js +504 -16
  28. package/dist/react/index.js.map +1 -1
  29. package/dist/react/index.mjs +502 -17
  30. package/dist/react/index.mjs.map +1 -1
  31. package/dist/{registry-DhbGbl9c.d.mts → registry-Cahd8Gn2.d.mts} +16 -1
  32. package/dist/{registry-B8A_EoxS.d.ts → registry-z-3WJsxp.d.ts} +16 -1
  33. package/dist/server/index.d.mts +5 -5
  34. package/dist/server/index.d.ts +5 -5
  35. package/dist/server/index.js +102 -1
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/server/index.mjs +102 -1
  38. package/dist/server/index.mjs.map +1 -1
  39. package/dist/{types-Bcj7ji97.d.mts → types-DAfCeWXc.d.mts} +22 -2
  40. package/dist/{types-BPNuYxDx.d.mts → types-Oz13YLZq.d.mts} +95 -1
  41. package/dist/{types-BPNuYxDx.d.ts → types-Oz13YLZq.d.ts} +95 -1
  42. package/dist/{types-BCYWwyVy.d.ts → types-ajakyp-l.d.ts} +22 -2
  43. package/dist/{useUIBridge-CDZbWxII.d.ts → useUIBridgeToast-D32KXyL1.d.ts} +154 -5
  44. package/dist/{useUIBridge-DfIEq9EQ.d.mts → useUIBridgeToast-GmT1TzUe.d.mts} +154 -5
  45. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -53,9 +53,34 @@ var NativeUIBridgeRegistry = class {
53
53
  this.components = /* @__PURE__ */ new Map();
54
54
  this.workflows = /* @__PURE__ */ new Map();
55
55
  this.eventListeners = /* @__PURE__ */ new Map();
56
+ this.enrichers = {};
57
+ this.snapshotExtras = /* @__PURE__ */ new Map();
56
58
  this.config = config;
57
59
  }
58
60
  // ============================================================================
61
+ // Snapshot Enricher Slots
62
+ // ============================================================================
63
+ /**
64
+ * Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling
65
+ * with a partial set merges into existing slots instead of clobbering them.
66
+ */
67
+ setEnrichers(e) {
68
+ this.enrichers = { ...this.enrichers, ...e };
69
+ }
70
+ /**
71
+ * Register a custom snapshot enricher. The returned object will be
72
+ * `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid
73
+ * clobbering canonical fields. Returns a disposer.
74
+ */
75
+ registerSnapshotEnricher(name, fn) {
76
+ this.snapshotExtras.set(name, fn);
77
+ return () => this.unregisterSnapshotEnricher(name);
78
+ }
79
+ /** Remove a custom snapshot enricher by name */
80
+ unregisterSnapshotEnricher(name) {
81
+ this.snapshotExtras.delete(name);
82
+ }
83
+ // ============================================================================
59
84
  // Element Management
60
85
  // ============================================================================
61
86
  /**
@@ -418,7 +443,7 @@ var NativeUIBridgeRegistry = class {
418
443
  const currentRoute = routeInfo.currentRoute;
419
444
  elements = elements.filter((e) => e.registrationRoute === currentRoute);
420
445
  }
421
- return {
446
+ const snapshot = {
422
447
  timestamp: Date.now(),
423
448
  elements: elements.map((e) => {
424
449
  const handlers = extractHandlerNames(e.props);
@@ -450,6 +475,49 @@ var NativeUIBridgeRegistry = class {
450
475
  currentRoute: routeInfo?.currentRoute ?? null,
451
476
  segments: routeInfo?.segments
452
477
  };
478
+ if (this.enrichers.modalDetector) {
479
+ try {
480
+ snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();
481
+ } catch (error3) {
482
+ if (this.config.verbose) {
483
+ console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error3);
484
+ }
485
+ }
486
+ }
487
+ if (this.enrichers.toastCapture) {
488
+ try {
489
+ snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();
490
+ } catch (error3) {
491
+ if (this.config.verbose) {
492
+ console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error3);
493
+ }
494
+ }
495
+ }
496
+ if (this.enrichers.undoTracker) {
497
+ try {
498
+ snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();
499
+ } catch (error3) {
500
+ if (this.config.verbose) {
501
+ console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error3);
502
+ }
503
+ }
504
+ }
505
+ if (this.snapshotExtras.size > 0) {
506
+ const ctx = { elements, currentRoute: routeInfo?.currentRoute ?? null };
507
+ for (const [name, fn] of this.snapshotExtras) {
508
+ try {
509
+ const extra = fn(ctx);
510
+ if (extra && typeof extra === "object") {
511
+ Object.assign(snapshot, extra);
512
+ }
513
+ } catch (error3) {
514
+ if (this.config.verbose) {
515
+ console.warn(`[ui-bridge-native] snapshot enricher "${name}" threw:`, error3);
516
+ }
517
+ }
518
+ }
519
+ }
520
+ return snapshot;
453
521
  }
454
522
  /**
455
523
  * Get registry statistics
@@ -595,6 +663,23 @@ function sleep(ms) {
595
663
  var DefaultNativeActionExecutor = class {
596
664
  constructor(registry) {
597
665
  this.registry = registry;
666
+ this.actionListeners = /* @__PURE__ */ new Set();
667
+ }
668
+ onActionExecuted(listener) {
669
+ this.actionListeners.add(listener);
670
+ return () => {
671
+ this.actionListeners.delete(listener);
672
+ };
673
+ }
674
+ /** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */
675
+ emitActionEvent(event) {
676
+ for (const listener of this.actionListeners) {
677
+ try {
678
+ listener(event);
679
+ } catch (error3) {
680
+ console.warn(`[ui-bridge-native] Action listener error:`, error3);
681
+ }
682
+ }
598
683
  }
599
684
  /**
600
685
  * Execute an action on an element
@@ -608,11 +693,23 @@ var DefaultNativeActionExecutor = class {
608
693
  registered = findElementByIdentifier(elementId) ?? void 0;
609
694
  }
610
695
  if (!registered) {
696
+ const failTime = Date.now();
697
+ const errorMsg = `Element not found: ${elementId}`;
698
+ this.emitActionEvent({
699
+ elementId,
700
+ action: request.action,
701
+ params: request.params,
702
+ success: false,
703
+ error: errorMsg,
704
+ timestamp: failTime,
705
+ requestId: request.requestId,
706
+ durationMs: failTime - startTime
707
+ });
611
708
  return {
612
709
  success: false,
613
- error: `Element not found: ${elementId}`,
614
- durationMs: Date.now() - startTime,
615
- timestamp: Date.now(),
710
+ error: errorMsg,
711
+ durationMs: failTime - startTime,
712
+ timestamp: failTime,
616
713
  requestId: request.requestId
617
714
  };
618
715
  }
@@ -620,33 +717,67 @@ var DefaultNativeActionExecutor = class {
620
717
  const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);
621
718
  waitDurationMs = waitResult.waitedMs;
622
719
  if (!waitResult.met) {
720
+ const failTime = Date.now();
721
+ const errorMsg = waitResult.error || "Wait condition not met";
722
+ this.emitActionEvent({
723
+ elementId,
724
+ action: request.action,
725
+ params: request.params,
726
+ success: false,
727
+ error: errorMsg,
728
+ timestamp: failTime,
729
+ requestId: request.requestId,
730
+ durationMs: failTime - startTime
731
+ });
623
732
  return {
624
733
  success: false,
625
- error: waitResult.error || "Wait condition not met",
626
- durationMs: Date.now() - startTime,
627
- timestamp: Date.now(),
734
+ error: errorMsg,
735
+ durationMs: failTime - startTime,
736
+ timestamp: failTime,
628
737
  requestId: request.requestId,
629
738
  waitDurationMs
630
739
  };
631
740
  }
632
741
  }
633
742
  const result = await this.performAction(registered, request.action, request.params);
743
+ const successTime = Date.now();
744
+ this.emitActionEvent({
745
+ elementId,
746
+ action: request.action,
747
+ params: request.params,
748
+ success: true,
749
+ timestamp: successTime,
750
+ requestId: request.requestId,
751
+ durationMs: successTime - startTime
752
+ });
634
753
  return {
635
754
  success: true,
636
755
  elementState: registered.getState(),
637
756
  result,
638
- durationMs: Date.now() - startTime,
639
- timestamp: Date.now(),
757
+ durationMs: successTime - startTime,
758
+ timestamp: successTime,
640
759
  requestId: request.requestId,
641
760
  waitDurationMs
642
761
  };
643
762
  } catch (error3) {
763
+ const failTime = Date.now();
764
+ const errorMsg = error3 instanceof Error ? error3.message : String(error3);
765
+ this.emitActionEvent({
766
+ elementId,
767
+ action: request.action,
768
+ params: request.params,
769
+ success: false,
770
+ error: errorMsg,
771
+ timestamp: failTime,
772
+ requestId: request.requestId,
773
+ durationMs: failTime - startTime
774
+ });
644
775
  return {
645
776
  success: false,
646
- error: error3 instanceof Error ? error3.message : String(error3),
777
+ error: errorMsg,
647
778
  stack: error3 instanceof Error ? error3.stack : void 0,
648
- durationMs: Date.now() - startTime,
649
- timestamp: Date.now(),
779
+ durationMs: failTime - startTime,
780
+ timestamp: failTime,
650
781
  requestId: request.requestId,
651
782
  waitDurationMs
652
783
  };
@@ -739,7 +870,7 @@ var DefaultNativeActionExecutor = class {
739
870
  if (!params?.text) {
740
871
  throw new Error("Type action requires text parameter");
741
872
  }
742
- if (params.clearFirst) {
873
+ if (params.clearFirst || params.clear) {
743
874
  await this.performClear(element, props);
744
875
  }
745
876
  if (params.delay && params.delay > 0) {
@@ -1972,6 +2103,10 @@ var WS_ROUTES = [
1972
2103
  { pattern: "health", handler: "health", httpMethods: ["GET"] },
1973
2104
  // Elements
1974
2105
  { pattern: "control/elements", handler: "getElements", httpMethods: ["GET"] },
2106
+ // Flat alias — matches the runner UI Bridge's `/ui-bridge/elements` path so
2107
+ // agents can use the same URL across runner + mobile without remembering
2108
+ // the `control/` prefix. Delegates to the same handler.
2109
+ { pattern: "elements", handler: "getElements", httpMethods: ["GET"] },
1975
2110
  { pattern: "control/element/:id", handler: "getElement", httpMethods: ["GET"] },
1976
2111
  { pattern: "control/element/:id/state", handler: "getElementState", httpMethods: ["GET"] },
1977
2112
  { pattern: "control/element/:id/action", handler: "executeAction", httpMethods: ["POST"] },
@@ -2022,6 +2157,87 @@ var WS_ROUTES = [
2022
2157
  // Meta / Introspection
2023
2158
  { pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] }
2024
2159
  ];
2160
+ var HANDLER_DESCRIPTIONS = {
2161
+ health: "Liveness probe; returns app metadata + element count",
2162
+ getElements: "Flat list of registered UI elements",
2163
+ getElement: "Single element by id",
2164
+ getElementState: "Dynamic state (value, focused, checked) for one element",
2165
+ executeAction: 'Run an action on an element (see "actions" below for names)',
2166
+ getComponents: "Higher-level components with action schemas",
2167
+ getComponent: "Component detail + per-action paramSchema",
2168
+ executeComponentAction: "Invoke a component action directly (preferred over button clicks)",
2169
+ find: "AI-powered element search by natural-language query",
2170
+ getSnapshot: "Full snapshot: elements + route + state. Supports ?visibleOnly=true and ?currentRouteOnly=true",
2171
+ getWorkflows: "List registered workflows",
2172
+ runWorkflow: "Trigger a workflow by id",
2173
+ pageRefresh: "Reload the current route",
2174
+ pageNavigate: "Imperative router.push(url) \u2014 preferred over tapping tab buttons",
2175
+ pageReplace: "router.replace(url)",
2176
+ pageGoBack: "router.back()",
2177
+ pageGoForward: "router.forward()",
2178
+ getScreenshot: "PNG screenshot of the current screen",
2179
+ getElementStyles: "Computed styles for an element (design review)",
2180
+ getElementStateStyles: "Computed styles per interaction state (hover/focus/disabled)",
2181
+ getDesignSnapshot: "Visual + style snapshot for design review",
2182
+ getResponsiveSnapshots: "Screenshots at multiple viewport widths",
2183
+ runDesignAudit: "Accessibility + visual audit",
2184
+ loadStyleGuide: "Load a design system style guide",
2185
+ getStyleGuide: "Fetch the currently loaded style guide",
2186
+ clearStyleGuide: "Unload the style guide",
2187
+ evaluateQuality: "Score the current UI against a quality context",
2188
+ getQualityContexts: "List available quality evaluation contexts",
2189
+ saveBaseline: "Save a quality baseline snapshot",
2190
+ diffBaseline: "Diff current UI against a saved baseline",
2191
+ getMethods: "List every handler name (legacy introspection)"
2192
+ };
2193
+ var ACTION_REFERENCE = [
2194
+ { name: "press", aliases: ["click"], params: "PressActionParams (optional position)" },
2195
+ { name: "longPress", params: "PressActionParams (optional position)" },
2196
+ { name: "doubleTap", params: "none" },
2197
+ {
2198
+ name: "type",
2199
+ params: "TypeActionParams { text, delay?, clear? | clearFirst? } \u2014 `clear:true` replaces content atomically"
2200
+ },
2201
+ { name: "clear", params: "none \u2014 empties the input" },
2202
+ { name: "focus", params: "none" },
2203
+ { name: "blur", params: "none" },
2204
+ { name: "scroll", params: "ScrollActionParams { offset? {x,y}, to? }" },
2205
+ { name: "swipe", params: "SwipeActionParams { direction, distance }" },
2206
+ { name: "toggle", params: "none \u2014 flips boolean controls" }
2207
+ ];
2208
+ function buildRoutesPayload() {
2209
+ const entries = [];
2210
+ for (const r of WS_ROUTES) {
2211
+ if (!r.httpMethods) continue;
2212
+ for (const m of r.httpMethods) {
2213
+ entries.push({ method: m, path: `/ui-bridge/${r.pattern}` });
2214
+ }
2215
+ }
2216
+ entries.push({ method: "GET", path: "/ui-bridge/_help" });
2217
+ entries.push({ method: "GET", path: "/ui-bridge/_routes" });
2218
+ entries.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
2219
+ return { routes: entries, count: entries.length };
2220
+ }
2221
+ function buildHelpPayload() {
2222
+ return {
2223
+ version: 1,
2224
+ description: "UI Bridge Native HTTP surface. Prefix every path with /ui-bridge/.",
2225
+ routes: WS_ROUTES.map((r) => ({
2226
+ pattern: r.pattern,
2227
+ httpMethods: r.httpMethods ?? [],
2228
+ wsOnly: !r.httpMethods,
2229
+ handler: r.handler,
2230
+ description: HANDLER_DESCRIPTIONS[r.handler] ?? ""
2231
+ })),
2232
+ actions: ACTION_REFERENCE,
2233
+ tips: [
2234
+ "Start with `GET /ui-bridge/control/snapshot?visibleOnly=true` to see what the user actually sees.",
2235
+ "Prefer `control/component/:id/action/:actionId` over button presses \u2014 typed paramSchema, no button-id guessing.",
2236
+ "Use `pageNavigate` for tab switches instead of pressing tab buttons; gesture state often swallows clicks.",
2237
+ "The `type` action supports `clear:true` to replace existing text atomically in one call."
2238
+ ]
2239
+ };
2240
+ }
2025
2241
  var NativeUIBridgeServer = class {
2026
2242
  constructor(registry, executor, config = {}) {
2027
2243
  this.registry = registry;
@@ -2038,7 +2254,9 @@ var NativeUIBridgeServer = class {
2038
2254
  cors: true,
2039
2255
  ...config
2040
2256
  };
2041
- this.handlers = createServerHandlers(registry, executor);
2257
+ this.handlers = createServerHandlers(registry, executor, {
2258
+ appInfo: this.config.appInfo
2259
+ });
2042
2260
  this.handlers.getMethods = async () => {
2043
2261
  const methods = WS_ROUTES.map((r) => ({
2044
2262
  method: r.pattern,
@@ -2842,6 +3060,20 @@ var NativeUIBridgeServer = class {
2842
3060
  };
2843
3061
  }
2844
3062
  const stripped = path.slice(PREFIX.length);
3063
+ if (method === "GET" && stripped === "_help") {
3064
+ return {
3065
+ success: true,
3066
+ data: buildHelpPayload(),
3067
+ timestamp: Date.now()
3068
+ };
3069
+ }
3070
+ if (method === "GET" && stripped === "_routes") {
3071
+ return {
3072
+ success: true,
3073
+ data: buildRoutesPayload(),
3074
+ timestamp: Date.now()
3075
+ };
3076
+ }
2845
3077
  let patternMatchedButWrongVerb = false;
2846
3078
  for (const route of WS_ROUTES) {
2847
3079
  const pathParams = parsePath(route.pattern, stripped);
@@ -3463,6 +3695,186 @@ function parseQueryParams(path) {
3463
3695
  }
3464
3696
  return result;
3465
3697
  }
3698
+
3699
+ // src/modal/modal-detector.ts
3700
+ var ModalDetector = class {
3701
+ constructor() {
3702
+ /** Map preserves O(1) idempotent replace by id; ordering comes from detectedAt. */
3703
+ this.modals = /* @__PURE__ */ new Map();
3704
+ }
3705
+ pushModal(modal) {
3706
+ const existing = this.modals.get(modal.id);
3707
+ const detectedAt = existing ? existing.detectedAt : Date.now();
3708
+ const next = {
3709
+ id: modal.id,
3710
+ title: modal.title,
3711
+ type: modal.type ?? "modal",
3712
+ blocking: modal.blocking ?? true,
3713
+ dismissible: modal.dismissible ?? true,
3714
+ detectedAt
3715
+ };
3716
+ this.modals.set(modal.id, next);
3717
+ }
3718
+ dismissModal(id) {
3719
+ this.modals.delete(id);
3720
+ }
3721
+ getSnapshotModalContext() {
3722
+ const active = this.getActive();
3723
+ const ctx = {
3724
+ modals: active,
3725
+ hasBlockingModal: active.some((m) => m.blocking),
3726
+ count: active.length
3727
+ };
3728
+ if (active.length > 0) {
3729
+ ctx.topModal = active[active.length - 1];
3730
+ }
3731
+ return ctx;
3732
+ }
3733
+ getActive() {
3734
+ return Array.from(this.modals.values()).sort((a, b) => a.detectedAt - b.detectedAt);
3735
+ }
3736
+ clear() {
3737
+ this.modals.clear();
3738
+ }
3739
+ };
3740
+
3741
+ // src/toast/toast-capture.ts
3742
+ var DEFAULT_MAX_RECENT = 50;
3743
+ var ToastCapture = class {
3744
+ constructor(config = {}) {
3745
+ this.active = /* @__PURE__ */ new Map();
3746
+ this.recent = [];
3747
+ this.totalCaptured = 0;
3748
+ this.maxRecent = config.maxRecent ?? DEFAULT_MAX_RECENT;
3749
+ }
3750
+ recordToast(toast) {
3751
+ if (this.active.has(toast.id)) {
3752
+ return;
3753
+ }
3754
+ const appearedAt = toast.appearedAt ?? Date.now();
3755
+ const entry = {
3756
+ id: toast.id,
3757
+ message: toast.message,
3758
+ level: toast.level ?? "info",
3759
+ appearedAt,
3760
+ visible: true,
3761
+ durationMs: 0
3762
+ };
3763
+ this.active.set(toast.id, entry);
3764
+ this.totalCaptured += 1;
3765
+ }
3766
+ dismissToast(id) {
3767
+ const entry = this.active.get(id);
3768
+ if (!entry) {
3769
+ return;
3770
+ }
3771
+ this.active.delete(id);
3772
+ const dismissedAt = Date.now();
3773
+ const dismissed = {
3774
+ ...entry,
3775
+ dismissedAt,
3776
+ visible: false,
3777
+ durationMs: dismissedAt - entry.appearedAt
3778
+ };
3779
+ this.recent.push(dismissed);
3780
+ if (this.recent.length > this.maxRecent) {
3781
+ this.recent.shift();
3782
+ }
3783
+ }
3784
+ getSnapshotToastContext() {
3785
+ const now = Date.now();
3786
+ const active = Array.from(this.active.values()).map((entry) => ({
3787
+ ...entry,
3788
+ durationMs: now - entry.appearedAt
3789
+ }));
3790
+ return {
3791
+ active,
3792
+ recent: [...this.recent],
3793
+ totalCaptured: this.totalCaptured
3794
+ };
3795
+ }
3796
+ clear() {
3797
+ this.active.clear();
3798
+ this.recent = [];
3799
+ this.totalCaptured = 0;
3800
+ }
3801
+ };
3802
+
3803
+ // src/undo/undo-tracker.ts
3804
+ var DEFAULT_MAX_HISTORY = 50;
3805
+ function buildSummary(ctx) {
3806
+ if (!ctx.canUndo && !ctx.canRedo) {
3807
+ return "no undo available";
3808
+ }
3809
+ const parts = [];
3810
+ if (ctx.canUndo) {
3811
+ parts.push(`undo: '${ctx.undoDescription ?? "available"}'`);
3812
+ }
3813
+ if (ctx.canRedo) {
3814
+ parts.push(`redo: '${ctx.redoDescription ?? "available"}'`);
3815
+ }
3816
+ return parts.join(" / ");
3817
+ }
3818
+ var UndoTracker = class {
3819
+ constructor(config = {}) {
3820
+ this.actions = [];
3821
+ this.declaredState = null;
3822
+ this.maxHistory = config.maxHistory ?? DEFAULT_MAX_HISTORY;
3823
+ }
3824
+ recordAction(record) {
3825
+ this.actions.push(record);
3826
+ if (this.actions.length > this.maxHistory) {
3827
+ this.actions = this.actions.slice(-this.maxHistory);
3828
+ }
3829
+ }
3830
+ setDeclaredState(state) {
3831
+ this.declaredState = state;
3832
+ }
3833
+ getActionHistory() {
3834
+ return this.actions.slice();
3835
+ }
3836
+ getSnapshotUndoContext() {
3837
+ const declared = this.declaredState;
3838
+ if (declared) {
3839
+ const summary2 = buildSummary({
3840
+ canUndo: declared.canUndo,
3841
+ canRedo: declared.canRedo,
3842
+ undoDescription: declared.undoDescription,
3843
+ redoDescription: declared.redoDescription
3844
+ });
3845
+ return {
3846
+ canUndo: declared.canUndo,
3847
+ canRedo: declared.canRedo,
3848
+ undoDescription: declared.undoDescription,
3849
+ redoDescription: declared.redoDescription,
3850
+ undoDepth: declared.undoDepth,
3851
+ redoDepth: declared.redoDepth,
3852
+ summary: summary2
3853
+ };
3854
+ }
3855
+ const canUndo = this.actions.some((a) => a.reversible !== false);
3856
+ const canRedo = false;
3857
+ const lastReversible = [...this.actions].reverse().find((a) => a.reversible !== false);
3858
+ const undoDescription = lastReversible?.description;
3859
+ const summary = buildSummary({ canUndo, canRedo, undoDescription });
3860
+ return {
3861
+ canUndo,
3862
+ canRedo,
3863
+ undoDescription,
3864
+ summary
3865
+ };
3866
+ }
3867
+ executeUndo() {
3868
+ return { ok: false, reason: "Not implemented in native" };
3869
+ }
3870
+ executeRedo() {
3871
+ return { ok: false, reason: "Not implemented in native" };
3872
+ }
3873
+ clear() {
3874
+ this.actions = [];
3875
+ this.declaredState = null;
3876
+ }
3877
+ };
3466
3878
  var UIBridgeNativeContext = createContext(null);
3467
3879
  function UIBridgeNativeProvider({
3468
3880
  children,
@@ -3479,6 +3891,9 @@ function UIBridgeNativeProvider({
3479
3891
  }) {
3480
3892
  const registryRef = useRef(null);
3481
3893
  const executorRef = useRef(null);
3894
+ const modalDetectorRef = useRef(null);
3895
+ const toastCaptureRef = useRef(null);
3896
+ const undoTrackerRef = useRef(null);
3482
3897
  const [serverRunning, setServerRunning] = useState(false);
3483
3898
  if (!registryRef.current) {
3484
3899
  registryRef.current = new NativeUIBridgeRegistry({
@@ -3492,6 +3907,12 @@ function UIBridgeNativeProvider({
3492
3907
  executorRef.current = createNativeActionExecutor(registry);
3493
3908
  }
3494
3909
  const executor = executorRef.current;
3910
+ if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();
3911
+ if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();
3912
+ if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();
3913
+ const modalDetector = modalDetectorRef.current;
3914
+ const toastCapture = toastCaptureRef.current;
3915
+ const undoTracker = undoTrackerRef.current;
3495
3916
  const serverRef = useRef(null);
3496
3917
  const eventBridgeRef = useRef(null);
3497
3918
  const announcerRef = useRef(null);
@@ -3506,7 +3927,8 @@ function UIBridgeNativeProvider({
3506
3927
  }
3507
3928
  const server = createNativeServer(registry, executor, {
3508
3929
  serverPort: config.serverPort || 8087,
3509
- cors: true
3930
+ cors: true,
3931
+ appInfo: config.appInfo
3510
3932
  });
3511
3933
  server.setAdapter(serverAdapter);
3512
3934
  if (navigationProvider) {
@@ -3593,7 +4015,8 @@ function UIBridgeNativeProvider({
3593
4015
  try {
3594
4016
  const bareServer = createNativeServer(registry, executor, {
3595
4017
  serverPort: config.serverPort || 8087,
3596
- cors: true
4018
+ cors: true,
4019
+ appInfo: config.appInfo
3597
4020
  });
3598
4021
  if (navigationProvider) bareServer.setNavigationProvider(navigationProvider);
3599
4022
  if (screenshotProvider) bareServer.setScreenshotProvider(screenshotProvider);
@@ -3678,6 +4101,22 @@ function UIBridgeNativeProvider({
3678
4101
  lastRoute = current;
3679
4102
  });
3680
4103
  }, [routeProvider, registry]);
4104
+ useEffect(() => {
4105
+ registry.setEnrichers({ modalDetector, toastCapture, undoTracker });
4106
+ }, [registry, modalDetector, toastCapture, undoTracker]);
4107
+ useEffect(() => {
4108
+ const unsubscribe = executor.onActionExecuted((event) => {
4109
+ if (!event.success) return;
4110
+ undoTracker.recordAction({
4111
+ id: event.requestId ?? `action-${event.timestamp}`,
4112
+ type: event.action,
4113
+ targetId: event.elementId,
4114
+ timestamp: event.timestamp,
4115
+ reversible: true
4116
+ });
4117
+ });
4118
+ return unsubscribe;
4119
+ }, [executor, undoTracker]);
3681
4120
  const on = useCallback(
3682
4121
  (type, listener) => registry.on(type, listener),
3683
4122
  [registry]
@@ -3692,6 +4131,9 @@ function UIBridgeNativeProvider({
3692
4131
  config,
3693
4132
  registry,
3694
4133
  executor,
4134
+ modalDetector,
4135
+ toastCapture,
4136
+ undoTracker,
3695
4137
  getElements,
3696
4138
  getComponents,
3697
4139
  createSnapshot,
@@ -3708,6 +4150,9 @@ function UIBridgeNativeProvider({
3708
4150
  config,
3709
4151
  registry,
3710
4152
  executor,
4153
+ modalDetector,
4154
+ toastCapture,
4155
+ undoTracker,
3711
4156
  getElements,
3712
4157
  getComponents,
3713
4158
  createSnapshot,
@@ -4188,6 +4633,37 @@ function useUIBridgeRequired() {
4188
4633
  }
4189
4634
  return result;
4190
4635
  }
4636
+ function useUIBridgeModal(opts) {
4637
+ const { modalDetector } = useUIBridgeNative();
4638
+ const { id, title, type, blocking, dismissible } = opts;
4639
+ useEffect(() => {
4640
+ modalDetector.pushModal({ id, title, type, blocking, dismissible });
4641
+ return () => {
4642
+ modalDetector.dismissModal(id);
4643
+ };
4644
+ }, [modalDetector, id, title, type, blocking, dismissible]);
4645
+ }
4646
+ function useUIBridgeToast(opts) {
4647
+ const { toastCapture } = useUIBridgeNative();
4648
+ const { id, message, level } = opts;
4649
+ useEffect(() => {
4650
+ toastCapture.recordToast({ id, message, level });
4651
+ return () => {
4652
+ toastCapture.dismissToast(id);
4653
+ };
4654
+ }, [toastCapture, id, message, level]);
4655
+ }
4656
+ function useToastRecorder() {
4657
+ const { toastCapture } = useUIBridgeNative();
4658
+ const recordToast = useCallback(
4659
+ (toast) => {
4660
+ toastCapture.recordToast(toast);
4661
+ },
4662
+ [toastCapture]
4663
+ );
4664
+ const dismissToast = useCallback((id) => toastCapture.dismissToast(id), [toastCapture]);
4665
+ return { recordToast, dismissToast };
4666
+ }
4191
4667
 
4192
4668
  // src/server/types.ts
4193
4669
  var UI_BRIDGE_NATIVE_ROUTES = {
@@ -5089,6 +5565,6 @@ var styles = StyleSheet.create({
5089
5565
  }
5090
5566
  });
5091
5567
 
5092
- export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, NativeUIBridgeRegistry, NativeUIBridgeServer, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, WebSocketConnection, WebSocketEventBridge, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, mapNativeStyleToExtended, matchesIdentifier, parseTreePath, resetGlobalRegistry, setGlobalRegistry, useUIBridge, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
5568
+ export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, ModalDetector, NativeUIBridgeRegistry, NativeUIBridgeServer, ToastCapture, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, UndoTracker, WebSocketConnection, WebSocketEventBridge, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, mapNativeStyleToExtended, matchesIdentifier, parseTreePath, resetGlobalRegistry, setGlobalRegistry, useToastRecorder, useUIBridge, useUIBridgeModal, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIBridgeToast, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
5093
5569
  //# sourceMappingURL=index.mjs.map
5094
5570
  //# sourceMappingURL=index.mjs.map