@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/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 half the
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.5,
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.text().then(
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 c = await first;
448
- if (c !== '{' && c !== '[') fail('G4', { route: pathname }, `the artifact ${pathname} did not answer JSON (starts with "${c}"; the HTML fallback?)`);
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 < opt.instrumentMin) fail('G18', at, `${label}: the stage covers ${(100 * f.share).toFixed(1)}% of the viewport (floor ${(100 * opt.instrumentMin).toFixed(0)}%)`);
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.19.11",
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
+ }
@@ -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",