@fasl-work/caos-app-shell 0.23.0 → 0.23.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/CHANGELOG.md +11 -0
- package/VERSION +1 -1
- package/gate/inpage.mjs +35 -7
- package/gate/run.mjs +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## [0.23.001] - 2026-11-07
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- The gate's document walk (`G.readDocument`) verifies a long, fully readable documentation page instead of stopping at a fixed budget of 600 samples and 30 seconds (known defect 55, issue 196). The budget now scales with the document: every scrollport the walk visits grants the samples of its own grid (the positions along its scroll extent, y by x), and the time allowed is 30 seconds plus 500 ms for every sample granted, under two absolute ceilings (20,000 samples, 15 minutes), so a page that never settles still fails with the same message. The report lists the cost of every walked state (`docWalks`: samples and time against the budget earned).
|
|
6
|
+
- The same walk counted a dense paragraph as unread when its inline math ended in a subscript and a comma followed at once: KaTeX builds a subscript from tables and an invisible 2px strut (`.vlist-s`) that overhang the math into the text beside it, and the hit test of the comma returned one of them instead of the paragraph. The hit test now looks through an element of the math that paints nothing at that point (no glyph of its own, no background, no border, no drawing) to the element beneath, and judges that by the unchanged rule (viewport, clip, scrollports, hit); math glyphs drawn over text still fail.
|
|
7
|
+
|
|
8
|
+
### Adoption
|
|
9
|
+
|
|
10
|
+
- No product change. A product pinned below 0.23.000 for this defect (CEFOP_DinHot, at 0.19.6) can move once a release carries this entry.
|
|
11
|
+
|
|
1
12
|
## [0.23.000] - 2026-11-06
|
|
2
13
|
|
|
3
14
|
### Added
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.23.
|
|
1
|
+
0.23.001
|
package/gate/inpage.mjs
CHANGED
|
@@ -923,8 +923,14 @@ export function installLib() {
|
|
|
923
923
|
/** SHG-079..082: collect glyph evidence by real scrolling, then compute the existing metric once.
|
|
924
924
|
* No DOM-presence fallback: every word fragment must fit the viewport, original clip bounds,
|
|
925
925
|
* scrollports and hit tests. Text-node identities deduplicate overlapping observations.
|
|
926
|
+
*
|
|
927
|
+
* The budget scales with the document (CAOS_APP_SHELL#196, known defect 55). A fixed count stopped a long, fully
|
|
928
|
+
* readable page. Now every scrollport the walk visits grants the samples of its own grid (the positions along its
|
|
929
|
+
* scroll extent, y by x, at the walk's viewport step), and the time allowed is `baseMs` plus `msPerSample` for every
|
|
930
|
+
* sample granted. `baseSamples` covers the first sample and the clicks of the flip cards and carousels. Two ceilings
|
|
931
|
+
* stay absolute, so a page that never settles still fails with the same message: `ceilingSamples`, `ceilingMs`.
|
|
926
932
|
*/
|
|
927
|
-
G.readDocument = async ({
|
|
933
|
+
G.readDocument = async ({ baseSamples = 100, baseMs = 30000, msPerSample = 500, ceilingSamples = 20000, ceilingMs = 900000 } = {}) => {
|
|
928
934
|
const page = document.querySelector('[data-doc-page]');
|
|
929
935
|
if (!page || !visible(page)) return null;
|
|
930
936
|
const started = performance.now();
|
|
@@ -934,9 +940,11 @@ export function installLib() {
|
|
|
934
940
|
const nodes = new Map(), witnessed = new Set(), flips = new Map(), decks = new Map();
|
|
935
941
|
const inkContext = document.createElement('canvas').getContext('2d');
|
|
936
942
|
if (!inkContext) throw new Error('document glyph measurement canvas is unavailable');
|
|
937
|
-
let samples = 0;
|
|
943
|
+
let samples = 0, granted = baseSamples;
|
|
944
|
+
const budget = () => ({ samples: Math.min(granted, ceilingSamples), ms: Math.min(baseMs + granted * msPerSample, ceilingMs) });
|
|
938
945
|
const check = () => {
|
|
939
|
-
|
|
946
|
+
const limit = budget();
|
|
947
|
+
if (++samples > limit.samples || performance.now() - started > limit.ms) throw new Error(`document traversal exceeded ${limit.samples} samples or ${Math.round(limit.ms)}ms; active document remains unverified`);
|
|
940
948
|
if (!page.isConnected || state.some((e) => !e.isConnected || e.getAttribute('aria-selected') !== 'true')) throw new Error('document traversal changed the active page/tab');
|
|
941
949
|
};
|
|
942
950
|
const frame = () => new Promise((resolve) => nativeRaf ? nativeRaf(() => nativeRaf(resolve)) : setTimeout(resolve, 32));
|
|
@@ -1038,9 +1046,23 @@ export function installLib() {
|
|
|
1038
1046
|
const first = Math.ceil(start), last = Math.floor(end) - 1;
|
|
1039
1047
|
return first <= last ? Math.max(first, Math.min(last, start + (end - start) * fraction)) : (start + end) / 2;
|
|
1040
1048
|
};
|
|
1049
|
+
// KaTeX builds a subscript from tables and a 2px strut (`.vlist-s`, a zero-width space in a 1px font) that overhang the
|
|
1050
|
+
// box of the math into the text beside it, so the hit test of the first glyph after the math (a comma) returns a
|
|
1051
|
+
// structural math element and not the paragraph. An element of the math that paints nothing at the point (no glyph of
|
|
1052
|
+
// its own, no background, no border, no drawing) cannot hide the text: look through it at what lies beneath, and judge
|
|
1053
|
+
// that by the same rule. A glyph of the math over the text is in the stack as a painting element, so it still fails.
|
|
1054
|
+
const paints = (e) => {
|
|
1055
|
+
if (/^(svg|img|canvas|video)$/i.test(e.tagName) || e.closest('svg')) return true;
|
|
1056
|
+
if ([...e.childNodes].some((c) => c.nodeType === 3 && /[^\s\u200b]/.test(c.nodeValue))) return true;
|
|
1057
|
+
const cs = getComputedStyle(e);
|
|
1058
|
+
return (parseColor(cs.backgroundColor)?.a ?? 0) > 0 || cs.backgroundImage !== 'none'
|
|
1059
|
+
|| ['Top', 'Right', 'Bottom', 'Left'].some((side) => parseFloat(cs[`border${side}Width`]) > 0 && (parseColor(cs[`border${side}Color`])?.a ?? 0) > 0);
|
|
1060
|
+
};
|
|
1061
|
+
const inert = (e) => !parent.closest('.katex') && Boolean(e.closest('.katex')) && !paints(e);
|
|
1041
1062
|
for (const q of [0.15, 0.5, 0.85]) for (const v of [0.15, 0.5, 0.85]) {
|
|
1042
1063
|
const x = sampleAt(rect.left, rect.right, q), y = sampleAt(rect.top, rect.bottom, v);
|
|
1043
|
-
|
|
1064
|
+
let hit = document.elementFromPoint(x, y);
|
|
1065
|
+
if (hit && inert(hit)) hit = document.elementsFromPoint(x, y).find((e) => !inert(e)) ?? null;
|
|
1044
1066
|
const transparentFallthrough = getComputedStyle(parent).pointerEvents === 'none' && hit?.contains(parent);
|
|
1045
1067
|
if (!hit || !(hit === parent || (control ? parent.contains(hit) : transparentFallthrough)) || pseudoCover(parent,x,y)) return false;
|
|
1046
1068
|
const link = parent.closest('a[href]');
|
|
@@ -1108,13 +1130,14 @@ export function installLib() {
|
|
|
1108
1130
|
const positions = (max, size) => {
|
|
1109
1131
|
const out = [0], step = Math.max(16, Math.floor(size * 0.65));
|
|
1110
1132
|
for (let p = step; p < max; p += step) {
|
|
1111
|
-
if (out.length >
|
|
1133
|
+
if (out.length > ceilingSamples) throw new Error('document scroll extent exceeds traversal budget');
|
|
1112
1134
|
out.push(p);
|
|
1113
1135
|
}
|
|
1114
1136
|
if (max > 0) out.push(max);
|
|
1115
1137
|
return out;
|
|
1116
1138
|
};
|
|
1117
1139
|
const sweep = async () => {
|
|
1140
|
+
granted += 1;
|
|
1118
1141
|
sample();
|
|
1119
1142
|
const containers = [...ancestorsUntil(page, document.body), page, ...page.querySelectorAll('*')].filter(scrollable);
|
|
1120
1143
|
const de = document.scrollingElement;
|
|
@@ -1125,7 +1148,9 @@ export function installLib() {
|
|
|
1125
1148
|
const s = getComputedStyle(e);
|
|
1126
1149
|
const maxX = /auto|scroll/.test(s.overflowX) ? e.scrollWidth - e.clientWidth : 0;
|
|
1127
1150
|
const maxY = e === de || /auto|scroll/.test(s.overflowY) ? e.scrollHeight - e.clientHeight : 0;
|
|
1128
|
-
|
|
1151
|
+
const ys = positions(maxY, Math.min(e.clientHeight, innerHeight)), xs = positions(maxX, Math.min(e.clientWidth, innerWidth));
|
|
1152
|
+
granted += ys.length * xs.length; // this scrollport's grid is the work the budget must cover
|
|
1153
|
+
for (const y of ys) for (const x of xs) {
|
|
1129
1154
|
// A settled card/animated illustration may reduce incidental overflow between frames.
|
|
1130
1155
|
// Clamp to the real extent; coverage still requires every fragment, never a partial word.
|
|
1131
1156
|
const wantedX = Math.min(x, e.scrollWidth - e.clientWidth), wantedY = Math.min(y, e.scrollHeight - e.clientHeight);
|
|
@@ -1184,7 +1209,10 @@ export function installLib() {
|
|
|
1184
1209
|
}
|
|
1185
1210
|
}
|
|
1186
1211
|
docEvidence = evidence;
|
|
1187
|
-
|
|
1212
|
+
const doc = G.docPage();
|
|
1213
|
+
// what the walk cost, so a gate run can report samples and time per state
|
|
1214
|
+
if (doc) doc.walk = { samples, budgetSamples: budget().samples, ms: Math.round(performance.now() - started), budgetMs: Math.round(budget().ms) };
|
|
1215
|
+
return doc;
|
|
1188
1216
|
} finally {
|
|
1189
1217
|
docEvidence = null;
|
|
1190
1218
|
try {
|
package/gate/run.mjs
CHANGED
|
@@ -127,6 +127,7 @@ export async function runGate(o) {
|
|
|
127
127
|
const failures = [];
|
|
128
128
|
const focusReported = new Set(); // G18: an App without a Focus control is reported once per route and mode
|
|
129
129
|
const waivers = []; // ADR-0017 2.1: every waiver a documentation page declares, with its verdict
|
|
130
|
+
const docWalks = []; // the document walk's cost per state (samples and time against the budget it earned), for the report
|
|
130
131
|
// The smallest measured values against each floor, so a pass shows its margin (and a CI font change that eats it
|
|
131
132
|
// is visible before it fails).
|
|
132
133
|
const minima = { drawnShare: null, stageFill: null };
|
|
@@ -642,7 +643,7 @@ export async function runGate(o) {
|
|
|
642
643
|
if (s.walkState?.doc) {
|
|
643
644
|
try {
|
|
644
645
|
const d = await page.evaluate(() => window.__caosGate.readDocument());
|
|
645
|
-
if (d) s.walkState.doc.push(d);
|
|
646
|
+
if (d) { s.walkState.doc.push(d); if (d.walk) docWalks.push({ route: where.route, lang: s.lang, trail: where.trail, ...d.walk }); }
|
|
646
647
|
} catch (e) {
|
|
647
648
|
fail('ADR-0017.2', where, `document readability traversal could not complete: ${e.message.split('\n')[0]}`);
|
|
648
649
|
}
|
|
@@ -1288,6 +1289,7 @@ export async function runGate(o) {
|
|
|
1288
1289
|
states,
|
|
1289
1290
|
minima,
|
|
1290
1291
|
waivers,
|
|
1292
|
+
docWalks,
|
|
1291
1293
|
checks,
|
|
1292
1294
|
captures: captures.length,
|
|
1293
1295
|
failures,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Shared web-app shell, workbench primitives, content primitives, design system and measured gate for the CAOS / Faena public apps (ADR-0016, ADR-0017, ADR-0071).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Felipe Santibáñez-Leal",
|