@qontinui/ui-bridge-native 0.6.8 → 0.6.10

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":
@@ -935,9 +935,10 @@ var DefaultNativeActionExecutor = class {
935
935
  requestId: request.requestId,
936
936
  durationMs: successTime - startTime
937
937
  });
938
+ const fresh = this.registry.getElement(registered.id) ?? registered;
938
939
  return {
939
940
  success: true,
940
- elementState: registered.getState(),
941
+ elementState: fresh.getState(),
941
942
  result,
942
943
  durationMs: successTime - startTime,
943
944
  timestamp: successTime,
@@ -990,6 +991,8 @@ var DefaultNativeActionExecutor = class {
990
991
  return this.performDoubleTap(props);
991
992
  case "type":
992
993
  return this.performType(element, props, params);
994
+ case "setValue":
995
+ return this.performSetValue(element, props, params);
993
996
  case "clear":
994
997
  return this.performClear(element, props);
995
998
  case "focus":
@@ -1082,6 +1085,38 @@ var DefaultNativeActionExecutor = class {
1082
1085
  this.registry.updateElementState(element.id, { value: params.text });
1083
1086
  }
1084
1087
  }
1088
+ /**
1089
+ * Perform setValue action.
1090
+ *
1091
+ * Web/runner UI Bridge parity: `setValue` replaces the input's value
1092
+ * atomically in a single call (no per-character keystroke simulation),
1093
+ * mirroring the web bridge's `setValue` semantics. On native there is no
1094
+ * DOM value to assign, so we drive the controlled-input handler
1095
+ * (`onChangeText`, falling back to `onChange`) with the full string. The
1096
+ * registry's `state.value` is then synced so a subsequent GET on the
1097
+ * element reflects the new value.
1098
+ *
1099
+ * Unlike `type`, this does not append to existing content and ignores
1100
+ * `delay` — it is the deterministic "set the field to exactly this" action.
1101
+ */
1102
+ async performSetValue(element, props, params) {
1103
+ const next = typeof params?.text === "string" ? params.text : typeof params?.value === "string" ? params.value : void 0;
1104
+ if (typeof next !== "string") {
1105
+ throw new Error('setValue action requires a string "text" (or "value") parameter');
1106
+ }
1107
+ if (typeof props.onChangeText === "function") {
1108
+ props.onChangeText(next);
1109
+ } else if (typeof props.onChange === "function") {
1110
+ props.onChange({
1111
+ nativeEvent: { text: next }
1112
+ });
1113
+ } else {
1114
+ throw new Error("No text change handler found on element");
1115
+ }
1116
+ if (element) {
1117
+ this.registry.updateElementState(element.id, { value: next });
1118
+ }
1119
+ }
1085
1120
  /**
1086
1121
  * Perform clear action
1087
1122
  */
@@ -2074,6 +2109,13 @@ function matchElementsByText(registry, text, opts) {
2074
2109
  scored.sort((a, b) => a.rank - b.rank);
2075
2110
  return scored.map((s) => s.element);
2076
2111
  }
2112
+ function resolveElementState(element) {
2113
+ const state = element.getState();
2114
+ if (element.type === "input" && state.value === void 0 && typeof element.props?.value === "string") {
2115
+ return { ...state, value: element.props.value };
2116
+ }
2117
+ return state;
2118
+ }
2077
2119
  async function executeWorkflowStep(step, registry, executor) {
2078
2120
  const startTime = Date.now();
2079
2121
  try {
@@ -2310,7 +2352,7 @@ function createServerHandlers(registry, executor, config) {
2310
2352
  type: element.type,
2311
2353
  label: element.label,
2312
2354
  identifier: element.getIdentifier(),
2313
- state: element.getState(),
2355
+ state: resolveElementState(element),
2314
2356
  actions: element.actions,
2315
2357
  customActions: element.customActions ? Object.keys(element.customActions) : void 0,
2316
2358
  registeredHandlers: handlers.length > 0 ? handlers : void 0,
@@ -2324,18 +2366,19 @@ function createServerHandlers(registry, executor, config) {
2324
2366
  if (!element) {
2325
2367
  return error2(`Element not found: ${id}`, "ELEMENT_NOT_FOUND");
2326
2368
  }
2327
- return success2({ state: element.getState() });
2369
+ return success2({ state: resolveElementState(element) });
2328
2370
  },
2329
2371
  executeAction: async (ctx) => {
2330
- const { id } = ctx.params;
2331
- const body = ctx.body;
2332
- if (!body?.action) {
2372
+ const rawBody = ctx.body ?? {};
2373
+ const envelope = rawBody.request && typeof rawBody.request === "object" ? rawBody.request : rawBody;
2374
+ const id = ctx.params.id ?? rawBody.id ?? "";
2375
+ if (!envelope?.action) {
2333
2376
  return error2("Action is required", "INVALID_REQUEST");
2334
2377
  }
2335
2378
  const response = await executor.executeAction(id, {
2336
- action: body.action,
2337
- params: body.params,
2338
- waitOptions: body.waitOptions
2379
+ action: envelope.action,
2380
+ params: envelope.params,
2381
+ waitOptions: envelope.waitOptions
2339
2382
  });
2340
2383
  if (!response.success) {
2341
2384
  return error2(response.error || "Action failed", "ACTION_FAILED");
@@ -2847,7 +2890,8 @@ function createServerHandlers(registry, executor, config) {
2847
2890
  return success2({
2848
2891
  entries: [],
2849
2892
  count: 0,
2850
- bufferSize: 0
2893
+ bufferSize: 0,
2894
+ installed: false
2851
2895
  });
2852
2896
  }
2853
2897
  const entries = buf.entries({
@@ -2857,7 +2901,8 @@ function createServerHandlers(registry, executor, config) {
2857
2901
  return success2({
2858
2902
  entries,
2859
2903
  count: entries.length,
2860
- bufferSize: buf.size()
2904
+ bufferSize: buf.size(),
2905
+ installed: buf.isInstalled()
2861
2906
  });
2862
2907
  },
2863
2908
  getNetworkRequests: async (ctx) => {
@@ -2868,7 +2913,8 @@ function createServerHandlers(registry, executor, config) {
2868
2913
  return success2({
2869
2914
  entries: [],
2870
2915
  count: 0,
2871
- bufferSize: 0
2916
+ bufferSize: 0,
2917
+ installed: false
2872
2918
  });
2873
2919
  }
2874
2920
  const entries = buf.entries({
@@ -2878,7 +2924,8 @@ function createServerHandlers(registry, executor, config) {
2878
2924
  return success2({
2879
2925
  entries,
2880
2926
  count: entries.length,
2881
- bufferSize: buf.size()
2927
+ bufferSize: buf.size(),
2928
+ installed: buf.isInstalled()
2882
2929
  });
2883
2930
  },
2884
2931
  // Test hooks — modal stack manipulation
@@ -2970,7 +3017,7 @@ function createServerHandlers(registry, executor, config) {
2970
3017
  };
2971
3018
  if (config?.appInfo) {
2972
3019
  response.uiBridge = {
2973
- version: "0.3.0",
3020
+ version: "0.6.10",
2974
3021
  ...config.appInfo,
2975
3022
  capabilities: ["elements", "components", "actions", "design"],
2976
3023
  elementCount: stats.elements,
@@ -3297,7 +3344,10 @@ var WS_ROUTES = [
3297
3344
  // same handler at both paths to keep agents portable across platforms.
3298
3345
  { pattern: "ai/find", handler: "find", httpMethods: ["POST"] },
3299
3346
  { pattern: "control/snapshot", handler: "getSnapshot", httpMethods: ["GET"] },
3300
- { pattern: "control/discover", handler: "getSnapshot" },
3347
+ // Discovery alias — web/runner parity with `POST /control/discover` (a
3348
+ // legacy alias for snapshot/find). Mounted on HTTP (GET+POST) so mobile
3349
+ // doesn't 404 where web returns 200; delegates to the snapshot handler.
3350
+ { pattern: "control/discover", handler: "getSnapshot", httpMethods: ["GET", "POST"] },
3301
3351
  // Page health — accepts both GET and POST so the canonical skill invocation
3302
3352
  // (`POST .../control/page-health` with an empty body) and ad-hoc curls both
3303
3353
  // work without a body-shape gotcha.
@@ -3391,19 +3441,26 @@ var WS_ROUTES = [
3391
3441
  httpMethods: ["POST"],
3392
3442
  requiresTestHooks: true
3393
3443
  },
3394
- // Observability — last-N console errors + network requests. testHooks-gated
3395
- // because patching `console.error` / `fetch` is invasive in production.
3444
+ // Observability — last-N console errors + network requests.
3445
+ //
3446
+ // Always mounted (not testHooks-gated) so they return a schema-valid 200
3447
+ // on every build instead of a 404 that agents can't distinguish from a
3448
+ // missing route. The INVASIVE part — monkey-patching `console.error` /
3449
+ // `fetch` / `XMLHttpRequest` — stays gated behind `testHooks` (see
3450
+ // NativeUIBridgeServer.start → buffer.install()). When the patches aren't
3451
+ // installed the handlers report `installed: false` + empty entries, so
3452
+ // production builds expose the contract without observing real users'
3453
+ // console/network traffic. Set `features.testHooks` (typically on
3454
+ // `__DEV__`) to populate the buffers with live data.
3396
3455
  {
3397
3456
  pattern: "control/console-errors",
3398
3457
  handler: "getConsoleErrors",
3399
- httpMethods: ["GET"],
3400
- requiresTestHooks: true
3458
+ httpMethods: ["GET"]
3401
3459
  },
3402
3460
  {
3403
3461
  pattern: "sdk/network-requests",
3404
3462
  handler: "getNetworkRequests",
3405
- httpMethods: ["GET"],
3406
- requiresTestHooks: true
3463
+ httpMethods: ["GET"]
3407
3464
  }
3408
3465
  ];
3409
3466
  var HANDLER_DESCRIPTIONS = {
@@ -3450,8 +3507,8 @@ var HANDLER_DESCRIPTIONS = {
3450
3507
  typeInto: "App-agnostic: type into the input matching a label: body { label, text, clear?, visibleOnly? }. (selector form is web-only)",
3451
3508
  readValue: "STUB \u2014 selector-based; NOT_SUPPORTED on native (no DOM). Use /control/element/:id/state to read a value.",
3452
3509
  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>",
3510
+ 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.",
3511
+ 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
3512
  pushModal: "TEST HOOK \u2014 push a modal onto the snapshot.modalStack via the registry ModalDetector",
3456
3513
  dismissModal: "TEST HOOK \u2014 dismiss a modal by id from the snapshot.modalStack via the registry ModalDetector",
3457
3514
  keepAwake: "TEST HOOK \u2014 keep the device screen awake via the injected keepAwakeProvider: body { enabled: boolean, durationMs?: number }"
@@ -5622,7 +5679,8 @@ function useUIElement(options) {
5622
5679
  parentPath,
5623
5680
  style,
5624
5681
  stateStyles: stateStylesProp,
5625
- handlers: handlersProp
5682
+ handlers: handlersProp,
5683
+ value: valueProp
5626
5684
  } = options;
5627
5685
  const treePath = parentPath ? `${parentPath}/${id}` : id;
5628
5686
  const bridgeProps = react.useMemo(
@@ -5648,9 +5706,12 @@ function useUIElement(options) {
5648
5706
  });
5649
5707
  registeredRef.current = true;
5650
5708
  registeredIdRef.current = id;
5709
+ if (typeof valueProp === "string") {
5710
+ bridge.registry.updateElementState(id, { value: valueProp });
5711
+ }
5651
5712
  setRegistered(true);
5652
5713
  scheduleDeadButtonWarning(bridge, id, type);
5653
- }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp]);
5714
+ }, [bridge, id, type, label, actions, customActions, treePath, style, stateStylesProp, valueProp]);
5654
5715
  const unregister = react.useCallback(() => {
5655
5716
  if (!bridge || !registeredRef.current) return;
5656
5717
  bridge.registry.unregisterElement(registeredIdRef.current ?? id);
@@ -5780,6 +5841,13 @@ function useUIElement(options) {
5780
5841
  }
5781
5842
  };
5782
5843
  }, [bridge, registered, id, handlersProp]);
5844
+ react.useEffect(() => {
5845
+ if (!bridge || !registered) return;
5846
+ if (typeof valueProp !== "string") return;
5847
+ const current = bridge.registry.getElement(id)?.getState().value;
5848
+ if (current === valueProp) return;
5849
+ bridge.registry.updateElementState(id, { value: valueProp });
5850
+ }, [bridge, registered, id, valueProp]);
5783
5851
  react.useCallback(
5784
5852
  (props) => {
5785
5853
  propsRef.current = { ...propsRef.current, ...props };