@qontinui/ui-bridge-native 0.6.8 → 0.6.9

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.
@@ -26,7 +26,7 @@ function inferActions(type) {
26
26
  case "pressable":
27
27
  return [...baseActions, "click", "press", "longPress", "doubleTap"];
28
28
  case "input":
29
- return [...baseActions, "click", "press", "type", "clear"];
29
+ return [...baseActions, "click", "press", "type", "setValue", "clear"];
30
30
  case "text":
31
31
  return [...baseActions, "click", "press", "longPress"];
32
32
  case "view":
@@ -988,6 +988,8 @@ var DefaultNativeActionExecutor = class {
988
988
  return this.performDoubleTap(props);
989
989
  case "type":
990
990
  return this.performType(element, props, params);
991
+ case "setValue":
992
+ return this.performSetValue(element, props, params);
991
993
  case "clear":
992
994
  return this.performClear(element, props);
993
995
  case "focus":
@@ -1080,6 +1082,38 @@ var DefaultNativeActionExecutor = class {
1080
1082
  this.registry.updateElementState(element.id, { value: params.text });
1081
1083
  }
1082
1084
  }
1085
+ /**
1086
+ * Perform setValue action.
1087
+ *
1088
+ * Web/runner UI Bridge parity: `setValue` replaces the input's value
1089
+ * atomically in a single call (no per-character keystroke simulation),
1090
+ * mirroring the web bridge's `setValue` semantics. On native there is no
1091
+ * DOM value to assign, so we drive the controlled-input handler
1092
+ * (`onChangeText`, falling back to `onChange`) with the full string. The
1093
+ * registry's `state.value` is then synced so a subsequent GET on the
1094
+ * element reflects the new value.
1095
+ *
1096
+ * Unlike `type`, this does not append to existing content and ignores
1097
+ * `delay` — it is the deterministic "set the field to exactly this" action.
1098
+ */
1099
+ async performSetValue(element, props, params) {
1100
+ const next = typeof params?.text === "string" ? params.text : typeof params?.value === "string" ? params.value : void 0;
1101
+ if (typeof next !== "string") {
1102
+ throw new Error('setValue action requires a string "text" (or "value") parameter');
1103
+ }
1104
+ if (typeof props.onChangeText === "function") {
1105
+ props.onChangeText(next);
1106
+ } else if (typeof props.onChange === "function") {
1107
+ props.onChange({
1108
+ nativeEvent: { text: next }
1109
+ });
1110
+ } else {
1111
+ throw new Error("No text change handler found on element");
1112
+ }
1113
+ if (element) {
1114
+ this.registry.updateElementState(element.id, { value: next });
1115
+ }
1116
+ }
1083
1117
  /**
1084
1118
  * Perform clear action
1085
1119
  */
@@ -2845,7 +2879,8 @@ function createServerHandlers(registry, executor, config) {
2845
2879
  return success2({
2846
2880
  entries: [],
2847
2881
  count: 0,
2848
- bufferSize: 0
2882
+ bufferSize: 0,
2883
+ installed: false
2849
2884
  });
2850
2885
  }
2851
2886
  const entries = buf.entries({
@@ -2855,7 +2890,8 @@ function createServerHandlers(registry, executor, config) {
2855
2890
  return success2({
2856
2891
  entries,
2857
2892
  count: entries.length,
2858
- bufferSize: buf.size()
2893
+ bufferSize: buf.size(),
2894
+ installed: buf.isInstalled()
2859
2895
  });
2860
2896
  },
2861
2897
  getNetworkRequests: async (ctx) => {
@@ -2866,7 +2902,8 @@ function createServerHandlers(registry, executor, config) {
2866
2902
  return success2({
2867
2903
  entries: [],
2868
2904
  count: 0,
2869
- bufferSize: 0
2905
+ bufferSize: 0,
2906
+ installed: false
2870
2907
  });
2871
2908
  }
2872
2909
  const entries = buf.entries({
@@ -2876,7 +2913,8 @@ function createServerHandlers(registry, executor, config) {
2876
2913
  return success2({
2877
2914
  entries,
2878
2915
  count: entries.length,
2879
- bufferSize: buf.size()
2916
+ bufferSize: buf.size(),
2917
+ installed: buf.isInstalled()
2880
2918
  });
2881
2919
  },
2882
2920
  // Test hooks — modal stack manipulation
@@ -3295,7 +3333,10 @@ var WS_ROUTES = [
3295
3333
  // same handler at both paths to keep agents portable across platforms.
3296
3334
  { pattern: "ai/find", handler: "find", httpMethods: ["POST"] },
3297
3335
  { pattern: "control/snapshot", handler: "getSnapshot", httpMethods: ["GET"] },
3298
- { pattern: "control/discover", handler: "getSnapshot" },
3336
+ // Discovery alias — web/runner parity with `POST /control/discover` (a
3337
+ // legacy alias for snapshot/find). Mounted on HTTP (GET+POST) so mobile
3338
+ // doesn't 404 where web returns 200; delegates to the snapshot handler.
3339
+ { pattern: "control/discover", handler: "getSnapshot", httpMethods: ["GET", "POST"] },
3299
3340
  // Page health — accepts both GET and POST so the canonical skill invocation
3300
3341
  // (`POST .../control/page-health` with an empty body) and ad-hoc curls both
3301
3342
  // work without a body-shape gotcha.
@@ -3389,19 +3430,26 @@ var WS_ROUTES = [
3389
3430
  httpMethods: ["POST"],
3390
3431
  requiresTestHooks: true
3391
3432
  },
3392
- // Observability — last-N console errors + network requests. testHooks-gated
3393
- // because patching `console.error` / `fetch` is invasive in production.
3433
+ // Observability — last-N console errors + network requests.
3434
+ //
3435
+ // Always mounted (not testHooks-gated) so they return a schema-valid 200
3436
+ // on every build instead of a 404 that agents can't distinguish from a
3437
+ // missing route. The INVASIVE part — monkey-patching `console.error` /
3438
+ // `fetch` / `XMLHttpRequest` — stays gated behind `testHooks` (see
3439
+ // NativeUIBridgeServer.start → buffer.install()). When the patches aren't
3440
+ // installed the handlers report `installed: false` + empty entries, so
3441
+ // production builds expose the contract without observing real users'
3442
+ // console/network traffic. Set `features.testHooks` (typically on
3443
+ // `__DEV__`) to populate the buffers with live data.
3394
3444
  {
3395
3445
  pattern: "control/console-errors",
3396
3446
  handler: "getConsoleErrors",
3397
- httpMethods: ["GET"],
3398
- requiresTestHooks: true
3447
+ httpMethods: ["GET"]
3399
3448
  },
3400
3449
  {
3401
3450
  pattern: "sdk/network-requests",
3402
3451
  handler: "getNetworkRequests",
3403
- httpMethods: ["GET"],
3404
- requiresTestHooks: true
3452
+ httpMethods: ["GET"]
3405
3453
  }
3406
3454
  ];
3407
3455
  var HANDLER_DESCRIPTIONS = {
@@ -3448,8 +3496,8 @@ var HANDLER_DESCRIPTIONS = {
3448
3496
  typeInto: "App-agnostic: type into the input matching a label: body { label, text, clear?, visibleOnly? }. (selector form is web-only)",
3449
3497
  readValue: "STUB \u2014 selector-based; NOT_SUPPORTED on native (no DOM). Use /control/element/:id/state to read a value.",
3450
3498
  findByText: "App-agnostic: list elements matching free-text (no press): body { text, visibleOnly? }",
3451
- getConsoleErrors: "TEST HOOK \u2014 last-N console.error/console.warn entries; ?since=<ms>&limit=<n>",
3452
- getNetworkRequests: "TEST HOOK \u2014 last-N fetch/XHR entries with status + duration; ?since=<ms>&limit=<n>",
3499
+ getConsoleErrors: "Last-N console.error/console.warn entries; ?since=<ms>&limit=<n>. `installed:false` when capture is off (no testHooks) \u2014 entries empty but schema-valid.",
3500
+ getNetworkRequests: "Last-N fetch/XHR entries with status + duration; ?since=<ms>&limit=<n>. `installed:false` when capture is off (no testHooks) \u2014 entries empty but schema-valid.",
3453
3501
  pushModal: "TEST HOOK \u2014 push a modal onto the snapshot.modalStack via the registry ModalDetector",
3454
3502
  dismissModal: "TEST HOOK \u2014 dismiss a modal by id from the snapshot.modalStack via the registry ModalDetector",
3455
3503
  keepAwake: "TEST HOOK \u2014 keep the device screen awake via the injected keepAwakeProvider: body { enabled: boolean, durationMs?: number }"
@@ -5620,7 +5668,8 @@ function useUIElement(options) {
5620
5668
  parentPath,
5621
5669
  style,
5622
5670
  stateStyles: stateStylesProp,
5623
- handlers: handlersProp
5671
+ handlers: handlersProp,
5672
+ value: valueProp
5624
5673
  } = options;
5625
5674
  const treePath = parentPath ? `${parentPath}/${id}` : id;
5626
5675
  const bridgeProps = useMemo(
@@ -5646,9 +5695,12 @@ function useUIElement(options) {
5646
5695
  });
5647
5696
  registeredRef.current = true;
5648
5697
  registeredIdRef.current = id;
5698
+ if (typeof valueProp === "string") {
5699
+ bridge.registry.updateElementState(id, { value: valueProp });
5700
+ }
5649
5701
  setRegistered(true);
5650
5702
  scheduleDeadButtonWarning(bridge, id, type);
5651
- }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp]);
5703
+ }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
5652
5704
  const unregister = useCallback(() => {
5653
5705
  if (!bridge || !registeredRef.current) return;
5654
5706
  bridge.registry.unregisterElement(registeredIdRef.current ?? id);
@@ -5778,6 +5830,13 @@ function useUIElement(options) {
5778
5830
  }
5779
5831
  };
5780
5832
  }, [bridge, registered, id, handlersProp]);
5833
+ useEffect(() => {
5834
+ if (!bridge || !registered) return;
5835
+ if (typeof valueProp !== "string") return;
5836
+ const current = bridge.registry.getElement(id)?.getState().value;
5837
+ if (current === valueProp) return;
5838
+ bridge.registry.updateElementState(id, { value: valueProp });
5839
+ }, [bridge, registered, id, valueProp]);
5781
5840
  useCallback(
5782
5841
  (props) => {
5783
5842
  propsRef.current = { ...propsRef.current, ...props };