@fasl-work/caos-app-shell 0.19.11 → 0.20.0
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 +29 -0
- package/VERSION +1 -1
- package/bin/caos-shell-gate.mjs +5 -2
- package/dist/chart.d.ts +2 -2
- package/dist/chart.js +2 -2
- package/dist/{chunk-FXPSZD6X.js → chunk-FPPJQC2J.js} +5 -0
- package/dist/index.d.ts +202 -6
- package/dist/index.js +302 -3
- package/dist/{tokens-BvU9bFPw.d.ts → tokens-CIdhVv-t.d.ts} +1 -1
- package/gate/artifacts.mjs +29 -0
- package/gate/inpage.mjs +68 -0
- package/gate/run.mjs +167 -12
- package/package.json +100 -100
- package/reserved-classes.json +60 -0
- package/styles.css +100 -4
package/gate/run.mjs
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// are JSON, not the HTML fallback; a missing asset answers 404; doc text clears a floor
|
|
10
10
|
// G5 scroll and reach boxes inside the viewport, the rail and their scrollers; tall views scroll; nothing is
|
|
11
11
|
// cut; truncated text carries its full text; a select shows its whole selected text (0.17.0); every control reachable by the pointer
|
|
12
|
-
// G6 painted area drawing surfaces have painted pixels, fill their stage, and cover at least
|
|
13
|
-
// viewport on the App route, measured on the screenshot, never on a host box
|
|
12
|
+
// G6 painted area drawing surfaces have painted pixels, fill their stage, and cover at least 60% of the
|
|
13
|
+
// viewport on the App route, measured on the screenshot, never on a host box (ADR-0082)
|
|
14
14
|
// G7 walk everything every route, tab, sub-tab and case, at five sizes, both themes, both languages,
|
|
15
15
|
// waiting for declared state (data-state, data-stale, data-drawn), never networkidle
|
|
16
16
|
// G8 idle at rest no sustained animation frames or DOM mutations after load and after a tab switch
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
// plus the ADR-0071 and ADR-0017 measures (viewport, one-row tabs, rail without scroll, centred prose, captions,
|
|
39
39
|
// references).
|
|
40
40
|
|
|
41
|
+
import { readJsonArtifact } from './artifacts.mjs';
|
|
41
42
|
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
42
43
|
import { join } from 'node:path';
|
|
43
44
|
import { decodePng, paintedBox, unionArea } from './png.mjs';
|
|
@@ -54,7 +55,7 @@ export const DEFAULTS = {
|
|
|
54
55
|
idleMs: 3000,
|
|
55
56
|
settleMs: 15000,
|
|
56
57
|
textFloor: 400,
|
|
57
|
-
instrumentMin: 0.
|
|
58
|
+
instrumentMin: 0.6,
|
|
58
59
|
stageFillMin: 0.3,
|
|
59
60
|
stageFloor: 140,
|
|
60
61
|
viewportMinWidth: 1280,
|
|
@@ -63,6 +64,7 @@ export const DEFAULTS = {
|
|
|
63
64
|
wideFont: true,
|
|
64
65
|
wideFontSizes: '390x844,1280x800',
|
|
65
66
|
requireFocus: false,
|
|
67
|
+
requireShowcase: false,
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
/** The wide fallback font of the G10 pass: wider than the Windows and macOS sans fonts; DejaVu Sans is what a Linux
|
|
@@ -135,6 +137,7 @@ export async function runGate(o) {
|
|
|
135
137
|
let states = 0;
|
|
136
138
|
const fail = (check, where, message) => failures.push({ check, route: where.route ?? '', mode: where.mode ?? '', trail: where.trail ?? '', message });
|
|
137
139
|
const artifacts = new Map();
|
|
140
|
+
const artifactChecks = [];
|
|
138
141
|
|
|
139
142
|
const browser = await chromium.launch();
|
|
140
143
|
const openContext = async ([w, h], theme, lang, wide = false) => {
|
|
@@ -193,10 +196,7 @@ export async function runGate(o) {
|
|
|
193
196
|
if ((type === 'fetch' || type === 'xhr') && pathname.endsWith('.json') && r.status() < 400 && !artifacts.has(pathname)) {
|
|
194
197
|
artifacts.set(
|
|
195
198
|
pathname,
|
|
196
|
-
r
|
|
197
|
-
(t) => t.trimStart().slice(0, 1),
|
|
198
|
-
() => '',
|
|
199
|
-
),
|
|
199
|
+
readJsonArtifact(r),
|
|
200
200
|
);
|
|
201
201
|
}
|
|
202
202
|
});
|
|
@@ -244,7 +244,6 @@ export async function runGate(o) {
|
|
|
244
244
|
if (res.status() !== 404) fail('G4', { route: '/__caos_gate_missing__.json' }, `a missing asset answered ${res.status()}, not 404 (an SPA fallback hides missing artifacts and broken deep links)`);
|
|
245
245
|
await ctx.close();
|
|
246
246
|
}
|
|
247
|
-
|
|
248
247
|
// ---- G1: the subject, and the route list (from the header nav unless given).
|
|
249
248
|
let routes = opt.routes ? String(opt.routes).split(',').map((r) => r.trim()).filter(Boolean) : null;
|
|
250
249
|
{
|
|
@@ -266,6 +265,7 @@ export async function runGate(o) {
|
|
|
266
265
|
sink.splice(0);
|
|
267
266
|
await ctx.close();
|
|
268
267
|
}
|
|
268
|
+
const storyRoutes = new Set(routes.filter((route) => /^\/story(?:\/|$)/.test(route)));
|
|
269
269
|
|
|
270
270
|
// ---- G18 on a surface App: under --require-focus the App route must be among the measured routes, or nothing would be measured
|
|
271
271
|
if (opt.requireFocus && workbenchRoutes.size === 0 && !routes.includes(appRoute)) {
|
|
@@ -381,7 +381,7 @@ export async function runGate(o) {
|
|
|
381
381
|
|
|
382
382
|
if (primary) {
|
|
383
383
|
// ADR-0071 rule 9 / G5: every header route reached by a real pointer click from the App route.
|
|
384
|
-
for (const route of routes.filter((r) => r !== workbenchRoute)) {
|
|
384
|
+
for (const route of routes.filter((r) => r !== workbenchRoute && !storyRoutes.has(r))) {
|
|
385
385
|
await page.goto(urlFor(workbenchRoute), { waitUntil: 'load', timeout: 30000 }).catch(() => undefined);
|
|
386
386
|
await settle(page);
|
|
387
387
|
const link = page.locator('header nav a[href]');
|
|
@@ -409,6 +409,159 @@ export async function runGate(o) {
|
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
+
// ---- G21 (ADR-0082): a required showcase story must be a real, directly addressable 3–6 step experience.
|
|
413
|
+
if (opt.requireShowcase) {
|
|
414
|
+
const stories = routes.filter((route) => /^\/story(?:\/|$)/.test(route));
|
|
415
|
+
if (stories.length === 0) {
|
|
416
|
+
fail('G21', { route: '/story', mode: 'routes' }, 'no /story/<step> route is declared; ADR-0082 requires a directly addressable guided story');
|
|
417
|
+
} else {
|
|
418
|
+
const size = primarySize;
|
|
419
|
+
const [w, h] = size;
|
|
420
|
+
const mode = `${w}x${h} ${themes[0]} ${langs[0]} story`;
|
|
421
|
+
const ctx = await openContext(size, themes[0], langs[0]);
|
|
422
|
+
const { page, sink } = await newPage(ctx);
|
|
423
|
+
const visited = [];
|
|
424
|
+
for (const route of stories) {
|
|
425
|
+
const at = { route, mode };
|
|
426
|
+
const res = await page.goto(urlFor(route), { waitUntil: 'load', timeout: 30000 }).catch((e) => e);
|
|
427
|
+
const status = res && typeof res.status === 'function' ? res.status() : `error ${res?.message ?? res}`;
|
|
428
|
+
if (status !== 200) { fail('G21', at, `direct story link answered ${status}`); continue; }
|
|
429
|
+
const settled = await settle(page);
|
|
430
|
+
if (settled) fail('G21', at, `story did not settle: ${settled}`);
|
|
431
|
+
const f = await page.evaluate(() => window.__caosGate.showcaseFacts());
|
|
432
|
+
if (!f.present) { fail('G21', at, 'route does not render [data-story]'); continue; }
|
|
433
|
+
if (f.count < 3 || f.count > 6) fail('G21', at, `story has ${f.count} visible steps; ADR-0082 requires 3 to 6`);
|
|
434
|
+
if (!f.headline || !/\d/.test(f.headline)) fail('G21', at, `step headline does not state a numeric finding ("${f.headline}")`);
|
|
435
|
+
if (f.headlineWords > 12) fail('G21', at, `finding headline has ${f.headlineWords} words; ADR-0082 caps it at 12`);
|
|
436
|
+
if (!f.stage || !f.panel || !f.layout) fail('G21', at, 'story needs one visible stage and one docked panel');
|
|
437
|
+
if (f.layout && w >= opt.viewportMinWidth) {
|
|
438
|
+
const share = (f.layout.width * f.layout.height) / (w * h);
|
|
439
|
+
if (share < 0.8) fail('G21', at, `story stage and docked panel cover ${(100 * share).toFixed(1)}% of the viewport (floor 80%, ADR-0082)`);
|
|
440
|
+
}
|
|
441
|
+
if (f.calls.length < 3) fail('G21', at, `stage has ${f.calls.length} visible anchored callouts; ADR-0082 requires at least 3`);
|
|
442
|
+
for (const call of f.calls) {
|
|
443
|
+
if (!call.value?.trim() || !call.unit?.trim()) fail('G21', at, `callout ${call.id} has no value or unit`);
|
|
444
|
+
if (!['alarm', 'warning', 'normal', 'selected', 'inactive'].includes(call.state)) fail('G21', at, `callout ${call.id} has no recognised semantic state`);
|
|
445
|
+
if (!call.source?.trim()) fail('G21', at, `callout ${call.id} has no value provenance`);
|
|
446
|
+
if (![call.anchor?.x, call.anchor?.y].every((v) => Number.isFinite(v) && v >= 0 && v <= 1)) fail('G21', at, `callout ${call.id} has no normalized subject anchor`);
|
|
447
|
+
else if (call.box && f.stage) {
|
|
448
|
+
const x = f.stage.left + call.anchor.x * f.stage.width, y = f.stage.top + call.anchor.y * f.stage.height;
|
|
449
|
+
const clearance = 8;
|
|
450
|
+
if (x >= call.box.left - clearance && x <= call.box.right + clearance && y >= call.box.top - clearance && y <= call.box.bottom + clearance)
|
|
451
|
+
fail('G21', at, `callout ${call.id} covers its own subject anchor`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (let i = 0; i < f.calls.length; i += 1) for (let j = i + 1; j < f.calls.length; j += 1) {
|
|
455
|
+
const a = f.calls[i].box, b = f.calls[j].box;
|
|
456
|
+
if (a && b && a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top) fail('G21', at, `callouts ${f.calls[i].id} and ${f.calls[j].id} overlap`);
|
|
457
|
+
}
|
|
458
|
+
if (f.calls.some(({ box }) => !box || box.left < f.stage.left || box.right > f.stage.right || box.top < f.stage.top || box.bottom > f.stage.bottom)) fail('G21', at, 'one or more callouts escape the stage instead of following its subject');
|
|
459
|
+
if (f.calls.length && await page.locator('[data-story] [data-caos-callout-leader]').count() < f.calls.length) fail('G21', at, 'not every scene callout has a leader to its subject anchor');
|
|
460
|
+
if (f.scroll.width > f.viewport.width + 1) fail('G21', at, `story page overflows horizontally (${f.scroll.width}px in ${f.viewport.width}px)`);
|
|
461
|
+
visited.push({ route, step: f.step, hrefs: f.links.map((x) => x.href) });
|
|
462
|
+
drain(sink, at);
|
|
463
|
+
}
|
|
464
|
+
const first = visited.find((x) => x.hrefs.length >= 3);
|
|
465
|
+
if (first) {
|
|
466
|
+
const hrefs = [...new Set(first.hrefs.map((href) => routeOf(new URL(href, base).href)).filter(Boolean))];
|
|
467
|
+
if (hrefs.length < 3 || hrefs.some((href) => !stories.includes(href))) fail('G21', { route: first.route, mode }, `stepper links do not directly address at least three declared story steps (${hrefs.join(', ') || 'none'})`);
|
|
468
|
+
await page.goto(urlFor(first.route), { waitUntil: 'load', timeout: 30000 }).catch(() => undefined);
|
|
469
|
+
await settle(page);
|
|
470
|
+
const perf = await page.evaluate(() => window.__caosGate.storyPerformanceFacts());
|
|
471
|
+
if (perf.lcp === null || perf.lcp > 2500) fail('G21', { route: first.route, mode }, `story LCP is ${perf.lcp === null ? 'unavailable' : `${perf.lcp.toFixed(0)} ms`}; ADR-0082 ceiling is 2500 ms`);
|
|
472
|
+
const assetBytes = perf.assets3d.reduce((sum, asset) => sum + asset.bytes, 0);
|
|
473
|
+
if (perf.assets3d.some((asset) => !asset.bytes)) fail('G21', { route: first.route, mode }, 'a 3D asset has no measurable compressed transfer size');
|
|
474
|
+
if (assetBytes > 15 * 1024 * 1024) fail('G21', { route: first.route, mode }, `compressed 3D assets total ${(assetBytes / 1024 / 1024).toFixed(2)} MiB; ADR-0082 ceiling is 15 MiB`);
|
|
475
|
+
const main = page.locator('[data-story]').first();
|
|
476
|
+
await main.focus().catch(() => undefined);
|
|
477
|
+
await page.keyboard.press('End').catch(() => undefined);
|
|
478
|
+
await page.waitForFunction((step) => window.__caosGate.showcaseFacts().step !== step, first.step, { timeout: 3000 }).catch(() => undefined);
|
|
479
|
+
const endFacts = await page.evaluate(() => window.__caosGate.showcaseFacts());
|
|
480
|
+
if (endFacts.step === first.step) fail('G21', { route: first.route, mode }, 'End key does not move the story to its final step');
|
|
481
|
+
await page.keyboard.press('Home').catch(() => undefined);
|
|
482
|
+
await page.waitForFunction((step) => window.__caosGate.showcaseFacts().step === step, first.step, { timeout: 3000 }).catch(() => undefined);
|
|
483
|
+
const homeFacts = await page.evaluate(() => window.__caosGate.showcaseFacts());
|
|
484
|
+
if (homeFacts.step !== first.step) fail('G21', { route: first.route, mode }, 'Home key does not return the story to its first step');
|
|
485
|
+
const sceneItem = page.locator('[data-story] [data-linked-view="scene"][data-linked-id]').first();
|
|
486
|
+
const panelItem = (id) => page.locator(`[data-story] [data-linked-view="panel"][data-linked-id="${id}"]`).first();
|
|
487
|
+
if (!(await sceneItem.count())) fail('G21', { route: first.route, mode }, 'story has no selectable linked scene objects');
|
|
488
|
+
else {
|
|
489
|
+
const id = await sceneItem.getAttribute('data-linked-id');
|
|
490
|
+
const panelMatch = panelItem(id);
|
|
491
|
+
if (!(await panelMatch.count())) fail('G21', { route: first.route, mode }, `scene object ${id} has no linked panel item`);
|
|
492
|
+
else {
|
|
493
|
+
await sceneItem.evaluate((el) => el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })));
|
|
494
|
+
await page.waitForFunction((target) => document.querySelector(`[data-linked-view="panel"][data-linked-id="${CSS.escape(target)}"][data-linked-selected="true"]`), id, { timeout: 2000 }).catch(() => undefined);
|
|
495
|
+
if (await panelMatch.getAttribute('data-linked-selected') !== 'true') fail('G21', { route: first.route, mode }, `selecting scene object ${id} does not select the matching panel item`);
|
|
496
|
+
await panelMatch.evaluate((el) => el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })));
|
|
497
|
+
await page.waitForFunction((target) => document.querySelector(`[data-linked-view="scene"][data-linked-id="${CSS.escape(target)}"][data-linked-selected="true"]`), id, { timeout: 2000 }).catch(() => undefined);
|
|
498
|
+
if (await sceneItem.getAttribute('data-linked-selected') !== 'true') fail('G21', { route: first.route, mode }, `selecting panel item ${id} does not highlight the matching scene object`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const cursorInput = page.locator('[data-story] [data-linked-cursor]').first();
|
|
502
|
+
if (!(await cursorInput.count())) fail('G21', { route: first.route, mode }, 'story has no shared scrub control for linked scene and plots');
|
|
503
|
+
else {
|
|
504
|
+
await cursorInput.evaluate((el) => { const input = el; const min = Number(input.min || 0), max = Number(input.max || 100); const next = String(Math.round(min + (max - min) * .63)); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(input, next); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); });
|
|
505
|
+
await page.waitForTimeout(80);
|
|
506
|
+
const cursorFacts = await page.evaluate(() => window.__caosGate.showcaseFacts());
|
|
507
|
+
const value = cursorFacts.cursorInputs[0]?.value;
|
|
508
|
+
if (!cursorFacts.cursors.length || cursorFacts.cursors.some((position) => position !== value)) fail('G21', { route: first.route, mode }, 'scrubbing does not move every linked-view marker to the same cursor');
|
|
509
|
+
}
|
|
510
|
+
const play = page.locator('[data-story] button[aria-pressed]').first();
|
|
511
|
+
if (!(await play.count())) fail('G21', { route: first.route, mode }, 'story has no play/pause control with an observable state');
|
|
512
|
+
else {
|
|
513
|
+
await play.click();
|
|
514
|
+
if (await play.getAttribute('aria-pressed') !== 'true') fail('G21', { route: first.route, mode }, 'play control does not enter playing state');
|
|
515
|
+
const link = page.locator('[data-story-link]').nth(1);
|
|
516
|
+
await link.click();
|
|
517
|
+
if (await page.locator('[data-story] button[aria-pressed]').first().getAttribute('aria-pressed') !== 'false') fail('G21', { route: first.route, mode }, 'story autoplay does not pause on reader interaction');
|
|
518
|
+
}
|
|
519
|
+
// Measure a real step change against ADR-0082: eased stage motion, interaction latency and frames while it moves.
|
|
520
|
+
await page.evaluate(() => { window.__caosGate.resetStoryInteractions(); window.__caosGate.storyFrameSample = window.__caosGate.sampleStoryFrames(1600); });
|
|
521
|
+
const next = page.locator('[data-story] .caos-story-controls button').nth(2);
|
|
522
|
+
if (await next.isEnabled()) {
|
|
523
|
+
await next.click();
|
|
524
|
+
await page.waitForTimeout(90);
|
|
525
|
+
const earlyMotion = await page.evaluate(() => window.__caosGate.showcaseFacts().motion);
|
|
526
|
+
await page.waitForTimeout(260);
|
|
527
|
+
const lateMotion = await page.evaluate(() => window.__caosGate.showcaseFacts().motion);
|
|
528
|
+
if (!earlyMotion || earlyMotion.animationName === 'none' || earlyMotion.animationName === 'auto' || earlyMotion.transform === 'none' || earlyMotion.transform === 'matrix(1, 0, 0, 1, 0, 0)') fail('G21', { route: first.route, mode }, 'story step has no visible eased stage transition');
|
|
529
|
+
else if (Number.parseFloat(earlyMotion.animationDuration) * (earlyMotion.animationDuration.endsWith('ms') ? 1 : 1000) < 200) fail('G21', { route: first.route, mode }, `normal stage transition is shorter than 200 ms (${earlyMotion.animationDuration})`);
|
|
530
|
+
if (!lateMotion || lateMotion.transform !== 'matrix(1, 0, 0, 1, 0, 0)') fail('G21', { route: first.route, mode }, 'the staged transition does not settle at its final state');
|
|
531
|
+
}
|
|
532
|
+
const frameFacts = await page.evaluate(() => window.__caosGate.storyFrameSample);
|
|
533
|
+
if (!frameFacts || frameFacts.median === null || frameFacts.p05 === null) fail('G21', { route: first.route, mode }, 'story animation frame rate could not be measured');
|
|
534
|
+
else {
|
|
535
|
+
if (frameFacts.median < 55) fail('G21', { route: first.route, mode }, `story median is ${frameFacts.median.toFixed(1)} fps; ADR-0082 floor is 55 fps`);
|
|
536
|
+
if (frameFacts.p05 < 30) fail('G21', { route: first.route, mode }, `story fifth-percentile frame rate is ${frameFacts.p05.toFixed(1)} fps; ADR-0082 floor is 30 fps`);
|
|
537
|
+
}
|
|
538
|
+
const interactions = await page.evaluate(() => window.__caosGate.storyPerformanceFacts().events);
|
|
539
|
+
if (!interactions.length) fail('G21', { route: first.route, mode }, 'browser interaction latency was not measurable');
|
|
540
|
+
else if (Math.max(...interactions.map((entry) => entry.duration)) > 200) fail('G21', { route: first.route, mode }, `story interaction latency is ${Math.max(...interactions.map((entry) => entry.duration)).toFixed(0)} ms; ADR-0082 ceiling is 200 ms`);
|
|
541
|
+
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
542
|
+
const anyOtherLink = page.locator('[data-story-link]').nth(1);
|
|
543
|
+
if (await anyOtherLink.count()) {
|
|
544
|
+
await anyOtherLink.click();
|
|
545
|
+
const reducedMotion = await page.evaluate(() => window.__caosGate.showcaseFacts().motion);
|
|
546
|
+
const durationMs = reducedMotion ? Number.parseFloat(reducedMotion.animationDuration) * (reducedMotion.animationDuration.endsWith('ms') ? 1 : 1000) : Infinity;
|
|
547
|
+
if (reducedMotion?.animationName !== 'none' && durationMs > 150) fail('G21', { route: first.route, mode: 'reduced motion' }, `stage motion lasts ${durationMs} ms with reduced motion requested; ceiling is 150 ms`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// The mobile story uses one column: the docked panel follows the stage and the three critical callouts remain visible.
|
|
551
|
+
const mobile = await openContext([390, 844], themes[0], langs[0]);
|
|
552
|
+
const { page: phone, sink: phoneSink } = await newPage(mobile);
|
|
553
|
+
await phone.goto(urlFor(stories[0]), { waitUntil: 'load', timeout: 30000 }).catch(() => undefined);
|
|
554
|
+
await settle(phone);
|
|
555
|
+
const mobileFacts = await phone.evaluate(() => window.__caosGate.showcaseFacts());
|
|
556
|
+
if (mobileFacts.calls.length !== 3) fail('G21', { route: stories[0], mode: '390x844 mobile story' }, `mobile story shows ${mobileFacts.calls.length} callouts; ADR-0082 requires the three primary callouts`);
|
|
557
|
+
if (!mobileFacts.stage || !mobileFacts.panel || mobileFacts.panel.top < mobileFacts.stage.bottom - 1) fail('G21', { route: stories[0], mode: '390x844 mobile story' }, 'the docked panel does not sit below the scene on mobile');
|
|
558
|
+
if (mobileFacts.scroll.width > mobileFacts.viewport.width + 1) fail('G21', { route: stories[0], mode: '390x844 mobile story' }, `mobile story overflows horizontally (${mobileFacts.scroll.width}px in ${mobileFacts.viewport.width}px)`);
|
|
559
|
+
drain(phoneSink, { route: stories[0], mode: '390x844 mobile story' });
|
|
560
|
+
await mobile.close();
|
|
561
|
+
await ctx.close();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
412
565
|
// ---- G10: the wide-font pass. Every route, tab and sampled case again, in Spanish (the longer labels) when it is
|
|
413
566
|
// measured, rendered in the wide fallback font from the first frame: what a reader with wider fonts sees.
|
|
414
567
|
if (opt.wideFont !== false && opt.wideFontSizes) {
|
|
@@ -444,8 +597,9 @@ export async function runGate(o) {
|
|
|
444
597
|
|
|
445
598
|
// ---- G4: artifacts answered as JSON.
|
|
446
599
|
for (const [pathname, first] of artifacts) {
|
|
447
|
-
const
|
|
448
|
-
|
|
600
|
+
const result = await first;
|
|
601
|
+
artifactChecks.push({ path: pathname, ...result });
|
|
602
|
+
if (!result.ok) fail('G4', { route: pathname }, `the artifact ${pathname} did not answer JSON (starts with "${result.first}"; ${result.error ?? 'the HTML fallback?'})`);
|
|
449
603
|
}
|
|
450
604
|
} finally {
|
|
451
605
|
await browser.close();
|
|
@@ -938,7 +1092,7 @@ export async function runGate(o) {
|
|
|
938
1092
|
const big = s.w >= opt.viewportMinWidth;
|
|
939
1093
|
if (big && f.rail && f.rail.scroll > f.rail.client + 1) fail('G18', at, `${label}: the rail scrolls (${f.rail.scroll}px of content in ${f.rail.client}px)`);
|
|
940
1094
|
if (f.tabRows.some((r) => r > 1)) fail('G18', at, `${label}: a tab row wraps onto ${Math.max(...f.tabRows)} rows`);
|
|
941
|
-
if (big && f.share <
|
|
1095
|
+
if (big && f.share < 0.8) fail('G18', at, `${label}: the stage covers ${(100 * f.share).toFixed(1)}% of the viewport (floor 80% on Focus and story routes, ADR-0082)`);
|
|
942
1096
|
if (f.exitInside === false) fail('G18', at, `${label}: the exit control is outside the viewport`);
|
|
943
1097
|
for (const c of f.covered) fail('G18', at, `${label}: ${c.chrome} covers the control "${c.control}" (the stage must clear the Focus chrome)`);
|
|
944
1098
|
if (f.cutSelects.count) fail('G5', at, `${label}: ${f.cutSelects.count} select(s) cut the selected text (a case name cut in its box, or a display that names another case than the one the select holds, or a plain select with a text wider than its room): ${f.cutSelects.list.join('; ')}`);
|
|
@@ -1131,6 +1285,7 @@ export async function runGate(o) {
|
|
|
1131
1285
|
checks,
|
|
1132
1286
|
captures: captures.length,
|
|
1133
1287
|
failures,
|
|
1288
|
+
artifactChecks,
|
|
1134
1289
|
};
|
|
1135
1290
|
writeFileSync(join(opt.out, 'gate-report.json'), JSON.stringify(report, null, 2));
|
|
1136
1291
|
// G14: the captures are for a person to read (the gate measures, it does not judge content); one page shows them.
|
package/package.json
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
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
|
-
"license": "MIT",
|
|
6
|
-
"author": "Felipe Santibáñez-Leal",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/fsantibanezleal/CAOS_APP_SHELL.git"
|
|
10
|
-
},
|
|
11
|
-
"type": "module",
|
|
12
|
-
"publishConfig": {
|
|
13
|
-
"access": "public"
|
|
14
|
-
},
|
|
15
|
-
"sideEffects": [
|
|
16
|
-
"*.css"
|
|
17
|
-
],
|
|
18
|
-
"exports": {
|
|
19
|
-
".": {
|
|
20
|
-
"types": "./dist/index.d.ts",
|
|
21
|
-
"import": "./dist/index.js"
|
|
22
|
-
},
|
|
23
|
-
"./chart": {
|
|
24
|
-
"types": "./dist/chart.d.ts",
|
|
25
|
-
"import": "./dist/chart.js"
|
|
26
|
-
},
|
|
27
|
-
"./keys": {
|
|
28
|
-
"types": "./dist/keys.d.ts",
|
|
29
|
-
"import": "./dist/keys.js"
|
|
30
|
-
},
|
|
31
|
-
"./styles.css": "./styles.css",
|
|
32
|
-
"./chart.css": "./chart.css",
|
|
33
|
-
"./reserved-classes.json": "./reserved-classes.json",
|
|
34
|
-
"./executive.schema.json": "./src/content/executive.schema.json",
|
|
35
|
-
"./serve": "./gate/serve.mjs",
|
|
36
|
-
"./gate": "./gate/run.mjs",
|
|
37
|
-
"./package.json": "./package.json"
|
|
38
|
-
},
|
|
39
|
-
"files": [
|
|
40
|
-
"dist",
|
|
41
|
-
"styles.css",
|
|
42
|
-
"chart.css",
|
|
43
|
-
"bin",
|
|
44
|
-
"gate",
|
|
45
|
-
"reserved-classes.json",
|
|
46
|
-
"src/content/executive.schema.json",
|
|
47
|
-
"VERSION",
|
|
48
|
-
"CHANGELOG.md"
|
|
49
|
-
],
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "tsup",
|
|
52
|
-
"test": "node --import tsx --test \"test/**/*.test.ts\" \"test/**/*.test.tsx\"",
|
|
53
|
-
"test:browser": "node --import tsx --test \"e2e/**/*.test.ts\"",
|
|
54
|
-
"prepare": "npm run build",
|
|
55
|
-
"prepublishOnly": "npm run build",
|
|
56
|
-
"prebuild": "node scripts/reserved-classes.mjs",
|
|
57
|
-
"test:gate": "node --test test/gate-selftest.mjs",
|
|
58
|
-
"check:sdd": "python scripts/check_sdd.py ."
|
|
59
|
-
},
|
|
60
|
-
"peerDependencies": {
|
|
61
|
-
"katex": "^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0",
|
|
62
|
-
"lucide-react": ">=0.400.0",
|
|
63
|
-
"react": "^18 || ^19",
|
|
64
|
-
"react-dom": "^18 || ^19",
|
|
65
|
-
"react-router": "^6 || ^7 || ^8",
|
|
66
|
-
"zustand": "^4 || ^5",
|
|
67
|
-
"playwright": ">=1.40",
|
|
68
|
-
"uplot": "^1.6.30"
|
|
69
|
-
},
|
|
70
|
-
"devDependencies": {
|
|
71
|
-
"@playwright/test": "1.63.0",
|
|
72
|
-
"@types/react": "^19.0.0",
|
|
73
|
-
"@types/react-dom": "^19.0.0",
|
|
74
|
-
"esbuild": "0.28.1",
|
|
75
|
-
"katex": "0.19.0",
|
|
76
|
-
"lucide-react": "1.43.0",
|
|
77
|
-
"react": "^19.0.0",
|
|
78
|
-
"react-dom": "^19.0.0",
|
|
79
|
-
"react-router": "^8.3.0",
|
|
80
|
-
"tsup": "^8.3.5",
|
|
81
|
-
"tsx": "^4.23.0",
|
|
82
|
-
"typescript": "^5.7.3",
|
|
83
|
-
"zustand": "^5.0.2",
|
|
84
|
-
"uplot": "1.6.32"
|
|
85
|
-
},
|
|
86
|
-
"overrides": {
|
|
87
|
-
"esbuild": "0.28.1"
|
|
88
|
-
},
|
|
89
|
-
"bin": {
|
|
90
|
-
"caos-shell-gate": "bin/caos-shell-gate.mjs"
|
|
91
|
-
},
|
|
92
|
-
"peerDependenciesMeta": {
|
|
93
|
-
"playwright": {
|
|
94
|
-
"optional": true
|
|
95
|
-
},
|
|
96
|
-
"uplot": {
|
|
97
|
-
"optional": true
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@fasl-work/caos-app-shell",
|
|
3
|
+
"version": "0.20.0",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"author": "Felipe Santibáñez-Leal",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/fsantibanezleal/CAOS_APP_SHELL.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"*.css"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./chart": {
|
|
24
|
+
"types": "./dist/chart.d.ts",
|
|
25
|
+
"import": "./dist/chart.js"
|
|
26
|
+
},
|
|
27
|
+
"./keys": {
|
|
28
|
+
"types": "./dist/keys.d.ts",
|
|
29
|
+
"import": "./dist/keys.js"
|
|
30
|
+
},
|
|
31
|
+
"./styles.css": "./styles.css",
|
|
32
|
+
"./chart.css": "./chart.css",
|
|
33
|
+
"./reserved-classes.json": "./reserved-classes.json",
|
|
34
|
+
"./executive.schema.json": "./src/content/executive.schema.json",
|
|
35
|
+
"./serve": "./gate/serve.mjs",
|
|
36
|
+
"./gate": "./gate/run.mjs",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"styles.css",
|
|
42
|
+
"chart.css",
|
|
43
|
+
"bin",
|
|
44
|
+
"gate",
|
|
45
|
+
"reserved-classes.json",
|
|
46
|
+
"src/content/executive.schema.json",
|
|
47
|
+
"VERSION",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\" \"test/**/*.test.tsx\"",
|
|
53
|
+
"test:browser": "node --import tsx --test \"e2e/**/*.test.ts\"",
|
|
54
|
+
"prepare": "npm run build",
|
|
55
|
+
"prepublishOnly": "npm run build",
|
|
56
|
+
"prebuild": "node scripts/reserved-classes.mjs",
|
|
57
|
+
"test:gate": "node --test test/gate-selftest.mjs",
|
|
58
|
+
"check:sdd": "python scripts/check_sdd.py ."
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"katex": "^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0",
|
|
62
|
+
"lucide-react": ">=0.400.0",
|
|
63
|
+
"react": "^18 || ^19",
|
|
64
|
+
"react-dom": "^18 || ^19",
|
|
65
|
+
"react-router": "^6 || ^7 || ^8",
|
|
66
|
+
"zustand": "^4 || ^5",
|
|
67
|
+
"playwright": ">=1.40",
|
|
68
|
+
"uplot": "^1.6.30"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@playwright/test": "1.63.0",
|
|
72
|
+
"@types/react": "^19.0.0",
|
|
73
|
+
"@types/react-dom": "^19.0.0",
|
|
74
|
+
"esbuild": "0.28.1",
|
|
75
|
+
"katex": "0.19.0",
|
|
76
|
+
"lucide-react": "1.43.0",
|
|
77
|
+
"react": "^19.0.0",
|
|
78
|
+
"react-dom": "^19.0.0",
|
|
79
|
+
"react-router": "^8.3.0",
|
|
80
|
+
"tsup": "^8.3.5",
|
|
81
|
+
"tsx": "^4.23.0",
|
|
82
|
+
"typescript": "^5.7.3",
|
|
83
|
+
"zustand": "^5.0.2",
|
|
84
|
+
"uplot": "1.6.32"
|
|
85
|
+
},
|
|
86
|
+
"overrides": {
|
|
87
|
+
"esbuild": "0.28.1"
|
|
88
|
+
},
|
|
89
|
+
"bin": {
|
|
90
|
+
"caos-shell-gate": "bin/caos-shell-gate.mjs"
|
|
91
|
+
},
|
|
92
|
+
"peerDependenciesMeta": {
|
|
93
|
+
"playwright": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"uplot": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/reserved-classes.json
CHANGED
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"caos-bars-legend",
|
|
31
31
|
"caos-bars-readout",
|
|
32
32
|
"caos-bars-swatch",
|
|
33
|
+
"caos-callout-eyebrow",
|
|
34
|
+
"caos-callout-layer",
|
|
35
|
+
"caos-callout-leaders",
|
|
36
|
+
"caos-callout-source",
|
|
37
|
+
"caos-callout-trend",
|
|
33
38
|
"caos-card",
|
|
34
39
|
"caos-chart",
|
|
35
40
|
"caos-chart-axis-title",
|
|
@@ -158,12 +163,34 @@
|
|
|
158
163
|
"caos-readout-title",
|
|
159
164
|
"caos-readout-unit",
|
|
160
165
|
"caos-readout-value",
|
|
166
|
+
"caos-scene-callout",
|
|
167
|
+
"caos-scene3d-controls",
|
|
168
|
+
"caos-scene3d-frame",
|
|
169
|
+
"caos-scene3d-legend",
|
|
170
|
+
"caos-scene3d-renderer",
|
|
171
|
+
"caos-scene3d-status",
|
|
172
|
+
"caos-scene3d-viewport",
|
|
161
173
|
"caos-stage",
|
|
162
174
|
"caos-stale",
|
|
163
175
|
"caos-stale-note",
|
|
164
176
|
"caos-stale-overlay",
|
|
165
177
|
"caos-stat-source",
|
|
166
178
|
"caos-stat-value",
|
|
179
|
+
"caos-story",
|
|
180
|
+
"caos-story-actions",
|
|
181
|
+
"caos-story-controls",
|
|
182
|
+
"caos-story-header",
|
|
183
|
+
"caos-story-heading",
|
|
184
|
+
"caos-story-headline",
|
|
185
|
+
"caos-story-layout",
|
|
186
|
+
"caos-story-panel",
|
|
187
|
+
"caos-story-stage",
|
|
188
|
+
"caos-story-step",
|
|
189
|
+
"caos-story-step-headline",
|
|
190
|
+
"caos-story-step-number",
|
|
191
|
+
"caos-story-step-verb",
|
|
192
|
+
"caos-story-stepper",
|
|
193
|
+
"caos-story-verb",
|
|
167
194
|
"caos-surface",
|
|
168
195
|
"caos-surface-actions",
|
|
169
196
|
"caos-surface-body",
|
|
@@ -318,12 +345,15 @@
|
|
|
318
345
|
"fill",
|
|
319
346
|
"fixed",
|
|
320
347
|
"good",
|
|
348
|
+
"is-active",
|
|
321
349
|
"live",
|
|
322
350
|
"lucide",
|
|
323
351
|
"on",
|
|
324
352
|
"precomputed",
|
|
325
353
|
"primary",
|
|
326
354
|
"rev",
|
|
355
|
+
"trend-down",
|
|
356
|
+
"trend-up",
|
|
327
357
|
"warn",
|
|
328
358
|
"wide"
|
|
329
359
|
],
|
|
@@ -356,6 +386,11 @@
|
|
|
356
386
|
"caos-bars-legend",
|
|
357
387
|
"caos-bars-readout",
|
|
358
388
|
"caos-bars-swatch",
|
|
389
|
+
"caos-callout-eyebrow",
|
|
390
|
+
"caos-callout-layer",
|
|
391
|
+
"caos-callout-leaders",
|
|
392
|
+
"caos-callout-source",
|
|
393
|
+
"caos-callout-trend",
|
|
359
394
|
"caos-card",
|
|
360
395
|
"caos-chart",
|
|
361
396
|
"caos-chart-axis-title",
|
|
@@ -486,12 +521,34 @@
|
|
|
486
521
|
"caos-readout-title",
|
|
487
522
|
"caos-readout-unit",
|
|
488
523
|
"caos-readout-value",
|
|
524
|
+
"caos-scene-callout",
|
|
525
|
+
"caos-scene3d-controls",
|
|
526
|
+
"caos-scene3d-frame",
|
|
527
|
+
"caos-scene3d-legend",
|
|
528
|
+
"caos-scene3d-renderer",
|
|
529
|
+
"caos-scene3d-status",
|
|
530
|
+
"caos-scene3d-viewport",
|
|
489
531
|
"caos-stage",
|
|
490
532
|
"caos-stale",
|
|
491
533
|
"caos-stale-note",
|
|
492
534
|
"caos-stale-overlay",
|
|
493
535
|
"caos-stat-source",
|
|
494
536
|
"caos-stat-value",
|
|
537
|
+
"caos-story",
|
|
538
|
+
"caos-story-actions",
|
|
539
|
+
"caos-story-controls",
|
|
540
|
+
"caos-story-header",
|
|
541
|
+
"caos-story-heading",
|
|
542
|
+
"caos-story-headline",
|
|
543
|
+
"caos-story-layout",
|
|
544
|
+
"caos-story-panel",
|
|
545
|
+
"caos-story-stage",
|
|
546
|
+
"caos-story-step",
|
|
547
|
+
"caos-story-step-headline",
|
|
548
|
+
"caos-story-step-number",
|
|
549
|
+
"caos-story-step-verb",
|
|
550
|
+
"caos-story-stepper",
|
|
551
|
+
"caos-story-verb",
|
|
495
552
|
"caos-surface",
|
|
496
553
|
"caos-surface-actions",
|
|
497
554
|
"caos-surface-body",
|
|
@@ -595,6 +652,7 @@
|
|
|
595
652
|
"header-sep",
|
|
596
653
|
"hint",
|
|
597
654
|
"icon-btn",
|
|
655
|
+
"is-active",
|
|
598
656
|
"katex",
|
|
599
657
|
"l-en",
|
|
600
658
|
"l-es",
|
|
@@ -644,6 +702,8 @@
|
|
|
644
702
|
"tone-good",
|
|
645
703
|
"tone-neutral",
|
|
646
704
|
"tone-warn",
|
|
705
|
+
"trend-down",
|
|
706
|
+
"trend-up",
|
|
647
707
|
"two-col",
|
|
648
708
|
"uplot",
|
|
649
709
|
"warn",
|