@fasl-work/caos-app-shell 0.6.13 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -32
- package/bin/caos-shell-gate.mjs +67 -0
- package/chart.css +19 -0
- package/dist/chart.d.ts +58 -0
- package/dist/chart.js +153 -0
- package/dist/chunk-LOSSTLPP.js +174 -0
- package/dist/chunk-X6Y5CDUD.js +16 -0
- package/dist/index.d.ts +398 -119
- package/dist/index.js +708 -184
- package/dist/keys-CnvqkckF.d.ts +135 -0
- package/dist/keys.d.ts +4 -0
- package/dist/keys.js +1 -0
- package/dist/text-BlY47nIt.d.ts +21 -0
- package/dist/tokens-CSV0MOgR.d.ts +41 -0
- package/gate/inpage.mjs +494 -0
- package/gate/png.mjs +149 -0
- package/gate/run.mjs +595 -0
- package/gate/serve.mjs +97 -0
- package/package.json +40 -7
- package/reserved-classes.json +224 -0
- package/styles.css +225 -9
package/dist/index.js
CHANGED
|
@@ -1,40 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, formatNumber } from './chunk-LOSSTLPP.js';
|
|
2
|
+
export { NOT_AVAILABLE, SHELL_TOKENS, Stage, applyTheme, formatNumber, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-LOSSTLPP.js';
|
|
3
|
+
export { LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-X6Y5CDUD.js';
|
|
4
|
+
import { createContext, Component, useState, useRef, useId, useEffect, useCallback, useContext, useMemo } from 'react';
|
|
5
|
+
import { useInRouterContext, NavLink, useLocation } from 'react-router';
|
|
3
6
|
import { Sun, Moon, Languages, Boxes, CodeXml, Globe, Briefcase, Info } from 'lucide-react';
|
|
4
|
-
import {
|
|
5
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
8
|
import katex from 'katex';
|
|
7
9
|
|
|
8
|
-
// src/shell/AppShell.tsx
|
|
9
|
-
var KEY = "caos.lang";
|
|
10
|
-
function readLang() {
|
|
11
|
-
try {
|
|
12
|
-
const s = localStorage.getItem(KEY);
|
|
13
|
-
if (s === "en" || s === "es") return s;
|
|
14
|
-
} catch {
|
|
15
|
-
}
|
|
16
|
-
return "en";
|
|
17
|
-
}
|
|
18
|
-
var useLangStore = create((set, get) => ({
|
|
19
|
-
lang: readLang(),
|
|
20
|
-
setLang: (l) => {
|
|
21
|
-
try {
|
|
22
|
-
localStorage.setItem(KEY, l);
|
|
23
|
-
} catch {
|
|
24
|
-
}
|
|
25
|
-
set({ lang: l });
|
|
26
|
-
},
|
|
27
|
-
toggle: () => {
|
|
28
|
-
const next = get().lang === "en" ? "es" : "en";
|
|
29
|
-
try {
|
|
30
|
-
localStorage.setItem(KEY, next);
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
33
|
-
set({ lang: next });
|
|
34
|
-
}
|
|
35
|
-
}));
|
|
36
|
-
var useShellLang = () => useLangStore((s) => s.lang);
|
|
37
|
-
|
|
38
10
|
// src/lib/chrome.ts
|
|
39
11
|
var CHROME = {
|
|
40
12
|
en: {
|
|
@@ -47,7 +19,7 @@ var CHROME = {
|
|
|
47
19
|
dark: "Dark",
|
|
48
20
|
attribution: "Developed by Felipe Santib\xE1\xF1ez-Leal",
|
|
49
21
|
complement: "A CAOS research project",
|
|
50
|
-
license: "
|
|
22
|
+
license: "",
|
|
51
23
|
version: "v"
|
|
52
24
|
},
|
|
53
25
|
es: {
|
|
@@ -60,47 +32,73 @@ var CHROME = {
|
|
|
60
32
|
dark: "Oscuro",
|
|
61
33
|
attribution: "Desarrollado por Felipe Santib\xE1\xF1ez-Leal",
|
|
62
34
|
complement: "Un proyecto de investigaci\xF3n CAOS",
|
|
63
|
-
license: "
|
|
35
|
+
license: "",
|
|
64
36
|
version: "v"
|
|
65
37
|
}
|
|
66
38
|
};
|
|
67
39
|
var chrome = (lang) => CHROME[lang];
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
40
|
+
function FailedView({ panel: panel2, error }) {
|
|
41
|
+
const lang = useShellLang();
|
|
42
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-panel-error", role: "alert", "data-panel-error": panel2, children: [
|
|
43
|
+
/* @__PURE__ */ jsx("strong", { children: lang === "es" ? "Esta vista fall\xF3." : "This view failed." }),
|
|
44
|
+
" ",
|
|
45
|
+
/* @__PURE__ */ jsx("span", { children: lang === "es" ? "El resto de la p\xE1gina sigue funcionando." : "The rest of the page still works." }),
|
|
46
|
+
/* @__PURE__ */ jsx("code", { children: error.message })
|
|
47
|
+
] });
|
|
77
48
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
49
|
+
var PanelBoundary = class extends Component {
|
|
50
|
+
state = { error: null };
|
|
51
|
+
static getDerivedStateFromError(error) {
|
|
52
|
+
return { error };
|
|
82
53
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
set({ theme: t });
|
|
54
|
+
componentDidCatch(error, info) {
|
|
55
|
+
console.error(`[caos-app-shell] view "${this.props.panel}" failed: ${error.message}`, info.componentStack ?? "");
|
|
56
|
+
}
|
|
57
|
+
render() {
|
|
58
|
+
if (this.state.error) return /* @__PURE__ */ jsx(FailedView, { panel: this.props.panel, error: this.state.error });
|
|
59
|
+
return this.props.children;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var FADE = 28;
|
|
63
|
+
function reveal(row, item) {
|
|
64
|
+
const r = row.getBoundingClientRect();
|
|
65
|
+
const b = item.getBoundingClientRect();
|
|
66
|
+
const margin = Math.max(0, Math.min(FADE, (r.width - b.width) / 2));
|
|
67
|
+
if (b.left < r.left + margin) {
|
|
68
|
+
row.scrollTo({ left: Math.max(0, row.scrollLeft - (r.left + margin - b.left)), behavior: "instant" });
|
|
69
|
+
} else if (b.right > r.right - margin) {
|
|
70
|
+
row.scrollTo({ left: Math.min(row.scrollWidth - row.clientWidth, row.scrollLeft + (b.right - (r.right - margin))), behavior: "instant" });
|
|
102
71
|
}
|
|
103
|
-
}
|
|
72
|
+
}
|
|
73
|
+
function useOverflowFade(ref, activeSelector, deps = []) {
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const el = ref.current;
|
|
76
|
+
if (!el) return;
|
|
77
|
+
const update = () => {
|
|
78
|
+
const start = el.scrollLeft > 1;
|
|
79
|
+
const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
|
|
80
|
+
el.dataset.fadeStart = start ? "1" : "0";
|
|
81
|
+
el.dataset.fadeEnd = end ? "1" : "0";
|
|
82
|
+
};
|
|
83
|
+
const focused = document.activeElement instanceof HTMLElement && el.contains(document.activeElement) && document.activeElement !== el ? document.activeElement : null;
|
|
84
|
+
const active = focused ?? (activeSelector ? el.querySelector(activeSelector) : null);
|
|
85
|
+
if (active && el.scrollWidth > el.clientWidth) reveal(el, active);
|
|
86
|
+
const onFocus = (e) => {
|
|
87
|
+
const target = e.target;
|
|
88
|
+
if (target && target !== el && el.contains(target) && el.scrollWidth > el.clientWidth) reveal(el, target);
|
|
89
|
+
};
|
|
90
|
+
update();
|
|
91
|
+
el.addEventListener("scroll", update, { passive: true });
|
|
92
|
+
el.addEventListener("focusin", onFocus);
|
|
93
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(update) : null;
|
|
94
|
+
ro?.observe(el);
|
|
95
|
+
return () => {
|
|
96
|
+
el.removeEventListener("scroll", update);
|
|
97
|
+
el.removeEventListener("focusin", onFocus);
|
|
98
|
+
ro?.disconnect();
|
|
99
|
+
};
|
|
100
|
+
}, deps);
|
|
101
|
+
}
|
|
104
102
|
function ThemeToggle() {
|
|
105
103
|
const theme = useThemeStore((s) => s.theme);
|
|
106
104
|
const toggleTheme = useThemeStore((s) => s.toggleTheme);
|
|
@@ -119,7 +117,29 @@ function LanguageToggle() {
|
|
|
119
117
|
/* @__PURE__ */ jsx("span", { className: "lang-code", children: lang })
|
|
120
118
|
] });
|
|
121
119
|
}
|
|
122
|
-
|
|
120
|
+
function validateArchitectureConfig(config) {
|
|
121
|
+
const out = [];
|
|
122
|
+
if (config.tabs.length < 5) out.push(`the architecture modal has ${config.tabs.length} tabs; ADR-0058 requires at least 5`);
|
|
123
|
+
const known = new Set(SHELL_TOKENS);
|
|
124
|
+
for (const t of config.tabs) {
|
|
125
|
+
if (!t.en?.trim() || !t.es?.trim() || !t.body_en?.trim() || !t.body_es?.trim()) {
|
|
126
|
+
out.push(`architecture tab "${t.id}" lacks a label or a body in one language`);
|
|
127
|
+
}
|
|
128
|
+
const svg = t.svg.trim();
|
|
129
|
+
if (!svg.startsWith("<svg")) {
|
|
130
|
+
out.push(`architecture tab "${t.id}": the diagram must be an inline SVG string (import it with ?raw), not a URL`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (const name of new Set([...svg.matchAll(/var\((--[a-z0-9-]+)/gi)].map((x) => x[1]))) {
|
|
134
|
+
if (!known.has(name)) out.push(`architecture tab "${t.id}" uses ${name}, which the shell does not define`);
|
|
135
|
+
}
|
|
136
|
+
const hex = svg.replace(/url\(\s*#[^)]*\)/gi, "").replace(/href\s*=\s*["']#[^"']*["']/gi, "").match(/#[0-9a-f]{3,8}\b/gi);
|
|
137
|
+
if (hex) {
|
|
138
|
+
out.push(`architecture tab "${t.id}" uses hex colours (${[...new Set(hex)].slice(0, 3).join(", ")}); use shell tokens so it follows the theme`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
123
143
|
function ArchitectureModal({ config, onClose }) {
|
|
124
144
|
const lang = useShellLang();
|
|
125
145
|
const es = lang === "es";
|
|
@@ -179,27 +199,10 @@ function ArchitectureModal({ config, onClose }) {
|
|
|
179
199
|
setSvg(raw);
|
|
180
200
|
return;
|
|
181
201
|
}
|
|
182
|
-
|
|
183
|
-
setSvg(cache[raw]);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
202
|
+
console.error(`[caos-app-shell] architecture tab "${tab.id}": the diagram must be an inline SVG string, not "${raw.slice(0, 40)}"`);
|
|
186
203
|
setSvg(null);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const url = baseUrl + raw.replace(/^\//, "");
|
|
190
|
-
fetch(url).then(async (r) => {
|
|
191
|
-
if (!r.ok) throw new Error(`${r.status}`);
|
|
192
|
-
return r.text();
|
|
193
|
-
}).then((text) => {
|
|
194
|
-
cache[raw] = text;
|
|
195
|
-
if (!cancelled) setSvg(text);
|
|
196
|
-
}).catch((e) => {
|
|
197
|
-
if (!cancelled) setErr(String(e?.message ?? e));
|
|
198
|
-
});
|
|
199
|
-
return () => {
|
|
200
|
-
cancelled = true;
|
|
201
|
-
};
|
|
202
|
-
}, [tab]);
|
|
204
|
+
setErr(es ? "diagrama no disponible" : "diagram unavailable");
|
|
205
|
+
}, [tab, es]);
|
|
203
206
|
useEffect(() => {
|
|
204
207
|
const element = diagramRef.current?.querySelector("svg");
|
|
205
208
|
if (!element) return;
|
|
@@ -275,7 +278,7 @@ function ArchitectureModal({ config, onClose }) {
|
|
|
275
278
|
t.id
|
|
276
279
|
);
|
|
277
280
|
}) }),
|
|
278
|
-
/* @__PURE__ */ jsxs("div", { id: `${id}-panel`, role: "tabpanel", "aria-labelledby": `${id}-${tab.id}`, "data-arch-lang": es ? "es" : "en", style: { overflowY: "auto", minHeight: 0, padding: "14px 16px 18px" }, children: [
|
|
281
|
+
/* @__PURE__ */ jsxs("div", { id: `${id}-panel`, role: "tabpanel", "aria-labelledby": `${id}-${tab.id}`, "data-arch-lang": es ? "es" : "en", "data-tab": tab.id, "data-state": err ? "missing" : svg === null ? "loading" : "ready", style: { overflowY: "auto", minHeight: 0, padding: "14px 16px 18px" }, children: [
|
|
279
282
|
body.map((p, i) => /* @__PURE__ */ jsx("p", { style: { color: "var(--color-fg)", fontSize: 13, lineHeight: 1.65, margin: i === 0 ? "0 0 10px" : "10px 0" }, children: p }, i)),
|
|
280
283
|
err && /* @__PURE__ */ jsxs("div", { style: { color: "var(--color-warn, #f87171)", fontSize: 12 }, children: [
|
|
281
284
|
"SVG: ",
|
|
@@ -283,7 +286,7 @@ function ArchitectureModal({ config, onClose }) {
|
|
|
283
286
|
] }),
|
|
284
287
|
!err && svg === null && /* @__PURE__ */ jsx("div", { style: { color: "var(--color-fg-faint)", fontSize: 12 }, children: "\u2026" }),
|
|
285
288
|
svg !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
286
|
-
/* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": fullSize, "aria-controls": `${id}-diagram`, onClick: () => setFullSize((value) => !value),
|
|
289
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": fullSize, "aria-controls": `${id}-diagram`, onClick: () => setFullSize((value) => !value), className: "caos-arch-toggle", children: fullSize ? es ? "Ajustar diagrama" : "Fit diagram" : es ? "Leer a tama\xF1o completo" : "Read at full size" }),
|
|
287
290
|
/* @__PURE__ */ jsx("div", { id: `${id}-diagram`, role: "region", "aria-label": es ? "Diagrama de arquitectura" : "Architecture diagram", tabIndex: 0, style: { border: "1px solid var(--color-border)", borderRadius: 8, padding: 8, background: "var(--color-bg, var(--color-surface))", overflow: "auto", maxWidth: "100%" }, children: /* @__PURE__ */ jsx(
|
|
288
291
|
"div",
|
|
289
292
|
{
|
|
@@ -303,53 +306,101 @@ function ArchitectureModal({ config, onClose }) {
|
|
|
303
306
|
}
|
|
304
307
|
var PERSONAL = "https://fsantibanezleal.github.io";
|
|
305
308
|
var PORTFOLIO = "https://fasl-work.com";
|
|
309
|
+
var DISPLAY_VERSION = /^\d+\.\d{2}\.\d{3}$/;
|
|
310
|
+
function validate(config) {
|
|
311
|
+
if (!DISPLAY_VERSION.test(config.version)) {
|
|
312
|
+
console.error(`[caos-app-shell] version "${config.version}" is not the display form X.XX.XXX (ADR-0068)`);
|
|
313
|
+
}
|
|
314
|
+
if (!config.license && !config.footer?.license) {
|
|
315
|
+
console.error("[caos-app-shell] ShellConfig.license is required: the footer shows the product licence");
|
|
316
|
+
}
|
|
317
|
+
if (config.visibility !== "public" && config.visibility !== "private") {
|
|
318
|
+
console.error('[caos-app-shell] ShellConfig.visibility is required ("public" or "private")');
|
|
319
|
+
}
|
|
320
|
+
if (config.architecture) {
|
|
321
|
+
for (const problem of validateArchitectureConfig(config.architecture)) console.error(`[caos-app-shell] ${problem}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function Nav({ routes, name, routed, pathname }) {
|
|
325
|
+
const lang = useShellLang();
|
|
326
|
+
const ref = useRef(null);
|
|
327
|
+
useOverflowFade(ref, ".nav-link.active", [lang, pathname]);
|
|
328
|
+
return /* @__PURE__ */ jsx("nav", { className: "main-nav", "aria-label": name, ref, children: routes.map(
|
|
329
|
+
(r) => routed ? /* @__PURE__ */ jsx(
|
|
330
|
+
NavLink,
|
|
331
|
+
{
|
|
332
|
+
to: r.path,
|
|
333
|
+
end: r.path === "/",
|
|
334
|
+
className: ({ isActive }) => isActive ? "nav-link active" : "nav-link",
|
|
335
|
+
children: lang === "es" ? r.es : r.en
|
|
336
|
+
},
|
|
337
|
+
r.path
|
|
338
|
+
) : /* @__PURE__ */ jsx("a", { href: r.path, className: pathname === r.path ? "nav-link active" : "nav-link", children: lang === "es" ? r.es : r.en }, r.path)
|
|
339
|
+
) });
|
|
340
|
+
}
|
|
341
|
+
function RoutedPath({ children }) {
|
|
342
|
+
const { pathname } = useLocation();
|
|
343
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(pathname) });
|
|
344
|
+
}
|
|
306
345
|
function AppShell({ config, children }) {
|
|
346
|
+
const routed = useInRouterContext();
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (!routed) {
|
|
349
|
+
console.error(
|
|
350
|
+
"[caos-app-shell] AppShell is outside a Router context. If a Router is mounted, two copies of react-router are installed: depend on react-router (not react-router-dom) and add resolve.dedupe ['react', 'react-dom', 'react-router'] to vite.config.ts."
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}, [routed]);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
validate(config);
|
|
356
|
+
}, []);
|
|
357
|
+
if (routed) return /* @__PURE__ */ jsx(RoutedPath, { children: (pathname) => /* @__PURE__ */ jsx(Frame, { config, pathname, routed: true, children }) });
|
|
358
|
+
return /* @__PURE__ */ jsx(Frame, { config, pathname: typeof window !== "undefined" ? window.location.pathname : "/", routed: false, children });
|
|
359
|
+
}
|
|
360
|
+
function Frame({ config, pathname, routed, children }) {
|
|
307
361
|
const lang = useShellLang();
|
|
308
362
|
const c = chrome(lang);
|
|
309
363
|
const routes = config.routes ?? [];
|
|
310
364
|
const personal = config.links.personal ?? PERSONAL;
|
|
311
365
|
const portfolio = config.links.portfolio ?? PORTFOLIO;
|
|
366
|
+
const isPublic = config.visibility !== "private";
|
|
312
367
|
const [archOpen, setArchOpen] = useState(false);
|
|
368
|
+
const footRef = useRef(null);
|
|
369
|
+
useOverflowFade(footRef, null, [lang, pathname]);
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
document.documentElement.lang = lang;
|
|
372
|
+
}, [lang]);
|
|
313
373
|
const archLabel = lang === "es" ? "Arquitectura / C\xF3mo funciona" : "Architecture / How it works";
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
374
|
+
const fixed = config.contain === true || config.fixed === true || (config.fixedRoutes ?? []).some((p) => p === pathname || p !== "/" && pathname.startsWith(p.replace(/\/$/, "") + "/"));
|
|
375
|
+
const license = config.license ? pick(config.license, lang) : config.footer?.license?.[lang] ?? "";
|
|
376
|
+
const brand = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
377
|
+
/* @__PURE__ */ jsx("span", { className: "brand-mark", children: config.product.mark ?? /* @__PURE__ */ jsx(Boxes, { size: 18, "aria-hidden": "true" }) }),
|
|
378
|
+
/* @__PURE__ */ jsx("span", { children: config.product.name })
|
|
379
|
+
] });
|
|
380
|
+
return /* @__PURE__ */ jsxs("div", { className: config.contain ? "app-shell fixed contain" : fixed ? "app-shell fixed" : "app-shell", children: [
|
|
317
381
|
/* @__PURE__ */ jsx("header", { className: "site-header", children: /* @__PURE__ */ jsxs("div", { className: "header-inner", children: [
|
|
318
|
-
/* @__PURE__ */
|
|
319
|
-
|
|
320
|
-
/* @__PURE__ */ jsx("span", { children: config.product.name })
|
|
321
|
-
] }),
|
|
322
|
-
routes.length > 1 && /* @__PURE__ */ jsx("nav", { className: "main-nav", "aria-label": config.product.name, children: routes.map((r) => /* @__PURE__ */ jsx(
|
|
323
|
-
NavLink,
|
|
324
|
-
{
|
|
325
|
-
to: r.path,
|
|
326
|
-
end: r.path === "/",
|
|
327
|
-
onFocus: (event) => event.currentTarget.scrollIntoView({ block: "nearest", inline: "nearest" }),
|
|
328
|
-
className: ({ isActive }) => isActive ? "nav-link active" : "nav-link",
|
|
329
|
-
children: lang === "es" ? r.es : r.en
|
|
330
|
-
},
|
|
331
|
-
r.path
|
|
332
|
-
)) }),
|
|
382
|
+
routed ? /* @__PURE__ */ jsx(NavLink, { to: "/", className: "brand", "aria-label": config.product.name, "data-brand": config.product.name, children: brand }) : /* @__PURE__ */ jsx("a", { href: "/", className: "brand", "aria-label": config.product.name, "data-brand": config.product.name, children: brand }),
|
|
383
|
+
routes.length > 1 && /* @__PURE__ */ jsx(Nav, { routes, name: config.product.name, routed, pathname }),
|
|
333
384
|
/* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
|
|
334
|
-
/* @__PURE__ */ jsx("a", { className: "icon-btn", href: config.links.github, target: "_blank", rel: "noreferrer noopener", "aria-label": c.github, title: c.github, children: /* @__PURE__ */ jsx(CodeXml, { size: 18, "aria-hidden": "true" }) }),
|
|
385
|
+
isPublic && /* @__PURE__ */ jsx("a", { className: "icon-btn", href: config.links.github, target: "_blank", rel: "noreferrer noopener", "aria-label": c.github, title: c.github, children: /* @__PURE__ */ jsx(CodeXml, { size: 18, "aria-hidden": "true" }) }),
|
|
335
386
|
/* @__PURE__ */ jsx("a", { className: "icon-btn", href: personal, target: "_blank", rel: "noreferrer noopener", "aria-label": c.personal, title: c.personal, children: /* @__PURE__ */ jsx(Globe, { size: 18, "aria-hidden": "true" }) }),
|
|
336
387
|
/* @__PURE__ */ jsx("a", { className: "icon-btn", href: portfolio, target: "_blank", rel: "noreferrer noopener", "aria-label": c.portfolio, title: c.portfolio, children: /* @__PURE__ */ jsx(Briefcase, { size: 18, "aria-hidden": "true" }) }),
|
|
337
|
-
config.architecture && /* @__PURE__ */ jsx("button", { className: "icon-btn", type: "button", onClick: () => setArchOpen(true), "aria-label": archLabel, title: archLabel, children: /* @__PURE__ */ jsx(Info, { size: 18, "aria-hidden": "true" }) }),
|
|
388
|
+
config.architecture && /* @__PURE__ */ jsx("button", { className: "icon-btn", type: "button", onClick: () => setArchOpen(true), "aria-label": archLabel, title: archLabel, "data-architecture-button": "", children: /* @__PURE__ */ jsx(Info, { size: 18, "aria-hidden": "true" }) }),
|
|
338
389
|
/* @__PURE__ */ jsx("span", { className: "header-sep", "aria-hidden": "true" }),
|
|
339
390
|
/* @__PURE__ */ jsx(LanguageToggle, {}),
|
|
340
391
|
/* @__PURE__ */ jsx(ThemeToggle, {})
|
|
341
392
|
] })
|
|
342
393
|
] }) }),
|
|
343
394
|
config.architecture && archOpen && /* @__PURE__ */ jsx(ArchitectureModal, { config: config.architecture, onClose: () => setArchOpen(false) }),
|
|
344
|
-
/* @__PURE__ */ jsx("main", { className: "page", children }),
|
|
345
|
-
/* @__PURE__ */ jsx("footer", { className: "site-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-inner", children: /* @__PURE__ */ jsxs("div", { className: "footer-meta", children: [
|
|
395
|
+
/* @__PURE__ */ jsx("main", { className: "page", children: /* @__PURE__ */ jsx(PanelBoundary, { panel: "page", children }) }),
|
|
396
|
+
/* @__PURE__ */ jsx("footer", { className: "site-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-inner", children: /* @__PURE__ */ jsxs("div", { className: "footer-meta", ref: footRef, children: [
|
|
346
397
|
/* @__PURE__ */ jsx("span", { children: config.product.name }),
|
|
347
398
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
348
399
|
/* @__PURE__ */ jsx("span", { children: c.complement }),
|
|
349
|
-
/* @__PURE__ */ jsx("span", { "
|
|
350
|
-
/* @__PURE__ */ jsx("span", { className: "footer-build", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
400
|
+
/* @__PURE__ */ jsx("span", { className: "footer-build", "data-version": config.version, "data-build": config.build ?? "", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
351
401
|
c.version,
|
|
352
|
-
config.version
|
|
402
|
+
config.version,
|
|
403
|
+
config.build ? ` \xB7 ${config.build}` : ""
|
|
353
404
|
] }) }),
|
|
354
405
|
config.footer?.attribution !== false && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
406
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
@@ -359,10 +410,14 @@ function AppShell({ config, children }) {
|
|
|
359
410
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
360
411
|
/* @__PURE__ */ jsx("span", { children: config.footer.provenance[lang] })
|
|
361
412
|
] }),
|
|
362
|
-
/* @__PURE__ */
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
413
|
+
isPublic && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
414
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
415
|
+
/* @__PURE__ */ jsx("a", { href: config.links.github, target: "_blank", rel: "noreferrer noopener", children: c.github })
|
|
416
|
+
] }),
|
|
417
|
+
license && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
418
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
419
|
+
/* @__PURE__ */ jsx("span", { className: "faint", children: license })
|
|
420
|
+
] }),
|
|
366
421
|
config.footer?.disclaimer && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
367
422
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
368
423
|
/* @__PURE__ */ jsx("span", { className: "faint", children: config.footer.disclaimer[lang] })
|
|
@@ -657,18 +712,20 @@ function CaseSelector(props) {
|
|
|
657
712
|
const t = { ...TEXT[lang], ...text };
|
|
658
713
|
const baseId = useId();
|
|
659
714
|
const param = typeof deepLink === "string" ? deepLink : "case";
|
|
715
|
+
const linkPending = useRef(Boolean(deepLink));
|
|
660
716
|
useEffect(() => {
|
|
661
|
-
if (!
|
|
717
|
+
if (!linkPending.current || typeof window === "undefined" || cases.length === 0) return;
|
|
718
|
+
linkPending.current = false;
|
|
662
719
|
const fromUrl = readCaseParam(window.location.search, param);
|
|
663
720
|
if (fromUrl && fromUrl !== selectedId && cases.some((c) => c.id === fromUrl)) onSelect(fromUrl);
|
|
664
|
-
}, []);
|
|
721
|
+
}, [cases]);
|
|
665
722
|
useEffect(() => {
|
|
666
|
-
if (!deepLink || typeof window === "undefined") return;
|
|
723
|
+
if (!deepLink || linkPending.current || !selectedId || typeof window === "undefined") return;
|
|
667
724
|
const next = withCaseParam(window.location.search, selectedId, param);
|
|
668
725
|
if (next !== window.location.search) {
|
|
669
726
|
window.history.replaceState(null, "", `${window.location.pathname}${next}${window.location.hash}`);
|
|
670
727
|
}
|
|
671
|
-
}, [deepLink, param, selectedId]);
|
|
728
|
+
}, [deepLink, param, selectedId, cases]);
|
|
672
729
|
const lanes = source ? sourcesPresent(cases) : [];
|
|
673
730
|
const visible = casesInSource(cases, source);
|
|
674
731
|
const groups = groupByCategory(visible);
|
|
@@ -693,14 +750,15 @@ function CaseSelector(props) {
|
|
|
693
750
|
layout === "select" && /* @__PURE__ */ jsx(
|
|
694
751
|
"select",
|
|
695
752
|
{
|
|
753
|
+
"data-control": "case",
|
|
696
754
|
className: "select cs-select",
|
|
697
755
|
"aria-label": ariaLabel ?? "Case selector",
|
|
698
756
|
value: selectedId,
|
|
699
757
|
onChange: (e) => onSelect(e.target.value),
|
|
700
|
-
children: groups.map((g) => /* @__PURE__ */ jsx("optgroup", { label: g.category || " ", children: g.cases.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, disabled: c.disabled, title: caseTooltip(c) || void 0, children:
|
|
758
|
+
children: groups.map((g) => /* @__PURE__ */ jsx("optgroup", { label: g.category || " ", children: g.cases.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, disabled: c.disabled, title: caseTooltip(c) || void 0, children: c.name }, c.id)) }, g.category || "_"))
|
|
701
759
|
}
|
|
702
760
|
),
|
|
703
|
-
layout === "chips" && groups.map((g) => /* @__PURE__ */ jsxs("div", { className: "cs-group", role: "group", "aria-label": g.category || void 0, children: [
|
|
761
|
+
layout === "chips" && groups.map((g) => /* @__PURE__ */ jsxs("div", { className: "cs-group", role: "group", "aria-label": g.category || void 0, "data-control": "case", children: [
|
|
704
762
|
g.category && /* @__PURE__ */ jsx("span", { className: "cs-group-label", children: g.category }),
|
|
705
763
|
/* @__PURE__ */ jsx("div", { className: "cs-chips", children: g.cases.map((c) => {
|
|
706
764
|
const on = c.id === selectedId;
|
|
@@ -715,8 +773,8 @@ function CaseSelector(props) {
|
|
|
715
773
|
disabled: c.disabled,
|
|
716
774
|
title: tip || void 0,
|
|
717
775
|
onClick: () => onSelect(c.id),
|
|
776
|
+
"data-case": c.id,
|
|
718
777
|
children: [
|
|
719
|
-
/* @__PURE__ */ jsx("span", { className: "cs-chip-id", children: c.id }),
|
|
720
778
|
/* @__PURE__ */ jsx("span", { className: "cs-chip-name", children: c.name }),
|
|
721
779
|
/* @__PURE__ */ jsx("span", { className: `cs-kind cs-kind-${caseKindOf(c)}`, "aria-hidden": "true", children: KIND_TAG[caseKindOf(c)] })
|
|
722
780
|
]
|
|
@@ -729,7 +787,7 @@ function CaseSelector(props) {
|
|
|
729
787
|
/* @__PURE__ */ jsxs("span", { className: "badge accent", children: [
|
|
730
788
|
t.modifiedPrefix,
|
|
731
789
|
" ",
|
|
732
|
-
/* @__PURE__ */ jsx("strong", { children: modified.
|
|
790
|
+
/* @__PURE__ */ jsx("strong", { children: modified.name })
|
|
733
791
|
] }),
|
|
734
792
|
onResetToCanonical && /* @__PURE__ */ jsx("button", { type: "button", className: "cs-reset", onClick: onResetToCanonical, children: t.reset })
|
|
735
793
|
] })
|
|
@@ -738,10 +796,21 @@ function CaseSelector(props) {
|
|
|
738
796
|
function caseKindOf(c) {
|
|
739
797
|
return c.kind ?? "synthetic";
|
|
740
798
|
}
|
|
741
|
-
|
|
799
|
+
var MAX_PEER_TABS = 6;
|
|
800
|
+
function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
742
801
|
const baseId = useId();
|
|
743
802
|
const first = tabs[0]?.id ?? "";
|
|
744
|
-
const [
|
|
803
|
+
const [own, setOwn] = useState(initial ?? first);
|
|
804
|
+
const active = value ?? own;
|
|
805
|
+
const rowRef = useRef(null);
|
|
806
|
+
useOverflowFade(rowRef, ".tab.active", [active, tabs.length]);
|
|
807
|
+
if (tabs.length > MAX_PEER_TABS) {
|
|
808
|
+
console.error(`[caos-app-shell] ${tabs.length} peer tabs (${ariaLabel ?? "unnamed"}); at most ${MAX_PEER_TABS}, then group (ADR-0071 rule 5)`);
|
|
809
|
+
}
|
|
810
|
+
function select(id) {
|
|
811
|
+
if (value === void 0) setOwn(id);
|
|
812
|
+
onChange?.(id);
|
|
813
|
+
}
|
|
745
814
|
function onKeyDown(e, idx) {
|
|
746
815
|
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft" && e.key !== "Home" && e.key !== "End") return;
|
|
747
816
|
e.preventDefault();
|
|
@@ -752,13 +821,14 @@ function Tabs({ tabs, initial, ariaLabel }) {
|
|
|
752
821
|
else if (e.key === "End") next = tabs.length - 1;
|
|
753
822
|
const target = tabs[next];
|
|
754
823
|
if (target) {
|
|
755
|
-
|
|
824
|
+
select(target.id);
|
|
756
825
|
document.getElementById(`${baseId}-tab-${target.id}`)?.focus();
|
|
757
826
|
}
|
|
758
827
|
}
|
|
828
|
+
const current = tabs.find((t) => t.id === active) ?? tabs[0];
|
|
759
829
|
return /* @__PURE__ */ jsxs("div", { className: "tabs", children: [
|
|
760
|
-
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, children: tabs.map((tab, idx) => {
|
|
761
|
-
const selected = tab.id ===
|
|
830
|
+
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, ref: rowRef, children: tabs.map((tab, idx) => {
|
|
831
|
+
const selected = tab.id === current?.id;
|
|
762
832
|
return /* @__PURE__ */ jsx(
|
|
763
833
|
"button",
|
|
764
834
|
{
|
|
@@ -769,38 +839,44 @@ function Tabs({ tabs, initial, ariaLabel }) {
|
|
|
769
839
|
"aria-controls": `${baseId}-panel-${tab.id}`,
|
|
770
840
|
tabIndex: selected ? 0 : -1,
|
|
771
841
|
className: selected ? "tab active" : "tab",
|
|
772
|
-
|
|
842
|
+
"data-tab": tab.id,
|
|
843
|
+
onClick: () => select(tab.id),
|
|
773
844
|
onKeyDown: (e) => onKeyDown(e, idx),
|
|
774
845
|
children: tab.label
|
|
775
846
|
},
|
|
776
847
|
tab.id
|
|
777
848
|
);
|
|
778
849
|
}) }),
|
|
779
|
-
|
|
850
|
+
current && /* @__PURE__ */ jsx(
|
|
780
851
|
"div",
|
|
781
852
|
{
|
|
782
|
-
id: `${baseId}-panel-${
|
|
853
|
+
id: `${baseId}-panel-${current.id}`,
|
|
783
854
|
role: "tabpanel",
|
|
784
|
-
"aria-labelledby": `${baseId}-tab-${
|
|
785
|
-
hidden: tab.id !== active,
|
|
855
|
+
"aria-labelledby": `${baseId}-tab-${current.id}`,
|
|
786
856
|
tabIndex: 0,
|
|
787
857
|
className: "tabpanel",
|
|
788
|
-
|
|
858
|
+
"data-panel": current.id,
|
|
859
|
+
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: current.content })
|
|
789
860
|
},
|
|
790
|
-
|
|
791
|
-
)
|
|
861
|
+
current.id
|
|
862
|
+
)
|
|
792
863
|
] });
|
|
793
864
|
}
|
|
794
|
-
function SubTabs({
|
|
795
|
-
tabs,
|
|
796
|
-
initial,
|
|
797
|
-
ariaLabel,
|
|
798
|
-
orientation = "horizontal"
|
|
799
|
-
}) {
|
|
865
|
+
function SubTabs({ tabs, initial, value, onChange, ariaLabel, orientation = "horizontal" }) {
|
|
800
866
|
const baseId = useId();
|
|
801
867
|
const first = tabs[0]?.id ?? "";
|
|
802
|
-
const [
|
|
868
|
+
const [own, setOwn] = useState(initial ?? first);
|
|
869
|
+
const active = value ?? own;
|
|
803
870
|
const vertical = orientation === "vertical";
|
|
871
|
+
const rowRef = useRef(null);
|
|
872
|
+
useOverflowFade(rowRef, ".subtab.active", [active, tabs.length, vertical]);
|
|
873
|
+
if (tabs.length > MAX_PEER_TABS) {
|
|
874
|
+
console.error(`[caos-app-shell] ${tabs.length} peer sub-tabs (${ariaLabel ?? "unnamed"}); at most ${MAX_PEER_TABS}, then group (ADR-0071 rule 5)`);
|
|
875
|
+
}
|
|
876
|
+
function select(id) {
|
|
877
|
+
if (value === void 0) setOwn(id);
|
|
878
|
+
onChange?.(id);
|
|
879
|
+
}
|
|
804
880
|
function onKeyDown(e, idx) {
|
|
805
881
|
const fwd = vertical ? "ArrowDown" : "ArrowRight";
|
|
806
882
|
const back = vertical ? "ArrowUp" : "ArrowLeft";
|
|
@@ -813,45 +889,90 @@ function SubTabs({
|
|
|
813
889
|
else if (e.key === "End") next = tabs.length - 1;
|
|
814
890
|
const target = tabs[next];
|
|
815
891
|
if (target) {
|
|
816
|
-
|
|
892
|
+
select(target.id);
|
|
817
893
|
document.getElementById(`${baseId}-subtab-${target.id}`)?.focus();
|
|
818
894
|
}
|
|
819
895
|
}
|
|
896
|
+
const current = tabs.find((t) => t.id === active) ?? tabs[0];
|
|
820
897
|
return /* @__PURE__ */ jsxs("div", { className: vertical ? "subtabs subtabs-vertical" : "subtabs", children: [
|
|
821
|
-
/* @__PURE__ */ jsx(
|
|
822
|
-
const selected = tab.id === active;
|
|
823
|
-
return /* @__PURE__ */ jsx(
|
|
824
|
-
"button",
|
|
825
|
-
{
|
|
826
|
-
id: `${baseId}-subtab-${tab.id}`,
|
|
827
|
-
role: "tab",
|
|
828
|
-
type: "button",
|
|
829
|
-
"aria-selected": selected,
|
|
830
|
-
"aria-controls": `${baseId}-subpanel-${tab.id}`,
|
|
831
|
-
tabIndex: selected ? 0 : -1,
|
|
832
|
-
className: selected ? "subtab active" : "subtab",
|
|
833
|
-
onClick: () => setActive(tab.id),
|
|
834
|
-
onKeyDown: (e) => onKeyDown(e, idx),
|
|
835
|
-
children: tab.label
|
|
836
|
-
},
|
|
837
|
-
tab.id
|
|
838
|
-
);
|
|
839
|
-
}) }),
|
|
840
|
-
/* @__PURE__ */ jsx("div", { className: "subtabpanels", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
898
|
+
/* @__PURE__ */ jsx(
|
|
841
899
|
"div",
|
|
842
900
|
{
|
|
843
|
-
|
|
901
|
+
className: "subtablist",
|
|
902
|
+
role: "tablist",
|
|
903
|
+
"aria-label": ariaLabel,
|
|
904
|
+
"aria-orientation": vertical ? "vertical" : "horizontal",
|
|
905
|
+
ref: rowRef,
|
|
906
|
+
children: tabs.map((tab, idx) => {
|
|
907
|
+
const selected = tab.id === current?.id;
|
|
908
|
+
return /* @__PURE__ */ jsx(
|
|
909
|
+
"button",
|
|
910
|
+
{
|
|
911
|
+
id: `${baseId}-subtab-${tab.id}`,
|
|
912
|
+
role: "tab",
|
|
913
|
+
type: "button",
|
|
914
|
+
"aria-selected": selected,
|
|
915
|
+
"aria-controls": `${baseId}-subpanel-${tab.id}`,
|
|
916
|
+
tabIndex: selected ? 0 : -1,
|
|
917
|
+
className: selected ? "subtab active" : "subtab",
|
|
918
|
+
"data-tab": tab.id,
|
|
919
|
+
onClick: () => select(tab.id),
|
|
920
|
+
onKeyDown: (e) => onKeyDown(e, idx),
|
|
921
|
+
children: tab.label
|
|
922
|
+
},
|
|
923
|
+
tab.id
|
|
924
|
+
);
|
|
925
|
+
})
|
|
926
|
+
}
|
|
927
|
+
),
|
|
928
|
+
/* @__PURE__ */ jsx("div", { className: "subtabpanels", children: current && /* @__PURE__ */ jsx(
|
|
929
|
+
"div",
|
|
930
|
+
{
|
|
931
|
+
id: `${baseId}-subpanel-${current.id}`,
|
|
844
932
|
role: "tabpanel",
|
|
845
|
-
"aria-labelledby": `${baseId}-subtab-${
|
|
846
|
-
hidden: tab.id !== active,
|
|
933
|
+
"aria-labelledby": `${baseId}-subtab-${current.id}`,
|
|
847
934
|
tabIndex: 0,
|
|
848
935
|
className: "subtabpanel",
|
|
849
|
-
|
|
936
|
+
"data-panel": current.id,
|
|
937
|
+
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: current.content })
|
|
850
938
|
},
|
|
851
|
-
|
|
852
|
-
)
|
|
939
|
+
current.id
|
|
940
|
+
) })
|
|
853
941
|
] });
|
|
854
942
|
}
|
|
943
|
+
function TabGroups({ groups, value, onChange, ariaLabel }) {
|
|
944
|
+
const [own, setOwn] = useState({ group: groups[0]?.id ?? "" });
|
|
945
|
+
const sel = value ?? own;
|
|
946
|
+
const group = groups.find((g) => g.id === sel.group) ?? groups[0];
|
|
947
|
+
const tabOf = (g, wanted) => g.tabs.find((t) => t.id === wanted)?.id ?? g.tabs[0]?.id ?? "";
|
|
948
|
+
function set(next) {
|
|
949
|
+
if (value === void 0) setOwn(next);
|
|
950
|
+
onChange?.(next);
|
|
951
|
+
}
|
|
952
|
+
return /* @__PURE__ */ jsx(
|
|
953
|
+
Tabs,
|
|
954
|
+
{
|
|
955
|
+
ariaLabel,
|
|
956
|
+
value: group?.id,
|
|
957
|
+
onChange: (gid) => {
|
|
958
|
+
const g = groups.find((x) => x.id === gid);
|
|
959
|
+
if (g) set({ group: g.id, tab: tabOf(g) });
|
|
960
|
+
},
|
|
961
|
+
tabs: groups.map((g) => ({
|
|
962
|
+
id: g.id,
|
|
963
|
+
label: g.label,
|
|
964
|
+
content: g.tabs.length === 1 ? g.tabs[0].content : /* @__PURE__ */ jsx(
|
|
965
|
+
SubTabs,
|
|
966
|
+
{
|
|
967
|
+
tabs: g.tabs,
|
|
968
|
+
value: tabOf(g, g.id === group?.id ? sel.tab : void 0),
|
|
969
|
+
onChange: (tid) => set({ group: g.id, tab: tid })
|
|
970
|
+
}
|
|
971
|
+
)
|
|
972
|
+
}))
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
}
|
|
855
976
|
function Callout({
|
|
856
977
|
variant = "note",
|
|
857
978
|
title,
|
|
@@ -864,6 +985,9 @@ function Callout({
|
|
|
864
985
|
] });
|
|
865
986
|
}
|
|
866
987
|
function Equation({ tex, caption }) {
|
|
988
|
+
if (caption === void 0 || caption === null || caption === "") {
|
|
989
|
+
console.error(`[caos-app-shell] Equation without a caption (ADR-0017 s2): ${tex.slice(0, 60)}`);
|
|
990
|
+
}
|
|
867
991
|
const html = katex.renderToString(tex, { displayMode: true, throwOnError: false });
|
|
868
992
|
return /* @__PURE__ */ jsxs("div", { className: "equation", children: [
|
|
869
993
|
/* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }),
|
|
@@ -883,7 +1007,11 @@ function Figure({ caption, children }) {
|
|
|
883
1007
|
var CitationsContext = createContext({ list: [], byId: {} });
|
|
884
1008
|
function CitationsProvider({ items, children }) {
|
|
885
1009
|
const byId = {};
|
|
886
|
-
for (const c of items)
|
|
1010
|
+
for (const c of items) {
|
|
1011
|
+
if (byId[c.id]) console.error(`[caos-app-shell] duplicate citation id ${c.id}`);
|
|
1012
|
+
if (!c.doi && !c.url) console.error(`[caos-app-shell] citation ${c.id} has no doi or url (ADR-0017 s4)`);
|
|
1013
|
+
byId[c.id] = c;
|
|
1014
|
+
}
|
|
887
1015
|
return /* @__PURE__ */ jsx(CitationsContext.Provider, { value: { list: items, byId }, children });
|
|
888
1016
|
}
|
|
889
1017
|
function href(c) {
|
|
@@ -891,14 +1019,19 @@ function href(c) {
|
|
|
891
1019
|
}
|
|
892
1020
|
function Cite({ id, paren = true }) {
|
|
893
1021
|
const { byId } = useContext(CitationsContext);
|
|
1022
|
+
const lang = useShellLang();
|
|
894
1023
|
const c = byId[id];
|
|
895
|
-
if (!c)
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1024
|
+
if (!c) {
|
|
1025
|
+
console.error(`[caos-app-shell] Cite of an unknown citation id ${id}`);
|
|
1026
|
+
return /* @__PURE__ */ jsxs("cite", { className: "cite-inline", children: [
|
|
1027
|
+
"[",
|
|
1028
|
+
id,
|
|
1029
|
+
"]"
|
|
1030
|
+
] });
|
|
1031
|
+
}
|
|
900
1032
|
const h = href(c);
|
|
901
|
-
const
|
|
1033
|
+
const text = pick(c.label, lang);
|
|
1034
|
+
const label = h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", children: text }) : /* @__PURE__ */ jsx("span", { children: text });
|
|
902
1035
|
return /* @__PURE__ */ jsxs("cite", { className: "cite-inline", children: [
|
|
903
1036
|
paren ? "(" : null,
|
|
904
1037
|
label,
|
|
@@ -916,6 +1049,7 @@ function Refs({ ids, label }) {
|
|
|
916
1049
|
] });
|
|
917
1050
|
}
|
|
918
1051
|
function ReferenceList({ ids, heading }) {
|
|
1052
|
+
console.error("[caos-app-shell] ReferenceList is banned (ADR-0017 s4): end each section with Refs");
|
|
919
1053
|
const { list, byId } = useContext(CitationsContext);
|
|
920
1054
|
const items = ids ? ids.map((k) => byId[k]).filter(Boolean) : list;
|
|
921
1055
|
return /* @__PURE__ */ jsxs("section", { className: "references", "aria-label": heading ?? "References", children: [
|
|
@@ -930,5 +1064,395 @@ function ReferenceList({ ids, heading }) {
|
|
|
930
1064
|
}) })
|
|
931
1065
|
] });
|
|
932
1066
|
}
|
|
1067
|
+
function DocPage({ title, lede, wide, children }) {
|
|
1068
|
+
const lang = useShellLang();
|
|
1069
|
+
return /* @__PURE__ */ jsxs("div", { className: wide ? "page-body wide prose caos-doc" : "page-body prose caos-doc", "data-doc-page": "", children: [
|
|
1070
|
+
/* @__PURE__ */ jsxs("div", { className: "page-head", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx("h1", { children: pick(title, lang) }),
|
|
1072
|
+
/* @__PURE__ */ jsx("p", { className: "lede", children: lede })
|
|
1073
|
+
] }),
|
|
1074
|
+
children
|
|
1075
|
+
] });
|
|
1076
|
+
}
|
|
1077
|
+
function DocSection({ title, id, refs, noRefsReason, children }) {
|
|
1078
|
+
const lang = useShellLang();
|
|
1079
|
+
const hasRefs = Boolean(refs && refs.length > 0);
|
|
1080
|
+
return /* @__PURE__ */ jsxs(
|
|
1081
|
+
"section",
|
|
1082
|
+
{
|
|
1083
|
+
id,
|
|
1084
|
+
className: "caos-doc-section",
|
|
1085
|
+
"data-doc-section": "",
|
|
1086
|
+
"data-no-refs-reason": hasRefs ? void 0 : pick(noRefsReason, lang) || "missing",
|
|
1087
|
+
children: [
|
|
1088
|
+
/* @__PURE__ */ jsx("h2", { children: pick(title, lang) }),
|
|
1089
|
+
children,
|
|
1090
|
+
hasRefs && /* @__PURE__ */ jsx(Refs, { ids: refs, label: lang === "es" ? "Referencias" : "References" })
|
|
1091
|
+
]
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
function isSections(rail) {
|
|
1096
|
+
return Array.isArray(rail) && rail.every((s) => typeof s === "object" && s !== null && "id" in s && "content" in s);
|
|
1097
|
+
}
|
|
1098
|
+
function WorkbenchLayout({ rail, railHead, children, railLabel, className }) {
|
|
1099
|
+
const lang = useShellLang();
|
|
1100
|
+
const sections = isSections(rail) ? rail : null;
|
|
1101
|
+
const [active, setActive] = useState(sections?.[0]?.id ?? "");
|
|
1102
|
+
const baseId = useId();
|
|
1103
|
+
const label = pick(railLabel ?? { en: "Controls", es: "Controles" }, lang);
|
|
1104
|
+
const current = sections?.find((s) => s.id === active) ?? sections?.[0];
|
|
1105
|
+
return /* @__PURE__ */ jsxs("div", { className: ["page-body", "wide", "caos-wb", className].filter(Boolean).join(" "), "data-workbench": "", children: [
|
|
1106
|
+
/* @__PURE__ */ jsxs("aside", { className: "caos-wb-rail", "aria-label": label, "data-rail": "", children: [
|
|
1107
|
+
railHead && /* @__PURE__ */ jsx("div", { className: "caos-wb-rail-head", children: railHead }),
|
|
1108
|
+
sections ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1109
|
+
/* @__PURE__ */ jsx("div", { className: "caos-wb-rail-sections", role: "tablist", "aria-label": label, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
1110
|
+
"button",
|
|
1111
|
+
{
|
|
1112
|
+
type: "button",
|
|
1113
|
+
role: "tab",
|
|
1114
|
+
id: `${baseId}-rs-${s.id}`,
|
|
1115
|
+
"aria-selected": s.id === current?.id,
|
|
1116
|
+
"aria-controls": `${baseId}-rp-${s.id}`,
|
|
1117
|
+
className: s.id === current?.id ? "chip on" : "chip",
|
|
1118
|
+
onClick: () => setActive(s.id),
|
|
1119
|
+
children: pick(s.label, lang)
|
|
1120
|
+
},
|
|
1121
|
+
s.id
|
|
1122
|
+
)) }),
|
|
1123
|
+
current && /* @__PURE__ */ jsx(
|
|
1124
|
+
"div",
|
|
1125
|
+
{
|
|
1126
|
+
className: "caos-wb-rail-panel",
|
|
1127
|
+
role: "tabpanel",
|
|
1128
|
+
id: `${baseId}-rp-${current.id}`,
|
|
1129
|
+
"aria-labelledby": `${baseId}-rs-${current.id}`,
|
|
1130
|
+
children: current.content
|
|
1131
|
+
}
|
|
1132
|
+
)
|
|
1133
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "caos-wb-rail-panel", children: rail })
|
|
1134
|
+
] }),
|
|
1135
|
+
/* @__PURE__ */ jsx("section", { className: "caos-wb-main", "data-instrument": "", children })
|
|
1136
|
+
] });
|
|
1137
|
+
}
|
|
1138
|
+
var WorkbenchStateContext = createContext(null);
|
|
1139
|
+
function useWorkbenchState() {
|
|
1140
|
+
return useContext(WorkbenchStateContext);
|
|
1141
|
+
}
|
|
1142
|
+
function stable(value) {
|
|
1143
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
1144
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
1145
|
+
const obj = value;
|
|
1146
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stable(obj[k])}`).join(",")}}`;
|
|
1147
|
+
}
|
|
1148
|
+
function makeStateKey(parts) {
|
|
1149
|
+
const text = stable(parts);
|
|
1150
|
+
let h = 2166136261;
|
|
1151
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1152
|
+
h ^= text.charCodeAt(i);
|
|
1153
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
1154
|
+
}
|
|
1155
|
+
return h.toString(16).padStart(8, "0");
|
|
1156
|
+
}
|
|
1157
|
+
function isStale(dataKey, current) {
|
|
1158
|
+
return Boolean(current && dataKey !== void 0 && dataKey !== current.stateKey);
|
|
1159
|
+
}
|
|
1160
|
+
var LANE_LABELS = {
|
|
1161
|
+
live: { en: "Live in your browser", es: "En vivo en tu navegador" },
|
|
1162
|
+
replay: { en: "Replay of the offline result", es: "Reproduce el resultado offline" },
|
|
1163
|
+
offline: { en: "Offline only", es: "Solo offline" }
|
|
1164
|
+
};
|
|
1165
|
+
var PROVENANCE_LABELS = {
|
|
1166
|
+
real: { en: "Real data", es: "Datos reales" },
|
|
1167
|
+
synthetic: { en: "Synthetic data", es: "Datos sint\xE9ticos" },
|
|
1168
|
+
published: { en: "Published reference", es: "Referencia publicada" }
|
|
1169
|
+
};
|
|
1170
|
+
function LaneBadge({ lane, provenance, label }) {
|
|
1171
|
+
const lang = useShellLang();
|
|
1172
|
+
return /* @__PURE__ */ jsxs("span", { className: "caos-badges", children: [
|
|
1173
|
+
/* @__PURE__ */ jsx("span", { className: `badge caos-lane caos-lane-${lane}`, "data-lane": lane, children: pick(label ?? LANE_LABELS[lane], lang) }),
|
|
1174
|
+
provenance && /* @__PURE__ */ jsx("span", { className: `badge caos-prov caos-prov-${provenance}`, "data-provenance": provenance, children: pick(PROVENANCE_LABELS[provenance], lang) })
|
|
1175
|
+
] });
|
|
1176
|
+
}
|
|
1177
|
+
function VariantBar({ variants, activeId, onSelect, title, lane }) {
|
|
1178
|
+
const lang = useShellLang();
|
|
1179
|
+
const active = variants.find((v) => v.id === activeId) ?? variants[0];
|
|
1180
|
+
const shownLane = active?.lane ?? lane;
|
|
1181
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-variant-bar", "data-variant-bar": "", "data-variant-count": variants.length, children: [
|
|
1182
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-variant-head", children: [
|
|
1183
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-variant-title", children: [
|
|
1184
|
+
pick(title ?? { en: "Variants", es: "Variantes" }, lang),
|
|
1185
|
+
" (",
|
|
1186
|
+
variants.length,
|
|
1187
|
+
")"
|
|
1188
|
+
] }),
|
|
1189
|
+
shownLane && /* @__PURE__ */ jsx(LaneBadge, { lane: shownLane })
|
|
1190
|
+
] }),
|
|
1191
|
+
/* @__PURE__ */ jsx("div", { className: "caos-chip-row", role: "radiogroup", "aria-label": pick(title ?? { en: "Variants", es: "Variantes" }, lang), "data-control": "variant", children: variants.map((v) => /* @__PURE__ */ jsx(
|
|
1192
|
+
"button",
|
|
1193
|
+
{
|
|
1194
|
+
type: "button",
|
|
1195
|
+
role: "radio",
|
|
1196
|
+
"aria-checked": v.id === active?.id,
|
|
1197
|
+
disabled: v.disabled,
|
|
1198
|
+
className: v.id === active?.id ? "chip on" : "chip",
|
|
1199
|
+
onClick: () => onSelect(v.id),
|
|
1200
|
+
children: pick(v.label, lang)
|
|
1201
|
+
},
|
|
1202
|
+
v.id
|
|
1203
|
+
)) }),
|
|
1204
|
+
active?.note && /* @__PURE__ */ jsx("p", { className: "caos-variant-note", children: pick(active.note, lang) })
|
|
1205
|
+
] });
|
|
1206
|
+
}
|
|
1207
|
+
var MAX_WORKBENCH_GROUPS = MAX_PEER_TABS;
|
|
1208
|
+
function panel(id, lane, provenance, content) {
|
|
1209
|
+
return /* @__PURE__ */ jsx("div", { className: "caos-cw-panel", "data-group": id, "data-lane": lane, "data-provenance": provenance, children: content });
|
|
1210
|
+
}
|
|
1211
|
+
function CaseWorkbench(props) {
|
|
1212
|
+
const { caseId, cases, source, controls, replayOnly, variants, rail, railLabel, groups, compare, context, group, onGroupChange, ariaLabel } = props;
|
|
1213
|
+
const lang = useShellLang();
|
|
1214
|
+
const variantId = variants?.activeId;
|
|
1215
|
+
const stateKey = useMemo(
|
|
1216
|
+
() => makeStateKey({ caseId, variantId: variantId ?? null, source: source ?? null, controls: controls ?? {} }),
|
|
1217
|
+
[caseId, variantId, source, controls]
|
|
1218
|
+
);
|
|
1219
|
+
const tabs = groups.map((g) => ({ id: g.id, label: pick(g.label, lang), content: panel(g.id, g.lane, g.provenance, g.content) }));
|
|
1220
|
+
if (compare) {
|
|
1221
|
+
tabs.push({
|
|
1222
|
+
id: "compare",
|
|
1223
|
+
label: pick(compare.label ?? { en: "Compare variants", es: "Comparar variantes" }, lang),
|
|
1224
|
+
content: panel("compare", compare.lane, compare.provenance, compare.content)
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
tabs.push({
|
|
1228
|
+
id: "context",
|
|
1229
|
+
label: pick(context.label ?? { en: "Context", es: "Contexto" }, lang),
|
|
1230
|
+
content: /* @__PURE__ */ jsx("div", { className: "prose caos-cw-context", children: context.content })
|
|
1231
|
+
});
|
|
1232
|
+
if (tabs.length > MAX_WORKBENCH_GROUPS) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
`CaseWorkbench: ${tabs.length} groups; at most ${MAX_WORKBENCH_GROUPS} peers, then group by question (ADR-0071 rule 5)`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const railHead = cases || variants ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1238
|
+
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases }),
|
|
1239
|
+
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
1240
|
+
] }) : null;
|
|
1241
|
+
return /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(WorkbenchLayout, { rail: rail ?? null, railHead, railLabel, children: /* @__PURE__ */ jsx(
|
|
1242
|
+
"div",
|
|
1243
|
+
{
|
|
1244
|
+
className: "caos-cw",
|
|
1245
|
+
"data-case-workbench": "",
|
|
1246
|
+
"data-group-count": tabs.length,
|
|
1247
|
+
"data-case": caseId,
|
|
1248
|
+
"data-variant": variantId ?? "",
|
|
1249
|
+
"data-source": source ?? "",
|
|
1250
|
+
"data-state-key": stateKey,
|
|
1251
|
+
"data-replay-only": replayOnly ? "1" : void 0,
|
|
1252
|
+
children: /* @__PURE__ */ jsx(
|
|
1253
|
+
Tabs,
|
|
1254
|
+
{
|
|
1255
|
+
tabs,
|
|
1256
|
+
value: group,
|
|
1257
|
+
onChange: onGroupChange,
|
|
1258
|
+
ariaLabel: pick(ariaLabel ?? { en: "Case views", es: "Vistas del caso" }, lang)
|
|
1259
|
+
}
|
|
1260
|
+
)
|
|
1261
|
+
}
|
|
1262
|
+
) }) });
|
|
1263
|
+
}
|
|
1264
|
+
function toneOf(it) {
|
|
1265
|
+
if (it.tone) return it.tone;
|
|
1266
|
+
if (!it.better || it.value === null || it.value === void 0 || !Number.isFinite(it.value)) return "neutral";
|
|
1267
|
+
const v = it.value;
|
|
1268
|
+
const higher = it.better === "higher";
|
|
1269
|
+
if (it.bad !== void 0 && (higher ? v <= it.bad : v >= it.bad)) return "bad";
|
|
1270
|
+
if (it.good !== void 0 && (higher ? v >= it.good : v <= it.good)) return "good";
|
|
1271
|
+
if (it.good !== void 0 || it.bad !== void 0) return "warn";
|
|
1272
|
+
return "neutral";
|
|
1273
|
+
}
|
|
1274
|
+
function checkItem(it, where) {
|
|
1275
|
+
if (!it.unit && !it.unitless && it.text === void 0) {
|
|
1276
|
+
console.error(`[caos-app-shell] readout "${where}" has no unit; give a unit or set unitless`);
|
|
1277
|
+
}
|
|
1278
|
+
if (typeof it.value === "number" && Number.isNaN(it.value)) {
|
|
1279
|
+
console.error(`[caos-app-shell] readout "${where}" received NaN; pass null when the value is not available`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
function Readout({ items, title, lane, provenance, dataKey }) {
|
|
1283
|
+
const lang = useShellLang();
|
|
1284
|
+
const ws = useWorkbenchState();
|
|
1285
|
+
const stale = isStale(dataKey, ws);
|
|
1286
|
+
if (!lane || !provenance) console.error("[caos-app-shell] Readout needs lane and provenance");
|
|
1287
|
+
return /* @__PURE__ */ jsxs(
|
|
1288
|
+
"div",
|
|
1289
|
+
{
|
|
1290
|
+
className: stale ? "caos-readout caos-stale" : "caos-readout",
|
|
1291
|
+
"data-readout": "",
|
|
1292
|
+
"data-lane": lane,
|
|
1293
|
+
"data-provenance": provenance,
|
|
1294
|
+
"data-state-key": dataKey ?? "",
|
|
1295
|
+
"data-stale": stale ? "1" : "0",
|
|
1296
|
+
children: [
|
|
1297
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-readout-head", children: [
|
|
1298
|
+
title && /* @__PURE__ */ jsx("p", { className: "caos-readout-title", children: pick(title, lang) }),
|
|
1299
|
+
/* @__PURE__ */ jsx(LaneBadge, { lane, provenance })
|
|
1300
|
+
] }),
|
|
1301
|
+
/* @__PURE__ */ jsx("dl", { children: items.map((it, i) => {
|
|
1302
|
+
const label = pick(it.label, lang);
|
|
1303
|
+
checkItem(it, label);
|
|
1304
|
+
const shown = it.text !== void 0 ? pick(it.text, lang) : formatNumber(it.value, lang, it.format ?? {});
|
|
1305
|
+
return /* @__PURE__ */ jsxs("div", { className: `caos-readout-row tone-${toneOf(it)}`, title: pick(it.hint, lang) || void 0, children: [
|
|
1306
|
+
/* @__PURE__ */ jsx("dt", { children: label }),
|
|
1307
|
+
/* @__PURE__ */ jsxs("dd", { children: [
|
|
1308
|
+
/* @__PURE__ */ jsx("span", { className: "caos-readout-value", children: shown }),
|
|
1309
|
+
it.unit && it.text === void 0 && /* @__PURE__ */ jsxs("span", { className: "caos-readout-unit", children: [
|
|
1310
|
+
" ",
|
|
1311
|
+
pick(it.unit, lang)
|
|
1312
|
+
] })
|
|
1313
|
+
] })
|
|
1314
|
+
] }, i);
|
|
1315
|
+
}) }),
|
|
1316
|
+
stale && /* @__PURE__ */ jsx("p", { className: "caos-stale-note", children: lang === "es" ? "Recalculando para la selecci\xF3n actual" : "Recomputing for the current selection" })
|
|
1317
|
+
]
|
|
1318
|
+
}
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
function Gauge({ title, value, min, max, zones, unit, unitless, format }) {
|
|
1322
|
+
const lang = useShellLang();
|
|
1323
|
+
checkItem({ value, unit, unitless }, pick(title, lang));
|
|
1324
|
+
const span = max - min || 1;
|
|
1325
|
+
const finite = value !== null && value !== void 0 && Number.isFinite(value);
|
|
1326
|
+
const pos = (v) => `${Math.min(100, Math.max(0, (v - min) / span * 100))}%`;
|
|
1327
|
+
const fmt = (v) => formatNumber(v, lang, format ?? {});
|
|
1328
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-gauge", "data-gauge": "", children: [
|
|
1329
|
+
/* @__PURE__ */ jsx("p", { className: "caos-gauge-title", children: pick(title, lang) }),
|
|
1330
|
+
/* @__PURE__ */ jsxs(
|
|
1331
|
+
"div",
|
|
1332
|
+
{
|
|
1333
|
+
className: "caos-gauge-track",
|
|
1334
|
+
role: "meter",
|
|
1335
|
+
"aria-valuemin": min,
|
|
1336
|
+
"aria-valuemax": max,
|
|
1337
|
+
"aria-valuenow": finite ? value : void 0,
|
|
1338
|
+
"aria-label": pick(title, lang),
|
|
1339
|
+
children: [
|
|
1340
|
+
zones.map((z, i) => /* @__PURE__ */ jsx("span", { className: `caos-gauge-zone tone-${z.tone}`, style: { left: pos(z.from), width: `calc(${pos(z.to)} - ${pos(z.from)})` } }, i)),
|
|
1341
|
+
finite && /* @__PURE__ */ jsx("span", { className: "caos-gauge-needle", style: { left: pos(value) } })
|
|
1342
|
+
]
|
|
1343
|
+
}
|
|
1344
|
+
),
|
|
1345
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-gauge-scale", children: [
|
|
1346
|
+
/* @__PURE__ */ jsx("span", { children: fmt(min) }),
|
|
1347
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-gauge-value", children: [
|
|
1348
|
+
fmt(value),
|
|
1349
|
+
finite && unit ? ` ${pick(unit, lang)}` : ""
|
|
1350
|
+
] }),
|
|
1351
|
+
/* @__PURE__ */ jsx("span", { children: fmt(max) })
|
|
1352
|
+
] }),
|
|
1353
|
+
/* @__PURE__ */ jsx("div", { className: "caos-gauge-legend", children: zones.map((z, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
1354
|
+
/* @__PURE__ */ jsx("i", { className: `dot tone-${z.tone}`, "aria-hidden": "true" }),
|
|
1355
|
+
pick(z.label, lang)
|
|
1356
|
+
] }, i)) })
|
|
1357
|
+
] });
|
|
1358
|
+
}
|
|
1359
|
+
function Verdict(props) {
|
|
1360
|
+
const lang = useShellLang();
|
|
1361
|
+
let tone;
|
|
1362
|
+
let text;
|
|
1363
|
+
if ("code" in props) {
|
|
1364
|
+
const m = props.messages[props.code];
|
|
1365
|
+
if (!m) console.error(`[caos-app-shell] Verdict code "${props.code}" has no message`);
|
|
1366
|
+
tone = m?.tone ?? "neutral";
|
|
1367
|
+
text = m ? pick(m.text, lang) : lang === "es" ? "estado desconocido" : "unknown status";
|
|
1368
|
+
} else {
|
|
1369
|
+
tone = props.tone;
|
|
1370
|
+
text = pick(props.verdict, lang);
|
|
1371
|
+
}
|
|
1372
|
+
return /* @__PURE__ */ jsxs("div", { className: `caos-verdict tone-${tone}${props.compact ? " compact" : ""}`, "data-verdict": tone, children: [
|
|
1373
|
+
/* @__PURE__ */ jsx("p", { className: "caos-verdict-title", children: pick(props.title, lang) }),
|
|
1374
|
+
/* @__PURE__ */ jsx("p", { className: "caos-verdict-text", children: text }),
|
|
1375
|
+
props.children
|
|
1376
|
+
] });
|
|
1377
|
+
}
|
|
1378
|
+
function PlotCard({ title, lane, provenance, dataKey, actions, note, fill, children }) {
|
|
1379
|
+
const lang = useShellLang();
|
|
1380
|
+
const ws = useWorkbenchState();
|
|
1381
|
+
const stale = isStale(dataKey, ws);
|
|
1382
|
+
const name = pick(title, lang);
|
|
1383
|
+
if (!lane || !provenance) console.error(`[caos-app-shell] PlotCard "${name}" needs lane and provenance`);
|
|
1384
|
+
return /* @__PURE__ */ jsxs(
|
|
1385
|
+
"figure",
|
|
1386
|
+
{
|
|
1387
|
+
className: ["caos-plot", fill ? "fill" : "", stale ? "caos-stale" : ""].filter(Boolean).join(" "),
|
|
1388
|
+
"data-plot": name,
|
|
1389
|
+
"data-lane": lane,
|
|
1390
|
+
"data-provenance": provenance,
|
|
1391
|
+
"data-state-key": dataKey ?? "",
|
|
1392
|
+
"data-stale": stale ? "1" : "0",
|
|
1393
|
+
children: [
|
|
1394
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "caos-plot-head", children: [
|
|
1395
|
+
/* @__PURE__ */ jsx("span", { className: "caos-plot-title", children: name }),
|
|
1396
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-plot-actions", children: [
|
|
1397
|
+
actions,
|
|
1398
|
+
/* @__PURE__ */ jsx(LaneBadge, { lane, provenance })
|
|
1399
|
+
] })
|
|
1400
|
+
] }),
|
|
1401
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-plot-body", children: [
|
|
1402
|
+
/* @__PURE__ */ jsx(PanelBoundary, { panel: name, children }),
|
|
1403
|
+
stale && /* @__PURE__ */ jsx("div", { className: "caos-stale-overlay", role: "status", children: lang === "es" ? "Recalculando para la selecci\xF3n actual" : "Recomputing for the current selection" })
|
|
1404
|
+
] }),
|
|
1405
|
+
note && /* @__PURE__ */ jsx("p", { className: "caos-plot-note", children: pick(note, lang) })
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
function Knob({ id, label, value, min, max, step, unit, hint, format, disabled, onChange }) {
|
|
1411
|
+
const lang = useShellLang();
|
|
1412
|
+
const inputId = useId();
|
|
1413
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-knob", "data-control": id, title: pick(hint, lang) || void 0, children: [
|
|
1414
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: "caos-knob-head", children: [
|
|
1415
|
+
/* @__PURE__ */ jsx("span", { children: pick(label, lang) }),
|
|
1416
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-knob-value", children: [
|
|
1417
|
+
formatNumber(value, lang, format ?? {}),
|
|
1418
|
+
unit ? ` ${pick(unit, lang)}` : ""
|
|
1419
|
+
] })
|
|
1420
|
+
] }),
|
|
1421
|
+
/* @__PURE__ */ jsx(
|
|
1422
|
+
"input",
|
|
1423
|
+
{
|
|
1424
|
+
id: inputId,
|
|
1425
|
+
className: "range",
|
|
1426
|
+
type: "range",
|
|
1427
|
+
min,
|
|
1428
|
+
max,
|
|
1429
|
+
step,
|
|
1430
|
+
value,
|
|
1431
|
+
disabled,
|
|
1432
|
+
onChange: (e) => onChange(Number(e.target.value))
|
|
1433
|
+
}
|
|
1434
|
+
)
|
|
1435
|
+
] });
|
|
1436
|
+
}
|
|
1437
|
+
function ChipGroup({ id, label, options, value, onChange }) {
|
|
1438
|
+
const lang = useShellLang();
|
|
1439
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-chipgroup", "data-control": id, children: [
|
|
1440
|
+
/* @__PURE__ */ jsx("span", { className: "caos-chipgroup-label", children: pick(label, lang) }),
|
|
1441
|
+
/* @__PURE__ */ jsx("div", { className: "caos-chip-row", role: "radiogroup", "aria-label": pick(label, lang), children: options.map((o) => /* @__PURE__ */ jsx(
|
|
1442
|
+
"button",
|
|
1443
|
+
{
|
|
1444
|
+
type: "button",
|
|
1445
|
+
role: "radio",
|
|
1446
|
+
"aria-checked": o.id === value,
|
|
1447
|
+
className: o.id === value ? "chip on" : "chip",
|
|
1448
|
+
disabled: o.disabled,
|
|
1449
|
+
title: pick(o.hint, lang) || void 0,
|
|
1450
|
+
onClick: () => onChange(o.id),
|
|
1451
|
+
children: pick(o.label, lang)
|
|
1452
|
+
},
|
|
1453
|
+
o.id
|
|
1454
|
+
)) })
|
|
1455
|
+
] });
|
|
1456
|
+
}
|
|
933
1457
|
|
|
934
|
-
export { AppShell, ArchitectureModal, Callout, CaseSelector, CitationsProvider, Cite, Equation, Figure, FocusShell, InlineMath, LanguageToggle, ReferenceList, Refs, SubTabs, Tabs, ThemeToggle, WorkbenchShell,
|
|
1458
|
+
export { AppShell, ArchitectureModal, Callout, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DocPage, DocSection, Equation, Figure, FocusShell, Gauge, InlineMath, Knob, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Readout, ReferenceList, Refs, SubTabs, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, isStale, makeStateKey, readCaseParam, sourcesPresent, usePausedViz, useWorkbenchState, validateArchitectureConfig, withCaseParam };
|