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