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