@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.
- package/dist/control/action-executor.d.ts +15 -0
- package/dist/control/action-executor.d.ts.map +1 -1
- package/dist/control/index.js +36 -1
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +36 -1
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +1 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -27
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.js +94 -26
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +94 -26
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts +10 -0
- package/dist/react/useUIElement.d.ts.map +1 -1
- package/dist/server/bind-retry.d.ts +64 -0
- package/dist/server/bind-retry.d.ts.map +1 -0
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +103 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +102 -23
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/types.d.ts +14 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
2369
|
+
return success2({ state: resolveElementState(element) });
|
|
2328
2370
|
},
|
|
2329
2371
|
executeAction: async (ctx) => {
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2332
|
-
|
|
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:
|
|
2337
|
-
params:
|
|
2338
|
-
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.
|
|
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
|
-
|
|
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.
|
|
3395
|
-
//
|
|
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: "
|
|
3454
|
-
getNetworkRequests: "
|
|
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 };
|