@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/dist/index.d.ts +90 -2
- package/dist/index.js +525 -39
- package/gate/areas.mjs +134 -6
- package/gate/inpage.mjs +87 -1
- package/package.json +4 -2
- package/reserved-classes.json +48 -0
- package/src/content/executive.schema.json +141 -0
- package/styles.css +33 -0
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 =
|
|
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.
|
|
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",
|
package/reserved-classes.json
CHANGED
|
@@ -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); }
|