@qontinui/ui-bridge-native 0.6.16 → 0.7.1
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/index.js +337 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +337 -36
- package/dist/index.mjs.map +1 -1
- package/dist/observation/index.d.ts +12 -0
- package/dist/observation/index.d.ts.map +1 -0
- package/dist/observation/observation.d.ts +233 -0
- package/dist/observation/observation.d.ts.map +1 -0
- package/dist/react/index.js +337 -36
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +337 -36
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/handlers.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 +341 -33
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +335 -34
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/page-health.d.ts +59 -7
- package/dist/server/page-health.d.ts.map +1 -1
- package/dist/server/types.d.ts +5 -3
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EACL,KAAK,sBAAsB,EAK5B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAwB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,KAAK,EAQV,oBAAoB,EAKrB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA4VhF;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,sBAAsB,EAChC,QAAQ,EAAE,oBAAoB,EAC9B,MAAM,CAAC,EAAE;IACP,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,GACA,oBAAoB,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,EACL,KAAK,sBAAsB,EAK5B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAwB,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,KAAK,EAQV,oBAAoB,EAKrB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA4VhF;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,sBAAsB,EAChC,QAAQ,EAAE,oBAAoB,EAC9B,MAAM,CAAC,EAAE;IACP,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,GACA,oBAAoB,CAmhCtB"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -15,4 +15,6 @@ export { WebSocketEventBridge } from './ws-event-bridge';
|
|
|
15
15
|
export { ConsoleErrorBuffer, NetworkRequestBuffer } from './observability';
|
|
16
16
|
export { bindWithRetry, isAddrInUseError } from './bind-retry';
|
|
17
17
|
export type { BindWithRetryOptions } from './bind-retry';
|
|
18
|
+
export { diagnosePageHealth, pageHealthUnknown, NATIVE_PAGE_HEALTH_PRODUCER_ID, type PageHealthValue, type PageHealthObservation, type PageHealthInput, type PageHealthOptions, type PageHealthElement, type PageHealthFinding, type PageHealthSeverity, } from './page-health';
|
|
19
|
+
export { Observation, ObservationError, UNKNOWN_CODES, isUnknownCode, type UnknownCode, type ObservationStatus, type ObservationProducer, type ObservationCoverage, type UnmeasuredDimension, type ObservationCache, type ObservationProvenance, type ObservationUnknown, type MeasuredObservation, type AbsentObservation, type UnknownObservation, } from '../observation';
|
|
18
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAIzD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,8BAA8B,EAC9B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1066,7 +1066,219 @@ function createDesignHandlers(registry, viewportProvider) {
|
|
|
1066
1066
|
};
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
|
+
// src/observation/observation.ts
|
|
1070
|
+
var UNKNOWN_CODES = [
|
|
1071
|
+
"app_unreachable",
|
|
1072
|
+
"capability_unavailable_in_build",
|
|
1073
|
+
"producer_failed",
|
|
1074
|
+
"producer_not_run",
|
|
1075
|
+
"input_missing",
|
|
1076
|
+
"below_confidence_floor",
|
|
1077
|
+
"model_reply_unparseable",
|
|
1078
|
+
"needs_multi_frame_input",
|
|
1079
|
+
"stale_input"
|
|
1080
|
+
];
|
|
1081
|
+
function isUnknownCode(value) {
|
|
1082
|
+
return typeof value === "string" && UNKNOWN_CODES.includes(value);
|
|
1083
|
+
}
|
|
1084
|
+
var ObservationError = class extends Error {
|
|
1085
|
+
constructor(message) {
|
|
1086
|
+
super(message);
|
|
1087
|
+
this.name = "ObservationError";
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
function toRfc3339(t) {
|
|
1091
|
+
const d = t instanceof Date ? t : new Date(t);
|
|
1092
|
+
if (Number.isNaN(d.getTime())) {
|
|
1093
|
+
throw new ObservationError(`not a valid timestamp: ${String(t)}`);
|
|
1094
|
+
}
|
|
1095
|
+
return d.toISOString();
|
|
1096
|
+
}
|
|
1097
|
+
function normalizeConfidence(c) {
|
|
1098
|
+
if (c === null || c === void 0 || !Number.isFinite(c)) return null;
|
|
1099
|
+
return Math.min(1, Math.max(0, c));
|
|
1100
|
+
}
|
|
1101
|
+
function fullCoverage(n) {
|
|
1102
|
+
return { considered: n, measured: n, unmeasured: [] };
|
|
1103
|
+
}
|
|
1104
|
+
function assertCount(value, what) {
|
|
1105
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
1106
|
+
throw new ObservationError(`${what} must be a non-negative integer, got ${String(value)}`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function assertCoverage(coverage) {
|
|
1110
|
+
if (!coverage || typeof coverage !== "object") {
|
|
1111
|
+
throw new ObservationError("coverage must be an object");
|
|
1112
|
+
}
|
|
1113
|
+
assertCount(coverage.considered, "coverage.considered");
|
|
1114
|
+
assertCount(coverage.measured, "coverage.measured");
|
|
1115
|
+
if (!Array.isArray(coverage.unmeasured)) {
|
|
1116
|
+
throw new ObservationError("coverage.unmeasured must be an array");
|
|
1117
|
+
}
|
|
1118
|
+
coverage.unmeasured.forEach((u, i) => {
|
|
1119
|
+
if (!u || typeof u.dimension !== "string") {
|
|
1120
|
+
throw new ObservationError(`coverage.unmeasured[${i}].dimension must be a string`);
|
|
1121
|
+
}
|
|
1122
|
+
assertCount(u.count, `coverage.unmeasured[${i}].count`);
|
|
1123
|
+
if (!isUnknownCode(u.code)) {
|
|
1124
|
+
throw new ObservationError(
|
|
1125
|
+
`coverage.unmeasured[${i}].code is not an UnknownCode: ${String(u.code)}`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
function assertSource(source) {
|
|
1131
|
+
if (source === null) return;
|
|
1132
|
+
if (typeof source !== "object" || Array.isArray(source)) {
|
|
1133
|
+
throw new ObservationError("source must be a JSON object or null");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
var RFC3339 = /^(\d{4})-(\d{2})-(\d{2})[Tt ](\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-](\d{2}):(\d{2}))$/;
|
|
1137
|
+
function isRfc3339(value) {
|
|
1138
|
+
if (typeof value !== "string") return false;
|
|
1139
|
+
const m = RFC3339.exec(value);
|
|
1140
|
+
if (!m) return false;
|
|
1141
|
+
const [y, mo, d, h, mi, s] = [m[1], m[2], m[3], m[4], m[5], m[6]].map(Number);
|
|
1142
|
+
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || s > 60) return false;
|
|
1143
|
+
const leap = y % 4 === 0 && y % 100 !== 0 || y % 400 === 0;
|
|
1144
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo - 1];
|
|
1145
|
+
if (d > daysInMonth) return false;
|
|
1146
|
+
if (m[9] !== void 0 && (Number(m[9]) > 23 || Number(m[10]) > 59)) return false;
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
function assertProvenance(prov) {
|
|
1150
|
+
if (!prov || typeof prov !== "object") throw new ObservationError("provenance is required");
|
|
1151
|
+
if (!prov.producer || typeof prov.producer.id !== "string" || typeof prov.producer.version !== "string") {
|
|
1152
|
+
throw new ObservationError("producer must carry a string id and version");
|
|
1153
|
+
}
|
|
1154
|
+
if (!isRfc3339(prov.evaluatedAt)) {
|
|
1155
|
+
throw new ObservationError(
|
|
1156
|
+
`evaluatedAt must be an RFC3339 date-time, got ${String(prov.evaluatedAt)}`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
if (prov.observedAt !== null && !isRfc3339(prov.observedAt)) {
|
|
1160
|
+
throw new ObservationError(
|
|
1161
|
+
`observedAt must be an RFC3339 date-time or null, got ${String(prov.observedAt)}`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
assertCoverage(prov.coverage);
|
|
1165
|
+
if (prov.confidence !== null && (typeof prov.confidence !== "number" || !Number.isFinite(prov.confidence) || prov.confidence < 0 || prov.confidence > 1)) {
|
|
1166
|
+
throw new ObservationError(
|
|
1167
|
+
`confidence must be in [0, 1] or null, got ${String(prov.confidence)}`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
if (prov.cache !== null) {
|
|
1171
|
+
if (typeof prov.cache !== "object" || typeof prov.cache.hit !== "boolean" || !Array.isArray(prov.cache.keyInputs) || !prov.cache.keyInputs.every((k) => typeof k === "string")) {
|
|
1172
|
+
throw new ObservationError(
|
|
1173
|
+
"cache must be { hit: boolean, storedAt, keyInputs: string[] } or null"
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
if (prov.cache.storedAt !== null && !isRfc3339(prov.cache.storedAt)) {
|
|
1177
|
+
throw new ObservationError(
|
|
1178
|
+
`cache.storedAt must be an RFC3339 date-time or null, got ${String(prov.cache.storedAt)}`
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
assertSource(prov.source);
|
|
1183
|
+
}
|
|
1184
|
+
function provenance(init) {
|
|
1185
|
+
if (!init.producer || typeof init.producer.id !== "string" || typeof init.producer.version !== "string") {
|
|
1186
|
+
throw new ObservationError("producer must carry a string id and version");
|
|
1187
|
+
}
|
|
1188
|
+
const coverage = init.coverage ?? fullCoverage(0);
|
|
1189
|
+
assertCoverage(coverage);
|
|
1190
|
+
const source = init.source ?? null;
|
|
1191
|
+
assertSource(source);
|
|
1192
|
+
const built = {
|
|
1193
|
+
producer: { id: init.producer.id, version: init.producer.version },
|
|
1194
|
+
observedAt: init.observedAt === void 0 || init.observedAt === null ? null : toRfc3339(init.observedAt),
|
|
1195
|
+
evaluatedAt: toRfc3339(init.evaluatedAt ?? /* @__PURE__ */ new Date()),
|
|
1196
|
+
coverage: {
|
|
1197
|
+
considered: coverage.considered,
|
|
1198
|
+
measured: coverage.measured,
|
|
1199
|
+
unmeasured: coverage.unmeasured.map((u) => ({
|
|
1200
|
+
dimension: u.dimension,
|
|
1201
|
+
count: u.count,
|
|
1202
|
+
code: u.code
|
|
1203
|
+
}))
|
|
1204
|
+
},
|
|
1205
|
+
confidence: normalizeConfidence(init.confidence),
|
|
1206
|
+
cache: init.cache ? {
|
|
1207
|
+
hit: init.cache.hit,
|
|
1208
|
+
storedAt: init.cache.storedAt === null ? null : toRfc3339(init.cache.storedAt),
|
|
1209
|
+
keyInputs: [...init.cache.keyInputs]
|
|
1210
|
+
} : null,
|
|
1211
|
+
source
|
|
1212
|
+
};
|
|
1213
|
+
assertProvenance(built);
|
|
1214
|
+
return built;
|
|
1215
|
+
}
|
|
1216
|
+
function measured(value, prov) {
|
|
1217
|
+
if (value === void 0) {
|
|
1218
|
+
throw new ObservationError(
|
|
1219
|
+
'measured requires a value; report "nothing there" as absent and "could not look" as unknown'
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
assertProvenance(prov);
|
|
1223
|
+
return { status: "measured", value, provenance: prov };
|
|
1224
|
+
}
|
|
1225
|
+
function absent(prov) {
|
|
1226
|
+
assertProvenance(prov);
|
|
1227
|
+
const { considered, measured: measured2, unmeasured } = prov.coverage;
|
|
1228
|
+
if (unmeasured.length > 0) {
|
|
1229
|
+
const dims = unmeasured.map((u) => `${u.dimension} x${u.count} (${u.code})`).join(", ");
|
|
1230
|
+
throw new ObservationError(
|
|
1231
|
+
`cannot assert absent over unmeasured coverage: ${dims}. Report the observation as measured (degraded) or unknown instead.`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
if (considered === 0) {
|
|
1235
|
+
throw new ObservationError(
|
|
1236
|
+
"cannot assert absent over zero considered items: the producer looked at nothing. Report the observation as unknown instead."
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
if (measured2 !== considered) {
|
|
1240
|
+
throw new ObservationError(
|
|
1241
|
+
`an \`absent\` observation must have measured every considered item (measured ${measured2} of ${considered}); a partial look is \`measured\` with its gaps in \`coverage.unmeasured\`, never \`absent\``
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
return { status: "absent", provenance: prov };
|
|
1245
|
+
}
|
|
1246
|
+
function unknown(code, detail, prov) {
|
|
1247
|
+
if (!isUnknownCode(code)) {
|
|
1248
|
+
throw new ObservationError(`not an UnknownCode: ${String(code)}`);
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof detail !== "string") {
|
|
1251
|
+
throw new ObservationError("unknown requires a string detail");
|
|
1252
|
+
}
|
|
1253
|
+
assertProvenance(prov);
|
|
1254
|
+
return { status: "unknown", unknown: { code, detail }, provenance: prov };
|
|
1255
|
+
}
|
|
1256
|
+
var Observation = {
|
|
1257
|
+
provenance,
|
|
1258
|
+
fullCoverage,
|
|
1259
|
+
measured,
|
|
1260
|
+
absent,
|
|
1261
|
+
unknown
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1069
1264
|
// src/server/page-health.ts
|
|
1265
|
+
var NATIVE_PAGE_HEALTH_PRODUCER_ID = "sdk-native/page-health";
|
|
1266
|
+
function defaultProducer() {
|
|
1267
|
+
return {
|
|
1268
|
+
id: NATIVE_PAGE_HEALTH_PRODUCER_ID,
|
|
1269
|
+
version: "0.7.1"
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function pageHealthUnknown(code, detail, options = {}) {
|
|
1273
|
+
return Observation.unknown(
|
|
1274
|
+
code,
|
|
1275
|
+
detail,
|
|
1276
|
+
Observation.provenance({
|
|
1277
|
+
producer: options.producer ?? defaultProducer(),
|
|
1278
|
+
evaluatedAt: options.evaluatedAt
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1070
1282
|
var GRID_SIZE = 20;
|
|
1071
1283
|
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
1072
1284
|
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
@@ -1096,14 +1308,7 @@ var LOADING_PHRASES = [
|
|
|
1096
1308
|
"initializing",
|
|
1097
1309
|
"fetching"
|
|
1098
1310
|
];
|
|
1099
|
-
var EMPTY_PHRASES = [
|
|
1100
|
-
"no data",
|
|
1101
|
-
"no results",
|
|
1102
|
-
"nothing here",
|
|
1103
|
-
"empty",
|
|
1104
|
-
"no items",
|
|
1105
|
-
"get started"
|
|
1106
|
-
];
|
|
1311
|
+
var EMPTY_PHRASES = ["no data", "no results", "nothing here", "empty", "no items", "get started"];
|
|
1107
1312
|
function severityRank(s) {
|
|
1108
1313
|
if (s === "CRITICAL") return 3;
|
|
1109
1314
|
if (s === "WARNING") return 2;
|
|
@@ -1135,13 +1340,90 @@ function normalizeRect(state, viewport) {
|
|
|
1135
1340
|
height: layout.height / viewport.height
|
|
1136
1341
|
};
|
|
1137
1342
|
}
|
|
1138
|
-
function diagnosePageHealth(
|
|
1343
|
+
function diagnosePageHealth(input, options = {}) {
|
|
1344
|
+
const producer = options.producer ?? defaultProducer();
|
|
1345
|
+
try {
|
|
1346
|
+
return observe(input, producer, options.evaluatedAt);
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
return Observation.unknown(
|
|
1349
|
+
"producer_failed",
|
|
1350
|
+
`page-health producer threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
1351
|
+
Observation.provenance({ producer })
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function observe(input, producer, evaluatedAt) {
|
|
1356
|
+
const base = {
|
|
1357
|
+
producer,
|
|
1358
|
+
observedAt: input.observedAt,
|
|
1359
|
+
evaluatedAt
|
|
1360
|
+
};
|
|
1361
|
+
if (!Array.isArray(input.elements)) {
|
|
1362
|
+
return Observation.unknown(
|
|
1363
|
+
"input_missing",
|
|
1364
|
+
"the registry handed over no `elements` array, so there was nothing to analyze; this says nothing about the screen itself",
|
|
1365
|
+
Observation.provenance({ ...base, observedAt: null })
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const elements = input.elements;
|
|
1369
|
+
const viewport = input.viewport;
|
|
1370
|
+
if (!viewport || !(viewport.width > 0) || !(viewport.height > 0)) {
|
|
1371
|
+
return Observation.unknown(
|
|
1372
|
+
"input_missing",
|
|
1373
|
+
viewport ? `the device viewport is degenerate (${viewport.width}x${viewport.height}), so no element can be placed on screen \u2014 pass \`body.viewport\` or wire \`viewportProvider\`` : "the device viewport is unknown, so no element can be placed on screen \u2014 pass `body.viewport` or wire `viewportProvider`",
|
|
1374
|
+
Observation.provenance(base)
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
if (elements.length === 0 && (input.registeredComponents ?? 0) === 0) {
|
|
1378
|
+
return Observation.unknown(
|
|
1379
|
+
"producer_not_run",
|
|
1380
|
+
"the registry reports zero elements and zero components \u2014 nothing has registered yet, so there was nothing to look at",
|
|
1381
|
+
Observation.provenance(base)
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
return analyze(elements, viewport, base);
|
|
1385
|
+
}
|
|
1386
|
+
function nothingOnScreenDetail(elements, visibleCount, visibilityUnstated) {
|
|
1387
|
+
if (visibilityUnstated === 0 && visibleCount > 0) {
|
|
1388
|
+
return `all ${visibleCount} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`;
|
|
1389
|
+
}
|
|
1390
|
+
if (visibilityUnstated > 0) {
|
|
1391
|
+
const geometry = visibleCount > 0 ? ` and none of the ${visibleCount} visible one(s) has a measured layout` : "";
|
|
1392
|
+
return `${visibilityUnstated} of ${elements.length} element(s) carry no boolean \`state.visible\`${geometry}, so no element can be placed on screen; spatial coverage, layout regions and visual anomalies cannot be measured`;
|
|
1393
|
+
}
|
|
1394
|
+
const unmounted = elements.filter((el) => el.state?.mounted === false).length;
|
|
1395
|
+
return `all ${elements.length} registered element(s) report visible: false (unmounted=${unmounted}, off-screen route or otherwise=${elements.length - unmounted}), so whatever is on screen is not a registered element and there is nothing on screen to measure; this says nothing about whether the screen is blank`;
|
|
1396
|
+
}
|
|
1397
|
+
function analyze(elements, viewport, base) {
|
|
1139
1398
|
const findings = [];
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const visible =
|
|
1399
|
+
const onScreen = elements.filter((el) => el.state?.visible === true);
|
|
1400
|
+
const visibilityUnstated = elements.filter(
|
|
1401
|
+
(el) => typeof el.state?.visible !== "boolean"
|
|
1402
|
+
).length;
|
|
1403
|
+
const visible = onScreen.flatMap((el) => {
|
|
1404
|
+
const rect = normalizeRect(el.state, viewport);
|
|
1405
|
+
return rect ? [{ el, rect }] : [];
|
|
1406
|
+
});
|
|
1407
|
+
const withoutGeometry = onScreen.length - visible.length;
|
|
1408
|
+
const unmeasured = [];
|
|
1409
|
+
if (withoutGeometry > 0) {
|
|
1410
|
+
unmeasured.push({ dimension: "geometry", count: withoutGeometry, code: "input_missing" });
|
|
1411
|
+
}
|
|
1412
|
+
if (visibilityUnstated > 0) {
|
|
1413
|
+
unmeasured.push({ dimension: "visibility", count: visibilityUnstated, code: "input_missing" });
|
|
1414
|
+
}
|
|
1415
|
+
const coverage = {
|
|
1416
|
+
considered: onScreen.length + visibilityUnstated,
|
|
1417
|
+
measured: visible.length,
|
|
1418
|
+
unmeasured
|
|
1419
|
+
};
|
|
1420
|
+
if (elements.length > 0 && visible.length === 0) {
|
|
1421
|
+
return Observation.unknown(
|
|
1422
|
+
"input_missing",
|
|
1423
|
+
nothingOnScreenDetail(elements, onScreen.length, visibilityUnstated),
|
|
1424
|
+
Observation.provenance({ ...base, coverage })
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1145
1427
|
const grid = Array.from(
|
|
1146
1428
|
{ length: GRID_SIZE },
|
|
1147
1429
|
() => Array.from({ length: GRID_SIZE }, () => false)
|
|
@@ -1228,7 +1510,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
1228
1510
|
for (const el of elements) {
|
|
1229
1511
|
const elType = el.type ?? "";
|
|
1230
1512
|
if (SKIP_TEXT_TYPES.has(elType)) continue;
|
|
1231
|
-
const text = (el.state
|
|
1513
|
+
const text = (el.state?.textContent ?? "").toString();
|
|
1232
1514
|
if (!text) continue;
|
|
1233
1515
|
const textLower = text.toLowerCase();
|
|
1234
1516
|
for (const p of ERROR_PHRASES) {
|
|
@@ -1269,7 +1551,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
1269
1551
|
for (const el of elements) {
|
|
1270
1552
|
if (!INTERACTIVE_TYPES2.has(el.type)) continue;
|
|
1271
1553
|
interactiveTotal++;
|
|
1272
|
-
if (el.state
|
|
1554
|
+
if (el.state?.enabled === false) interactiveDisabled++;
|
|
1273
1555
|
}
|
|
1274
1556
|
const interactiveSeverity = interactiveTotal > 0 && interactiveDisabled / interactiveTotal > 0.5 ? "WARNING" : "OK";
|
|
1275
1557
|
findings.push({
|
|
@@ -1293,16 +1575,18 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
1293
1575
|
detail: `zero_size=${zeroSize}, outside_viewport=${outsideViewport} (horizontal-overflow only; below/above-fold scrollable content is not an anomaly)`,
|
|
1294
1576
|
data: { zero_size: zeroSize, outside_viewport: outsideViewport }
|
|
1295
1577
|
});
|
|
1296
|
-
const heatmap = grid.map(
|
|
1297
|
-
|
|
1578
|
+
const heatmap = grid.map((row) => row.map((cell) => cell ? "#" : ".").join(""));
|
|
1579
|
+
return Observation.measured(
|
|
1580
|
+
{
|
|
1581
|
+
summary: worstSeverity(findings),
|
|
1582
|
+
findings,
|
|
1583
|
+
heatmap,
|
|
1584
|
+
element_count: elements.length,
|
|
1585
|
+
visible_count: visible.length
|
|
1586
|
+
},
|
|
1587
|
+
// A deduction over the registry, not an estimate.
|
|
1588
|
+
Observation.provenance({ ...base, coverage })
|
|
1298
1589
|
);
|
|
1299
|
-
return {
|
|
1300
|
-
summary: worstSeverity(findings),
|
|
1301
|
-
findings,
|
|
1302
|
-
heatmap,
|
|
1303
|
-
element_count: elements.length,
|
|
1304
|
-
visible_count: visible.length
|
|
1305
|
-
};
|
|
1306
1590
|
}
|
|
1307
1591
|
|
|
1308
1592
|
// src/server/handlers.ts
|
|
@@ -1750,13 +2034,30 @@ function createServerHandlers(registry, executor, config) {
|
|
|
1750
2034
|
},
|
|
1751
2035
|
getPageHealth: async (ctx) => {
|
|
1752
2036
|
const bodyViewport = ctx.body?.viewport;
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2037
|
+
let viewport;
|
|
2038
|
+
try {
|
|
2039
|
+
viewport = bodyViewport ?? config?.viewportProvider?.() ?? null;
|
|
2040
|
+
} catch {
|
|
2041
|
+
viewport = null;
|
|
2042
|
+
}
|
|
2043
|
+
const observedAt = Date.now();
|
|
2044
|
+
let elements;
|
|
2045
|
+
let registeredComponents;
|
|
2046
|
+
try {
|
|
2047
|
+
elements = registry.getAllElements().map((e) => ({
|
|
2048
|
+
type: e.type,
|
|
2049
|
+
state: e.getState()
|
|
2050
|
+
}));
|
|
2051
|
+
registeredComponents = registry.getAllComponents().length;
|
|
2052
|
+
} catch (err) {
|
|
2053
|
+
return success2(
|
|
2054
|
+
pageHealthUnknown(
|
|
2055
|
+
"producer_failed",
|
|
2056
|
+
`could not read the registry: ${err instanceof Error ? err.message : String(err)}`
|
|
2057
|
+
)
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
return success2(diagnosePageHealth({ elements, registeredComponents, viewport, observedAt }));
|
|
1760
2061
|
},
|
|
1761
2062
|
// Workflows
|
|
1762
2063
|
getWorkflows: async () => {
|
|
@@ -2260,7 +2561,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
2260
2561
|
};
|
|
2261
2562
|
if (config?.appInfo) {
|
|
2262
2563
|
response.uiBridge = {
|
|
2263
|
-
version: "0.
|
|
2564
|
+
version: "0.7.1",
|
|
2264
2565
|
...config.appInfo,
|
|
2265
2566
|
capabilities: ["elements", "components", "actions", "design"],
|
|
2266
2567
|
elementCount: stats.elements,
|
|
@@ -4551,6 +4852,7 @@ async function bindWithRetry(attempt, options = {}) {
|
|
|
4551
4852
|
}
|
|
4552
4853
|
|
|
4553
4854
|
exports.ConsoleErrorBuffer = ConsoleErrorBuffer;
|
|
4855
|
+
exports.NATIVE_PAGE_HEALTH_PRODUCER_ID = NATIVE_PAGE_HEALTH_PRODUCER_ID;
|
|
4554
4856
|
exports.NativeUIBridgeServer = NativeUIBridgeServer;
|
|
4555
4857
|
exports.NetworkRequestBuffer = NetworkRequestBuffer;
|
|
4556
4858
|
exports.OPCODE_BINARY = OPCODE_BINARY;
|
|
@@ -4559,7 +4861,10 @@ exports.OPCODE_CONTINUATION = OPCODE_CONTINUATION;
|
|
|
4559
4861
|
exports.OPCODE_PING = OPCODE_PING;
|
|
4560
4862
|
exports.OPCODE_PONG = OPCODE_PONG;
|
|
4561
4863
|
exports.OPCODE_TEXT = OPCODE_TEXT;
|
|
4864
|
+
exports.Observation = Observation;
|
|
4865
|
+
exports.ObservationError = ObservationError;
|
|
4562
4866
|
exports.UI_BRIDGE_NATIVE_ROUTES = UI_BRIDGE_NATIVE_ROUTES;
|
|
4867
|
+
exports.UNKNOWN_CODES = UNKNOWN_CODES;
|
|
4563
4868
|
exports.WebSocketConnection = WebSocketConnection;
|
|
4564
4869
|
exports.WebSocketEventBridge = WebSocketEventBridge;
|
|
4565
4870
|
exports.bindWithRetry = bindWithRetry;
|
|
@@ -4570,6 +4875,7 @@ exports.createNativeServer = createNativeServer;
|
|
|
4570
4875
|
exports.createServerHandlers = createServerHandlers;
|
|
4571
4876
|
exports.decodeFrames = decodeFrames;
|
|
4572
4877
|
exports.decodeTextPayload = decodeTextPayload;
|
|
4878
|
+
exports.diagnosePageHealth = diagnosePageHealth;
|
|
4573
4879
|
exports.encodeCloseFrame = encodeCloseFrame;
|
|
4574
4880
|
exports.encodePingFrame = encodePingFrame;
|
|
4575
4881
|
exports.encodePongFrame = encodePongFrame;
|
|
@@ -4578,6 +4884,8 @@ exports.httpStatusForResponse = httpStatusForResponse;
|
|
|
4578
4884
|
exports.isAddrInUseError = isAddrInUseError;
|
|
4579
4885
|
exports.isBatchRequest = isBatchRequest;
|
|
4580
4886
|
exports.isSubscribe = isSubscribe;
|
|
4887
|
+
exports.isUnknownCode = isUnknownCode;
|
|
4581
4888
|
exports.isUnsubscribe = isUnsubscribe;
|
|
4889
|
+
exports.pageHealthUnknown = pageHealthUnknown;
|
|
4582
4890
|
//# sourceMappingURL=index.js.map
|
|
4583
4891
|
//# sourceMappingURL=index.js.map
|