@fasl-work/caos-app-shell 0.10.0 → 0.11.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/README.md +11 -2
- package/bin/caos-shell-gate.mjs +3 -1
- package/dist/chart.d.ts +19 -2
- package/dist/chart.js +50 -8
- package/dist/index.d.ts +91 -9
- package/dist/index.js +188 -71
- package/gate/areas.mjs +327 -0
- package/gate/inpage.mjs +338 -0
- package/gate/run.mjs +45 -7
- package/package.json +3 -2
- package/reserved-classes.json +18 -0
- package/styles.css +36 -3
package/gate/inpage.mjs
CHANGED
|
@@ -31,6 +31,105 @@ export function installLib() {
|
|
|
31
31
|
return s.visibility !== 'hidden' && s.display !== 'none' && Number(s.opacity) !== 0;
|
|
32
32
|
};
|
|
33
33
|
const shown = (el) => el.getClientRects().length > 0 && (el.checkVisibility ? el.checkVisibility({ checkVisibilityCSS: true }) : true);
|
|
34
|
+
const FONT_FLOOR = 8; // px: a smaller font is not legible, whatever its line box
|
|
35
|
+
const fontPx = (el) => Number.parseFloat(getComputedStyle(el).fontSize);
|
|
36
|
+
/**
|
|
37
|
+
* The box a `clip-path` leaves of an element's own box `ar`, or null when the gate cannot read the shape: `inset()` is
|
|
38
|
+
* computed, any other shape (circle, polygon, path, url) is taken as cutting the element away.
|
|
39
|
+
*/
|
|
40
|
+
const clipPathBox = (cp, ar) => {
|
|
41
|
+
const m = /^inset\((.*)\)$/.exec(cp.trim());
|
|
42
|
+
if (!m) return null;
|
|
43
|
+
const v = m[1].split(/\bround\b/)[0].trim().split(/\s+/).filter(Boolean);
|
|
44
|
+
if (v.length < 1 || v.length > 4) return null;
|
|
45
|
+
const [top, right, bottom, left] = [v[0], v[1] ?? v[0], v[2] ?? v[0], v[3] ?? v[1] ?? v[0]];
|
|
46
|
+
const px = (x, ref) => (x.endsWith('%') ? (Number.parseFloat(x) / 100) * ref : Number.parseFloat(x));
|
|
47
|
+
const o = { top: px(top, ar.height), right: px(right, ar.width), bottom: px(bottom, ar.height), left: px(left, ar.width) };
|
|
48
|
+
if (Object.values(o).some((n) => Number.isNaN(n))) return null;
|
|
49
|
+
return { left: ar.left + o.left, top: ar.top + o.top, right: ar.right - o.right, bottom: ar.bottom - o.bottom };
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The part of `rect` a reader can see: inside `within`, inside every ancestor from `el` up (below `within`) that
|
|
53
|
+
* hides its overflow (`el` itself only when `self`: its overflow cuts its own text, not its own box) and inside every
|
|
54
|
+
* `clip-path` of `el` and of those ancestors. Null when nothing is left.
|
|
55
|
+
*/
|
|
56
|
+
const clipRect = (rect, el, within, self) => {
|
|
57
|
+
const b = within.getBoundingClientRect();
|
|
58
|
+
let left = Math.max(rect.left, b.left);
|
|
59
|
+
let top = Math.max(rect.top, b.top);
|
|
60
|
+
let right = Math.min(rect.right, b.right);
|
|
61
|
+
let bottom = Math.min(rect.bottom, b.bottom);
|
|
62
|
+
for (let a = el; a && a !== within && a !== document.body; a = a.parentElement) {
|
|
63
|
+
const s = getComputedStyle(a);
|
|
64
|
+
const ar = a.getBoundingClientRect();
|
|
65
|
+
if (a !== el || self) {
|
|
66
|
+
if (/hidden|clip/.test(s.overflowX)) {
|
|
67
|
+
left = Math.max(left, ar.left);
|
|
68
|
+
right = Math.min(right, ar.right);
|
|
69
|
+
}
|
|
70
|
+
if (/hidden|clip/.test(s.overflowY)) {
|
|
71
|
+
top = Math.max(top, ar.top);
|
|
72
|
+
bottom = Math.min(bottom, ar.bottom);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (s.clipPath && s.clipPath !== 'none') {
|
|
76
|
+
const c = clipPathBox(s.clipPath, ar);
|
|
77
|
+
if (!c) return null;
|
|
78
|
+
left = Math.max(left, c.left);
|
|
79
|
+
top = Math.max(top, c.top);
|
|
80
|
+
right = Math.min(right, c.right);
|
|
81
|
+
bottom = Math.min(bottom, c.bottom);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return right > left && bottom > top ? { left, top, right, bottom } : null;
|
|
85
|
+
};
|
|
86
|
+
const textNodesOf = (el) => {
|
|
87
|
+
const out = [];
|
|
88
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
89
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
90
|
+
if (!/\S/.test(n.nodeValue || '') || !n.parentElement || n.parentElement.closest('script, style, .katex-mathml')) continue;
|
|
91
|
+
out.push(n);
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
};
|
|
95
|
+
const probe = document.createRange();
|
|
96
|
+
/** Whether a reader sees the glyphs of a text node: its element is rendered, its font is legible, and a line of it lies inside what is not clipped. */
|
|
97
|
+
const glyphsSeen = (node, within, minW, minH) => {
|
|
98
|
+
const p = node.parentElement;
|
|
99
|
+
if (!p || (p.checkVisibility && !p.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) || fontPx(p) < FONT_FLOOR) return false;
|
|
100
|
+
probe.selectNodeContents(node);
|
|
101
|
+
for (const rc of probe.getClientRects()) {
|
|
102
|
+
if (rc.width <= 0 || rc.height <= 0) continue;
|
|
103
|
+
const c = clipRect(rc, p, within, true);
|
|
104
|
+
if (c && c.right - c.left >= minW && c.bottom - c.top >= minH) return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* What a reader can read (ADR-0080 decision 3, ADR-0017 2.1): the element is rendered (a box, opacity above 0, CSS
|
|
110
|
+
* visibility), its font is not zero, the part of its box that lies inside `within` (and inside every ancestor that
|
|
111
|
+
* hides its overflow, and every clip-path) is at least minW by minH px, and the glyphs of its text are painted where
|
|
112
|
+
* that is so: a text node counts when its font is at least FONT_FLOOR px and a line of it is inside what is not clipped
|
|
113
|
+
* (text pushed out of its box by `text-indent`, cut by `clip-path` or set in a 1 px font is not read). An element with
|
|
114
|
+
* no text (a drawing) is judged by its box. `only` judges one text node of the element. A 1 by 1 px screen-reader-only
|
|
115
|
+
* note, a transparent one, one positioned off-screen or one cut off by a clipping box is not readable. `shown` stays
|
|
116
|
+
* for `data-state="loading"`, which must catch every pending element, visible or not.
|
|
117
|
+
*/
|
|
118
|
+
const readable = (el, within, minW = 40, minH = 12, only = null) => {
|
|
119
|
+
if (!visible(el) || !within) return false;
|
|
120
|
+
if (fontPx(el) < 1) return false;
|
|
121
|
+
const box = clipRect(el.getBoundingClientRect(), el, within, false);
|
|
122
|
+
if (!box || box.right - box.left < minW || box.bottom - box.top < minH) return false;
|
|
123
|
+
if (!only && el.matches('svg, canvas, img, video')) return true; // a drawing: the box decides, its labels are not the text of a reader
|
|
124
|
+
const nodes = only ? [only] : textNodesOf(el);
|
|
125
|
+
if (nodes.length === 0) return true;
|
|
126
|
+
return nodes.some((n) => glyphsSeen(n, within, Math.min(minW, 2), Math.min(minH, 4)));
|
|
127
|
+
};
|
|
128
|
+
/** The words and the letters and digits of the text a reader sees in an element (rendered text: hidden descendants are not in it). */
|
|
129
|
+
const renderedText = (el) => {
|
|
130
|
+
const t = el.innerText || '';
|
|
131
|
+
return { chars: t.replace(/[^\p{L}\p{N}]+/gu, '').length, words: t.split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w)).length };
|
|
132
|
+
};
|
|
34
133
|
const path = (el) => {
|
|
35
134
|
const parts = [];
|
|
36
135
|
let e = el;
|
|
@@ -200,6 +299,8 @@ export function installLib() {
|
|
|
200
299
|
let instrument = null;
|
|
201
300
|
const surfaces = [];
|
|
202
301
|
let prose = false;
|
|
302
|
+
let unavailable = null; // data-state="unavailable" declared in the instrument: 'explained' (a reason a reader can read), 'unexplained' (readable, no reason) or 'unseen' (no reader can see it)
|
|
303
|
+
let drawn = 0; // drawing elements rendered in the instrument, whatever their size or visibility
|
|
203
304
|
if (inst && visible(inst)) {
|
|
204
305
|
const ir = inst.getBoundingClientRect();
|
|
205
306
|
const cx0 = Math.max(0, ir.left);
|
|
@@ -209,6 +310,32 @@ export function installLib() {
|
|
|
209
310
|
instrument = { x: cx0, y: cy0, w: Math.max(0, cx1 - cx0), h: Math.max(0, cy1 - cy0) };
|
|
210
311
|
const ctx = inst.querySelector('.caos-cw-context');
|
|
211
312
|
prose = Boolean(ctx && visible(ctx));
|
|
313
|
+
// a declaration counts when a reader can see the element (a box of 40 by 12 px at least, inside the instrument,
|
|
314
|
+
// opaque); its reason is the text rendered, 12 letters or digits in two words at least
|
|
315
|
+
const declared = [...inst.querySelectorAll('[data-state="unavailable"]')].filter(shown);
|
|
316
|
+
const seen = declared.filter((el) => readable(el, inst));
|
|
317
|
+
if (seen.length) {
|
|
318
|
+
unavailable = seen.some((el) => {
|
|
319
|
+
const t = renderedText(el);
|
|
320
|
+
return t.chars >= 12 && t.words >= 2;
|
|
321
|
+
})
|
|
322
|
+
? 'explained'
|
|
323
|
+
: 'unexplained';
|
|
324
|
+
} else if (declared.length) unavailable = 'unseen';
|
|
325
|
+
// a drawing element rendered at all (a collapsed or invisible chart too) cancels the exemption; a card that holds the note is not one
|
|
326
|
+
drawn = [...inst.querySelectorAll('canvas, img, video, table, svg, [data-plot], [data-drawn]')].filter((el) => {
|
|
327
|
+
if (el.getClientRects().length === 0) return false;
|
|
328
|
+
const tag = el.tagName.toLowerCase();
|
|
329
|
+
if (tag === 'svg') {
|
|
330
|
+
if (el.parentElement && el.parentElement.closest('svg')) return false;
|
|
331
|
+
if (el.classList.contains('lucide') || el.closest('button, a, summary, [role="tab"], [role="button"]')) return false;
|
|
332
|
+
const r = el.getBoundingClientRect();
|
|
333
|
+
if (el.getAttribute('aria-hidden') === 'true' && r.width > 0 && r.height > 0 && r.width <= 32 && r.height <= 32) return false;
|
|
334
|
+
}
|
|
335
|
+
if (tag === 'table' && el.parentElement && el.parentElement.closest('table')) return false;
|
|
336
|
+
if (el.matches('[data-plot], [data-drawn]') && declared.some((d) => el.contains(d))) return false;
|
|
337
|
+
return true;
|
|
338
|
+
}).length;
|
|
212
339
|
for (const el of inst.querySelectorAll('canvas, svg, img, table, video')) {
|
|
213
340
|
const tag = el.tagName.toLowerCase();
|
|
214
341
|
if (tag === 'svg' && el.parentElement && el.parentElement.closest('svg')) continue;
|
|
@@ -254,6 +381,18 @@ export function installLib() {
|
|
|
254
381
|
? { case: wb.getAttribute('data-case'), key: wb.getAttribute('data-state-key'), replayOnly: wb.getAttribute('data-replay-only') === '1' }
|
|
255
382
|
: null;
|
|
256
383
|
|
|
384
|
+
// The references of a page or open tab (ADR-0017 s4): one block, the last thing in its scope (the document page
|
|
385
|
+
// or the tab panel that holds it).
|
|
386
|
+
const refScope = (el) => el.closest('[role="tabpanel"], [data-doc-page]');
|
|
387
|
+
const refBlocks = [...document.querySelectorAll('[data-doc-references]')].filter(visible);
|
|
388
|
+
const isLastInScope = (block) => {
|
|
389
|
+
const scope = refScope(block);
|
|
390
|
+
for (let el = block; el && el !== scope; el = el.parentElement) {
|
|
391
|
+
for (let n = el.nextElementSibling; n; n = n.nextElementSibling) if (visible(n)) return false;
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
};
|
|
395
|
+
|
|
257
396
|
let offCentre = null;
|
|
258
397
|
const proseBody = document.querySelector('.page-body:not(.wide)');
|
|
259
398
|
if (proseBody && visible(proseBody)) {
|
|
@@ -278,19 +417,217 @@ export function installLib() {
|
|
|
278
417
|
instrument,
|
|
279
418
|
surfaces,
|
|
280
419
|
prose,
|
|
420
|
+
unavailable,
|
|
421
|
+
drawn,
|
|
281
422
|
workbench,
|
|
282
423
|
offCentre,
|
|
283
424
|
pageText: pageBody ? (pageBody.innerText || '').trim().length : 0,
|
|
284
425
|
referenceLists: [...document.querySelectorAll('.reference-list')].filter(visible).length,
|
|
426
|
+
// shell 0.11.0: the references of a page or open tab are one block at its end (ADR-0017 s4)
|
|
427
|
+
referenceBlocks: refBlocks.length,
|
|
428
|
+
referencesNotLast: refBlocks.filter((b) => !isLastInScope(b)).length,
|
|
429
|
+
refsDeclaredNoBlock: [...document.querySelectorAll('[data-refs]')].filter((sec) => visible(sec) && !refBlocks.some((b) => refScope(b) === refScope(sec))).length,
|
|
285
430
|
uncaptioned: [...document.querySelectorAll('.equation')].filter((e) => visible(e) && !e.querySelector('.equation-caption')).length,
|
|
286
431
|
// charts whose axis gives a tick the label of the tick before it (UPlotChart declares data-ticks-repeat)
|
|
287
432
|
repeatedTicks: [...document.querySelectorAll('[data-ticks-repeat]')]
|
|
288
433
|
.filter((el) => visible(el) && Number(el.getAttribute('data-ticks-repeat')) > 0)
|
|
289
434
|
.map((el) => ({ label: describe(el.closest('[data-plot]') || el), n: Number(el.getAttribute('data-ticks-repeat')) })),
|
|
435
|
+
// the drawing a filling card keeps under its note (G6 stage floor): the body of every visible `PlotCard fill`
|
|
436
|
+
fillStages: [...document.querySelectorAll('.caos-plot.fill')]
|
|
437
|
+
.filter(visible)
|
|
438
|
+
.map((card) => ({ label: describe(card), h: (card.querySelector(':scope > .caos-plot-body') || card).getBoundingClientRect().height })),
|
|
290
439
|
sectionsWithoutRefs: [...document.querySelectorAll('[data-no-refs-reason="missing"]')].filter(visible).length,
|
|
291
440
|
};
|
|
292
441
|
};
|
|
293
442
|
|
|
443
|
+
// ---- 0.11.0: the required areas of the documentation pages (ADR-0017 section 2.1) ------------------------------
|
|
444
|
+
|
|
445
|
+
let docBound = null; // the page being measured: what lies outside it, or is clipped out of it, is not read
|
|
446
|
+
const wordsOf = (el) => {
|
|
447
|
+
// the words a reader reads: visible text outside equations; `linkWords` of them inside links
|
|
448
|
+
let words = 0;
|
|
449
|
+
let linkWords = 0;
|
|
450
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
451
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
452
|
+
const parent = n.parentElement;
|
|
453
|
+
if (!parent || parent.closest('.katex, script, style') || !readable(parent, docBound, 4, 4, n)) continue;
|
|
454
|
+
const k = (n.nodeValue || '').split(/\s+/).filter(Boolean).length;
|
|
455
|
+
words += k;
|
|
456
|
+
if (parent.closest('a')) linkWords += k;
|
|
457
|
+
}
|
|
458
|
+
return { words, linkWords };
|
|
459
|
+
};
|
|
460
|
+
const tabLabel = (t) => (t.textContent || '').trim().replace(/\s+/g, ' ');
|
|
461
|
+
/** The tabs from the outermost one to the one that opens `panel`: their labels (for messages) and their ids (for rules). */
|
|
462
|
+
const chainOf = (panel) => {
|
|
463
|
+
const labels = [];
|
|
464
|
+
const ids = [];
|
|
465
|
+
let p = panel;
|
|
466
|
+
while (p) {
|
|
467
|
+
const id = p.getAttribute('aria-labelledby');
|
|
468
|
+
const tab = id ? document.getElementById(id) : null;
|
|
469
|
+
if (!tab) break;
|
|
470
|
+
labels.unshift(tabLabel(tab));
|
|
471
|
+
ids.unshift(tab.getAttribute('data-tab') || tabLabel(tab));
|
|
472
|
+
const tl = tab.closest('[role="tablist"]');
|
|
473
|
+
p = tl && tl.parentElement ? tl.parentElement.closest('[role="tabpanel"]') : null;
|
|
474
|
+
}
|
|
475
|
+
return { label: labels.join(' > '), key: ids.join('/'), head: ids[0] ?? null };
|
|
476
|
+
};
|
|
477
|
+
const textOf = (el) => (el.innerText || '').trim().replace(/\s+/g, ' '); // rendered text: a hidden descendant is not in it
|
|
478
|
+
const norm = (s) => String(s || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
479
|
+
/** A part of a statement a reader sees: the declared text is in the rendered text, in the readable part (`sel`) when the element has one. */
|
|
480
|
+
const statPart = (el, want, sel) => {
|
|
481
|
+
if (!want) return false;
|
|
482
|
+
const kids = [...el.querySelectorAll(sel)];
|
|
483
|
+
return kids.length ? kids.some((k) => readable(k, docBound, 4, 4) && norm(k.innerText).includes(want)) : norm(el.innerText).includes(want);
|
|
484
|
+
};
|
|
485
|
+
/** A quantified statement: visible, a digit in its value, and the unit and the source it declares readable beside it (the `Stat` component). */
|
|
486
|
+
const validStat = (el) => {
|
|
487
|
+
if (!readable(el, docBound, 4, 4)) return false;
|
|
488
|
+
const value = el.querySelector('.caos-stat-value');
|
|
489
|
+
return /\d/.test((value ? value.innerText : el.innerText) || '') && statPart(el, norm(el.getAttribute('data-stat-unit')), '.caos-stat-unit') && statPart(el, norm(el.getAttribute('data-stat-source')), '.caos-stat-source');
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* What a documentation page shows in this state: its lede, its waivers, the leaf topics open, the views (the outermost
|
|
494
|
+
* tabs), every visible section with what it holds, and the figures and captioned equations of the open view. The rules
|
|
495
|
+
* (gate/areas.mjs) are applied by the runner; this only measures. A hidden element is never counted, a paragraph is
|
|
496
|
+
* counted once per distinct text, and the words are those of the displayed language.
|
|
497
|
+
*/
|
|
498
|
+
G.docPage = () => {
|
|
499
|
+
const page = document.querySelector('[data-doc-page]');
|
|
500
|
+
if (!page || !visible(page)) return null;
|
|
501
|
+
docBound = page;
|
|
502
|
+
const seen = (el) => readable(el, page, 4, 4);
|
|
503
|
+
const lede = page.querySelector('.lede');
|
|
504
|
+
const panels = [...page.querySelectorAll('[role="tabpanel"]')].filter(visible);
|
|
505
|
+
// a tab list inside a section belongs to that section: its panels are not topics, and it does not make its topic a branch
|
|
506
|
+
const inSection = (el) => Boolean(el.closest('[data-doc-section]'));
|
|
507
|
+
const leaves = panels.filter((p) => !inSection(p) && ![...p.querySelectorAll('[role="tablist"]')].some((tl) => visible(tl) && !inSection(tl)));
|
|
508
|
+
const topicPanel = (el) => {
|
|
509
|
+
let p = el.closest('[role="tabpanel"]');
|
|
510
|
+
while (p && inSection(p)) p = p.parentElement ? p.parentElement.closest('[role="tabpanel"]') : null;
|
|
511
|
+
return p;
|
|
512
|
+
};
|
|
513
|
+
const topics = leaves.length ? leaves.map((p) => chainOf(p)) : [{ label: '', key: '', head: null }];
|
|
514
|
+
// the outermost tab list: the views of the page (the Introduction: executive, then technical)
|
|
515
|
+
const outer = [...page.querySelectorAll('[role="tablist"]')].filter(visible).find((tl) => !tl.closest('[role="tabpanel"]') && !inSection(tl));
|
|
516
|
+
const outerTabs = outer ? [...outer.querySelectorAll('[role="tab"]')] : [];
|
|
517
|
+
const outerPanel = outer && outer.parentElement ? [...outer.parentElement.children].find((c) => c.getAttribute('role') === 'tabpanel' && visible(c)) : null;
|
|
518
|
+
const captioned = (fig) => {
|
|
519
|
+
const cap = fig.querySelector('figcaption');
|
|
520
|
+
if (!cap || !textOf(cap)) return null;
|
|
521
|
+
const svg = fig.querySelector('svg');
|
|
522
|
+
const img = [...fig.querySelectorAll('img')].find((i) => /\.svg(\?|#|$)/i.test(i.getAttribute('src') || ''));
|
|
523
|
+
if (!(svg && visible(svg)) && !(img && visible(img))) return null;
|
|
524
|
+
return textOf(cap);
|
|
525
|
+
};
|
|
526
|
+
const equationCaption = (eq) => {
|
|
527
|
+
const cap = eq.querySelector('.equation-caption');
|
|
528
|
+
return cap && textOf(cap) ? textOf(cap) : null;
|
|
529
|
+
};
|
|
530
|
+
const sections = [...page.querySelectorAll('[data-doc-section]')].filter(visible).map((sec) => {
|
|
531
|
+
const mine = (el) => el.closest('[data-doc-section]') === sec && seen(el);
|
|
532
|
+
const dense = new Set();
|
|
533
|
+
for (const p of sec.querySelectorAll('p')) {
|
|
534
|
+
if (!mine(p) || p.closest('.callout, figure, .equation, table, [data-glossary], [data-stat]')) continue;
|
|
535
|
+
const { words, linkWords } = wordsOf(p);
|
|
536
|
+
if (words >= 40 && linkWords * 2 < words) dense.add(textOf(p).toLowerCase());
|
|
537
|
+
}
|
|
538
|
+
const callouts = [...sec.querySelectorAll('.callout')].filter(mine);
|
|
539
|
+
const variant = (c) => c.getAttribute('data-callout') || (c.classList.contains('callout-honest') ? 'honest' : c.classList.contains('callout-strong') ? 'strong' : 'note');
|
|
540
|
+
// what is distinct in this state, so the same thing seen in another state (a sub-tab of the section) counts once
|
|
541
|
+
const keyed = (els, key) => {
|
|
542
|
+
const seenKey = new Map();
|
|
543
|
+
return els.map((e) => {
|
|
544
|
+
const k = key(e);
|
|
545
|
+
const n = seenKey.get(k) ?? 0;
|
|
546
|
+
seenKey.set(k, n + 1);
|
|
547
|
+
return `${k}#${n}`;
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
const hasText = (el) => /[\p{L}\p{N}]/u.test(textOf(el));
|
|
551
|
+
// glossary items a reader sees: a term and a definition, both visible and holding a letter or a digit
|
|
552
|
+
const glossary = [...sec.querySelectorAll('[data-glossary]')].filter(mine).reduce((a, g) => {
|
|
553
|
+
const dts = [...g.querySelectorAll('dt')];
|
|
554
|
+
if (!dts.length) return a + [...g.querySelectorAll(':scope > li')].filter((li) => seen(li) && hasText(li)).length;
|
|
555
|
+
return a + dts.filter((dt) => {
|
|
556
|
+
const dd = dt.nextElementSibling;
|
|
557
|
+
return seen(dt) && hasText(dt) && dd && dd.tagName === 'DD' && seen(dd) && hasText(dd);
|
|
558
|
+
}).length;
|
|
559
|
+
}, 0);
|
|
560
|
+
const plots = [...sec.querySelectorAll('[data-plot]')].filter(mine);
|
|
561
|
+
const bare = [...sec.querySelectorAll('canvas, svg[data-chart]')].filter((c) => mine(c) && !c.closest('[data-plot]'));
|
|
562
|
+
const panel = topicPanel(sec);
|
|
563
|
+
const topic = panel ? chainOf(panel) : { label: '', key: '', head: null };
|
|
564
|
+
// the first table of the section: its rows of three or more cells, each proved by a quantified statement
|
|
565
|
+
const table = [...sec.querySelectorAll('table')].find(mine);
|
|
566
|
+
const tableRows = table ? [...table.querySelectorAll('tbody tr')].filter((r) => seen(r) && r.querySelectorAll('td, th').length >= 3) : [];
|
|
567
|
+
const list = [...sec.querySelectorAll('ul, ol')].find((l) => mine(l) && !l.closest('[data-glossary]') && !l.parentElement.closest('ul, ol'));
|
|
568
|
+
const items = list ? [...list.querySelectorAll(':scope > li')].filter(seen) : [];
|
|
569
|
+
// the rows of the area: those of its first table, or the items of its first list when the list comes first
|
|
570
|
+
const rowEls = list && (!table || !(table.compareDocumentPosition(list) & Node.DOCUMENT_POSITION_FOLLOWING)) ? items : tableRows;
|
|
571
|
+
const nonEmpty = (q, sel) => {
|
|
572
|
+
const e = q.querySelector(sel);
|
|
573
|
+
return Boolean(e && seen(e) && /[\p{L}\p{N}]/u.test(textOf(e)));
|
|
574
|
+
};
|
|
575
|
+
const eqEls = [...sec.querySelectorAll('.equation')].filter((e) => mine(e) && equationCaption(e));
|
|
576
|
+
const figEls = [...sec.querySelectorAll('figure')].filter((f) => mine(f) && captioned(f));
|
|
577
|
+
const honestEls = callouts.filter((c) => variant(c) === 'honest');
|
|
578
|
+
const strongEls = callouts.filter((c) => variant(c) === 'strong');
|
|
579
|
+
return {
|
|
580
|
+
title: ((sec.querySelector('h2') || {}).textContent || '').trim().slice(0, 60),
|
|
581
|
+
area: sec.getAttribute('data-area'),
|
|
582
|
+
kind: sec.getAttribute('data-method-kind'),
|
|
583
|
+
topic: topic.label,
|
|
584
|
+
topicKey: topic.key,
|
|
585
|
+
view: topic.head,
|
|
586
|
+
dense: dense.size,
|
|
587
|
+
denseKeys: [...dense],
|
|
588
|
+
equations: eqEls.length,
|
|
589
|
+
equationKeys: keyed(eqEls, (e) => `${equationCaption(e)}|${(e.querySelector('annotation') || {}).textContent || ''}`),
|
|
590
|
+
figures: figEls.length,
|
|
591
|
+
figureKeys: keyed(figEls, (f) => captioned(f)),
|
|
592
|
+
callouts: callouts.length,
|
|
593
|
+
calloutKeys: keyed(callouts, (c) => `${variant(c)}|${textOf(c)}`),
|
|
594
|
+
honest: honestEls.length,
|
|
595
|
+
honestKeys: keyed(honestEls, (c) => `${variant(c)}|${textOf(c)}`),
|
|
596
|
+
strong: strongEls.length,
|
|
597
|
+
strongKeys: keyed(strongEls, (c) => `${variant(c)}|${textOf(c)}`),
|
|
598
|
+
refs: Number(sec.getAttribute('data-refs')) || 0,
|
|
599
|
+
glossary,
|
|
600
|
+
steps: Math.max(0, ...[...sec.querySelectorAll('ol')].filter(mine).map((o) => [...o.querySelectorAll(':scope > li')].filter((li) => seen(li) && hasText(li)).length)),
|
|
601
|
+
tables: [...sec.querySelectorAll('table')].filter((t) => mine(t) && !t.parentElement.closest('table')).length,
|
|
602
|
+
codes: [...sec.querySelectorAll('pre')].filter(mine).length,
|
|
603
|
+
charts: plots.length + bare.length,
|
|
604
|
+
// a control registered with the panel (data-control), not any button or link
|
|
605
|
+
controls: [...sec.querySelectorAll('[data-control]')].filter(mine).length,
|
|
606
|
+
stats: [...sec.querySelectorAll('[data-stat]')].filter((e) => mine(e) && validStat(e)).length,
|
|
607
|
+
rows: rowEls.length,
|
|
608
|
+
rowsNoStat: rowEls.filter((r) => ![...r.querySelectorAll('[data-stat]')].some(validStat)).length,
|
|
609
|
+
items: items.length,
|
|
610
|
+
thinItems: items.filter((li) => wordsOf(li).words < 6).length,
|
|
611
|
+
qa: [...sec.querySelectorAll('[data-qa]')].filter((q) => mine(q) && nonEmpty(q, '[data-question]') && nonEmpty(q, '[data-answer]')).length,
|
|
612
|
+
};
|
|
613
|
+
});
|
|
614
|
+
const inView = (sel) => (outerPanel ? [...outerPanel.querySelectorAll(sel)] : [...page.querySelectorAll(sel)]).filter(seen);
|
|
615
|
+
const selected = outerTabs.find((t) => t.getAttribute('aria-selected') === 'true');
|
|
616
|
+
return {
|
|
617
|
+
ledeWords: lede && visible(lede) ? wordsOf(lede).words : 0,
|
|
618
|
+
waiver: page.getAttribute('data-floor-waiver'),
|
|
619
|
+
topics,
|
|
620
|
+
views: outerTabs.map((t) => t.getAttribute('data-tab') || tabLabel(t)),
|
|
621
|
+
view: selected ? selected.getAttribute('data-tab') || tabLabel(selected) : null,
|
|
622
|
+
// the executive view holds none anywhere on the page, the part outside the two views included
|
|
623
|
+
viewEquations: [...page.querySelectorAll('.equation')].filter(seen).length,
|
|
624
|
+
viewGlossaries: [...page.querySelectorAll('[data-glossary]')].filter(seen).length,
|
|
625
|
+
sections,
|
|
626
|
+
figures: inView('figure').map(captioned).filter(Boolean),
|
|
627
|
+
equations: inView('.equation').map(equationCaption).filter(Boolean),
|
|
628
|
+
};
|
|
629
|
+
};
|
|
630
|
+
|
|
294
631
|
/** A tall view must move when scrolled (S1, G5). Restores the positions it changed. */
|
|
295
632
|
G.scrolls = () => {
|
|
296
633
|
const out = [];
|
|
@@ -486,6 +823,7 @@ export function installLib() {
|
|
|
486
823
|
owner,
|
|
487
824
|
name: tl.getAttribute('aria-label') || '',
|
|
488
825
|
source: Boolean(tl.closest('.cs-source-tabs') || tl.classList.contains('cs-source-tabs')),
|
|
826
|
+
inSection: Boolean(tl.closest('[data-doc-section]')),
|
|
489
827
|
tabs: tabs.map((t) => (t.getAttribute('data-tab') || (t.textContent || '').trim()).slice(0, 40)),
|
|
490
828
|
active: tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true'),
|
|
491
829
|
};
|
package/gate/run.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// wide-font pass: Verdana, else DejaVu Sans, the Linux runner's font)
|
|
22
22
|
// G11 Spanish numbers a page in Spanish writes no decimal point (canvas text excepted)
|
|
23
23
|
// G12 sticky lists a vertical sub-tab list is still in view at the end of a long section
|
|
24
|
-
// G13 contrast every visible text at WCAG AA against the colour behind it (primary size, both themes)
|
|
24
|
+
// G13 contrast every visible text at WCAG AA against the colour behind it (primary size, both themes; first language, and every language on a documentation route)
|
|
25
25
|
// G14 captures index gate-output/index.html shows every capture by route and mode, the failures first
|
|
26
26
|
// plus the ADR-0071 and ADR-0017 measures (viewport, one-row tabs, rail without scroll, centred prose, captions,
|
|
27
27
|
// references).
|
|
@@ -31,6 +31,7 @@ import { join } from 'node:path';
|
|
|
31
31
|
import { decodePng, paintedBox, unionArea } from './png.mjs';
|
|
32
32
|
import { servePages } from './serve.mjs';
|
|
33
33
|
import { installLib } from './inpage.mjs';
|
|
34
|
+
import { DOC_ROUTES, docFloorFailures } from './areas.mjs';
|
|
34
35
|
|
|
35
36
|
export const DEFAULTS = {
|
|
36
37
|
sizes: '390x844,768x1024,1280x800,1600x900,2560x1440',
|
|
@@ -42,6 +43,7 @@ export const DEFAULTS = {
|
|
|
42
43
|
textFloor: 400,
|
|
43
44
|
instrumentMin: 0.5,
|
|
44
45
|
stageFillMin: 0.3,
|
|
46
|
+
stageFloor: 140,
|
|
45
47
|
viewportMinWidth: 1280,
|
|
46
48
|
out: 'gate-output',
|
|
47
49
|
maxShots: 40,
|
|
@@ -104,6 +106,7 @@ export async function runGate(o) {
|
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
const failures = [];
|
|
109
|
+
const waivers = []; // ADR-0017 2.1: every waiver a documentation page declares, with its verdict
|
|
107
110
|
// The smallest measured values against each floor, so a pass shows its margin (and a CI font change that eats it
|
|
108
111
|
// is visible before it fails).
|
|
109
112
|
const minima = { drawnShare: null, stageFill: null };
|
|
@@ -254,12 +257,13 @@ export async function runGate(o) {
|
|
|
254
257
|
const mode = `${w}x${h} ${theme} ${lang}`;
|
|
255
258
|
const primary = size === primarySize && theme === themes[0] && lang === langs[0];
|
|
256
259
|
const idleHere = theme === themes[0] && lang === langs[0];
|
|
257
|
-
const contrast = size === primarySize && lang === langs[0];
|
|
258
260
|
const ctx = await openContext(size, theme, lang);
|
|
259
261
|
const { page, sink } = await newPage(ctx);
|
|
260
262
|
for (const route of routes) {
|
|
261
263
|
const isWorkbench = workbenchRoutes.has(route);
|
|
262
264
|
const at = { route, mode };
|
|
265
|
+
// G13 runs in the first language; the documentation routes (the prose a reader reads) in every language
|
|
266
|
+
const contrast = size === primarySize && (lang === langs[0] || DOC_ROUTES.includes(route));
|
|
263
267
|
const res = await page.goto(urlFor(route), { waitUntil: 'load', timeout: 30000 }).catch((e) => e);
|
|
264
268
|
const status = res && typeof res.status === 'function' ? res.status() : `error ${res?.message ?? res}`;
|
|
265
269
|
if (status !== 200) {
|
|
@@ -279,7 +283,9 @@ export async function runGate(o) {
|
|
|
279
283
|
if (!String(id.lang ?? '').startsWith(lang)) fail('G2', at, `asked for ${lang} through ${LANG_STORAGE_KEY}; <html lang> is ${id.lang}`);
|
|
280
284
|
if (route !== '/' && id.activeHref && routeOf(id.activeHref) !== route) fail('G4', at, `loaded directly, the page shows route ${routeOf(id.activeHref)}`);
|
|
281
285
|
|
|
282
|
-
|
|
286
|
+
// ADR-0017 2.1: the documentation floors are measured once per language, at the primary size and first theme
|
|
287
|
+
const docFloors = size === primarySize && theme === themes[0] && DOC_ROUTES.includes(route);
|
|
288
|
+
const walkState = { tested: new Set(), keyedViews: 0, controls: 0, tablists: 0, doc: docFloors ? [] : null };
|
|
283
289
|
await processState(page, sink, { ...at, trail: '' }, { route, isWorkbench, primary, idle: idleHere, first: true, size, w, h, theme, lang, walkState, contrast });
|
|
284
290
|
|
|
285
291
|
if (primary && route !== '/') {
|
|
@@ -300,7 +306,8 @@ export async function runGate(o) {
|
|
|
300
306
|
sink.splice(0);
|
|
301
307
|
}
|
|
302
308
|
|
|
303
|
-
await walkTabs(page, sink, { ...at }, [], new Set(), { route, isWorkbench, primary, idle: primary, size, w, h, theme, lang, walkState, contrast });
|
|
309
|
+
const leaves = await walkTabs(page, sink, { ...at }, [], new Set(), { route, isWorkbench, primary, idle: primary, size, w, h, theme, lang, walkState, contrast });
|
|
310
|
+
if (walkState.doc) docResult(at, lang, walkState.doc, Math.max(1, leaves));
|
|
304
311
|
|
|
305
312
|
if (isWorkbench) await walkCases(page, sink, at, { route, isWorkbench, primary, size, w, h, theme, lang, walkState, contrast });
|
|
306
313
|
|
|
@@ -410,11 +417,19 @@ export async function runGate(o) {
|
|
|
410
417
|
if (f.truncatedCount) fail('G5', where, `${f.truncatedCount} truncated text(s) without the full text in a title: ${f.truncated.join('; ')}`);
|
|
411
418
|
for (const c of f.clipped) fail('G5', where, c);
|
|
412
419
|
if (f.offCentre !== null && f.offCentre > 2) fail('ADR-0017.1', where, `the prose page is off-centre by ${f.offCentre.toFixed(1)}px`);
|
|
413
|
-
if (f.referenceLists) fail('ADR-0017.4', where, `${f.referenceLists} bibliography
|
|
420
|
+
if (f.referenceLists) fail('ADR-0017.4', where, `${f.referenceLists} bibliography outside the end-of-page references block (a .reference-list); give each DocSection its refs and let the page list them once`);
|
|
421
|
+
if (f.referencesNotLast) fail('ADR-0017.4', where, `${f.referencesNotLast} references block(s) with content after them; references must be the last block of the page or tab`);
|
|
422
|
+
if (f.refsDeclaredNoBlock) fail('ADR-0017.4', where, `${f.refsDeclaredNoBlock} section(s) cite references the page never lists (no references block in their page or tab)`);
|
|
414
423
|
if (f.uncaptioned) fail('ADR-0017.2', where, `${f.uncaptioned} equation(s) without a caption`);
|
|
424
|
+
if (s.walkState?.doc) {
|
|
425
|
+
const d = await page.evaluate(() => window.__caosGate.docPage());
|
|
426
|
+
if (d) s.walkState.doc.push(d);
|
|
427
|
+
}
|
|
415
428
|
for (const r of f.repeatedTicks ?? []) {
|
|
416
429
|
fail('G6', where, `${r.label}: ${r.n} axis tick label(s) repeat the label before them (give the axis fixed decimals)`);
|
|
417
430
|
}
|
|
431
|
+
// a filling card whose note leaves its drawing under the floor (CAOS_APP_SHELL#95); below 1280 px the document scrolls
|
|
432
|
+
if (big) for (const c of f.fillStages ?? []) if (c.h < opt.stageFloor) fail('G6', where, `the drawing of ${c.label} keeps ${Math.round(c.h)} px under its note (floor ${opt.stageFloor} px)`);
|
|
418
433
|
for (const m of await page.evaluate(() => window.__caosGate.drawingText())) fail('G10', where, m);
|
|
419
434
|
if (s.lang === 'es') for (const m of await page.evaluate(() => window.__caosGate.decimals())) fail('G11', where, m);
|
|
420
435
|
if (s.w > 760) for (const m of await page.evaluate(() => window.__caosGate.sticky())) fail('G12', where, m);
|
|
@@ -452,6 +467,13 @@ export async function runGate(o) {
|
|
|
452
467
|
|
|
453
468
|
async function paintedChecks(page, f, where, big) {
|
|
454
469
|
if (f.prose) return; // the context write-up is prose, measured as a document, not as a drawing
|
|
470
|
+
if (f.surfaces.length === 0 && f.unavailable) {
|
|
471
|
+
// a declared absence a reader can see and read (ADR-0080 decision 3): nothing to draw, every other check still ran
|
|
472
|
+
if (f.drawn > 0) fail('G6', where, `a drawing is present but not visible beside a data-state="unavailable" note (${f.drawn} drawing element(s) rendered in the instrument, collapsed, hidden or too small to see): draw it, or remove it`);
|
|
473
|
+
else if (f.unavailable === 'unexplained') fail('G6', where, 'the instrument declares data-state="unavailable" and draws nothing, but gives no reason (the text a reader sees needs at least 12 letters or digits in two words or more, saying why the data is absent)');
|
|
474
|
+
else if (f.unavailable === 'unseen') fail('G6', where, 'the instrument declares data-state="unavailable" and draws nothing, but no reader can see the note (it needs a visible box of at least 40 x 12 px inside the instrument, not transparent, not clipped, not off-screen)');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
455
477
|
if (f.surfaces.length === 0) {
|
|
456
478
|
fail('G6', where, 'nothing is drawn in the instrument (no canvas, svg drawing, image or table)');
|
|
457
479
|
return;
|
|
@@ -527,7 +549,9 @@ export async function runGate(o) {
|
|
|
527
549
|
}
|
|
528
550
|
}
|
|
529
551
|
|
|
552
|
+
/** Walks every tab of every list owned by `trail`; returns the number of leaf topics found (a tab with no list inside it is one; a list inside a documentation section adds none). */
|
|
530
553
|
async function walkTabs(page, sink, where, trail, handled, s) {
|
|
554
|
+
let leaves = 0;
|
|
531
555
|
const lists = (await page.evaluate(() => window.__caosGate.tablists())).filter((l) => !handled.has(l.key) && !l.source);
|
|
532
556
|
const parentKey = trail.length ? trail[trail.length - 1].key : null;
|
|
533
557
|
const mine = lists.filter((l) => (parentKey ? l.owner === parentKey : l.owner === null));
|
|
@@ -546,13 +570,16 @@ export async function runGate(o) {
|
|
|
546
570
|
await tab.click({ timeout: 5000 });
|
|
547
571
|
} catch (e) {
|
|
548
572
|
fail('G7', at, `the tab could not be clicked: ${e.message.split('\n')[0]}`);
|
|
573
|
+
if (!tl.inSection) leaves += 1;
|
|
549
574
|
continue;
|
|
550
575
|
}
|
|
551
576
|
const why = await settle(page);
|
|
552
577
|
if (why) fail('G7', at, `never settled: ${why}`);
|
|
553
578
|
await processState(page, sink, at, { ...s, first: false, idle: s.idle });
|
|
554
579
|
}
|
|
555
|
-
await walkTabs(page, sink, where, here, new Set(handled), s);
|
|
580
|
+
const inner = await walkTabs(page, sink, where, here, new Set(handled), s);
|
|
581
|
+
// the tabs of a list inside a documentation section are visited, but they are parts of that section, not topics
|
|
582
|
+
if (!tl.inSection) leaves += inner === 0 ? 1 : inner;
|
|
556
583
|
}
|
|
557
584
|
if (tl.active >= 0 && tl.tabs.length > 1) {
|
|
558
585
|
await page.locator(tl.key).locator('[role="tab"]').filter({ visible: true }).nth(tl.active).click({ timeout: 5000 }).catch(() => undefined);
|
|
@@ -560,6 +587,14 @@ export async function runGate(o) {
|
|
|
560
587
|
sink.splice(0);
|
|
561
588
|
}
|
|
562
589
|
}
|
|
590
|
+
return leaves;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** ADR-0017 2.1: applies the area table (gate/areas.mjs) to what a documentation page showed in one language. */
|
|
594
|
+
function docResult(at, lang, states, expectedTopics) {
|
|
595
|
+
const r = docFloorFailures({ route: at.route, lang, states, expectedTopics });
|
|
596
|
+
for (const f of r.failures) fail('ADR-0017.2', { ...at, trail: f.trail }, f.message);
|
|
597
|
+
for (const w of r.waivers) waivers.push({ route: at.route, lang, ...w });
|
|
563
598
|
}
|
|
564
599
|
|
|
565
600
|
async function walkCases(page, sink, at, s) {
|
|
@@ -671,6 +706,7 @@ export async function runGate(o) {
|
|
|
671
706
|
wideFontSizes: opt.wideFont === false ? '' : opt.wideFontSizes,
|
|
672
707
|
states,
|
|
673
708
|
minima,
|
|
709
|
+
waivers,
|
|
674
710
|
checks,
|
|
675
711
|
captures: captures.length,
|
|
676
712
|
failures,
|
|
@@ -708,8 +744,10 @@ export function captureIndex(report, captures) {
|
|
|
708
744
|
'.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px}figure{margin:0;background:#fff;border:1px solid #d0d7de;padding:6px}' +
|
|
709
745
|
'img{width:100%;height:auto;display:block}figcaption{font-size:11px;color:#57606a;margin-top:4px}';
|
|
710
746
|
const head = `caos-shell-gate: ${esc(report.expectBrand)} at ${esc(report.base)}: ${report.ok ? 'OK' : `${report.failures.length} failure(s)`} in ${report.states} states, ${captures.length} captures`;
|
|
747
|
+
const wrows = (report.waivers ?? []).map((w) => `<tr><td>${esc(w.route)}</td><td>${esc(w.lang)}</td><td>${esc(w.floor)}</td><td>${esc(w.reason)}</td><td>${w.valid ? 'accepted' : 'refused'}</td></tr>`).join('');
|
|
748
|
+
const waiverTable = wrows ? `<h2>Waivers (${report.waivers.length})</h2><table><tr><td>route</td><td>language</td><td>floor</td><td>reason</td><td>verdict</td></tr>${wrows}</table>` : '';
|
|
711
749
|
const table = rows ? `<table><tr><td>check</td><td>route</td><td>mode</td><td>trail</td><td>message</td></tr>${rows}</table>` : '';
|
|
712
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${head}</title><style>${style}</style></head><body><h1>${head}</h1>${table}${sections}</body></html>\n`;
|
|
750
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${head}</title><style>${style}</style></head><body><h1>${head}</h1>${table}${waiverTable}${sections}</body></html>\n`;
|
|
713
751
|
}
|
|
714
752
|
|
|
715
753
|
function slug(route) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"prepare": "npm run build",
|
|
51
51
|
"prepublishOnly": "npm run build",
|
|
52
52
|
"prebuild": "node scripts/reserved-classes.mjs",
|
|
53
|
-
"test:gate": "node --test test/gate-selftest.mjs"
|
|
53
|
+
"test:gate": "node --test test/gate-selftest.mjs",
|
|
54
|
+
"check:sdd": "python scripts/check_sdd.py ."
|
|
54
55
|
},
|
|
55
56
|
"peerDependencies": {
|
|
56
57
|
"katex": "^0.16.0",
|
package/reserved-classes.json
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"caos-cw-context",
|
|
36
36
|
"caos-cw-panel",
|
|
37
37
|
"caos-cw-panel-head",
|
|
38
|
+
"caos-doc-reference-list",
|
|
39
|
+
"caos-doc-references",
|
|
38
40
|
"caos-doc-section",
|
|
39
41
|
"caos-field",
|
|
40
42
|
"caos-focus-actions",
|
|
@@ -52,6 +54,8 @@
|
|
|
52
54
|
"caos-gauge-track",
|
|
53
55
|
"caos-gauge-value",
|
|
54
56
|
"caos-gauge-zone",
|
|
57
|
+
"caos-glossary",
|
|
58
|
+
"caos-glossary-item",
|
|
55
59
|
"caos-knob",
|
|
56
60
|
"caos-knob-head",
|
|
57
61
|
"caos-knob-value",
|
|
@@ -69,6 +73,9 @@
|
|
|
69
73
|
"caos-prov-published",
|
|
70
74
|
"caos-prov-real",
|
|
71
75
|
"caos-prov-synthetic",
|
|
76
|
+
"caos-qa",
|
|
77
|
+
"caos-qa-answer",
|
|
78
|
+
"caos-qa-question",
|
|
72
79
|
"caos-readout",
|
|
73
80
|
"caos-readout-head",
|
|
74
81
|
"caos-readout-row",
|
|
@@ -79,6 +86,8 @@
|
|
|
79
86
|
"caos-stale",
|
|
80
87
|
"caos-stale-note",
|
|
81
88
|
"caos-stale-overlay",
|
|
89
|
+
"caos-stat-source",
|
|
90
|
+
"caos-stat-value",
|
|
82
91
|
"caos-surface",
|
|
83
92
|
"caos-surface-actions",
|
|
84
93
|
"caos-surface-body",
|
|
@@ -268,6 +277,8 @@
|
|
|
268
277
|
"caos-cw-context",
|
|
269
278
|
"caos-cw-panel",
|
|
270
279
|
"caos-cw-panel-head",
|
|
280
|
+
"caos-doc-reference-list",
|
|
281
|
+
"caos-doc-references",
|
|
271
282
|
"caos-doc-section",
|
|
272
283
|
"caos-field",
|
|
273
284
|
"caos-focus-actions",
|
|
@@ -285,6 +296,8 @@
|
|
|
285
296
|
"caos-gauge-track",
|
|
286
297
|
"caos-gauge-value",
|
|
287
298
|
"caos-gauge-zone",
|
|
299
|
+
"caos-glossary",
|
|
300
|
+
"caos-glossary-item",
|
|
288
301
|
"caos-knob",
|
|
289
302
|
"caos-knob-head",
|
|
290
303
|
"caos-knob-value",
|
|
@@ -302,6 +315,9 @@
|
|
|
302
315
|
"caos-prov-published",
|
|
303
316
|
"caos-prov-real",
|
|
304
317
|
"caos-prov-synthetic",
|
|
318
|
+
"caos-qa",
|
|
319
|
+
"caos-qa-answer",
|
|
320
|
+
"caos-qa-question",
|
|
305
321
|
"caos-readout",
|
|
306
322
|
"caos-readout-head",
|
|
307
323
|
"caos-readout-row",
|
|
@@ -312,6 +328,8 @@
|
|
|
312
328
|
"caos-stale",
|
|
313
329
|
"caos-stale-note",
|
|
314
330
|
"caos-stale-overlay",
|
|
331
|
+
"caos-stat-source",
|
|
332
|
+
"caos-stat-value",
|
|
315
333
|
"caos-surface",
|
|
316
334
|
"caos-surface-actions",
|
|
317
335
|
"caos-surface-body",
|