@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
package/dist/react/index.js
CHANGED
|
@@ -2381,7 +2381,7 @@ var DefaultNativeActionExecutor = class {
|
|
|
2381
2381
|
if (rect && clip && !isEmptyRect(clip) && rect.right > rect.left && rect.bottom > rect.top && rect.top >= clip.top && rect.left >= clip.left && rect.bottom <= clip.bottom && rect.right <= clip.right) {
|
|
2382
2382
|
return { alreadyVisible: true, scrolled: false };
|
|
2383
2383
|
}
|
|
2384
|
-
const
|
|
2384
|
+
const measured2 = await new Promise(
|
|
2385
2385
|
(resolve, reject) => {
|
|
2386
2386
|
let settled = false;
|
|
2387
2387
|
const timer = setTimeout(() => {
|
|
@@ -2418,13 +2418,13 @@ var DefaultNativeActionExecutor = class {
|
|
|
2418
2418
|
}
|
|
2419
2419
|
}
|
|
2420
2420
|
);
|
|
2421
|
-
if (ancestorRect && Number.isFinite(
|
|
2421
|
+
if (ancestorRect && Number.isFinite(measured2.x) && Number.isFinite(measured2.width) && measured2.x + measured2.width > ancestorRect.right - ancestorRect.left + 1) {
|
|
2422
2422
|
throw new NativeActionError(
|
|
2423
2423
|
`scrollIntoView is not supported on element "${element.id}": it is laid out beyond the horizontal extent of its scroll ancestor "${ancestorId}" (sideways overflow, a horizontal container, or a nested horizontal container \u2014 declare that inner container as its scrollAncestorId); only vertical scrolling is supported`,
|
|
2424
2424
|
"NOT_SUPPORTED"
|
|
2425
2425
|
);
|
|
2426
2426
|
}
|
|
2427
|
-
container.scrollTo({ y: Math.max(0,
|
|
2427
|
+
container.scrollTo({ y: Math.max(0, measured2.y - padding), animated: false });
|
|
2428
2428
|
return { alreadyVisible: false, scrolled: true };
|
|
2429
2429
|
}
|
|
2430
2430
|
/**
|
|
@@ -3161,7 +3161,219 @@ function createDesignHandlers(registry, viewportProvider) {
|
|
|
3161
3161
|
};
|
|
3162
3162
|
}
|
|
3163
3163
|
|
|
3164
|
+
// src/observation/observation.ts
|
|
3165
|
+
var UNKNOWN_CODES = [
|
|
3166
|
+
"app_unreachable",
|
|
3167
|
+
"capability_unavailable_in_build",
|
|
3168
|
+
"producer_failed",
|
|
3169
|
+
"producer_not_run",
|
|
3170
|
+
"input_missing",
|
|
3171
|
+
"below_confidence_floor",
|
|
3172
|
+
"model_reply_unparseable",
|
|
3173
|
+
"needs_multi_frame_input",
|
|
3174
|
+
"stale_input"
|
|
3175
|
+
];
|
|
3176
|
+
function isUnknownCode(value) {
|
|
3177
|
+
return typeof value === "string" && UNKNOWN_CODES.includes(value);
|
|
3178
|
+
}
|
|
3179
|
+
var ObservationError = class extends Error {
|
|
3180
|
+
constructor(message) {
|
|
3181
|
+
super(message);
|
|
3182
|
+
this.name = "ObservationError";
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
function toRfc3339(t) {
|
|
3186
|
+
const d = t instanceof Date ? t : new Date(t);
|
|
3187
|
+
if (Number.isNaN(d.getTime())) {
|
|
3188
|
+
throw new ObservationError(`not a valid timestamp: ${String(t)}`);
|
|
3189
|
+
}
|
|
3190
|
+
return d.toISOString();
|
|
3191
|
+
}
|
|
3192
|
+
function normalizeConfidence(c) {
|
|
3193
|
+
if (c === null || c === void 0 || !Number.isFinite(c)) return null;
|
|
3194
|
+
return Math.min(1, Math.max(0, c));
|
|
3195
|
+
}
|
|
3196
|
+
function fullCoverage(n) {
|
|
3197
|
+
return { considered: n, measured: n, unmeasured: [] };
|
|
3198
|
+
}
|
|
3199
|
+
function assertCount(value, what) {
|
|
3200
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
3201
|
+
throw new ObservationError(`${what} must be a non-negative integer, got ${String(value)}`);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
function assertCoverage(coverage) {
|
|
3205
|
+
if (!coverage || typeof coverage !== "object") {
|
|
3206
|
+
throw new ObservationError("coverage must be an object");
|
|
3207
|
+
}
|
|
3208
|
+
assertCount(coverage.considered, "coverage.considered");
|
|
3209
|
+
assertCount(coverage.measured, "coverage.measured");
|
|
3210
|
+
if (!Array.isArray(coverage.unmeasured)) {
|
|
3211
|
+
throw new ObservationError("coverage.unmeasured must be an array");
|
|
3212
|
+
}
|
|
3213
|
+
coverage.unmeasured.forEach((u, i) => {
|
|
3214
|
+
if (!u || typeof u.dimension !== "string") {
|
|
3215
|
+
throw new ObservationError(`coverage.unmeasured[${i}].dimension must be a string`);
|
|
3216
|
+
}
|
|
3217
|
+
assertCount(u.count, `coverage.unmeasured[${i}].count`);
|
|
3218
|
+
if (!isUnknownCode(u.code)) {
|
|
3219
|
+
throw new ObservationError(
|
|
3220
|
+
`coverage.unmeasured[${i}].code is not an UnknownCode: ${String(u.code)}`
|
|
3221
|
+
);
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
function assertSource(source) {
|
|
3226
|
+
if (source === null) return;
|
|
3227
|
+
if (typeof source !== "object" || Array.isArray(source)) {
|
|
3228
|
+
throw new ObservationError("source must be a JSON object or null");
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
var RFC3339 = /^(\d{4})-(\d{2})-(\d{2})[Tt ](\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-](\d{2}):(\d{2}))$/;
|
|
3232
|
+
function isRfc3339(value) {
|
|
3233
|
+
if (typeof value !== "string") return false;
|
|
3234
|
+
const m = RFC3339.exec(value);
|
|
3235
|
+
if (!m) return false;
|
|
3236
|
+
const [y, mo, d, h, mi, s] = [m[1], m[2], m[3], m[4], m[5], m[6]].map(Number);
|
|
3237
|
+
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || s > 60) return false;
|
|
3238
|
+
const leap = y % 4 === 0 && y % 100 !== 0 || y % 400 === 0;
|
|
3239
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo - 1];
|
|
3240
|
+
if (d > daysInMonth) return false;
|
|
3241
|
+
if (m[9] !== void 0 && (Number(m[9]) > 23 || Number(m[10]) > 59)) return false;
|
|
3242
|
+
return true;
|
|
3243
|
+
}
|
|
3244
|
+
function assertProvenance(prov) {
|
|
3245
|
+
if (!prov || typeof prov !== "object") throw new ObservationError("provenance is required");
|
|
3246
|
+
if (!prov.producer || typeof prov.producer.id !== "string" || typeof prov.producer.version !== "string") {
|
|
3247
|
+
throw new ObservationError("producer must carry a string id and version");
|
|
3248
|
+
}
|
|
3249
|
+
if (!isRfc3339(prov.evaluatedAt)) {
|
|
3250
|
+
throw new ObservationError(
|
|
3251
|
+
`evaluatedAt must be an RFC3339 date-time, got ${String(prov.evaluatedAt)}`
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
if (prov.observedAt !== null && !isRfc3339(prov.observedAt)) {
|
|
3255
|
+
throw new ObservationError(
|
|
3256
|
+
`observedAt must be an RFC3339 date-time or null, got ${String(prov.observedAt)}`
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
assertCoverage(prov.coverage);
|
|
3260
|
+
if (prov.confidence !== null && (typeof prov.confidence !== "number" || !Number.isFinite(prov.confidence) || prov.confidence < 0 || prov.confidence > 1)) {
|
|
3261
|
+
throw new ObservationError(
|
|
3262
|
+
`confidence must be in [0, 1] or null, got ${String(prov.confidence)}`
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
if (prov.cache !== null) {
|
|
3266
|
+
if (typeof prov.cache !== "object" || typeof prov.cache.hit !== "boolean" || !Array.isArray(prov.cache.keyInputs) || !prov.cache.keyInputs.every((k) => typeof k === "string")) {
|
|
3267
|
+
throw new ObservationError(
|
|
3268
|
+
"cache must be { hit: boolean, storedAt, keyInputs: string[] } or null"
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
if (prov.cache.storedAt !== null && !isRfc3339(prov.cache.storedAt)) {
|
|
3272
|
+
throw new ObservationError(
|
|
3273
|
+
`cache.storedAt must be an RFC3339 date-time or null, got ${String(prov.cache.storedAt)}`
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
assertSource(prov.source);
|
|
3278
|
+
}
|
|
3279
|
+
function provenance(init) {
|
|
3280
|
+
if (!init.producer || typeof init.producer.id !== "string" || typeof init.producer.version !== "string") {
|
|
3281
|
+
throw new ObservationError("producer must carry a string id and version");
|
|
3282
|
+
}
|
|
3283
|
+
const coverage = init.coverage ?? fullCoverage(0);
|
|
3284
|
+
assertCoverage(coverage);
|
|
3285
|
+
const source = init.source ?? null;
|
|
3286
|
+
assertSource(source);
|
|
3287
|
+
const built = {
|
|
3288
|
+
producer: { id: init.producer.id, version: init.producer.version },
|
|
3289
|
+
observedAt: init.observedAt === void 0 || init.observedAt === null ? null : toRfc3339(init.observedAt),
|
|
3290
|
+
evaluatedAt: toRfc3339(init.evaluatedAt ?? /* @__PURE__ */ new Date()),
|
|
3291
|
+
coverage: {
|
|
3292
|
+
considered: coverage.considered,
|
|
3293
|
+
measured: coverage.measured,
|
|
3294
|
+
unmeasured: coverage.unmeasured.map((u) => ({
|
|
3295
|
+
dimension: u.dimension,
|
|
3296
|
+
count: u.count,
|
|
3297
|
+
code: u.code
|
|
3298
|
+
}))
|
|
3299
|
+
},
|
|
3300
|
+
confidence: normalizeConfidence(init.confidence),
|
|
3301
|
+
cache: init.cache ? {
|
|
3302
|
+
hit: init.cache.hit,
|
|
3303
|
+
storedAt: init.cache.storedAt === null ? null : toRfc3339(init.cache.storedAt),
|
|
3304
|
+
keyInputs: [...init.cache.keyInputs]
|
|
3305
|
+
} : null,
|
|
3306
|
+
source
|
|
3307
|
+
};
|
|
3308
|
+
assertProvenance(built);
|
|
3309
|
+
return built;
|
|
3310
|
+
}
|
|
3311
|
+
function measured(value, prov) {
|
|
3312
|
+
if (value === void 0) {
|
|
3313
|
+
throw new ObservationError(
|
|
3314
|
+
'measured requires a value; report "nothing there" as absent and "could not look" as unknown'
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
assertProvenance(prov);
|
|
3318
|
+
return { status: "measured", value, provenance: prov };
|
|
3319
|
+
}
|
|
3320
|
+
function absent(prov) {
|
|
3321
|
+
assertProvenance(prov);
|
|
3322
|
+
const { considered, measured: measured2, unmeasured } = prov.coverage;
|
|
3323
|
+
if (unmeasured.length > 0) {
|
|
3324
|
+
const dims = unmeasured.map((u) => `${u.dimension} x${u.count} (${u.code})`).join(", ");
|
|
3325
|
+
throw new ObservationError(
|
|
3326
|
+
`cannot assert absent over unmeasured coverage: ${dims}. Report the observation as measured (degraded) or unknown instead.`
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
if (considered === 0) {
|
|
3330
|
+
throw new ObservationError(
|
|
3331
|
+
"cannot assert absent over zero considered items: the producer looked at nothing. Report the observation as unknown instead."
|
|
3332
|
+
);
|
|
3333
|
+
}
|
|
3334
|
+
if (measured2 !== considered) {
|
|
3335
|
+
throw new ObservationError(
|
|
3336
|
+
`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\``
|
|
3337
|
+
);
|
|
3338
|
+
}
|
|
3339
|
+
return { status: "absent", provenance: prov };
|
|
3340
|
+
}
|
|
3341
|
+
function unknown(code, detail, prov) {
|
|
3342
|
+
if (!isUnknownCode(code)) {
|
|
3343
|
+
throw new ObservationError(`not an UnknownCode: ${String(code)}`);
|
|
3344
|
+
}
|
|
3345
|
+
if (typeof detail !== "string") {
|
|
3346
|
+
throw new ObservationError("unknown requires a string detail");
|
|
3347
|
+
}
|
|
3348
|
+
assertProvenance(prov);
|
|
3349
|
+
return { status: "unknown", unknown: { code, detail }, provenance: prov };
|
|
3350
|
+
}
|
|
3351
|
+
var Observation = {
|
|
3352
|
+
provenance,
|
|
3353
|
+
fullCoverage,
|
|
3354
|
+
measured,
|
|
3355
|
+
absent,
|
|
3356
|
+
unknown
|
|
3357
|
+
};
|
|
3358
|
+
|
|
3164
3359
|
// src/server/page-health.ts
|
|
3360
|
+
var NATIVE_PAGE_HEALTH_PRODUCER_ID = "sdk-native/page-health";
|
|
3361
|
+
function defaultProducer() {
|
|
3362
|
+
return {
|
|
3363
|
+
id: NATIVE_PAGE_HEALTH_PRODUCER_ID,
|
|
3364
|
+
version: "0.7.1"
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
function pageHealthUnknown(code, detail, options = {}) {
|
|
3368
|
+
return Observation.unknown(
|
|
3369
|
+
code,
|
|
3370
|
+
detail,
|
|
3371
|
+
Observation.provenance({
|
|
3372
|
+
producer: options.producer ?? defaultProducer(),
|
|
3373
|
+
evaluatedAt: options.evaluatedAt
|
|
3374
|
+
})
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3165
3377
|
var GRID_SIZE = 20;
|
|
3166
3378
|
var NAV_TYPES = /* @__PURE__ */ new Set(["button", "heading", "badge", "status-message"]);
|
|
3167
3379
|
var SKIP_TEXT_TYPES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem"]);
|
|
@@ -3191,14 +3403,7 @@ var LOADING_PHRASES = [
|
|
|
3191
3403
|
"initializing",
|
|
3192
3404
|
"fetching"
|
|
3193
3405
|
];
|
|
3194
|
-
var EMPTY_PHRASES = [
|
|
3195
|
-
"no data",
|
|
3196
|
-
"no results",
|
|
3197
|
-
"nothing here",
|
|
3198
|
-
"empty",
|
|
3199
|
-
"no items",
|
|
3200
|
-
"get started"
|
|
3201
|
-
];
|
|
3406
|
+
var EMPTY_PHRASES = ["no data", "no results", "nothing here", "empty", "no items", "get started"];
|
|
3202
3407
|
function severityRank(s) {
|
|
3203
3408
|
if (s === "CRITICAL") return 3;
|
|
3204
3409
|
if (s === "WARNING") return 2;
|
|
@@ -3230,13 +3435,90 @@ function normalizeRect(state, viewport) {
|
|
|
3230
3435
|
height: layout.height / viewport.height
|
|
3231
3436
|
};
|
|
3232
3437
|
}
|
|
3233
|
-
function diagnosePageHealth(
|
|
3438
|
+
function diagnosePageHealth(input, options = {}) {
|
|
3439
|
+
const producer = options.producer ?? defaultProducer();
|
|
3440
|
+
try {
|
|
3441
|
+
return observe(input, producer, options.evaluatedAt);
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
return Observation.unknown(
|
|
3444
|
+
"producer_failed",
|
|
3445
|
+
`page-health producer threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
3446
|
+
Observation.provenance({ producer })
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
function observe(input, producer, evaluatedAt) {
|
|
3451
|
+
const base = {
|
|
3452
|
+
producer,
|
|
3453
|
+
observedAt: input.observedAt,
|
|
3454
|
+
evaluatedAt
|
|
3455
|
+
};
|
|
3456
|
+
if (!Array.isArray(input.elements)) {
|
|
3457
|
+
return Observation.unknown(
|
|
3458
|
+
"input_missing",
|
|
3459
|
+
"the registry handed over no `elements` array, so there was nothing to analyze; this says nothing about the screen itself",
|
|
3460
|
+
Observation.provenance({ ...base, observedAt: null })
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
const elements = input.elements;
|
|
3464
|
+
const viewport = input.viewport;
|
|
3465
|
+
if (!viewport || !(viewport.width > 0) || !(viewport.height > 0)) {
|
|
3466
|
+
return Observation.unknown(
|
|
3467
|
+
"input_missing",
|
|
3468
|
+
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`",
|
|
3469
|
+
Observation.provenance(base)
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
if (elements.length === 0 && (input.registeredComponents ?? 0) === 0) {
|
|
3473
|
+
return Observation.unknown(
|
|
3474
|
+
"producer_not_run",
|
|
3475
|
+
"the registry reports zero elements and zero components \u2014 nothing has registered yet, so there was nothing to look at",
|
|
3476
|
+
Observation.provenance(base)
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
return analyze(elements, viewport, base);
|
|
3480
|
+
}
|
|
3481
|
+
function nothingOnScreenDetail(elements, visibleCount, visibilityUnstated) {
|
|
3482
|
+
if (visibilityUnstated === 0 && visibleCount > 0) {
|
|
3483
|
+
return `all ${visibleCount} visible element(s) have no measured layout yet, so spatial coverage, layout regions and visual anomalies cannot be measured`;
|
|
3484
|
+
}
|
|
3485
|
+
if (visibilityUnstated > 0) {
|
|
3486
|
+
const geometry = visibleCount > 0 ? ` and none of the ${visibleCount} visible one(s) has a measured layout` : "";
|
|
3487
|
+
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`;
|
|
3488
|
+
}
|
|
3489
|
+
const unmounted = elements.filter((el) => el.state?.mounted === false).length;
|
|
3490
|
+
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`;
|
|
3491
|
+
}
|
|
3492
|
+
function analyze(elements, viewport, base) {
|
|
3234
3493
|
const findings = [];
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
const visible =
|
|
3494
|
+
const onScreen = elements.filter((el) => el.state?.visible === true);
|
|
3495
|
+
const visibilityUnstated = elements.filter(
|
|
3496
|
+
(el) => typeof el.state?.visible !== "boolean"
|
|
3497
|
+
).length;
|
|
3498
|
+
const visible = onScreen.flatMap((el) => {
|
|
3499
|
+
const rect = normalizeRect(el.state, viewport);
|
|
3500
|
+
return rect ? [{ el, rect }] : [];
|
|
3501
|
+
});
|
|
3502
|
+
const withoutGeometry = onScreen.length - visible.length;
|
|
3503
|
+
const unmeasured = [];
|
|
3504
|
+
if (withoutGeometry > 0) {
|
|
3505
|
+
unmeasured.push({ dimension: "geometry", count: withoutGeometry, code: "input_missing" });
|
|
3506
|
+
}
|
|
3507
|
+
if (visibilityUnstated > 0) {
|
|
3508
|
+
unmeasured.push({ dimension: "visibility", count: visibilityUnstated, code: "input_missing" });
|
|
3509
|
+
}
|
|
3510
|
+
const coverage = {
|
|
3511
|
+
considered: onScreen.length + visibilityUnstated,
|
|
3512
|
+
measured: visible.length,
|
|
3513
|
+
unmeasured
|
|
3514
|
+
};
|
|
3515
|
+
if (elements.length > 0 && visible.length === 0) {
|
|
3516
|
+
return Observation.unknown(
|
|
3517
|
+
"input_missing",
|
|
3518
|
+
nothingOnScreenDetail(elements, onScreen.length, visibilityUnstated),
|
|
3519
|
+
Observation.provenance({ ...base, coverage })
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
3240
3522
|
const grid = Array.from(
|
|
3241
3523
|
{ length: GRID_SIZE },
|
|
3242
3524
|
() => Array.from({ length: GRID_SIZE }, () => false)
|
|
@@ -3323,7 +3605,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
3323
3605
|
for (const el of elements) {
|
|
3324
3606
|
const elType = el.type ?? "";
|
|
3325
3607
|
if (SKIP_TEXT_TYPES.has(elType)) continue;
|
|
3326
|
-
const text = (el.state
|
|
3608
|
+
const text = (el.state?.textContent ?? "").toString();
|
|
3327
3609
|
if (!text) continue;
|
|
3328
3610
|
const textLower = text.toLowerCase();
|
|
3329
3611
|
for (const p of ERROR_PHRASES) {
|
|
@@ -3364,7 +3646,7 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
3364
3646
|
for (const el of elements) {
|
|
3365
3647
|
if (!INTERACTIVE_TYPES2.has(el.type)) continue;
|
|
3366
3648
|
interactiveTotal++;
|
|
3367
|
-
if (el.state
|
|
3649
|
+
if (el.state?.enabled === false) interactiveDisabled++;
|
|
3368
3650
|
}
|
|
3369
3651
|
const interactiveSeverity = interactiveTotal > 0 && interactiveDisabled / interactiveTotal > 0.5 ? "WARNING" : "OK";
|
|
3370
3652
|
findings.push({
|
|
@@ -3388,16 +3670,18 @@ function diagnosePageHealth(elements, viewport) {
|
|
|
3388
3670
|
detail: `zero_size=${zeroSize}, outside_viewport=${outsideViewport} (horizontal-overflow only; below/above-fold scrollable content is not an anomaly)`,
|
|
3389
3671
|
data: { zero_size: zeroSize, outside_viewport: outsideViewport }
|
|
3390
3672
|
});
|
|
3391
|
-
const heatmap = grid.map(
|
|
3392
|
-
|
|
3673
|
+
const heatmap = grid.map((row) => row.map((cell) => cell ? "#" : ".").join(""));
|
|
3674
|
+
return Observation.measured(
|
|
3675
|
+
{
|
|
3676
|
+
summary: worstSeverity(findings),
|
|
3677
|
+
findings,
|
|
3678
|
+
heatmap,
|
|
3679
|
+
element_count: elements.length,
|
|
3680
|
+
visible_count: visible.length
|
|
3681
|
+
},
|
|
3682
|
+
// A deduction over the registry, not an estimate.
|
|
3683
|
+
Observation.provenance({ ...base, coverage })
|
|
3393
3684
|
);
|
|
3394
|
-
return {
|
|
3395
|
-
summary: worstSeverity(findings),
|
|
3396
|
-
findings,
|
|
3397
|
-
heatmap,
|
|
3398
|
-
element_count: elements.length,
|
|
3399
|
-
visible_count: visible.length
|
|
3400
|
-
};
|
|
3401
3685
|
}
|
|
3402
3686
|
|
|
3403
3687
|
// src/server/handlers.ts
|
|
@@ -3845,13 +4129,30 @@ function createServerHandlers(registry, executor, config) {
|
|
|
3845
4129
|
},
|
|
3846
4130
|
getPageHealth: async (ctx) => {
|
|
3847
4131
|
const bodyViewport = ctx.body?.viewport;
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4132
|
+
let viewport;
|
|
4133
|
+
try {
|
|
4134
|
+
viewport = bodyViewport ?? config?.viewportProvider?.() ?? null;
|
|
4135
|
+
} catch {
|
|
4136
|
+
viewport = null;
|
|
4137
|
+
}
|
|
4138
|
+
const observedAt = Date.now();
|
|
4139
|
+
let elements;
|
|
4140
|
+
let registeredComponents;
|
|
4141
|
+
try {
|
|
4142
|
+
elements = registry.getAllElements().map((e) => ({
|
|
4143
|
+
type: e.type,
|
|
4144
|
+
state: e.getState()
|
|
4145
|
+
}));
|
|
4146
|
+
registeredComponents = registry.getAllComponents().length;
|
|
4147
|
+
} catch (err) {
|
|
4148
|
+
return success2(
|
|
4149
|
+
pageHealthUnknown(
|
|
4150
|
+
"producer_failed",
|
|
4151
|
+
`could not read the registry: ${err instanceof Error ? err.message : String(err)}`
|
|
4152
|
+
)
|
|
4153
|
+
);
|
|
4154
|
+
}
|
|
4155
|
+
return success2(diagnosePageHealth({ elements, registeredComponents, viewport, observedAt }));
|
|
3855
4156
|
},
|
|
3856
4157
|
// Workflows
|
|
3857
4158
|
getWorkflows: async () => {
|
|
@@ -4355,7 +4656,7 @@ function createServerHandlers(registry, executor, config) {
|
|
|
4355
4656
|
};
|
|
4356
4657
|
if (config?.appInfo) {
|
|
4357
4658
|
response.uiBridge = {
|
|
4358
|
-
version: "0.
|
|
4659
|
+
version: "0.7.1",
|
|
4359
4660
|
...config.appInfo,
|
|
4360
4661
|
capabilities: ["elements", "components", "actions", "design"],
|
|
4361
4662
|
elementCount: stats.elements,
|