@qontinui/ui-bridge-native 0.6.1 → 0.6.3
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/core/index.js +45 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +45 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +19 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/index.js +333 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +333 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.js +328 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +328 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useUIElement.d.ts +19 -0
- package/dist/react/useUIElement.d.ts.map +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.js +268 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +268 -0
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/page-health.d.ts +72 -0
- package/dist/server/page-health.d.ts.map +1 -0
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -351,6 +351,51 @@ var NativeUIBridgeRegistry = class {
|
|
|
351
351
|
this.emit("element:stateChanged", { id, state: newState });
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Update an element's descriptive metadata (label / accessibilityLabel /
|
|
356
|
+
* testId). Sister to {@link updateElementState} (which carries layout +
|
|
357
|
+
* visibility) and {@link updateElementProps} (which carries event handlers).
|
|
358
|
+
*
|
|
359
|
+
* Idempotent — if every passed field equals the existing value, the call
|
|
360
|
+
* returns without mutating the entry or emitting an event. This keeps
|
|
361
|
+
* `useUIElement().updateLabel(...)` cheap when consumers re-publish from a
|
|
362
|
+
* render path that produces the same string on every tick.
|
|
363
|
+
*
|
|
364
|
+
* Returns `true` when the entry was mutated, `false` otherwise. Callers
|
|
365
|
+
* that want to gate a `console.warn` on no-op invocations can use the
|
|
366
|
+
* return value instead of recomputing equality.
|
|
367
|
+
*/
|
|
368
|
+
updateElementMeta(id, meta) {
|
|
369
|
+
const element = this.elements.get(id);
|
|
370
|
+
if (!element) return false;
|
|
371
|
+
const currentIdentifier = element.getIdentifier();
|
|
372
|
+
const nextLabel = meta.label !== void 0 ? meta.label : element.label;
|
|
373
|
+
const nextAccessibilityLabel = meta.accessibilityLabel !== void 0 ? meta.accessibilityLabel : currentIdentifier.accessibilityLabel;
|
|
374
|
+
const nextTestId = meta.testId !== void 0 ? meta.testId : currentIdentifier.testId;
|
|
375
|
+
const labelChanged = nextLabel !== element.label;
|
|
376
|
+
const accessibilityLabelChanged = nextAccessibilityLabel !== currentIdentifier.accessibilityLabel;
|
|
377
|
+
const testIdChanged = nextTestId !== currentIdentifier.testId;
|
|
378
|
+
if (!labelChanged && !accessibilityLabelChanged && !testIdChanged) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
const updated = {
|
|
382
|
+
...element,
|
|
383
|
+
label: nextLabel,
|
|
384
|
+
getIdentifier: () => ({
|
|
385
|
+
uiId: id,
|
|
386
|
+
testId: nextTestId,
|
|
387
|
+
accessibilityLabel: nextAccessibilityLabel,
|
|
388
|
+
treePath: currentIdentifier.treePath
|
|
389
|
+
})
|
|
390
|
+
};
|
|
391
|
+
this.elements.set(id, updated);
|
|
392
|
+
this.emit("element:registered", {
|
|
393
|
+
id,
|
|
394
|
+
type: element.type,
|
|
395
|
+
label: nextLabel
|
|
396
|
+
});
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
354
399
|
/**
|
|
355
400
|
* Update element props (for action execution)
|
|
356
401
|
*/
|
|
@@ -1783,6 +1828,245 @@ function createDesignHandlers(registry) {
|
|
|
1783
1828
|
};
|
|
1784
1829
|
}
|
|
1785
1830
|
|
|
1831
|
+
// src/server/page-health.ts
|
|
1832
|
+
var GRID_SIZE = 20;
|
|
1833
|
+
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
1834
|
+
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
1835
|
+
var INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
|
|
1836
|
+
"button",
|
|
1837
|
+
"input",
|
|
1838
|
+
"switch",
|
|
1839
|
+
"checkbox",
|
|
1840
|
+
"radio",
|
|
1841
|
+
"touchable",
|
|
1842
|
+
"listItem"
|
|
1843
|
+
]);
|
|
1844
|
+
var ERROR_PHRASES = [
|
|
1845
|
+
"error occurred",
|
|
1846
|
+
"failed to",
|
|
1847
|
+
"exception",
|
|
1848
|
+
"crash",
|
|
1849
|
+
"unavailable",
|
|
1850
|
+
"something went wrong",
|
|
1851
|
+
"could not"
|
|
1852
|
+
];
|
|
1853
|
+
var LOADING_PHRASES = [
|
|
1854
|
+
"loading",
|
|
1855
|
+
"starting",
|
|
1856
|
+
"connecting",
|
|
1857
|
+
"please wait",
|
|
1858
|
+
"initializing",
|
|
1859
|
+
"fetching"
|
|
1860
|
+
];
|
|
1861
|
+
var EMPTY_PHRASES = [
|
|
1862
|
+
"no data",
|
|
1863
|
+
"no results",
|
|
1864
|
+
"nothing here",
|
|
1865
|
+
"empty",
|
|
1866
|
+
"no items",
|
|
1867
|
+
"get started"
|
|
1868
|
+
];
|
|
1869
|
+
function severityRank(s) {
|
|
1870
|
+
if (s === "CRITICAL") return 3;
|
|
1871
|
+
if (s === "WARNING") return 2;
|
|
1872
|
+
return 1;
|
|
1873
|
+
}
|
|
1874
|
+
function worstSeverity(findings) {
|
|
1875
|
+
let worst = "OK";
|
|
1876
|
+
for (const f of findings) {
|
|
1877
|
+
if (severityRank(f.severity) > severityRank(worst)) {
|
|
1878
|
+
worst = f.severity;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return worst;
|
|
1882
|
+
}
|
|
1883
|
+
function truncate(s, n) {
|
|
1884
|
+
if (s.length <= n) return s;
|
|
1885
|
+
return [...s].slice(0, n).join("");
|
|
1886
|
+
}
|
|
1887
|
+
function normalizeRect(state, viewport) {
|
|
1888
|
+
const layout = state.layout;
|
|
1889
|
+
if (!layout) return null;
|
|
1890
|
+
if (viewport.width <= 0 || viewport.height <= 0) return null;
|
|
1891
|
+
const px = layout.pageX ?? layout.x;
|
|
1892
|
+
const py = layout.pageY ?? layout.y;
|
|
1893
|
+
return {
|
|
1894
|
+
x: px / viewport.width,
|
|
1895
|
+
y: py / viewport.height,
|
|
1896
|
+
width: layout.width / viewport.width,
|
|
1897
|
+
height: layout.height / viewport.height
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
function diagnosePageHealth(elements, viewport) {
|
|
1901
|
+
const findings = [];
|
|
1902
|
+
const projected = elements.map((el) => ({
|
|
1903
|
+
el,
|
|
1904
|
+
rect: el.state.visible ? normalizeRect(el.state, viewport) : null
|
|
1905
|
+
}));
|
|
1906
|
+
const visible = projected.filter((p) => p.rect !== null);
|
|
1907
|
+
const grid = Array.from(
|
|
1908
|
+
{ length: GRID_SIZE },
|
|
1909
|
+
() => Array.from({ length: GRID_SIZE }, () => false)
|
|
1910
|
+
);
|
|
1911
|
+
for (const { rect } of visible) {
|
|
1912
|
+
const colStart = Math.max(0, Math.floor(rect.x * GRID_SIZE));
|
|
1913
|
+
const colEnd = Math.min(GRID_SIZE, Math.ceil((rect.x + rect.width) * GRID_SIZE));
|
|
1914
|
+
const rowStart = Math.max(0, Math.floor(rect.y * GRID_SIZE));
|
|
1915
|
+
const rowEnd = Math.min(GRID_SIZE, Math.ceil((rect.y + rect.height) * GRID_SIZE));
|
|
1916
|
+
for (let r = rowStart; r < rowEnd; r++) {
|
|
1917
|
+
for (let c = colStart; c < colEnd; c++) {
|
|
1918
|
+
grid[r][c] = true;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
const totalCells = GRID_SIZE * GRID_SIZE;
|
|
1923
|
+
const halfCells = totalCells / 2;
|
|
1924
|
+
let filled = 0;
|
|
1925
|
+
let leftFilled = 0;
|
|
1926
|
+
let rightFilled = 0;
|
|
1927
|
+
const half = GRID_SIZE / 2;
|
|
1928
|
+
for (let r = 0; r < GRID_SIZE; r++) {
|
|
1929
|
+
for (let c = 0; c < GRID_SIZE; c++) {
|
|
1930
|
+
if (grid[r][c]) {
|
|
1931
|
+
filled++;
|
|
1932
|
+
if (c < half) leftFilled++;
|
|
1933
|
+
else rightFilled++;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
const coveragePct = Math.round(filled / totalCells * 100);
|
|
1938
|
+
const leftHalfPct = Math.round(leftFilled / halfCells * 100);
|
|
1939
|
+
const rightHalfPct = Math.round(rightFilled / halfCells * 100);
|
|
1940
|
+
let spatialSeverity = "OK";
|
|
1941
|
+
if (coveragePct < 15) spatialSeverity = "CRITICAL";
|
|
1942
|
+
else if (coveragePct < 30) spatialSeverity = "WARNING";
|
|
1943
|
+
if (rightHalfPct < 5 && leftHalfPct > 20) spatialSeverity = "CRITICAL";
|
|
1944
|
+
findings.push({
|
|
1945
|
+
check: "spatial_coverage",
|
|
1946
|
+
severity: spatialSeverity,
|
|
1947
|
+
detail: `Elements cover ${coveragePct}% of viewport. Left=${leftHalfPct}%, Right=${rightHalfPct}%`,
|
|
1948
|
+
data: {
|
|
1949
|
+
coverage_pct: coveragePct,
|
|
1950
|
+
left_half_pct: leftHalfPct,
|
|
1951
|
+
right_half_pct: rightHalfPct
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
let sidebar = 0;
|
|
1955
|
+
let header = 0;
|
|
1956
|
+
let content = 0;
|
|
1957
|
+
for (const { rect } of visible) {
|
|
1958
|
+
const cx = rect.x + rect.width / 2;
|
|
1959
|
+
const cy = rect.y + rect.height / 2;
|
|
1960
|
+
if (cx < 0.2) sidebar++;
|
|
1961
|
+
else if (cy < 0.08) header++;
|
|
1962
|
+
else content++;
|
|
1963
|
+
}
|
|
1964
|
+
let layoutSeverity = "OK";
|
|
1965
|
+
if (content === 0) layoutSeverity = "CRITICAL";
|
|
1966
|
+
else if (content < 3) layoutSeverity = "WARNING";
|
|
1967
|
+
findings.push({
|
|
1968
|
+
check: "layout_regions",
|
|
1969
|
+
severity: layoutSeverity,
|
|
1970
|
+
detail: `sidebar=${sidebar}, header=${header}, content=${content}`,
|
|
1971
|
+
data: { sidebar, header, content }
|
|
1972
|
+
});
|
|
1973
|
+
const typeCounts = {};
|
|
1974
|
+
for (const el of elements) {
|
|
1975
|
+
const t = el.type || "unknown";
|
|
1976
|
+
typeCounts[t] = (typeCounts[t] ?? 0) + 1;
|
|
1977
|
+
}
|
|
1978
|
+
const allNav = elements.length > 5 && Object.keys(typeCounts).every((k) => NAV_TYPES.has(k));
|
|
1979
|
+
const diversitySeverity = allNav ? "WARNING" : "OK";
|
|
1980
|
+
findings.push({
|
|
1981
|
+
check: "element_diversity",
|
|
1982
|
+
severity: diversitySeverity,
|
|
1983
|
+
detail: `${Object.keys(typeCounts).length} type(s) across ${elements.length} elements${allNav ? " (navigation-only)" : ""}`,
|
|
1984
|
+
data: { types: typeCounts }
|
|
1985
|
+
});
|
|
1986
|
+
const detectedErrors = [];
|
|
1987
|
+
const detectedLoading = [];
|
|
1988
|
+
const detectedEmpty = [];
|
|
1989
|
+
const detectedCss = [];
|
|
1990
|
+
for (const el of elements) {
|
|
1991
|
+
const elType = el.type ?? "";
|
|
1992
|
+
if (SKIP_TEXT_TYPES.has(elType)) continue;
|
|
1993
|
+
const text = (el.state.textContent ?? "").toString();
|
|
1994
|
+
if (!text) continue;
|
|
1995
|
+
const textLower = text.toLowerCase();
|
|
1996
|
+
for (const p of ERROR_PHRASES) {
|
|
1997
|
+
if (textLower.includes(p)) {
|
|
1998
|
+
detectedErrors.push(truncate(text, 120));
|
|
1999
|
+
break;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
for (const p of LOADING_PHRASES) {
|
|
2003
|
+
if (textLower.includes(p)) {
|
|
2004
|
+
detectedLoading.push(truncate(text, 120));
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
for (const p of EMPTY_PHRASES) {
|
|
2009
|
+
if (textLower.includes(p)) {
|
|
2010
|
+
detectedEmpty.push(truncate(text, 120));
|
|
2011
|
+
break;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
let textSeverity = "OK";
|
|
2016
|
+
if (detectedErrors.length > 0) textSeverity = "CRITICAL";
|
|
2017
|
+
else if (detectedLoading.length > 0 || detectedEmpty.length > 0) textSeverity = "WARNING";
|
|
2018
|
+
findings.push({
|
|
2019
|
+
check: "text_signals",
|
|
2020
|
+
severity: textSeverity,
|
|
2021
|
+
detail: `errors=${detectedErrors.length}, loading=${detectedLoading.length}, empty=${detectedEmpty.length}, css_signals=${detectedCss.length}`,
|
|
2022
|
+
data: {
|
|
2023
|
+
errors: detectedErrors,
|
|
2024
|
+
loading: detectedLoading,
|
|
2025
|
+
empty: detectedEmpty,
|
|
2026
|
+
css_signals: detectedCss
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
let interactiveTotal = 0;
|
|
2030
|
+
let interactiveDisabled = 0;
|
|
2031
|
+
for (const el of elements) {
|
|
2032
|
+
if (!INTERACTIVE_TYPES.has(el.type)) continue;
|
|
2033
|
+
interactiveTotal++;
|
|
2034
|
+
if (el.state.enabled === false) interactiveDisabled++;
|
|
2035
|
+
}
|
|
2036
|
+
const interactiveSeverity = interactiveTotal > 0 && interactiveDisabled / interactiveTotal > 0.5 ? "WARNING" : "OK";
|
|
2037
|
+
findings.push({
|
|
2038
|
+
check: "interactive_readiness",
|
|
2039
|
+
severity: interactiveSeverity,
|
|
2040
|
+
detail: `${interactiveTotal} interactive elements, ${interactiveDisabled} disabled`,
|
|
2041
|
+
data: { total: interactiveTotal, disabled: interactiveDisabled }
|
|
2042
|
+
});
|
|
2043
|
+
let zeroSize = 0;
|
|
2044
|
+
let outsideViewport = 0;
|
|
2045
|
+
for (const { rect } of visible) {
|
|
2046
|
+
if (rect.width === 0 || rect.height === 0) zeroSize++;
|
|
2047
|
+
if (rect.x + rect.width < 0 || rect.y + rect.height < 0 || rect.x > 1 || rect.y > 1) {
|
|
2048
|
+
outsideViewport++;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
const anomalySeverity = zeroSize > 0 || outsideViewport > 0 ? "WARNING" : "OK";
|
|
2052
|
+
findings.push({
|
|
2053
|
+
check: "visual_anomalies",
|
|
2054
|
+
severity: anomalySeverity,
|
|
2055
|
+
detail: `zero_size=${zeroSize}, outside_viewport=${outsideViewport}`,
|
|
2056
|
+
data: { zero_size: zeroSize, outside_viewport: outsideViewport }
|
|
2057
|
+
});
|
|
2058
|
+
const heatmap = grid.map(
|
|
2059
|
+
(row) => row.map((cell) => cell ? "#" : ".").join("")
|
|
2060
|
+
);
|
|
2061
|
+
return {
|
|
2062
|
+
summary: worstSeverity(findings),
|
|
2063
|
+
findings,
|
|
2064
|
+
heatmap,
|
|
2065
|
+
element_count: elements.length,
|
|
2066
|
+
visible_count: visible.length
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1786
2070
|
// src/server/handlers.ts
|
|
1787
2071
|
async function executeWorkflowStep(step, registry, executor) {
|
|
1788
2072
|
const startTime = Date.now();
|
|
@@ -2167,6 +2451,25 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2167
2451
|
const snapshot = registry.createSnapshot(void 0, { visibleOnly, currentRouteOnly });
|
|
2168
2452
|
return success2(snapshot);
|
|
2169
2453
|
},
|
|
2454
|
+
getPageHealth: async (ctx) => {
|
|
2455
|
+
const bodyViewport = ctx.body?.viewport;
|
|
2456
|
+
let viewport = bodyViewport;
|
|
2457
|
+
if (!viewport) {
|
|
2458
|
+
try {
|
|
2459
|
+
const { Dimensions: Dimensions2 } = __require("react-native");
|
|
2460
|
+
const win = Dimensions2.get("window");
|
|
2461
|
+
viewport = { width: win.width, height: win.height };
|
|
2462
|
+
} catch {
|
|
2463
|
+
viewport = { width: 0, height: 0 };
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
const elements = registry.getAllElements().map((e) => ({
|
|
2467
|
+
type: e.type,
|
|
2468
|
+
state: e.getState()
|
|
2469
|
+
}));
|
|
2470
|
+
const report = diagnosePageHealth(elements, viewport);
|
|
2471
|
+
return success2(report);
|
|
2472
|
+
},
|
|
2170
2473
|
// Workflows
|
|
2171
2474
|
getWorkflows: async () => {
|
|
2172
2475
|
const workflows = registry.getAllWorkflows().map((w) => ({
|
|
@@ -2915,6 +3218,10 @@ var WS_ROUTES = [
|
|
|
2915
3218
|
{ pattern: "ai/find", handler: "find", httpMethods: ["POST"] },
|
|
2916
3219
|
{ pattern: "control/snapshot", handler: "getSnapshot", httpMethods: ["GET"] },
|
|
2917
3220
|
{ pattern: "control/discover", handler: "getSnapshot" },
|
|
3221
|
+
// Page health — accepts both GET and POST so the canonical skill invocation
|
|
3222
|
+
// (`POST .../control/page-health` with an empty body) and ad-hoc curls both
|
|
3223
|
+
// work without a body-shape gotcha.
|
|
3224
|
+
{ pattern: "control/page-health", handler: "getPageHealth", httpMethods: ["GET", "POST"] },
|
|
2918
3225
|
// Workflows
|
|
2919
3226
|
{ pattern: "control/workflows", handler: "getWorkflows", httpMethods: ["GET"] },
|
|
2920
3227
|
{ pattern: "control/workflow/:id/run", handler: "runWorkflow", httpMethods: ["POST"] },
|
|
@@ -3019,6 +3326,7 @@ var HANDLER_DESCRIPTIONS = {
|
|
|
3019
3326
|
executeComponentAction: "Invoke a component action directly (preferred over button clicks)",
|
|
3020
3327
|
find: "AI-powered element search by natural-language query",
|
|
3021
3328
|
getSnapshot: "Full snapshot: elements + route + state. Supports ?visibleOnly=true and ?currentRouteOnly=true",
|
|
3329
|
+
getPageHealth: 'Holistic page health diagnostic over current snapshot elements (spatial coverage, layout regions, text/anomaly signals, heatmap). Optional body: { viewport?: { width, height } } to override Dimensions.get("window").',
|
|
3022
3330
|
getWorkflows: "List registered workflows",
|
|
3023
3331
|
runWorkflow: "Trigger a workflow by id",
|
|
3024
3332
|
pageRefresh: "Reload the current route",
|
|
@@ -5166,6 +5474,7 @@ function flattenStateStyles(stateStyles) {
|
|
|
5166
5474
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
5167
5475
|
}
|
|
5168
5476
|
var warnedDeadButtonIds = /* @__PURE__ */ new Set();
|
|
5477
|
+
var warnedRedundantUpdateLabelIds = /* @__PURE__ */ new Set();
|
|
5169
5478
|
function scheduleDeadButtonWarning(bridge, id, type) {
|
|
5170
5479
|
if (type !== "button" && type !== "pressable") return;
|
|
5171
5480
|
if (warnedDeadButtonIds.has(id)) return;
|
|
@@ -5394,6 +5703,23 @@ function useUIElement(options) {
|
|
|
5394
5703
|
},
|
|
5395
5704
|
[bridge, id]
|
|
5396
5705
|
);
|
|
5706
|
+
const updateLabel = react.useCallback(
|
|
5707
|
+
(newLabel) => {
|
|
5708
|
+
if (!bridge || !registered) return;
|
|
5709
|
+
const changed = bridge.registry.updateElementMeta(id, {
|
|
5710
|
+
label: newLabel,
|
|
5711
|
+
accessibilityLabel: newLabel,
|
|
5712
|
+
testId: id
|
|
5713
|
+
});
|
|
5714
|
+
if (!changed && !warnedRedundantUpdateLabelIds.has(id)) {
|
|
5715
|
+
warnedRedundantUpdateLabelIds.add(id);
|
|
5716
|
+
console.warn(
|
|
5717
|
+
`[UI Bridge] updateLabel("${newLabel}") on element "${id}" was a no-op (label unchanged). Gate the call behind a useEffect with [label] deps to avoid redundant publishes.`
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
},
|
|
5721
|
+
[bridge, registered, id]
|
|
5722
|
+
);
|
|
5397
5723
|
const updateStyle = react.useCallback(
|
|
5398
5724
|
(newStyle, newStateStyles) => {
|
|
5399
5725
|
if (!bridge || !registered) return;
|
|
@@ -5419,7 +5745,8 @@ function useUIElement(options) {
|
|
|
5419
5745
|
register,
|
|
5420
5746
|
unregister,
|
|
5421
5747
|
registeredElement,
|
|
5422
|
-
updateStyle
|
|
5748
|
+
updateStyle,
|
|
5749
|
+
updateLabel
|
|
5423
5750
|
};
|
|
5424
5751
|
}
|
|
5425
5752
|
function useUIElementWithProps(options) {
|
|
@@ -5733,6 +6060,11 @@ var UI_BRIDGE_NATIVE_ROUTES = {
|
|
|
5733
6060
|
path: "/ui-bridge/control/snapshot",
|
|
5734
6061
|
description: "Get full bridge snapshot"
|
|
5735
6062
|
},
|
|
6063
|
+
PAGE_HEALTH: {
|
|
6064
|
+
method: "POST",
|
|
6065
|
+
path: "/ui-bridge/control/page-health",
|
|
6066
|
+
description: "Holistic page health diagnostic: spatial coverage, layout regions, text signals, interactive readiness, anomalies + ASCII heatmap"
|
|
6067
|
+
},
|
|
5736
6068
|
// Workflows
|
|
5737
6069
|
GET_WORKFLOWS: {
|
|
5738
6070
|
method: "GET",
|