@fasl-work/caos-app-shell 0.6.8 → 0.7.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/README.md +68 -32
- package/bin/caos-shell-gate.mjs +67 -0
- package/chart.css +11 -0
- package/dist/chart.d.ts +56 -0
- package/dist/chart.js +149 -0
- package/dist/chunk-OV4HK6EQ.js +172 -0
- package/dist/chunk-X6Y5CDUD.js +16 -0
- package/dist/index.d.ts +422 -127
- package/dist/index.js +730 -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-BCqyHYx2.d.ts +40 -0
- package/gate/inpage.mjs +454 -0
- package/gate/png.mjs +149 -0
- package/gate/run.mjs +594 -0
- package/gate/serve.mjs +97 -0
- package/package.json +41 -7
- package/reserved-classes.json +221 -0
- package/styles.css +267 -13
package/dist/index.js
CHANGED
|
@@ -1,40 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, formatNumber } from './chunk-OV4HK6EQ.js';
|
|
2
|
+
export { NOT_AVAILABLE, SHELL_TOKENS, Stage, applyTheme, formatNumber, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-OV4HK6EQ.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,102 @@ 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
400
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
350
|
-
/* @__PURE__ */ jsx("span", { className: "footer-build", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
401
|
+
/* @__PURE__ */ jsx("span", { className: "footer-build", "data-version": config.version, "data-build": config.build ?? "", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
351
402
|
c.version,
|
|
352
|
-
config.version
|
|
403
|
+
config.version,
|
|
404
|
+
config.build ? ` \xB7 ${config.build}` : ""
|
|
353
405
|
] }) }),
|
|
354
406
|
config.footer?.attribution !== false && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
407
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
@@ -359,10 +411,14 @@ function AppShell({ config, children }) {
|
|
|
359
411
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
360
412
|
/* @__PURE__ */ jsx("span", { children: config.footer.provenance[lang] })
|
|
361
413
|
] }),
|
|
362
|
-
/* @__PURE__ */
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
414
|
+
isPublic && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
415
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
416
|
+
/* @__PURE__ */ jsx("a", { href: config.links.github, target: "_blank", rel: "noreferrer noopener", children: c.github })
|
|
417
|
+
] }),
|
|
418
|
+
license && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
419
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
420
|
+
/* @__PURE__ */ jsx("span", { className: "faint", children: license })
|
|
421
|
+
] }),
|
|
366
422
|
config.footer?.disclaimer && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
367
423
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
368
424
|
/* @__PURE__ */ jsx("span", { className: "faint", children: config.footer.disclaimer[lang] })
|
|
@@ -419,6 +475,27 @@ function WorkbenchShell({
|
|
|
419
475
|
overlays
|
|
420
476
|
] });
|
|
421
477
|
}
|
|
478
|
+
function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel }) {
|
|
479
|
+
return /* @__PURE__ */ jsxs("main", { className: "caos-focus-shell", children: [
|
|
480
|
+
/* @__PURE__ */ jsxs("section", { className: "caos-focus-stage", "aria-label": stageLabel, children: [
|
|
481
|
+
/* @__PURE__ */ jsx("div", { className: "caos-focus-instrument", children: stage }),
|
|
482
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-focus-label", children: [
|
|
483
|
+
/* @__PURE__ */ jsx("strong", { children: title }),
|
|
484
|
+
/* @__PURE__ */ jsx("span", { children: description })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsx("div", { className: "caos-focus-hud", "aria-label": "HUD", children: hud.map((item) => /* @__PURE__ */ jsxs("div", { children: [
|
|
487
|
+
/* @__PURE__ */ jsx("strong", { children: item.value }),
|
|
488
|
+
/* @__PURE__ */ jsx("span", { children: item.label })
|
|
489
|
+
] }, item.label)) }),
|
|
490
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-focus-actions", children: [
|
|
491
|
+
/* @__PURE__ */ jsx(LanguageToggle, {}),
|
|
492
|
+
/* @__PURE__ */ jsx(ThemeToggle, {}),
|
|
493
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onExit, children: exitLabel })
|
|
494
|
+
] })
|
|
495
|
+
] }),
|
|
496
|
+
/* @__PURE__ */ jsx("aside", { className: "caos-focus-rail", children: rail })
|
|
497
|
+
] });
|
|
498
|
+
}
|
|
422
499
|
|
|
423
500
|
// src/lib/vizLoop.ts
|
|
424
501
|
function createVizLoop(frame, options, deps) {
|
|
@@ -601,7 +678,7 @@ var TEXT = {
|
|
|
601
678
|
synthetic: "Synthetic",
|
|
602
679
|
real: "Real",
|
|
603
680
|
uploaded: "Uploaded",
|
|
604
|
-
lockedNote: "Scenario knobs are locked on a real sample
|
|
681
|
+
lockedNote: "Scenario knobs are locked on a real sample: you pick which datum; every tool runs on it as-is.",
|
|
605
682
|
modifiedPrefix: "modified from",
|
|
606
683
|
reset: "reset"
|
|
607
684
|
},
|
|
@@ -636,18 +713,20 @@ function CaseSelector(props) {
|
|
|
636
713
|
const t = { ...TEXT[lang], ...text };
|
|
637
714
|
const baseId = useId();
|
|
638
715
|
const param = typeof deepLink === "string" ? deepLink : "case";
|
|
716
|
+
const linkPending = useRef(Boolean(deepLink));
|
|
639
717
|
useEffect(() => {
|
|
640
|
-
if (!
|
|
718
|
+
if (!linkPending.current || typeof window === "undefined" || cases.length === 0) return;
|
|
719
|
+
linkPending.current = false;
|
|
641
720
|
const fromUrl = readCaseParam(window.location.search, param);
|
|
642
721
|
if (fromUrl && fromUrl !== selectedId && cases.some((c) => c.id === fromUrl)) onSelect(fromUrl);
|
|
643
|
-
}, []);
|
|
722
|
+
}, [cases]);
|
|
644
723
|
useEffect(() => {
|
|
645
|
-
if (!deepLink || typeof window === "undefined") return;
|
|
724
|
+
if (!deepLink || linkPending.current || !selectedId || typeof window === "undefined") return;
|
|
646
725
|
const next = withCaseParam(window.location.search, selectedId, param);
|
|
647
726
|
if (next !== window.location.search) {
|
|
648
727
|
window.history.replaceState(null, "", `${window.location.pathname}${next}${window.location.hash}`);
|
|
649
728
|
}
|
|
650
|
-
}, [deepLink, param, selectedId]);
|
|
729
|
+
}, [deepLink, param, selectedId, cases]);
|
|
651
730
|
const lanes = source ? sourcesPresent(cases) : [];
|
|
652
731
|
const visible = casesInSource(cases, source);
|
|
653
732
|
const groups = groupByCategory(visible);
|
|
@@ -672,14 +751,15 @@ function CaseSelector(props) {
|
|
|
672
751
|
layout === "select" && /* @__PURE__ */ jsx(
|
|
673
752
|
"select",
|
|
674
753
|
{
|
|
754
|
+
"data-control": "case",
|
|
675
755
|
className: "select cs-select",
|
|
676
756
|
"aria-label": ariaLabel ?? "Case selector",
|
|
677
757
|
value: selectedId,
|
|
678
758
|
onChange: (e) => onSelect(e.target.value),
|
|
679
|
-
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:
|
|
759
|
+
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 || "_"))
|
|
680
760
|
}
|
|
681
761
|
),
|
|
682
|
-
layout === "chips" && groups.map((g) => /* @__PURE__ */ jsxs("div", { className: "cs-group", role: "group", "aria-label": g.category || void 0, children: [
|
|
762
|
+
layout === "chips" && groups.map((g) => /* @__PURE__ */ jsxs("div", { className: "cs-group", role: "group", "aria-label": g.category || void 0, "data-control": "case", children: [
|
|
683
763
|
g.category && /* @__PURE__ */ jsx("span", { className: "cs-group-label", children: g.category }),
|
|
684
764
|
/* @__PURE__ */ jsx("div", { className: "cs-chips", children: g.cases.map((c) => {
|
|
685
765
|
const on = c.id === selectedId;
|
|
@@ -694,8 +774,8 @@ function CaseSelector(props) {
|
|
|
694
774
|
disabled: c.disabled,
|
|
695
775
|
title: tip || void 0,
|
|
696
776
|
onClick: () => onSelect(c.id),
|
|
777
|
+
"data-case": c.id,
|
|
697
778
|
children: [
|
|
698
|
-
/* @__PURE__ */ jsx("span", { className: "cs-chip-id", children: c.id }),
|
|
699
779
|
/* @__PURE__ */ jsx("span", { className: "cs-chip-name", children: c.name }),
|
|
700
780
|
/* @__PURE__ */ jsx("span", { className: `cs-kind cs-kind-${caseKindOf(c)}`, "aria-hidden": "true", children: KIND_TAG[caseKindOf(c)] })
|
|
701
781
|
]
|
|
@@ -708,7 +788,7 @@ function CaseSelector(props) {
|
|
|
708
788
|
/* @__PURE__ */ jsxs("span", { className: "badge accent", children: [
|
|
709
789
|
t.modifiedPrefix,
|
|
710
790
|
" ",
|
|
711
|
-
/* @__PURE__ */ jsx("strong", { children: modified.
|
|
791
|
+
/* @__PURE__ */ jsx("strong", { children: modified.name })
|
|
712
792
|
] }),
|
|
713
793
|
onResetToCanonical && /* @__PURE__ */ jsx("button", { type: "button", className: "cs-reset", onClick: onResetToCanonical, children: t.reset })
|
|
714
794
|
] })
|
|
@@ -717,10 +797,21 @@ function CaseSelector(props) {
|
|
|
717
797
|
function caseKindOf(c) {
|
|
718
798
|
return c.kind ?? "synthetic";
|
|
719
799
|
}
|
|
720
|
-
|
|
800
|
+
var MAX_PEER_TABS = 6;
|
|
801
|
+
function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
721
802
|
const baseId = useId();
|
|
722
803
|
const first = tabs[0]?.id ?? "";
|
|
723
|
-
const [
|
|
804
|
+
const [own, setOwn] = useState(initial ?? first);
|
|
805
|
+
const active = value ?? own;
|
|
806
|
+
const rowRef = useRef(null);
|
|
807
|
+
useOverflowFade(rowRef, ".tab.active", [active, tabs.length]);
|
|
808
|
+
if (tabs.length > MAX_PEER_TABS) {
|
|
809
|
+
console.error(`[caos-app-shell] ${tabs.length} peer tabs (${ariaLabel ?? "unnamed"}); at most ${MAX_PEER_TABS}, then group (ADR-0071 rule 5)`);
|
|
810
|
+
}
|
|
811
|
+
function select(id) {
|
|
812
|
+
if (value === void 0) setOwn(id);
|
|
813
|
+
onChange?.(id);
|
|
814
|
+
}
|
|
724
815
|
function onKeyDown(e, idx) {
|
|
725
816
|
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft" && e.key !== "Home" && e.key !== "End") return;
|
|
726
817
|
e.preventDefault();
|
|
@@ -731,13 +822,14 @@ function Tabs({ tabs, initial, ariaLabel }) {
|
|
|
731
822
|
else if (e.key === "End") next = tabs.length - 1;
|
|
732
823
|
const target = tabs[next];
|
|
733
824
|
if (target) {
|
|
734
|
-
|
|
825
|
+
select(target.id);
|
|
735
826
|
document.getElementById(`${baseId}-tab-${target.id}`)?.focus();
|
|
736
827
|
}
|
|
737
828
|
}
|
|
829
|
+
const current = tabs.find((t) => t.id === active) ?? tabs[0];
|
|
738
830
|
return /* @__PURE__ */ jsxs("div", { className: "tabs", children: [
|
|
739
|
-
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, children: tabs.map((tab, idx) => {
|
|
740
|
-
const selected = tab.id ===
|
|
831
|
+
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, ref: rowRef, children: tabs.map((tab, idx) => {
|
|
832
|
+
const selected = tab.id === current?.id;
|
|
741
833
|
return /* @__PURE__ */ jsx(
|
|
742
834
|
"button",
|
|
743
835
|
{
|
|
@@ -748,38 +840,44 @@ function Tabs({ tabs, initial, ariaLabel }) {
|
|
|
748
840
|
"aria-controls": `${baseId}-panel-${tab.id}`,
|
|
749
841
|
tabIndex: selected ? 0 : -1,
|
|
750
842
|
className: selected ? "tab active" : "tab",
|
|
751
|
-
|
|
843
|
+
"data-tab": tab.id,
|
|
844
|
+
onClick: () => select(tab.id),
|
|
752
845
|
onKeyDown: (e) => onKeyDown(e, idx),
|
|
753
846
|
children: tab.label
|
|
754
847
|
},
|
|
755
848
|
tab.id
|
|
756
849
|
);
|
|
757
850
|
}) }),
|
|
758
|
-
|
|
851
|
+
current && /* @__PURE__ */ jsx(
|
|
759
852
|
"div",
|
|
760
853
|
{
|
|
761
|
-
id: `${baseId}-panel-${
|
|
854
|
+
id: `${baseId}-panel-${current.id}`,
|
|
762
855
|
role: "tabpanel",
|
|
763
|
-
"aria-labelledby": `${baseId}-tab-${
|
|
764
|
-
hidden: tab.id !== active,
|
|
856
|
+
"aria-labelledby": `${baseId}-tab-${current.id}`,
|
|
765
857
|
tabIndex: 0,
|
|
766
858
|
className: "tabpanel",
|
|
767
|
-
|
|
859
|
+
"data-panel": current.id,
|
|
860
|
+
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: current.content })
|
|
768
861
|
},
|
|
769
|
-
|
|
770
|
-
)
|
|
862
|
+
current.id
|
|
863
|
+
)
|
|
771
864
|
] });
|
|
772
865
|
}
|
|
773
|
-
function SubTabs({
|
|
774
|
-
tabs,
|
|
775
|
-
initial,
|
|
776
|
-
ariaLabel,
|
|
777
|
-
orientation = "horizontal"
|
|
778
|
-
}) {
|
|
866
|
+
function SubTabs({ tabs, initial, value, onChange, ariaLabel, orientation = "horizontal" }) {
|
|
779
867
|
const baseId = useId();
|
|
780
868
|
const first = tabs[0]?.id ?? "";
|
|
781
|
-
const [
|
|
869
|
+
const [own, setOwn] = useState(initial ?? first);
|
|
870
|
+
const active = value ?? own;
|
|
782
871
|
const vertical = orientation === "vertical";
|
|
872
|
+
const rowRef = useRef(null);
|
|
873
|
+
useOverflowFade(rowRef, ".subtab.active", [active, tabs.length, vertical]);
|
|
874
|
+
if (tabs.length > MAX_PEER_TABS) {
|
|
875
|
+
console.error(`[caos-app-shell] ${tabs.length} peer sub-tabs (${ariaLabel ?? "unnamed"}); at most ${MAX_PEER_TABS}, then group (ADR-0071 rule 5)`);
|
|
876
|
+
}
|
|
877
|
+
function select(id) {
|
|
878
|
+
if (value === void 0) setOwn(id);
|
|
879
|
+
onChange?.(id);
|
|
880
|
+
}
|
|
783
881
|
function onKeyDown(e, idx) {
|
|
784
882
|
const fwd = vertical ? "ArrowDown" : "ArrowRight";
|
|
785
883
|
const back = vertical ? "ArrowUp" : "ArrowLeft";
|
|
@@ -792,45 +890,90 @@ function SubTabs({
|
|
|
792
890
|
else if (e.key === "End") next = tabs.length - 1;
|
|
793
891
|
const target = tabs[next];
|
|
794
892
|
if (target) {
|
|
795
|
-
|
|
893
|
+
select(target.id);
|
|
796
894
|
document.getElementById(`${baseId}-subtab-${target.id}`)?.focus();
|
|
797
895
|
}
|
|
798
896
|
}
|
|
897
|
+
const current = tabs.find((t) => t.id === active) ?? tabs[0];
|
|
799
898
|
return /* @__PURE__ */ jsxs("div", { className: vertical ? "subtabs subtabs-vertical" : "subtabs", children: [
|
|
800
|
-
/* @__PURE__ */ jsx(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
"
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
899
|
+
/* @__PURE__ */ jsx(
|
|
900
|
+
"div",
|
|
901
|
+
{
|
|
902
|
+
className: "subtablist",
|
|
903
|
+
role: "tablist",
|
|
904
|
+
"aria-label": ariaLabel,
|
|
905
|
+
"aria-orientation": vertical ? "vertical" : "horizontal",
|
|
906
|
+
ref: rowRef,
|
|
907
|
+
children: tabs.map((tab, idx) => {
|
|
908
|
+
const selected = tab.id === current?.id;
|
|
909
|
+
return /* @__PURE__ */ jsx(
|
|
910
|
+
"button",
|
|
911
|
+
{
|
|
912
|
+
id: `${baseId}-subtab-${tab.id}`,
|
|
913
|
+
role: "tab",
|
|
914
|
+
type: "button",
|
|
915
|
+
"aria-selected": selected,
|
|
916
|
+
"aria-controls": `${baseId}-subpanel-${tab.id}`,
|
|
917
|
+
tabIndex: selected ? 0 : -1,
|
|
918
|
+
className: selected ? "subtab active" : "subtab",
|
|
919
|
+
"data-tab": tab.id,
|
|
920
|
+
onClick: () => select(tab.id),
|
|
921
|
+
onKeyDown: (e) => onKeyDown(e, idx),
|
|
922
|
+
children: tab.label
|
|
923
|
+
},
|
|
924
|
+
tab.id
|
|
925
|
+
);
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
),
|
|
929
|
+
/* @__PURE__ */ jsx("div", { className: "subtabpanels", children: current && /* @__PURE__ */ jsx(
|
|
820
930
|
"div",
|
|
821
931
|
{
|
|
822
|
-
id: `${baseId}-subpanel-${
|
|
932
|
+
id: `${baseId}-subpanel-${current.id}`,
|
|
823
933
|
role: "tabpanel",
|
|
824
|
-
"aria-labelledby": `${baseId}-subtab-${
|
|
825
|
-
hidden: tab.id !== active,
|
|
934
|
+
"aria-labelledby": `${baseId}-subtab-${current.id}`,
|
|
826
935
|
tabIndex: 0,
|
|
827
936
|
className: "subtabpanel",
|
|
828
|
-
|
|
937
|
+
"data-panel": current.id,
|
|
938
|
+
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: current.content })
|
|
829
939
|
},
|
|
830
|
-
|
|
831
|
-
)
|
|
940
|
+
current.id
|
|
941
|
+
) })
|
|
832
942
|
] });
|
|
833
943
|
}
|
|
944
|
+
function TabGroups({ groups, value, onChange, ariaLabel }) {
|
|
945
|
+
const [own, setOwn] = useState({ group: groups[0]?.id ?? "" });
|
|
946
|
+
const sel = value ?? own;
|
|
947
|
+
const group = groups.find((g) => g.id === sel.group) ?? groups[0];
|
|
948
|
+
const tabOf = (g, wanted) => g.tabs.find((t) => t.id === wanted)?.id ?? g.tabs[0]?.id ?? "";
|
|
949
|
+
function set(next) {
|
|
950
|
+
if (value === void 0) setOwn(next);
|
|
951
|
+
onChange?.(next);
|
|
952
|
+
}
|
|
953
|
+
return /* @__PURE__ */ jsx(
|
|
954
|
+
Tabs,
|
|
955
|
+
{
|
|
956
|
+
ariaLabel,
|
|
957
|
+
value: group?.id,
|
|
958
|
+
onChange: (gid) => {
|
|
959
|
+
const g = groups.find((x) => x.id === gid);
|
|
960
|
+
if (g) set({ group: g.id, tab: tabOf(g) });
|
|
961
|
+
},
|
|
962
|
+
tabs: groups.map((g) => ({
|
|
963
|
+
id: g.id,
|
|
964
|
+
label: g.label,
|
|
965
|
+
content: g.tabs.length === 1 ? g.tabs[0].content : /* @__PURE__ */ jsx(
|
|
966
|
+
SubTabs,
|
|
967
|
+
{
|
|
968
|
+
tabs: g.tabs,
|
|
969
|
+
value: tabOf(g, g.id === group?.id ? sel.tab : void 0),
|
|
970
|
+
onChange: (tid) => set({ group: g.id, tab: tid })
|
|
971
|
+
}
|
|
972
|
+
)
|
|
973
|
+
}))
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
}
|
|
834
977
|
function Callout({
|
|
835
978
|
variant = "note",
|
|
836
979
|
title,
|
|
@@ -843,6 +986,9 @@ function Callout({
|
|
|
843
986
|
] });
|
|
844
987
|
}
|
|
845
988
|
function Equation({ tex, caption }) {
|
|
989
|
+
if (caption === void 0 || caption === null || caption === "") {
|
|
990
|
+
console.error(`[caos-app-shell] Equation without a caption (ADR-0017 s2): ${tex.slice(0, 60)}`);
|
|
991
|
+
}
|
|
846
992
|
const html = katex.renderToString(tex, { displayMode: true, throwOnError: false });
|
|
847
993
|
return /* @__PURE__ */ jsxs("div", { className: "equation", children: [
|
|
848
994
|
/* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }),
|
|
@@ -862,7 +1008,11 @@ function Figure({ caption, children }) {
|
|
|
862
1008
|
var CitationsContext = createContext({ list: [], byId: {} });
|
|
863
1009
|
function CitationsProvider({ items, children }) {
|
|
864
1010
|
const byId = {};
|
|
865
|
-
for (const c of items)
|
|
1011
|
+
for (const c of items) {
|
|
1012
|
+
if (byId[c.id]) console.error(`[caos-app-shell] duplicate citation id ${c.id}`);
|
|
1013
|
+
if (!c.doi && !c.url) console.error(`[caos-app-shell] citation ${c.id} has no doi or url (ADR-0017 s4)`);
|
|
1014
|
+
byId[c.id] = c;
|
|
1015
|
+
}
|
|
866
1016
|
return /* @__PURE__ */ jsx(CitationsContext.Provider, { value: { list: items, byId }, children });
|
|
867
1017
|
}
|
|
868
1018
|
function href(c) {
|
|
@@ -870,14 +1020,19 @@ function href(c) {
|
|
|
870
1020
|
}
|
|
871
1021
|
function Cite({ id, paren = true }) {
|
|
872
1022
|
const { byId } = useContext(CitationsContext);
|
|
1023
|
+
const lang = useShellLang();
|
|
873
1024
|
const c = byId[id];
|
|
874
|
-
if (!c)
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1025
|
+
if (!c) {
|
|
1026
|
+
console.error(`[caos-app-shell] Cite of an unknown citation id ${id}`);
|
|
1027
|
+
return /* @__PURE__ */ jsxs("cite", { className: "cite-inline", children: [
|
|
1028
|
+
"[",
|
|
1029
|
+
id,
|
|
1030
|
+
"]"
|
|
1031
|
+
] });
|
|
1032
|
+
}
|
|
879
1033
|
const h = href(c);
|
|
880
|
-
const
|
|
1034
|
+
const text = pick(c.label, lang);
|
|
1035
|
+
const label = h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", children: text }) : /* @__PURE__ */ jsx("span", { children: text });
|
|
881
1036
|
return /* @__PURE__ */ jsxs("cite", { className: "cite-inline", children: [
|
|
882
1037
|
paren ? "(" : null,
|
|
883
1038
|
label,
|
|
@@ -895,6 +1050,7 @@ function Refs({ ids, label }) {
|
|
|
895
1050
|
] });
|
|
896
1051
|
}
|
|
897
1052
|
function ReferenceList({ ids, heading }) {
|
|
1053
|
+
console.error("[caos-app-shell] ReferenceList is banned (ADR-0017 s4): end each section with Refs");
|
|
898
1054
|
const { list, byId } = useContext(CitationsContext);
|
|
899
1055
|
const items = ids ? ids.map((k) => byId[k]).filter(Boolean) : list;
|
|
900
1056
|
return /* @__PURE__ */ jsxs("section", { className: "references", "aria-label": heading ?? "References", children: [
|
|
@@ -909,5 +1065,395 @@ function ReferenceList({ ids, heading }) {
|
|
|
909
1065
|
}) })
|
|
910
1066
|
] });
|
|
911
1067
|
}
|
|
1068
|
+
function DocPage({ title, lede, wide, children }) {
|
|
1069
|
+
const lang = useShellLang();
|
|
1070
|
+
return /* @__PURE__ */ jsxs("div", { className: wide ? "page-body wide prose caos-doc" : "page-body prose caos-doc", "data-doc-page": "", children: [
|
|
1071
|
+
/* @__PURE__ */ jsxs("div", { className: "page-head", children: [
|
|
1072
|
+
/* @__PURE__ */ jsx("h1", { children: pick(title, lang) }),
|
|
1073
|
+
/* @__PURE__ */ jsx("p", { className: "lede", children: lede })
|
|
1074
|
+
] }),
|
|
1075
|
+
children
|
|
1076
|
+
] });
|
|
1077
|
+
}
|
|
1078
|
+
function DocSection({ title, id, refs, noRefsReason, children }) {
|
|
1079
|
+
const lang = useShellLang();
|
|
1080
|
+
const hasRefs = Boolean(refs && refs.length > 0);
|
|
1081
|
+
return /* @__PURE__ */ jsxs(
|
|
1082
|
+
"section",
|
|
1083
|
+
{
|
|
1084
|
+
id,
|
|
1085
|
+
className: "caos-doc-section",
|
|
1086
|
+
"data-doc-section": "",
|
|
1087
|
+
"data-no-refs-reason": hasRefs ? void 0 : pick(noRefsReason, lang) || "missing",
|
|
1088
|
+
children: [
|
|
1089
|
+
/* @__PURE__ */ jsx("h2", { children: pick(title, lang) }),
|
|
1090
|
+
children,
|
|
1091
|
+
hasRefs && /* @__PURE__ */ jsx(Refs, { ids: refs, label: lang === "es" ? "Referencias" : "References" })
|
|
1092
|
+
]
|
|
1093
|
+
}
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
function isSections(rail) {
|
|
1097
|
+
return Array.isArray(rail) && rail.every((s) => typeof s === "object" && s !== null && "id" in s && "content" in s);
|
|
1098
|
+
}
|
|
1099
|
+
function WorkbenchLayout({ rail, railHead, children, railLabel, className }) {
|
|
1100
|
+
const lang = useShellLang();
|
|
1101
|
+
const sections = isSections(rail) ? rail : null;
|
|
1102
|
+
const [active, setActive] = useState(sections?.[0]?.id ?? "");
|
|
1103
|
+
const baseId = useId();
|
|
1104
|
+
const label = pick(railLabel ?? { en: "Controls", es: "Controles" }, lang);
|
|
1105
|
+
const current = sections?.find((s) => s.id === active) ?? sections?.[0];
|
|
1106
|
+
return /* @__PURE__ */ jsxs("div", { className: ["page-body", "wide", "caos-wb", className].filter(Boolean).join(" "), "data-workbench": "", children: [
|
|
1107
|
+
/* @__PURE__ */ jsxs("aside", { className: "caos-wb-rail", "aria-label": label, "data-rail": "", children: [
|
|
1108
|
+
railHead && /* @__PURE__ */ jsx("div", { className: "caos-wb-rail-head", children: railHead }),
|
|
1109
|
+
sections ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1110
|
+
/* @__PURE__ */ jsx("div", { className: "caos-wb-rail-sections", role: "tablist", "aria-label": label, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
1111
|
+
"button",
|
|
1112
|
+
{
|
|
1113
|
+
type: "button",
|
|
1114
|
+
role: "tab",
|
|
1115
|
+
id: `${baseId}-rs-${s.id}`,
|
|
1116
|
+
"aria-selected": s.id === current?.id,
|
|
1117
|
+
"aria-controls": `${baseId}-rp-${s.id}`,
|
|
1118
|
+
className: s.id === current?.id ? "chip on" : "chip",
|
|
1119
|
+
onClick: () => setActive(s.id),
|
|
1120
|
+
children: pick(s.label, lang)
|
|
1121
|
+
},
|
|
1122
|
+
s.id
|
|
1123
|
+
)) }),
|
|
1124
|
+
current && /* @__PURE__ */ jsx(
|
|
1125
|
+
"div",
|
|
1126
|
+
{
|
|
1127
|
+
className: "caos-wb-rail-panel",
|
|
1128
|
+
role: "tabpanel",
|
|
1129
|
+
id: `${baseId}-rp-${current.id}`,
|
|
1130
|
+
"aria-labelledby": `${baseId}-rs-${current.id}`,
|
|
1131
|
+
children: current.content
|
|
1132
|
+
}
|
|
1133
|
+
)
|
|
1134
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "caos-wb-rail-panel", children: rail })
|
|
1135
|
+
] }),
|
|
1136
|
+
/* @__PURE__ */ jsx("section", { className: "caos-wb-main", "data-instrument": "", children })
|
|
1137
|
+
] });
|
|
1138
|
+
}
|
|
1139
|
+
var WorkbenchStateContext = createContext(null);
|
|
1140
|
+
function useWorkbenchState() {
|
|
1141
|
+
return useContext(WorkbenchStateContext);
|
|
1142
|
+
}
|
|
1143
|
+
function stable(value) {
|
|
1144
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
1145
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
1146
|
+
const obj = value;
|
|
1147
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stable(obj[k])}`).join(",")}}`;
|
|
1148
|
+
}
|
|
1149
|
+
function makeStateKey(parts) {
|
|
1150
|
+
const text = stable(parts);
|
|
1151
|
+
let h = 2166136261;
|
|
1152
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1153
|
+
h ^= text.charCodeAt(i);
|
|
1154
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
1155
|
+
}
|
|
1156
|
+
return h.toString(16).padStart(8, "0");
|
|
1157
|
+
}
|
|
1158
|
+
function isStale(dataKey, current) {
|
|
1159
|
+
return Boolean(current && dataKey !== void 0 && dataKey !== current.stateKey);
|
|
1160
|
+
}
|
|
1161
|
+
var LANE_LABELS = {
|
|
1162
|
+
live: { en: "Live in your browser", es: "En vivo en tu navegador" },
|
|
1163
|
+
replay: { en: "Replay of the offline result", es: "Reproduce el resultado offline" },
|
|
1164
|
+
offline: { en: "Offline only", es: "Solo offline" }
|
|
1165
|
+
};
|
|
1166
|
+
var PROVENANCE_LABELS = {
|
|
1167
|
+
real: { en: "Real data", es: "Datos reales" },
|
|
1168
|
+
synthetic: { en: "Synthetic data", es: "Datos sint\xE9ticos" },
|
|
1169
|
+
published: { en: "Published reference", es: "Referencia publicada" }
|
|
1170
|
+
};
|
|
1171
|
+
function LaneBadge({ lane, provenance, label }) {
|
|
1172
|
+
const lang = useShellLang();
|
|
1173
|
+
return /* @__PURE__ */ jsxs("span", { className: "caos-badges", children: [
|
|
1174
|
+
/* @__PURE__ */ jsx("span", { className: `badge caos-lane caos-lane-${lane}`, "data-lane": lane, children: pick(label ?? LANE_LABELS[lane], lang) }),
|
|
1175
|
+
provenance && /* @__PURE__ */ jsx("span", { className: `badge caos-prov caos-prov-${provenance}`, "data-provenance": provenance, children: pick(PROVENANCE_LABELS[provenance], lang) })
|
|
1176
|
+
] });
|
|
1177
|
+
}
|
|
1178
|
+
function VariantBar({ variants, activeId, onSelect, title, lane }) {
|
|
1179
|
+
const lang = useShellLang();
|
|
1180
|
+
const active = variants.find((v) => v.id === activeId) ?? variants[0];
|
|
1181
|
+
const shownLane = active?.lane ?? lane;
|
|
1182
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-variant-bar", "data-variant-bar": "", "data-variant-count": variants.length, children: [
|
|
1183
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-variant-head", children: [
|
|
1184
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-variant-title", children: [
|
|
1185
|
+
pick(title ?? { en: "Variants", es: "Variantes" }, lang),
|
|
1186
|
+
" (",
|
|
1187
|
+
variants.length,
|
|
1188
|
+
")"
|
|
1189
|
+
] }),
|
|
1190
|
+
shownLane && /* @__PURE__ */ jsx(LaneBadge, { lane: shownLane })
|
|
1191
|
+
] }),
|
|
1192
|
+
/* @__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(
|
|
1193
|
+
"button",
|
|
1194
|
+
{
|
|
1195
|
+
type: "button",
|
|
1196
|
+
role: "radio",
|
|
1197
|
+
"aria-checked": v.id === active?.id,
|
|
1198
|
+
disabled: v.disabled,
|
|
1199
|
+
className: v.id === active?.id ? "chip on" : "chip",
|
|
1200
|
+
onClick: () => onSelect(v.id),
|
|
1201
|
+
children: pick(v.label, lang)
|
|
1202
|
+
},
|
|
1203
|
+
v.id
|
|
1204
|
+
)) }),
|
|
1205
|
+
active?.note && /* @__PURE__ */ jsx("p", { className: "caos-variant-note", children: pick(active.note, lang) })
|
|
1206
|
+
] });
|
|
1207
|
+
}
|
|
1208
|
+
var MAX_WORKBENCH_GROUPS = MAX_PEER_TABS;
|
|
1209
|
+
function panel(id, lane, provenance, content) {
|
|
1210
|
+
return /* @__PURE__ */ jsx("div", { className: "caos-cw-panel", "data-group": id, "data-lane": lane, "data-provenance": provenance, children: content });
|
|
1211
|
+
}
|
|
1212
|
+
function CaseWorkbench(props) {
|
|
1213
|
+
const { caseId, cases, source, controls, replayOnly, variants, rail, railLabel, groups, compare, context, group, onGroupChange, ariaLabel } = props;
|
|
1214
|
+
const lang = useShellLang();
|
|
1215
|
+
const variantId = variants?.activeId;
|
|
1216
|
+
const stateKey = useMemo(
|
|
1217
|
+
() => makeStateKey({ caseId, variantId: variantId ?? null, source: source ?? null, controls: controls ?? {} }),
|
|
1218
|
+
[caseId, variantId, source, controls]
|
|
1219
|
+
);
|
|
1220
|
+
const tabs = groups.map((g) => ({ id: g.id, label: pick(g.label, lang), content: panel(g.id, g.lane, g.provenance, g.content) }));
|
|
1221
|
+
if (compare) {
|
|
1222
|
+
tabs.push({
|
|
1223
|
+
id: "compare",
|
|
1224
|
+
label: pick(compare.label ?? { en: "Compare variants", es: "Comparar variantes" }, lang),
|
|
1225
|
+
content: panel("compare", compare.lane, compare.provenance, compare.content)
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
tabs.push({
|
|
1229
|
+
id: "context",
|
|
1230
|
+
label: pick(context.label ?? { en: "Context", es: "Contexto" }, lang),
|
|
1231
|
+
content: /* @__PURE__ */ jsx("div", { className: "prose caos-cw-context", children: context.content })
|
|
1232
|
+
});
|
|
1233
|
+
if (tabs.length > MAX_WORKBENCH_GROUPS) {
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`CaseWorkbench: ${tabs.length} groups; at most ${MAX_WORKBENCH_GROUPS} peers, then group by question (ADR-0071 rule 5)`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const railHead = cases || variants ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1239
|
+
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases }),
|
|
1240
|
+
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
1241
|
+
] }) : null;
|
|
1242
|
+
return /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(WorkbenchLayout, { rail: rail ?? null, railHead, railLabel, children: /* @__PURE__ */ jsx(
|
|
1243
|
+
"div",
|
|
1244
|
+
{
|
|
1245
|
+
className: "caos-cw",
|
|
1246
|
+
"data-case-workbench": "",
|
|
1247
|
+
"data-group-count": tabs.length,
|
|
1248
|
+
"data-case": caseId,
|
|
1249
|
+
"data-variant": variantId ?? "",
|
|
1250
|
+
"data-source": source ?? "",
|
|
1251
|
+
"data-state-key": stateKey,
|
|
1252
|
+
"data-replay-only": replayOnly ? "1" : void 0,
|
|
1253
|
+
children: /* @__PURE__ */ jsx(
|
|
1254
|
+
Tabs,
|
|
1255
|
+
{
|
|
1256
|
+
tabs,
|
|
1257
|
+
value: group,
|
|
1258
|
+
onChange: onGroupChange,
|
|
1259
|
+
ariaLabel: pick(ariaLabel ?? { en: "Case views", es: "Vistas del caso" }, lang)
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
}
|
|
1263
|
+
) }) });
|
|
1264
|
+
}
|
|
1265
|
+
function toneOf(it) {
|
|
1266
|
+
if (it.tone) return it.tone;
|
|
1267
|
+
if (!it.better || it.value === null || it.value === void 0 || !Number.isFinite(it.value)) return "neutral";
|
|
1268
|
+
const v = it.value;
|
|
1269
|
+
const higher = it.better === "higher";
|
|
1270
|
+
if (it.bad !== void 0 && (higher ? v <= it.bad : v >= it.bad)) return "bad";
|
|
1271
|
+
if (it.good !== void 0 && (higher ? v >= it.good : v <= it.good)) return "good";
|
|
1272
|
+
if (it.good !== void 0 || it.bad !== void 0) return "warn";
|
|
1273
|
+
return "neutral";
|
|
1274
|
+
}
|
|
1275
|
+
function checkItem(it, where) {
|
|
1276
|
+
if (!it.unit && !it.unitless && it.text === void 0) {
|
|
1277
|
+
console.error(`[caos-app-shell] readout "${where}" has no unit; give a unit or set unitless`);
|
|
1278
|
+
}
|
|
1279
|
+
if (typeof it.value === "number" && Number.isNaN(it.value)) {
|
|
1280
|
+
console.error(`[caos-app-shell] readout "${where}" received NaN; pass null when the value is not available`);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function Readout({ items, title, lane, provenance, dataKey }) {
|
|
1284
|
+
const lang = useShellLang();
|
|
1285
|
+
const ws = useWorkbenchState();
|
|
1286
|
+
const stale = isStale(dataKey, ws);
|
|
1287
|
+
if (!lane || !provenance) console.error("[caos-app-shell] Readout needs lane and provenance");
|
|
1288
|
+
return /* @__PURE__ */ jsxs(
|
|
1289
|
+
"div",
|
|
1290
|
+
{
|
|
1291
|
+
className: stale ? "caos-readout caos-stale" : "caos-readout",
|
|
1292
|
+
"data-readout": "",
|
|
1293
|
+
"data-lane": lane,
|
|
1294
|
+
"data-provenance": provenance,
|
|
1295
|
+
"data-state-key": dataKey ?? "",
|
|
1296
|
+
"data-stale": stale ? "1" : "0",
|
|
1297
|
+
children: [
|
|
1298
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-readout-head", children: [
|
|
1299
|
+
title && /* @__PURE__ */ jsx("p", { className: "caos-readout-title", children: pick(title, lang) }),
|
|
1300
|
+
/* @__PURE__ */ jsx(LaneBadge, { lane, provenance })
|
|
1301
|
+
] }),
|
|
1302
|
+
/* @__PURE__ */ jsx("dl", { children: items.map((it, i) => {
|
|
1303
|
+
const label = pick(it.label, lang);
|
|
1304
|
+
checkItem(it, label);
|
|
1305
|
+
const shown = it.text !== void 0 ? pick(it.text, lang) : formatNumber(it.value, lang, it.format ?? {});
|
|
1306
|
+
return /* @__PURE__ */ jsxs("div", { className: `caos-readout-row tone-${toneOf(it)}`, title: pick(it.hint, lang) || void 0, children: [
|
|
1307
|
+
/* @__PURE__ */ jsx("dt", { children: label }),
|
|
1308
|
+
/* @__PURE__ */ jsxs("dd", { children: [
|
|
1309
|
+
/* @__PURE__ */ jsx("span", { className: "caos-readout-value", children: shown }),
|
|
1310
|
+
it.unit && it.text === void 0 && /* @__PURE__ */ jsxs("span", { className: "caos-readout-unit", children: [
|
|
1311
|
+
" ",
|
|
1312
|
+
pick(it.unit, lang)
|
|
1313
|
+
] })
|
|
1314
|
+
] })
|
|
1315
|
+
] }, i);
|
|
1316
|
+
}) }),
|
|
1317
|
+
stale && /* @__PURE__ */ jsx("p", { className: "caos-stale-note", children: lang === "es" ? "Recalculando para la selecci\xF3n actual" : "Recomputing for the current selection" })
|
|
1318
|
+
]
|
|
1319
|
+
}
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
function Gauge({ title, value, min, max, zones, unit, unitless, format }) {
|
|
1323
|
+
const lang = useShellLang();
|
|
1324
|
+
checkItem({ value, unit, unitless }, pick(title, lang));
|
|
1325
|
+
const span = max - min || 1;
|
|
1326
|
+
const finite = value !== null && value !== void 0 && Number.isFinite(value);
|
|
1327
|
+
const pos = (v) => `${Math.min(100, Math.max(0, (v - min) / span * 100))}%`;
|
|
1328
|
+
const fmt = (v) => formatNumber(v, lang, format ?? {});
|
|
1329
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-gauge", "data-gauge": "", children: [
|
|
1330
|
+
/* @__PURE__ */ jsx("p", { className: "caos-gauge-title", children: pick(title, lang) }),
|
|
1331
|
+
/* @__PURE__ */ jsxs(
|
|
1332
|
+
"div",
|
|
1333
|
+
{
|
|
1334
|
+
className: "caos-gauge-track",
|
|
1335
|
+
role: "meter",
|
|
1336
|
+
"aria-valuemin": min,
|
|
1337
|
+
"aria-valuemax": max,
|
|
1338
|
+
"aria-valuenow": finite ? value : void 0,
|
|
1339
|
+
"aria-label": pick(title, lang),
|
|
1340
|
+
children: [
|
|
1341
|
+
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)),
|
|
1342
|
+
finite && /* @__PURE__ */ jsx("span", { className: "caos-gauge-needle", style: { left: pos(value) } })
|
|
1343
|
+
]
|
|
1344
|
+
}
|
|
1345
|
+
),
|
|
1346
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-gauge-scale", children: [
|
|
1347
|
+
/* @__PURE__ */ jsx("span", { children: fmt(min) }),
|
|
1348
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-gauge-value", children: [
|
|
1349
|
+
fmt(value),
|
|
1350
|
+
finite && unit ? ` ${pick(unit, lang)}` : ""
|
|
1351
|
+
] }),
|
|
1352
|
+
/* @__PURE__ */ jsx("span", { children: fmt(max) })
|
|
1353
|
+
] }),
|
|
1354
|
+
/* @__PURE__ */ jsx("div", { className: "caos-gauge-legend", children: zones.map((z, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
1355
|
+
/* @__PURE__ */ jsx("i", { className: `dot tone-${z.tone}`, "aria-hidden": "true" }),
|
|
1356
|
+
pick(z.label, lang)
|
|
1357
|
+
] }, i)) })
|
|
1358
|
+
] });
|
|
1359
|
+
}
|
|
1360
|
+
function Verdict(props) {
|
|
1361
|
+
const lang = useShellLang();
|
|
1362
|
+
let tone;
|
|
1363
|
+
let text;
|
|
1364
|
+
if ("code" in props) {
|
|
1365
|
+
const m = props.messages[props.code];
|
|
1366
|
+
if (!m) console.error(`[caos-app-shell] Verdict code "${props.code}" has no message`);
|
|
1367
|
+
tone = m?.tone ?? "neutral";
|
|
1368
|
+
text = m ? pick(m.text, lang) : lang === "es" ? "estado desconocido" : "unknown status";
|
|
1369
|
+
} else {
|
|
1370
|
+
tone = props.tone;
|
|
1371
|
+
text = pick(props.verdict, lang);
|
|
1372
|
+
}
|
|
1373
|
+
return /* @__PURE__ */ jsxs("div", { className: `caos-verdict tone-${tone}${props.compact ? " compact" : ""}`, "data-verdict": tone, children: [
|
|
1374
|
+
/* @__PURE__ */ jsx("p", { className: "caos-verdict-title", children: pick(props.title, lang) }),
|
|
1375
|
+
/* @__PURE__ */ jsx("p", { className: "caos-verdict-text", children: text }),
|
|
1376
|
+
props.children
|
|
1377
|
+
] });
|
|
1378
|
+
}
|
|
1379
|
+
function PlotCard({ title, lane, provenance, dataKey, actions, note, fill, children }) {
|
|
1380
|
+
const lang = useShellLang();
|
|
1381
|
+
const ws = useWorkbenchState();
|
|
1382
|
+
const stale = isStale(dataKey, ws);
|
|
1383
|
+
const name = pick(title, lang);
|
|
1384
|
+
if (!lane || !provenance) console.error(`[caos-app-shell] PlotCard "${name}" needs lane and provenance`);
|
|
1385
|
+
return /* @__PURE__ */ jsxs(
|
|
1386
|
+
"figure",
|
|
1387
|
+
{
|
|
1388
|
+
className: ["caos-plot", fill ? "fill" : "", stale ? "caos-stale" : ""].filter(Boolean).join(" "),
|
|
1389
|
+
"data-plot": name,
|
|
1390
|
+
"data-lane": lane,
|
|
1391
|
+
"data-provenance": provenance,
|
|
1392
|
+
"data-state-key": dataKey ?? "",
|
|
1393
|
+
"data-stale": stale ? "1" : "0",
|
|
1394
|
+
children: [
|
|
1395
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "caos-plot-head", children: [
|
|
1396
|
+
/* @__PURE__ */ jsx("span", { className: "caos-plot-title", children: name }),
|
|
1397
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-plot-actions", children: [
|
|
1398
|
+
actions,
|
|
1399
|
+
/* @__PURE__ */ jsx(LaneBadge, { lane, provenance })
|
|
1400
|
+
] })
|
|
1401
|
+
] }),
|
|
1402
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-plot-body", children: [
|
|
1403
|
+
/* @__PURE__ */ jsx(PanelBoundary, { panel: name, children }),
|
|
1404
|
+
stale && /* @__PURE__ */ jsx("div", { className: "caos-stale-overlay", role: "status", children: lang === "es" ? "Recalculando para la selecci\xF3n actual" : "Recomputing for the current selection" })
|
|
1405
|
+
] }),
|
|
1406
|
+
note && /* @__PURE__ */ jsx("p", { className: "caos-plot-note", children: pick(note, lang) })
|
|
1407
|
+
]
|
|
1408
|
+
}
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
function Knob({ id, label, value, min, max, step, unit, hint, format, disabled, onChange }) {
|
|
1412
|
+
const lang = useShellLang();
|
|
1413
|
+
const inputId = useId();
|
|
1414
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-knob", "data-control": id, title: pick(hint, lang) || void 0, children: [
|
|
1415
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: "caos-knob-head", children: [
|
|
1416
|
+
/* @__PURE__ */ jsx("span", { children: pick(label, lang) }),
|
|
1417
|
+
/* @__PURE__ */ jsxs("span", { className: "caos-knob-value", children: [
|
|
1418
|
+
formatNumber(value, lang, format ?? {}),
|
|
1419
|
+
unit ? ` ${pick(unit, lang)}` : ""
|
|
1420
|
+
] })
|
|
1421
|
+
] }),
|
|
1422
|
+
/* @__PURE__ */ jsx(
|
|
1423
|
+
"input",
|
|
1424
|
+
{
|
|
1425
|
+
id: inputId,
|
|
1426
|
+
className: "range",
|
|
1427
|
+
type: "range",
|
|
1428
|
+
min,
|
|
1429
|
+
max,
|
|
1430
|
+
step,
|
|
1431
|
+
value,
|
|
1432
|
+
disabled,
|
|
1433
|
+
onChange: (e) => onChange(Number(e.target.value))
|
|
1434
|
+
}
|
|
1435
|
+
)
|
|
1436
|
+
] });
|
|
1437
|
+
}
|
|
1438
|
+
function ChipGroup({ id, label, options, value, onChange }) {
|
|
1439
|
+
const lang = useShellLang();
|
|
1440
|
+
return /* @__PURE__ */ jsxs("div", { className: "caos-chipgroup", "data-control": id, children: [
|
|
1441
|
+
/* @__PURE__ */ jsx("span", { className: "caos-chipgroup-label", children: pick(label, lang) }),
|
|
1442
|
+
/* @__PURE__ */ jsx("div", { className: "caos-chip-row", role: "radiogroup", "aria-label": pick(label, lang), children: options.map((o) => /* @__PURE__ */ jsx(
|
|
1443
|
+
"button",
|
|
1444
|
+
{
|
|
1445
|
+
type: "button",
|
|
1446
|
+
role: "radio",
|
|
1447
|
+
"aria-checked": o.id === value,
|
|
1448
|
+
className: o.id === value ? "chip on" : "chip",
|
|
1449
|
+
disabled: o.disabled,
|
|
1450
|
+
title: pick(o.hint, lang) || void 0,
|
|
1451
|
+
onClick: () => onChange(o.id),
|
|
1452
|
+
children: pick(o.label, lang)
|
|
1453
|
+
},
|
|
1454
|
+
o.id
|
|
1455
|
+
)) })
|
|
1456
|
+
] });
|
|
1457
|
+
}
|
|
912
1458
|
|
|
913
|
-
export { AppShell, ArchitectureModal, Callout, CaseSelector, CitationsProvider, Cite, Equation, Figure, InlineMath, LanguageToggle, ReferenceList, Refs, SubTabs, Tabs, ThemeToggle, WorkbenchShell,
|
|
1459
|
+
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 };
|