@fasl-work/caos-app-shell 0.11.2 → 0.12.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/areas.mjs CHANGED
@@ -80,13 +80,10 @@ const A = (en, es, requires, extra = {}) => ({ what: { en, es }, requires, ...ex
80
80
  export const PAGES = {
81
81
  '/introduction': {
82
82
  name: 'Introduction',
83
- // two top-level topics: the executive view first (the default), then the technical view
83
+ // two top-level topics: the executive view first (the default), then the technical view. The executive view has no
84
+ // areas: it is the shell's ExecutiveView, checked by executiveFailures below (0.12.0, problem-context-first.md).
84
85
  views: ['executive', 'technical'],
85
86
  areas: {
86
- initiative: A('the initiative an executive owns, named by role and industry, and the quantified reason to act now', 'la iniciativa que un ejecutivo tiene a su cargo, nombrada por rol e industria, y la razón cuantificada para actuar ahora', { dense: 1, stats: 1 }, { view: 'executive' }),
87
- outcomes: A('the transformation story: the vision first, then each capability tied to a business outcome with a quantified proof point', 'la historia de transformación: primero la visión, luego cada capacidad atada a un resultado de negocio con una prueba cuantificada', { dense: 1, rows: 3, rowsNoStat: 0 }, { view: 'executive' }),
88
- positioning: A('the value story: four value points, each tied back to the problem of that executive', 'la historia de valor: cuatro puntos de valor, cada uno atado al problema de ese ejecutivo', { items: 4, thinItems: 0 }, { view: 'executive' }),
89
- credibility: A('delivery and credibility: the business questions that executive would ask, each answered plainly with the honest limits', 'entrega y credibilidad: las preguntas de negocio que haría ese ejecutivo, cada una respondida con claridad y con los límites honestos', { qa: 3, honest: 1 }, { view: 'executive' }),
90
87
  problem: A('the industrial or scientific problem, with the concrete equipment, setting and who it serves', 'el problema industrial o científico, con el equipo concreto, el contexto y a quién sirve', { dense: 2 }, { view: 'technical' }),
91
88
  principles: A('the physics or the domain principles the product rests on', 'la física o los principios del dominio en que se apoya el producto', { dense: 2 }, { view: 'technical' }),
92
89
  math: A('the governing mathematics', 'la matemática que gobierna', { equations: 1, glossary: 10 }, { view: 'technical' }),
@@ -168,6 +165,131 @@ export const waivable = (route) => [
168
165
  ];
169
166
  /** The executive view of the Introduction is the decision maker's page: none of its areas, and not the view, can be waived. */
170
167
  export const NOT_WAIVABLE_NOTE = 'the executive view of the Introduction cannot be waived';
168
+ /** What a waiver may not name: the executive view and the four areas it replaced (0.12.0). */
169
+ export const EXECUTIVE_FLOORS = ['executive', 'executive-view', 'initiative', 'outcomes', 'positioning', 'credibility'];
170
+
171
+ // ------------------------------------------------------------------------------------------------ the executive view
172
+ // The executive view of the Introduction is the shell's ExecutiveView fed by the product's content/executive.json
173
+ // (conventions/problem-context-first.md). Its structure is `src/content/executive.schema.json`; its writing limits are
174
+ // here AND in `src/content/executive.ts` (test/executive.test.ts keeps the two equal): the gate measures the rendered
175
+ // page, the component validates the data.
176
+ export const EXEC_SCHEMA_VERSION = '1.0.0';
177
+ export const EXEC_LIMITS = {
178
+ maxWords: 30,
179
+ maxAnswerSentences: 2,
180
+ equation: /\$|\\\(|\\\[|\\[a-zA-Z]+/,
181
+ citations: [/\(\s*Source/i, /\[\d+\]/, /\(\s*ECB/, /\(\w[^()]*\d{4}\)/],
182
+ minLabelPx: 9,
183
+ };
184
+ /** The four sections of the view: title and rubric question (Felipe's rubric, verbatim). */
185
+ export const EXEC_SECTIONS = [
186
+ { title: { en: 'Industry & Exec Tailoring', es: 'Industria y foco ejecutivo' }, question: { en: 'Built for a C-suite exec?', es: '¿Pensado para un ejecutivo de primer nivel?' } },
187
+ { title: { en: 'AI Transformation Story', es: 'Historia de transformación con IA' }, question: { en: 'Outcomes, not a feature tour', es: 'Resultados, no un recorrido de funciones' } },
188
+ { title: { en: '4Cs Positioning', es: 'Posicionamiento 4C' }, question: { en: 'Value story, not features dump', es: 'Historia de valor, no lista de funciones' } },
189
+ { title: { en: 'Delivery & Credibility', es: 'Entrega y credibilidad' }, question: { en: 'Trust them in front of a CxO?', es: '¿Confiaría en ellos frente a un ejecutivo?' } },
190
+ ];
191
+ /** The illustrations in the order of the page and the section each sits in. */
192
+ export const EXEC_ILLUSTRATIONS = [
193
+ { name: 'initiative', section: 1 },
194
+ { name: 'problem', section: 1 },
195
+ { name: 'solution', section: 2 },
196
+ ];
197
+ export const EXEC_PENDING = { en: "Pending: the 4 C's are defined by the product owner", es: 'Pendiente: las 4 C las define el responsable del producto' };
198
+
199
+ /** How many sentences a field of the view may hold: one for the single-sentence fields, two for an answer, no limit otherwise. */
200
+ export const execSentenceLimit = (path) =>
201
+ /^(initiative|vision)$|^outcomes\[\d+\]\.proof\.statement$|^four_cs\[\d+\]\.exec_problem$|^illustrations\.\w+\.caption$/.test(path)
202
+ ? 1
203
+ : /^dialogue\[\d+\]\.answer$/.test(path)
204
+ ? EXEC_LIMITS.maxAnswerSentences
205
+ : undefined;
206
+
207
+ const sentencesOf = (text) =>
208
+ text
209
+ .split(/(?<=[.?!])\s+/)
210
+ .map((x) => x.trim())
211
+ .filter(Boolean);
212
+ const wordsIn = (text) => text.split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
213
+
214
+ /**
215
+ * The problems of the executive view in one language, from what the page showed (`G.docPage().executive` of
216
+ * gate/inpage.mjs). Pure. `facts` is null or {count: 0} when the page renders no ExecutiveView.
217
+ * @returns {string[]} one text per problem
218
+ */
219
+ export function executiveFailures(facts, lang) {
220
+ const L = lang.toUpperCase();
221
+ const out = [];
222
+ const bad = (m) => out.push(`Introduction, executive view (${L}): ${m}`);
223
+ if (!facts || facts.count === 0) {
224
+ bad("no ExecutiveView ([data-executive-view]); the executive view is the shell's ExecutiveView fed by content/executive.json (docs/06_executive-view.md)");
225
+ return out;
226
+ }
227
+ if (facts.count > 1) bad(`${facts.count} ExecutiveView elements; exactly 1 is required`);
228
+ if (facts.schema !== EXEC_SCHEMA_VERSION) bad(`the view declares schema "${facts.schema ?? 'none'}", expected "${EXEC_SCHEMA_VERSION}"`);
229
+ if (facts.valid !== 'true') {
230
+ bad(`the data file is not valid in the page: ${String(facts.errors || 'no reason given').slice(0, 400)}`);
231
+ return out;
232
+ }
233
+ if (facts.docSections > 0) bad(`${facts.docSections} DocSection(s) placed in the executive view (${facts.docSectionTitles.join(', ')}); the view has exactly its four sections and takes none`);
234
+ if (facts.strangers.length > 0) bad(`${facts.strangers.length} block(s) beside the ExecutiveView (${facts.strangers.slice(0, 3).map((x) => `"${x}"`).join(', ')}); the view is the whole of the executive view`);
235
+ if (!facts.inOpenView) bad('the ExecutiveView is not in the open executive view');
236
+ // the four sections, in order, with their titles and questions
237
+ if (facts.sections.length !== 4) bad(`${facts.sections.length} section(s), exactly 4 are required`);
238
+ EXEC_SECTIONS.forEach((def, i) => {
239
+ const got = facts.sections[i];
240
+ if (!got) return;
241
+ if (got.n !== i + 1) bad(`section ${i + 1} is out of order (it declares ${got.n})`);
242
+ if (got.title !== def.title[lang]) bad(`section ${i + 1} is titled "${got.title}", expected "${def.title[lang]}"`);
243
+ if (got.question !== def.question[lang]) bad(`section ${i + 1} asks "${got.question}", expected "${def.question[lang]}"`);
244
+ });
245
+ const s3 = facts.sections[2];
246
+ if (s3) {
247
+ if (s3.pending === 1 && s3.rows === 0) {
248
+ if (s3.pendingText !== EXEC_PENDING[lang]) bad(`section 3 declares the pending state "${s3.pendingText}", expected "${EXEC_PENDING[lang]}"`);
249
+ if (s3.pendingState !== 'unavailable') bad('section 3 is pending without data-state="unavailable"');
250
+ } else if (s3.rows !== 4) bad(`section 3 holds ${s3.rows} row(s) of the 4 C's, exactly 4 are required (or the pending state)`);
251
+ }
252
+ // the writing limits, on the text a reader sees
253
+ const unreadable = [];
254
+ for (const f of facts.fields) {
255
+ if (!f.readable) {
256
+ unreadable.push(f.path);
257
+ continue;
258
+ }
259
+ const sentences = sentencesOf(f.text);
260
+ for (const s of sentences) {
261
+ const n = wordsIn(s);
262
+ if (n > EXEC_LIMITS.maxWords) bad(`field "${f.path}": a sentence of ${n} words; the limit is ${EXEC_LIMITS.maxWords}`);
263
+ }
264
+ const max = execSentenceLimit(f.path);
265
+ if (max !== undefined && sentences.length > max) bad(`field "${f.path}": ${sentences.length} sentences; the limit is ${max}`);
266
+ if (EXEC_LIMITS.equation.test(f.text)) bad(`field "${f.path}": holds an equation or a TeX marker`);
267
+ if (EXEC_LIMITS.citations.some((re) => re.test(f.text))) bad(`field "${f.path}": holds an inline citation (sources are numbered notes)`);
268
+ }
269
+ if (unreadable.length > 0) bad(`${unreadable.length} field(s) are in the page and a reader cannot read them: ${unreadable.slice(0, 4).join(', ')}`);
270
+ if (facts.notes !== facts.figures) bad(`${facts.notes} numbered source note(s) for ${facts.figures} figure(s)`);
271
+ // the three illustrations, in their places
272
+ const names = facts.illustrations.map((x) => x.name);
273
+ if (names.join() !== EXEC_ILLUSTRATIONS.map((x) => x.name).join()) bad(`the illustrations are [${names.join(', ')}], expected [initiative, problem, solution] in that order`);
274
+ for (const ill of facts.illustrations) {
275
+ const want = EXEC_ILLUSTRATIONS.find((x) => x.name === ill.name);
276
+ const name = `illustration "${ill.name}"`;
277
+ if (want && ill.section !== want.section) bad(`${name} sits in section ${ill.section}, it belongs in section ${want.section}`);
278
+ if (ill.state === 'error') {
279
+ bad(`${name}: the file "${ill.file}" did not load (${ill.why})`);
280
+ continue;
281
+ }
282
+ if (!ill.svg) bad(`${name} is not an inline SVG`);
283
+ if (ill.img > 0) bad(`${name} is drawn through an img tag; it must be inline so it reads the theme`);
284
+ if (ill.chart > 0) bad(`${name} declares a chart (data-chart); the executive illustrations are drawings, not charts`);
285
+ if (ill.literal.length > 0) bad(`${name} uses literal colour(s) ${ill.literal.slice(0, 4).join(', ')}; colour only with var(--...) theme tokens, none or currentColor`);
286
+ if (ill.outside.length > 0) bad(`${name}: label(s) outside the viewBox: ${ill.outside.slice(0, 3).map((x) => `"${x}"`).join(', ')}`);
287
+ if (ill.labels === 0) bad(`${name} carries no label in this language`);
288
+ if (ill.small.length > 0) bad(`${name}: label(s) smaller than ${EXEC_LIMITS.minLabelPx}px as rendered: ${ill.small.slice(0, 3).join(', ')}`);
289
+ if (!ill.caption) bad(`${name} has no caption under it`);
290
+ }
291
+ return [...new Set(out)];
292
+ }
171
293
 
172
294
  /** Parses `data-floor-waiver` ("floor: reason; floor: reason") into entries. */
173
295
  export function parseWaivers(attr) {
@@ -232,7 +354,7 @@ export function docFloorFailures({ route, lang, states, expectedTopics }) {
232
354
  const waivers = entries.map((w) => ({ ...w, valid: ok.has(w.floor) && reasonOk(w.reason) }));
233
355
  for (const w of entries) {
234
356
  if (!ok.has(w.floor)) {
235
- const exec = P.areas[w.floor]?.view === 'executive';
357
+ const exec = route === '/introduction' && EXECUTIVE_FLOORS.includes(w.floor);
236
358
  bad('', `${NAME} (${L}): the waiver for "${w.floor}" is refused: ${exec ? NOT_WAIVABLE_NOTE : `it names no floor of this page (${[...ok].join(', ')})`}`);
237
359
  } else if (!reasonOk(w.reason)) {
238
360
  const s = reasonStats(w.reason);
@@ -246,6 +368,7 @@ export function docFloorFailures({ route, lang, states, expectedTopics }) {
246
368
  const sections = new Map();
247
369
  const figures = new Set();
248
370
  const equations = new Set();
371
+ const execMessages = new Set();
249
372
  for (const st of states) {
250
373
  for (const t of st.topics) topics.set(t.key, t.label);
251
374
  const ordinal = new Map();
@@ -259,11 +382,16 @@ export function docFloorFailures({ route, lang, states, expectedTopics }) {
259
382
  for (const f of st.figures) figures.add(`${st.topics.map((t) => t.key).join('|')}::${f}`);
260
383
  for (const e of st.equations) equations.add(`${st.topics.map((t) => t.key).join('|')}::${e}`);
261
384
  }
385
+ if (route === '/introduction' && st.view !== 'executive' && st.executive && st.executive.count > 0) {
386
+ bad('', `${NAME} (${L}): the ExecutiveView sits in the "${st.view ?? 'none'}" view; it belongs to the executive view`);
387
+ }
262
388
  if (route === '/introduction' && st.view === 'executive') {
389
+ for (const m of executiveFailures(st.executive, lang)) execMessages.add(m);
263
390
  if (st.viewEquations > 0) bad(st.topics[0]?.label ?? '', `${NAME}, executive view (${L}): ${st.viewEquations} equation(s); the executive view holds no equation`);
264
391
  if (st.viewGlossaries > 0) bad(st.topics[0]?.label ?? '', `${NAME}, executive view (${L}): ${st.viewGlossaries} symbol glossary(ies); the executive view holds none`);
265
392
  }
266
393
  }
394
+ for (const m of execMessages) bad('', m);
267
395
  const seen = [...sections.values()].map(mergeSections);
268
396
  const tname = (label) => (label ? `topic "${label}"` : 'the page');
269
397
 
package/gate/inpage.mjs CHANGED
@@ -7,6 +7,8 @@
7
7
  export function installLib() {
8
8
  const G = (window.__caosGate = window.__caosGate || {});
9
9
  G.raf = 0;
10
+ // The smallest rendered size of an illustration label (EXEC_LIMITS.minLabelPx of gate/areas.mjs; test/executive.test.ts keeps them equal).
11
+ const EXEC_MIN_LABEL_PX = 9;
10
12
  G.counting = false;
11
13
  const nativeRaf = window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : null;
12
14
  if (nativeRaf) {
@@ -611,8 +613,91 @@ export function installLib() {
611
613
  qa: [...sec.querySelectorAll('[data-qa]')].filter((q) => mine(q) && nonEmpty(q, '[data-question]') && nonEmpty(q, '[data-answer]')).length,
612
614
  };
613
615
  });
614
- const inView = (sel) => (outerPanel ? [...outerPanel.querySelectorAll(sel)] : [...page.querySelectorAll(sel)]).filter(seen);
615
616
  const selected = outerTabs.find((t) => t.getAttribute('aria-selected') === 'true');
617
+ // The executive view (shell 0.12.0): what the ExecutiveView shows, for executiveFailures of gate/areas.mjs.
618
+ const execRoots = [...page.querySelectorAll('[data-executive-view]')].filter(visible);
619
+ const executive = (() => {
620
+ if (!execRoots.length) return { count: 0 };
621
+ const root = execRoots[0];
622
+ const isTok = (v) => {
623
+ const x = String(v || '').trim().toLowerCase();
624
+ return !x || ['none', 'currentcolor', 'transparent', 'inherit', 'initial', 'unset', 'context-fill', 'context-stroke'].includes(x) || x.startsWith('var(--') || x.startsWith('url(');
625
+ };
626
+ const COLOR_PROPS = ['fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color', 'color'];
627
+ const literalsOf = (svg) => {
628
+ const out = new Set();
629
+ const scan = (css) => {
630
+ for (const m of css.matchAll(/(?:^|[;{\s])(fill|stroke|stop-color|flood-color|lighting-color|color)\s*:\s*([^;}]+)/gi)) if (!isTok(m[2])) out.add(`${m[1].toLowerCase()}: ${m[2].trim()}`);
631
+ };
632
+ for (const el of [svg, ...svg.querySelectorAll('*')]) {
633
+ for (const p of COLOR_PROPS) if (el.hasAttribute(p) && !isTok(el.getAttribute(p))) out.add(`${p}: ${el.getAttribute(p).trim()}`);
634
+ if (el.hasAttribute('style')) scan(el.getAttribute('style'));
635
+ if (el.localName === 'style') scan(el.textContent || '');
636
+ }
637
+ return [...out];
638
+ };
639
+ const illustrations = [...root.querySelectorAll('[data-exec-illustration]')].map((fig) => {
640
+ const svg = fig.querySelector('svg');
641
+ const sec = fig.closest('[data-exec-section]');
642
+ const cap = fig.querySelector('figcaption');
643
+ const info = { name: fig.getAttribute('data-exec-illustration'), section: sec ? Number(sec.getAttribute('data-exec-section')) : 0, file: fig.getAttribute('data-exec-file'), state: fig.getAttribute('data-state'), why: textOf(fig.querySelector('.caos-exec-fig-error') || fig).slice(0, 80), svg: Boolean(svg), img: fig.querySelectorAll('img').length, chart: fig.querySelectorAll('[data-chart], [data-plot]').length, caption: Boolean(cap && textOf(cap)), literal: [], outside: [], small: [], labels: 0 };
644
+ if (!svg) return info;
645
+ info.literal = literalsOf(svg);
646
+ const vb = svg.viewBox && svg.viewBox.baseVal;
647
+ const w = svg.getBoundingClientRect().width;
648
+ const scale = vb && vb.width ? w / vb.width : 1;
649
+ for (const t of svg.querySelectorAll('text')) {
650
+ const label = (t.textContent || '').trim();
651
+ if (!label || !visible(t)) continue;
652
+ info.labels += 1;
653
+ if (vb && vb.width) {
654
+ const b = t.getBBox();
655
+ if (b.x < vb.x - 1 || b.y < vb.y - 1 || b.x + b.width > vb.x + vb.width + 1 || b.y + b.height > vb.y + vb.height + 1) info.outside.push(label.slice(0, 40));
656
+ }
657
+ const px = parseFloat(getComputedStyle(t).fontSize) * scale;
658
+ if (px < EXEC_MIN_LABEL_PX) info.small.push(`"${label.slice(0, 30)}" at ${px.toFixed(1)}px`);
659
+ }
660
+ return info;
661
+ });
662
+ const sections = [...root.querySelectorAll('[data-exec-section]')].map((sec) => {
663
+ const pending = sec.querySelector('[data-exec-pending]');
664
+ return {
665
+ n: Number(sec.getAttribute('data-exec-section')),
666
+ title: textOf(sec.querySelector('h2') || sec),
667
+ question: textOf(sec.querySelector('[data-exec-question]') || sec),
668
+ pending: pending && seen(pending) ? 1 : 0,
669
+ pendingText: pending ? textOf(pending) : '',
670
+ pendingState: pending ? pending.getAttribute('data-state') : null,
671
+ rows: [...sec.querySelectorAll('[data-exec-c]')].filter(seen).length,
672
+ };
673
+ });
674
+ const panelOpen = outerPanel ? outerPanel.contains(root) : true;
675
+ const execOpen = selected && (selected.getAttribute('data-tab') || tabLabel(selected)) === 'executive';
676
+ const area = execOpen && outerPanel ? outerPanel : null;
677
+ const docSections = area ? [...area.querySelectorAll('[data-doc-section]')].filter((e) => visible(e) && !root.contains(e)) : [];
678
+ // anything of the open executive view that is neither the ExecutiveView nor the references block of the panel
679
+ const strangers = area
680
+ ? [...area.querySelectorAll('p, ul, ol, table, figure, h1, h2, h3, h4, pre, canvas, .callout, .equation, .caos-qa')]
681
+ .filter((e) => visible(e) && !root.contains(e) && !e.closest('[data-doc-references], [data-doc-section]'))
682
+ .map((e) => textOf(e).slice(0, 50) || e.tagName.toLowerCase())
683
+ : [];
684
+ return {
685
+ count: execRoots.length,
686
+ schema: root.getAttribute('data-executive-schema'),
687
+ valid: root.getAttribute('data-executive-valid'),
688
+ errors: root.getAttribute('data-executive-errors') || '',
689
+ inOpenView: panelOpen,
690
+ docSections: docSections.length,
691
+ docSectionTitles: docSections.map((e) => textOf(e.querySelector('h2') || e).slice(0, 40)),
692
+ strangers,
693
+ sections,
694
+ fields: [...root.querySelectorAll('[data-exec-field]')].map((e) => ({ path: e.getAttribute('data-exec-field'), text: textOf(e), readable: seen(e) })),
695
+ notes: root.querySelectorAll('[data-exec-notes] > li').length,
696
+ figures: root.querySelectorAll('.caos-exec-cite').length,
697
+ illustrations,
698
+ };
699
+ })();
700
+ const inView = (sel) => (outerPanel ? [...outerPanel.querySelectorAll(sel)] : [...page.querySelectorAll(sel)]).filter(seen);
616
701
  return {
617
702
  ledeWords: lede && visible(lede) ? wordsOf(lede).words : 0,
618
703
  waiver: page.getAttribute('data-floor-waiver'),
@@ -623,6 +708,7 @@ export function installLib() {
623
708
  viewEquations: [...page.querySelectorAll('.equation')].filter(seen).length,
624
709
  viewGlossaries: [...page.querySelectorAll('[data-glossary]')].filter(seen).length,
625
710
  sections,
711
+ executive,
626
712
  figures: inView('figure').map(captioned).filter(Boolean),
627
713
  equations: inView('.equation').map(equationCaption).filter(Boolean),
628
714
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fasl-work/caos-app-shell",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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",
@@ -31,6 +31,7 @@
31
31
  "./styles.css": "./styles.css",
32
32
  "./chart.css": "./chart.css",
33
33
  "./reserved-classes.json": "./reserved-classes.json",
34
+ "./executive.schema.json": "./src/content/executive.schema.json",
34
35
  "./serve": "./gate/serve.mjs",
35
36
  "./gate": "./gate/run.mjs",
36
37
  "./package.json": "./package.json"
@@ -41,7 +42,8 @@
41
42
  "chart.css",
42
43
  "bin",
43
44
  "gate",
44
- "reserved-classes.json"
45
+ "reserved-classes.json",
46
+ "src/content/executive.schema.json"
45
47
  ],
46
48
  "scripts": {
47
49
  "build": "tsup",
@@ -38,6 +38,30 @@
38
38
  "caos-doc-reference-list",
39
39
  "caos-doc-references",
40
40
  "caos-doc-section",
41
+ "caos-exec",
42
+ "caos-exec-art",
43
+ "caos-exec-art-wait",
44
+ "caos-exec-cite",
45
+ "caos-exec-cs",
46
+ "caos-exec-error",
47
+ "caos-exec-facts",
48
+ "caos-exec-fig",
49
+ "caos-exec-fig-error",
50
+ "caos-exec-initiative",
51
+ "caos-exec-label",
52
+ "caos-exec-notes",
53
+ "caos-exec-notes-title",
54
+ "caos-exec-notes-wrap",
55
+ "caos-exec-outcome",
56
+ "caos-exec-outcomes",
57
+ "caos-exec-pending",
58
+ "caos-exec-proof",
59
+ "caos-exec-question",
60
+ "caos-exec-section",
61
+ "caos-exec-sep",
62
+ "caos-exec-value",
63
+ "caos-exec-vision",
64
+ "caos-exec-whynow",
41
65
  "caos-field",
42
66
  "caos-focus-actions",
43
67
  "caos-focus-hud",
@@ -280,6 +304,30 @@
280
304
  "caos-doc-reference-list",
281
305
  "caos-doc-references",
282
306
  "caos-doc-section",
307
+ "caos-exec",
308
+ "caos-exec-art",
309
+ "caos-exec-art-wait",
310
+ "caos-exec-cite",
311
+ "caos-exec-cs",
312
+ "caos-exec-error",
313
+ "caos-exec-facts",
314
+ "caos-exec-fig",
315
+ "caos-exec-fig-error",
316
+ "caos-exec-initiative",
317
+ "caos-exec-label",
318
+ "caos-exec-notes",
319
+ "caos-exec-notes-title",
320
+ "caos-exec-notes-wrap",
321
+ "caos-exec-outcome",
322
+ "caos-exec-outcomes",
323
+ "caos-exec-pending",
324
+ "caos-exec-proof",
325
+ "caos-exec-question",
326
+ "caos-exec-section",
327
+ "caos-exec-sep",
328
+ "caos-exec-value",
329
+ "caos-exec-vision",
330
+ "caos-exec-whynow",
283
331
  "caos-field",
284
332
  "caos-focus-actions",
285
333
  "caos-focus-hud",
@@ -0,0 +1,141 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/fsantibanezleal/CAOS_APP_SHELL/blob/main/src/content/executive.schema.json",
4
+ "title": "CAOS executive view data, version 1.0.0",
5
+ "description": "The one data file a CAOS product fills for the executive view of its Introduction (content/executive.json). Every text is { en, es }; nothing outside these fields is allowed. The writing limits (30 words per sentence, an answer of at most 2 sentences, one sentence for initiative, vision, statement, exec_problem and caption, no equation, no inline citation), the four_cs rule (absent until FOUR_CS names the 4 C's) and the sources-by-field rule are checked by validateExecutive in executive.ts, on top of this structure.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["executive", "organisation", "initiative", "why_now", "vision", "outcomes", "dialogue", "illustrations", "sources"],
9
+ "properties": {
10
+ "executive": { "$ref": "#/$defs/text", "description": "One C-suite role, never a list." },
11
+ "organisation": { "$ref": "#/$defs/text", "description": "One industry and one context, never several regions." },
12
+ "initiative": { "$ref": "#/$defs/text", "description": "One sentence: what that executive owns and must achieve." },
13
+ "why_now": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["figure", "deadline"],
17
+ "properties": {
18
+ "figure": { "$ref": "#/$defs/figure" },
19
+ "deadline": { "$ref": "#/$defs/text", "description": "One date or deadline, for example 'by 1 December 2026'." }
20
+ }
21
+ },
22
+ "vision": { "$ref": "#/$defs/text", "description": "One sentence: what changes for the executive's business. Comes first in section 2." },
23
+ "outcomes": {
24
+ "type": "array",
25
+ "minItems": 3,
26
+ "maxItems": 5,
27
+ "items": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "required": ["capability", "outcome", "proof"],
31
+ "properties": {
32
+ "capability": { "$ref": "#/$defs/text", "description": "What the product does, in plain words." },
33
+ "outcome": { "$ref": "#/$defs/text", "description": "The business result, non-technical." },
34
+ "proof": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["value", "unit", "statement"],
38
+ "properties": {
39
+ "value": { "$ref": "#/$defs/text" },
40
+ "unit": { "$ref": "#/$defs/text" },
41
+ "statement": { "$ref": "#/$defs/text", "description": "A one-line statement of the proof point." }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "four_cs": {
48
+ "type": "array",
49
+ "minItems": 4,
50
+ "maxItems": 4,
51
+ "description": "Exactly 4 rows in the owner's order. Absent while FOUR_CS is empty; required, with c equal to the name, once it holds 4 names.",
52
+ "items": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "required": ["c", "exec_problem"],
56
+ "properties": {
57
+ "c": { "$ref": "#/$defs/text" },
58
+ "exec_problem": { "$ref": "#/$defs/text", "description": "One sentence tying the C to this executive's problem." }
59
+ }
60
+ }
61
+ },
62
+ "dialogue": {
63
+ "type": "array",
64
+ "minItems": 3,
65
+ "maxItems": 5,
66
+ "items": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "required": ["question", "answer"],
70
+ "properties": {
71
+ "question": { "$ref": "#/$defs/text", "description": "A business question that executive asks." },
72
+ "answer": { "$ref": "#/$defs/text", "description": "At most 2 sentences." }
73
+ }
74
+ }
75
+ },
76
+ "illustrations": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["initiative", "problem", "solution"],
80
+ "properties": {
81
+ "initiative": { "$ref": "#/$defs/illustration" },
82
+ "problem": { "$ref": "#/$defs/illustration" },
83
+ "solution": { "$ref": "#/$defs/illustration" }
84
+ }
85
+ },
86
+ "sources": {
87
+ "type": "array",
88
+ "minItems": 1,
89
+ "description": "The source of every figure (why_now.figure and each outcomes[i].proof), by field path; shown as numbered notes, never inline.",
90
+ "items": {
91
+ "type": "object",
92
+ "additionalProperties": false,
93
+ "required": ["field", "source"],
94
+ "properties": {
95
+ "field": { "type": "string", "minLength": 1 },
96
+ "source": { "$ref": "#/$defs/text" }
97
+ }
98
+ }
99
+ }
100
+ },
101
+ "$defs": {
102
+ "text": {
103
+ "type": "object",
104
+ "additionalProperties": false,
105
+ "required": ["en", "es"],
106
+ "properties": {
107
+ "en": { "type": "string", "minLength": 1 },
108
+ "es": { "type": "string", "minLength": 1 }
109
+ }
110
+ },
111
+ "figure": {
112
+ "type": "object",
113
+ "additionalProperties": false,
114
+ "required": ["value", "unit"],
115
+ "properties": {
116
+ "value": { "$ref": "#/$defs/text" },
117
+ "unit": { "$ref": "#/$defs/text" }
118
+ }
119
+ },
120
+ "illustration": {
121
+ "type": "object",
122
+ "additionalProperties": false,
123
+ "required": ["file", "caption"],
124
+ "properties": {
125
+ "file": {
126
+ "description": "The inline SVG: one path, or one path per language.",
127
+ "anyOf": [
128
+ { "type": "string", "minLength": 1 },
129
+ {
130
+ "type": "object",
131
+ "additionalProperties": false,
132
+ "required": ["en", "es"],
133
+ "properties": { "en": { "type": "string", "minLength": 1 }, "es": { "type": "string", "minLength": 1 } }
134
+ }
135
+ ]
136
+ },
137
+ "caption": { "$ref": "#/$defs/text", "description": "One sentence saying what the reader should take from the illustration." }
138
+ }
139
+ }
140
+ }
141
+ }
package/styles.css CHANGED
@@ -715,3 +715,36 @@ svg.lucide { flex: none; }
715
715
  [data-arch-lang="es"] .l-es { display: inline; }
716
716
  .caos-architecture-diagram > svg { display: block; width: 100%; height: auto; max-width: none; }
717
717
  .caos-arch-toggle { margin: 0 0 8px; font-size: 12px; padding: 5px 10px; color: var(--color-fg); background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm); cursor: pointer; }
718
+
719
+ /* The executive view (shell 0.12.0, conventions/problem-context-first.md): four sections in order, three inline illustrations,
720
+ numbered source notes. The product fills content/executive.json; it never styles this view. Illustrations are inline SVG
721
+ coloured with the theme tokens; a drawing carrying both languages shows the labels of the displayed one. */
722
+ .caos-exec { display: flex; flex-direction: column; gap: 2rem; min-width: 0; }
723
+ .caos-exec-section { min-width: 0; padding-top: 1.25rem; border-top: 1px solid var(--color-border); }
724
+ .caos-exec-section:first-child { border-top: 0; padding-top: 0; }
725
+ .caos-exec-section > h2 { margin-block: 0 0.15rem; }
726
+ .caos-exec-question { margin: 0 0 1rem; font-size: var(--text-sm); color: var(--color-fg-faint); font-style: italic; }
727
+ .caos-exec-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.75rem 1.5rem; margin: 0 0 1rem; }
728
+ .caos-exec-facts dt, .caos-exec-label { display: block; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); font-weight: 650; }
729
+ .caos-exec-facts dd { margin: 0.1rem 0 0; font-weight: 600; overflow-wrap: anywhere; }
730
+ .caos-exec-initiative, .caos-exec-vision { font-size: var(--text-lg); line-height: 1.45; margin: 0 0 1rem; }
731
+ .caos-exec-whynow { margin: 1rem 0; overflow-wrap: anywhere; }
732
+ .caos-exec-whynow .caos-exec-label { display: block; margin-bottom: 0.15rem; }
733
+ .caos-exec-value { font-size: var(--text-xl); font-variant-numeric: tabular-nums; color: var(--color-accent); }
734
+ .caos-exec-sep { color: var(--color-fg-faint); }
735
+ .caos-exec-cite { margin-left: 0.15rem; font-size: var(--text-xs); }
736
+ .caos-exec-fig { margin: 1.25rem 0; }
737
+ .caos-exec-art { width: 100%; max-width: 640px; margin-inline: auto; }
738
+ .caos-exec-art > svg { display: block; width: 100%; height: auto; }
739
+ .caos-exec-art-wait { aspect-ratio: 8 / 5; background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); }
740
+ .caos-exec-fig svg text { font-family: var(--font-sans); }
741
+ .caos-exec-fig[data-lang="en"] [data-lang="es"], .caos-exec-fig[data-lang="es"] [data-lang="en"] { display: none; }
742
+ .caos-exec-fig-error { margin: 0 auto; max-width: 640px; padding: 1rem; border: 1px solid var(--color-bad); border-radius: var(--radius-md); color: var(--color-fg); overflow-wrap: anywhere; }
743
+ .caos-exec-outcomes, .caos-exec-cs { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 0.9rem; }
744
+ .caos-exec-outcome, .caos-exec-cs > li { min-width: 0; padding: 0.8rem 1rem; background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: flex; flex-direction: column; gap: 0.6rem; overflow-wrap: anywhere; }
745
+ .caos-exec-proof { margin-top: auto; }
746
+ .caos-exec-pending { margin: 0; padding: 0.8rem 1rem; border: 1px dashed var(--color-border); border-radius: var(--radius-md); color: var(--color-fg-subtle); }
747
+ .caos-exec-notes-wrap { border-top: 1px solid var(--color-border); padding-top: 0.75rem; }
748
+ .caos-exec-notes-title { margin: 0 0 0.35rem; font-size: var(--text-sm); color: var(--color-fg-faint); }
749
+ .caos-exec-notes { margin: 0; padding-left: 1.4rem; font-size: var(--text-sm); color: var(--color-fg-subtle); }
750
+ .caos-exec-error { padding: 1rem 1.25rem; border: 1px solid var(--color-bad); border-radius: var(--radius-md); }