@inflexa-ai/harness 0.19.1 → 0.19.2

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.
Files changed (50) hide show
  1. package/dist/agents/report-session-agent.d.ts +7 -0
  2. package/dist/agents/report-session-agent.js +2 -1
  3. package/dist/auth/local-run-authorizer.d.ts +3 -2
  4. package/dist/auth/local-run-authorizer.js +4 -2
  5. package/dist/execution/run-authorizer.d.ts +18 -0
  6. package/dist/execution/run-canceler.d.ts +67 -0
  7. package/dist/execution/run-canceler.js +130 -0
  8. package/dist/index.d.ts +5 -2
  9. package/dist/index.js +6 -1
  10. package/dist/report-render/assets.d.ts +42 -0
  11. package/dist/report-render/assets.js +61 -0
  12. package/dist/report-render/chart.js +31 -10
  13. package/dist/report-render/design.d.ts +120 -0
  14. package/dist/report-render/design.js +808 -0
  15. package/dist/report-render/page.d.ts +32 -115
  16. package/dist/report-render/page.js +130 -273
  17. package/dist/report-render/render.js +34 -11
  18. package/dist/report-render/views/chart-view.d.ts +6 -3
  19. package/dist/report-render/views/chart-view.js +10 -5
  20. package/dist/report-render/views/page-view.d.ts +19 -8
  21. package/dist/report-render/views/page-view.js +31 -11
  22. package/dist/report-render/views/prose.d.ts +1 -0
  23. package/dist/report-render/views/prose.js +7 -13
  24. package/dist/report-render/views/references-view.js +5 -5
  25. package/dist/report-render/views/values.d.ts +15 -6
  26. package/dist/report-render/views/values.js +29 -10
  27. package/dist/runtime/assemble.d.ts +10 -0
  28. package/dist/runtime/assemble.js +1 -0
  29. package/dist/sandbox/k8s-client.js +18 -0
  30. package/dist/sandbox/types.d.ts +6 -0
  31. package/dist/state/analyses.d.ts +3 -26
  32. package/dist/state/analyses.js +6 -64
  33. package/dist/state/index.d.ts +2 -2
  34. package/dist/state/index.js +2 -2
  35. package/dist/state/init.js +3 -3
  36. package/dist/state/runs.d.ts +10 -0
  37. package/dist/state/runs.js +21 -0
  38. package/dist/tasks/data-profile.d.ts +4 -0
  39. package/dist/tasks/data-profile.js +14 -0
  40. package/dist/tools/approval/gateway.d.ts +8 -1
  41. package/dist/tools/approval/gateway.js +9 -2
  42. package/dist/tools/approval/queries.d.ts +9 -2
  43. package/dist/tools/approval/queries.js +12 -4
  44. package/dist/tools/report-session/index.d.ts +1 -1
  45. package/dist/tools/report-session/index.js +1 -1
  46. package/dist/tools/report-session/preview-report.d.ts +11 -1
  47. package/dist/tools/report-session/preview-report.js +35 -12
  48. package/dist/workflows/sandbox-step.d.ts +4 -0
  49. package/dist/workflows/sandbox-step.js +21 -2
  50. package/package.json +5 -1
@@ -1,123 +1,15 @@
1
1
  /**
2
- * The page constants: the CDN pins, the inline style, the ECharts theme, and the chart bootstrap.
2
+ * The page constants: the head references, the readiness names, and the two page-side scripts.
3
3
  *
4
- * These constants are copies. The old template pack at `templates/report-html` holds the source, and the
5
- * old render path owns that directory. The renderer never reads that directory at run time, thus the
6
- * output stays a pure function of its inputs. A copy that drifts is a maintenance cost, and it is the
7
- * price of a page with no local asset and no template engine.
4
+ * A script here is browser source text, not module code. Thus it reads no module binding, and each value
5
+ * that it needs is interpolated at build time. The style rules and the ECharts theme live in `design.ts`.
8
6
  */
9
7
  /**
10
- * The head references: the Tailwind runtime, the ECharts runtime, the two fonts, and the preconnect. Each
11
- * tag carries the pinned version and, where the source pins one, the integrity hash. The bytes are exact
12
- * copies, thus the page stays deterministic and the hashes stay valid.
8
+ * The head references of the staged assets. The page loads the chart runtime from the sibling assets
9
+ * directory, thus the head names no remote host. The manifest gives the staged name, thus the tag and the
10
+ * stage step of the caller cannot disagree.
13
11
  */
14
- export declare const CDN_HEAD = "<script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.2/dist/index.global.min.js\" integrity=\"sha384-shaHAtPgz0ulP7R/YmFe0nZtC8FxdhJPi73vwJQcADVttxvHLJBJt/pjzkLSbIvL\" crossorigin=\"anonymous\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js\" integrity=\"sha384-Mx5lkUEQPM1pOJCwFtUICyX45KNojXbkWdYhkKUKsbv391mavbfoAmONbzkgYPzR\" crossorigin=\"anonymous\"></script>\n<link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\" crossorigin>\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource-variable/space-grotesk@5.1.1/index.css\" rel=\"stylesheet\" integrity=\"sha384-b3Dtcpab/ltw+oDnVHstCCa9ENEb2hKouCSRAfb8o0ROkWOb2uNWYG6+l7HGrHmG\" crossorigin=\"anonymous\">\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource-variable/ibm-plex-mono@5.1.1/index.css\" rel=\"stylesheet\">";
15
- /**
16
- * The style rules of the page. The renderer inlines them in one `<style>` block, thus the page holds the
17
- * whole design token set and every component rule with no external stylesheet.
18
- */
19
- export declare const PAGE_CSS = "/* \u2500\u2500 Color Palette \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:root {\n /* Primary Scale */\n --color-primary-50: #f0f1fe; /* Tag bg, CTA section bg */\n --color-primary-100: #dde0fc;\n --color-primary-200: #bcc2f9; /* Badge borders, corner accent default */\n --color-primary-300: #9ba5f5; /* Corner button accent default */\n --color-primary-400: #7987f0; /* Section illustration icons */\n --color-primary-500: #576dea; /* Primary action, section labels, links */\n --color-primary-600: #4458d4; /* Button hover */\n --color-primary-700: #3545b0; /* Tag text on primary-50 bg */\n\n /* Text */\n --color-heading: #0f172a; /* slate-900 \u2014 all headings */\n --color-text: #475569; /* slate-600 \u2014 body text */\n --color-text-secondary: #64748b; /* slate-500 \u2014 captions, metadata */\n --color-text-muted: #94a3b8; /* slate-400 \u2014 timestamps, footnotes */\n\n /* Surface */\n --color-bg: #ffffff; /* Page background (white) */\n --color-bg-alt: #f8fafc; /* Alternating section bg (slate-50) */\n --color-card: #ffffff; /* Cards (white) */\n --color-border: #e2e8f0; /* slate-200 \u2014 card borders, dividers */\n --color-border-hover: #cbd5e1; /* slate-300 \u2014 hover borders */\n\n /* Data Visualization */\n --color-up: #ef4444; /* Red 500 \u2014 upregulated, positive */\n --color-down: #576dea; /* Primary 500 \u2014 downregulated, negative */\n --color-ns: #94a3b8; /* Slate 400 \u2014 not significant */\n\n /* Semantic (Tags \u2014 solid bg + colored text) */\n --color-high-bg: #f0fdf4; /* green-50 */\n --color-high-text: #15803d; /* green-700 */\n --color-medium-bg: #fffbeb; /* amber-50 */\n --color-medium-text:#b45309; /* amber-700 */\n --color-low-bg: #fef2f2; /* red-50 */\n --color-low-text: #b91c1c; /* red-700 */\n\n /* Stat card accent colors */\n --color-stat-primary: #576dea;\n --color-stat-green: #22c55e;\n --color-stat-red: #ef4444;\n --color-stat-purple: #a78bfa;\n --color-stat-amber: #f59e0b;\n}\n\n/* \u2500\u2500 Typography \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\nbody {\n font-family: 'Space Grotesk Variable', system-ui, sans-serif;\n font-size: 16px;\n line-height: 1.6;\n color: var(--color-text);\n}\n\n.font-mono, code, .gene {\n font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;\n}\n\n/* Rule: Headings use font-sans (Space Grotesk), never font-mono.\n Labels, tags, badges, data, product names always use font-mono (IBM Plex Mono). */\n\n/* \u2500\u2500 Corner Accent Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.corner-accents {\n position: relative;\n border: 1px solid var(--color-border);\n background: var(--color-card);\n /* No border-radius \u2014 square corners */\n}\n.corner-accents::before,\n.corner-accents::after {\n content: '';\n position: absolute;\n width: 20px;\n height: 20px;\n transition: all 0.3s ease;\n pointer-events: none;\n}\n.corner-accents::before {\n top: -1px; left: -1px;\n border-top: 2px solid var(--color-primary-200);\n border-left: 2px solid var(--color-primary-200);\n}\n.corner-accents::after {\n bottom: -1px; right: -1px;\n border-bottom: 2px solid var(--color-primary-200);\n border-right: 2px solid var(--color-primary-200);\n}\n.corner-accents:hover::before,\n.corner-accents:hover::after {\n width: 28px;\n height: 28px;\n border-color: var(--color-primary-500);\n}\n.corner-accents:hover {\n box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.08);\n}\n\n/* \u2500\u2500 Window Chrome Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Exception: uses rounded-xl (only rounded component) */\n.window-chrome {\n border-radius: 12px;\n border: 1px solid var(--color-border);\n background: var(--color-card);\n box-shadow: 0 8px 30px -8px rgba(15,23,42,0.08), 0 4px 12px -4px rgba(15,23,42,0.04);\n transition: all 0.5s ease;\n}\n.window-chrome:hover {\n transform: translateY(-4px);\n box-shadow: 0 25px 50px -12px rgba(15,23,42,0.18), 0 12px 24px -8px rgba(15,23,42,0.1);\n border-color: var(--color-border-hover);\n}\n.window-chrome .chrome-dots span { transition: background-color 0.3s ease; }\n.window-chrome:hover .chrome-dots .dot-1 { background-color: #fb7185; }\n.window-chrome:hover .chrome-dots .dot-2 { background-color: #fbbf24; }\n.window-chrome:hover .chrome-dots .dot-3 { background-color: #4ade80; }\n\n/* \u2500\u2500 Terminal Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.terminal-card {\n position: relative;\n overflow: hidden;\n}\n.terminal-card .terminal-header {\n background: #1e293b; /* slate-800 */\n}\n.terminal-card .terminal-body-dark {\n background: #0f172a; /* slate-900 */\n color: #e2e8f0;\n}\n.terminal-card::after {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n opacity: 0;\n background: repeating-linear-gradient(\n 0deg, transparent, transparent 2px,\n rgba(148, 163, 184, 0.03) 2px, rgba(148, 163, 184, 0.03) 4px\n );\n transition: opacity 0.3s ease;\n}\n.terminal-card:hover::after { opacity: 1; }\n\n/* \u2500\u2500 Block Progress Bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Usage: <span class=\"block-progress\"><span class=\"filled-high\">\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588</span><span class=\"empty\">\u2591\u2591</span> <span class=\"score\">0.82</span></span> */\n.block-progress {\n font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;\n font-size: 14px;\n}\n.block-progress .filled-high { color: var(--color-primary-500); } /* \u2265 0.8 */\n.block-progress .filled-mid { color: #f59e0b; } /* \u2265 0.6 */\n.block-progress .filled-low { color: #f43f5e; } /* < 0.6 */\n.block-progress .empty { color: #e2e8f0; }\n.block-progress .score { font-size: 12px; color: #94a3b8; margin-left: 8px; }\n\n/* \u2500\u2500 Sidebar Navigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* When a sidebar is rendered (#report-sidebar), the body shifts right on lg+\n to make room. Styling matches the corner-accent / mono-label language. */\n@media (min-width: 1024px) {\n body:has(#report-sidebar) { padding-left: 15rem; }\n}\n.sidebar-link {\n border-left: 2px solid transparent;\n}\n.sidebar-link.active {\n color: var(--color-primary-500);\n background-color: var(--color-primary-50);\n border-left-color: var(--color-primary-500);\n font-weight: 600;\n}\n\n/* \u2500\u2500 Insight Box \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.insight-box {\n background: var(--color-card);\n border: 1px solid var(--color-border);\n /* No border-radius \u2014 uses corner-accents class */\n}\n\n/* \u2500\u2500 Section Textures \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Apply to <section>: combine one pattern + noise, e.g. class=\"texture-dots texture-noise\" */\n/* Textures should be felt, not seen \u2014 low opacity, faded edges */\n\n/* \u2500\u2500 Fade-In \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fade-in {\n opacity: 0;\n transform: translateY(20px);\n transition: opacity 0.7s ease-out, transform 0.7s ease-out;\n}\n.fade-in-visible {\n opacity: 1;\n transform: translateY(0);\n}\n/* Stagger with data-delay attribute: <div class=\"fade-in\" data-delay=\"100\"> */";
20
- /**
21
- * The registered name of the ECharts theme. The skeleton registers the theme under this name, and the
22
- * bootstrap reads the same name. One source prevents a silent mismatch between the two sites.
23
- */
24
- export declare const ECHARTS_THEME_NAME = "inflexa";
25
- /**
26
- * The ECharts theme as a plain object. The renderer serializes this object and registers it, thus the
27
- * theme never rides a JSON file and no run-time read is necessary. The palette and the axis styles mirror
28
- * the light design tokens of the page.
29
- */
30
- export declare const ECHARTS_THEME: {
31
- color: string[];
32
- backgroundColor: string;
33
- textStyle: {
34
- fontFamily: string;
35
- fontSize: number;
36
- color: string;
37
- };
38
- title: {
39
- show: boolean;
40
- };
41
- legend: {
42
- bottom: number;
43
- textStyle: {
44
- fontSize: number;
45
- color: string;
46
- };
47
- itemGap: number;
48
- itemWidth: number;
49
- itemHeight: number;
50
- };
51
- grid: {
52
- left: number;
53
- right: number;
54
- top: number;
55
- bottom: number;
56
- containLabel: boolean;
57
- };
58
- tooltip: {
59
- backgroundColor: string;
60
- borderColor: string;
61
- textStyle: {
62
- color: string;
63
- fontSize: number;
64
- };
65
- extraCssText: string;
66
- };
67
- toolbox: {
68
- feature: {
69
- saveAsImage: {
70
- title: string;
71
- pixelRatio: number;
72
- };
73
- };
74
- iconStyle: {
75
- borderColor: string;
76
- };
77
- right: number;
78
- top: number;
79
- };
80
- xAxis: {
81
- axisLine: {
82
- lineStyle: {
83
- color: string;
84
- };
85
- };
86
- axisTick: {
87
- lineStyle: {
88
- color: string;
89
- };
90
- };
91
- axisLabel: {
92
- color: string;
93
- fontSize: number;
94
- };
95
- splitLine: {
96
- lineStyle: {
97
- color: string;
98
- type: string;
99
- };
100
- };
101
- };
102
- yAxis: {
103
- axisLine: {
104
- show: boolean;
105
- };
106
- axisTick: {
107
- show: boolean;
108
- };
109
- axisLabel: {
110
- color: string;
111
- fontSize: number;
112
- };
113
- splitLine: {
114
- lineStyle: {
115
- color: string;
116
- type: string;
117
- };
118
- };
119
- };
120
- };
12
+ export declare const ASSET_HEAD: string;
121
13
  /**
122
14
  * The name of the readiness event, and the name of the window sentinel that guards a late listener. The
123
15
  * bootstrap below emits both names, thus this module owns them. A capture that waits for the page reads the
@@ -144,7 +36,32 @@ export declare const PAGE_READY_TIMEOUT_MS = 8000;
144
36
  * not at a timeout. The sentinel guards a listener that registers after the dispatch, thus a late listener
145
37
  * still resolves.
146
38
  *
39
+ * The signal passes through the reveal gate of the fade-in script. Thus the signal comes after the first
40
+ * reveal pass, and a capture reads a settled page instead of a page in the middle of a transition.
41
+ *
147
42
  * Each chart initializes inside a guard. One malformed option is exactly the fault that a look must
148
43
  * diagnose, thus it must never stop a sibling chart and it must never withhold the readiness signal.
149
44
  */
150
45
  export declare const CHART_BOOTSTRAP: string;
46
+ /**
47
+ * The page-side script that reveals each `fade-in` element as it enters the viewport. The `data-delay`
48
+ * attribute holds a stagger in milliseconds, and the observer releases each element one time.
49
+ *
50
+ * The threshold is zero, thus one visible pixel reveals an element. A band is a whole top-level section,
51
+ * and a tall band can never cover a fraction of the viewport. A threshold above zero leaves such a band at
52
+ * zero opacity for the whole life of the page.
53
+ *
54
+ * The first callback of the observer is the first reveal pass. It reveals what the viewport already holds,
55
+ * and it drops the transition for that reveal with the `fade-in-instant` class. Thus the page settles in
56
+ * one frame. An element that arrives later keeps the transition and the stagger.
57
+ *
58
+ * The script installs the reveal gate on the window object before it observes anything. The chart bootstrap
59
+ * runs after this script and it signals readiness through that gate. Thus the readiness signal always comes
60
+ * after the first reveal pass. A page with no `fade-in` element, and a browser with no
61
+ * `IntersectionObserver`, opens the gate at once.
62
+ *
63
+ * A browser with no `IntersectionObserver` reveals every element at once. Thus the content is visible in
64
+ * each browser, and the reveal is decoration alone. The print rules and the reduced-motion rules show every
65
+ * element without this script.
66
+ */
67
+ export declare const FADE_IN_OBSERVER = "(function () {\n var waiting = [];\n var settled = false;\n function settle() {\n if (settled) {\n return;\n }\n settled = true;\n for (var w = 0; w < waiting.length; w++) {\n waiting[w]();\n }\n waiting = [];\n }\n window.__inflexaWhenRevealed = function (done) {\n if (settled) {\n done();\n return;\n }\n waiting.push(done);\n };\n function reveal(node) {\n node.classList.add(\"fade-in-visible\");\n }\n function revealSettled(node) {\n node.classList.add(\"fade-in-instant\");\n node.classList.add(\"fade-in-visible\");\n }\n function start() {\n var nodes = document.querySelectorAll(\".fade-in\");\n if (typeof IntersectionObserver === \"undefined\" || nodes.length === 0) {\n for (var i = 0; i < nodes.length; i++) {\n revealSettled(nodes[i]);\n }\n settle();\n return;\n }\n var firstPass = true;\n var observer = new IntersectionObserver(function (entries) {\n var instant = firstPass;\n firstPass = false;\n for (var j = 0; j < entries.length; j++) {\n var entry = entries[j];\n if (!entry.isIntersecting) {\n continue;\n }\n var target = entry.target;\n observer.unobserve(target);\n if (instant) {\n revealSettled(target);\n continue;\n }\n var delay = parseInt(target.getAttribute(\"data-delay\") || \"0\", 10) || 0;\n setTimeout(reveal.bind(null, target), delay);\n }\n if (instant) {\n settle();\n }\n }, { threshold: 0 });\n for (var k = 0; k < nodes.length; k++) {\n observer.observe(nodes[k]);\n }\n setTimeout(settle, 2000);\n }\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", start);\n } else {\n start();\n }\n})();";
@@ -1,279 +1,17 @@
1
1
  /**
2
- * The page constants: the CDN pins, the inline style, the ECharts theme, and the chart bootstrap.
2
+ * The page constants: the head references, the readiness names, and the two page-side scripts.
3
3
  *
4
- * These constants are copies. The old template pack at `templates/report-html` holds the source, and the
5
- * old render path owns that directory. The renderer never reads that directory at run time, thus the
6
- * output stays a pure function of its inputs. A copy that drifts is a maintenance cost, and it is the
7
- * price of a page with no local asset and no template engine.
4
+ * A script here is browser source text, not module code. Thus it reads no module binding, and each value
5
+ * that it needs is interpolated at build time. The style rules and the ECharts theme live in `design.ts`.
8
6
  */
7
+ import { assetSource, ECHARTS_ASSET } from "./assets.js";
8
+ import { ECHARTS_THEME_NAME } from "./design.js";
9
9
  /**
10
- * The head references: the Tailwind runtime, the ECharts runtime, the two fonts, and the preconnect. Each
11
- * tag carries the pinned version and, where the source pins one, the integrity hash. The bytes are exact
12
- * copies, thus the page stays deterministic and the hashes stay valid.
10
+ * The head references of the staged assets. The page loads the chart runtime from the sibling assets
11
+ * directory, thus the head names no remote host. The manifest gives the staged name, thus the tag and the
12
+ * stage step of the caller cannot disagree.
13
13
  */
14
- export const CDN_HEAD = `<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.2/dist/index.global.min.js" integrity="sha384-shaHAtPgz0ulP7R/YmFe0nZtC8FxdhJPi73vwJQcADVttxvHLJBJt/pjzkLSbIvL" crossorigin="anonymous"></script>
15
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js" integrity="sha384-Mx5lkUEQPM1pOJCwFtUICyX45KNojXbkWdYhkKUKsbv391mavbfoAmONbzkgYPzR" crossorigin="anonymous"></script>
16
- <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
17
- <link href="https://cdn.jsdelivr.net/npm/@fontsource-variable/space-grotesk@5.1.1/index.css" rel="stylesheet" integrity="sha384-b3Dtcpab/ltw+oDnVHstCCa9ENEb2hKouCSRAfb8o0ROkWOb2uNWYG6+l7HGrHmG" crossorigin="anonymous">
18
- <link href="https://cdn.jsdelivr.net/npm/@fontsource-variable/ibm-plex-mono@5.1.1/index.css" rel="stylesheet">`;
19
- /**
20
- * The style rules of the page. The renderer inlines them in one `<style>` block, thus the page holds the
21
- * whole design token set and every component rule with no external stylesheet.
22
- */
23
- export const PAGE_CSS = `/* ── Color Palette ────────────────────────────────────── */
24
- :root {
25
- /* Primary Scale */
26
- --color-primary-50: #f0f1fe; /* Tag bg, CTA section bg */
27
- --color-primary-100: #dde0fc;
28
- --color-primary-200: #bcc2f9; /* Badge borders, corner accent default */
29
- --color-primary-300: #9ba5f5; /* Corner button accent default */
30
- --color-primary-400: #7987f0; /* Section illustration icons */
31
- --color-primary-500: #576dea; /* Primary action, section labels, links */
32
- --color-primary-600: #4458d4; /* Button hover */
33
- --color-primary-700: #3545b0; /* Tag text on primary-50 bg */
34
-
35
- /* Text */
36
- --color-heading: #0f172a; /* slate-900 — all headings */
37
- --color-text: #475569; /* slate-600 — body text */
38
- --color-text-secondary: #64748b; /* slate-500 — captions, metadata */
39
- --color-text-muted: #94a3b8; /* slate-400 — timestamps, footnotes */
40
-
41
- /* Surface */
42
- --color-bg: #ffffff; /* Page background (white) */
43
- --color-bg-alt: #f8fafc; /* Alternating section bg (slate-50) */
44
- --color-card: #ffffff; /* Cards (white) */
45
- --color-border: #e2e8f0; /* slate-200 — card borders, dividers */
46
- --color-border-hover: #cbd5e1; /* slate-300 — hover borders */
47
-
48
- /* Data Visualization */
49
- --color-up: #ef4444; /* Red 500 — upregulated, positive */
50
- --color-down: #576dea; /* Primary 500 — downregulated, negative */
51
- --color-ns: #94a3b8; /* Slate 400 — not significant */
52
-
53
- /* Semantic (Tags — solid bg + colored text) */
54
- --color-high-bg: #f0fdf4; /* green-50 */
55
- --color-high-text: #15803d; /* green-700 */
56
- --color-medium-bg: #fffbeb; /* amber-50 */
57
- --color-medium-text:#b45309; /* amber-700 */
58
- --color-low-bg: #fef2f2; /* red-50 */
59
- --color-low-text: #b91c1c; /* red-700 */
60
-
61
- /* Stat card accent colors */
62
- --color-stat-primary: #576dea;
63
- --color-stat-green: #22c55e;
64
- --color-stat-red: #ef4444;
65
- --color-stat-purple: #a78bfa;
66
- --color-stat-amber: #f59e0b;
67
- }
68
-
69
- /* ── Typography ───────────────────────────────────────── */
70
- body {
71
- font-family: 'Space Grotesk Variable', system-ui, sans-serif;
72
- font-size: 16px;
73
- line-height: 1.6;
74
- color: var(--color-text);
75
- }
76
-
77
- .font-mono, code, .gene {
78
- font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;
79
- }
80
-
81
- /* Rule: Headings use font-sans (Space Grotesk), never font-mono.
82
- Labels, tags, badges, data, product names always use font-mono (IBM Plex Mono). */
83
-
84
- /* ── Corner Accent Cards ─────────────────────────────── */
85
- .corner-accents {
86
- position: relative;
87
- border: 1px solid var(--color-border);
88
- background: var(--color-card);
89
- /* No border-radius — square corners */
90
- }
91
- .corner-accents::before,
92
- .corner-accents::after {
93
- content: '';
94
- position: absolute;
95
- width: 20px;
96
- height: 20px;
97
- transition: all 0.3s ease;
98
- pointer-events: none;
99
- }
100
- .corner-accents::before {
101
- top: -1px; left: -1px;
102
- border-top: 2px solid var(--color-primary-200);
103
- border-left: 2px solid var(--color-primary-200);
104
- }
105
- .corner-accents::after {
106
- bottom: -1px; right: -1px;
107
- border-bottom: 2px solid var(--color-primary-200);
108
- border-right: 2px solid var(--color-primary-200);
109
- }
110
- .corner-accents:hover::before,
111
- .corner-accents:hover::after {
112
- width: 28px;
113
- height: 28px;
114
- border-color: var(--color-primary-500);
115
- }
116
- .corner-accents:hover {
117
- box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.08);
118
- }
119
-
120
- /* ── Window Chrome Panel ─────────────────────────────── */
121
- /* Exception: uses rounded-xl (only rounded component) */
122
- .window-chrome {
123
- border-radius: 12px;
124
- border: 1px solid var(--color-border);
125
- background: var(--color-card);
126
- box-shadow: 0 8px 30px -8px rgba(15,23,42,0.08), 0 4px 12px -4px rgba(15,23,42,0.04);
127
- transition: all 0.5s ease;
128
- }
129
- .window-chrome:hover {
130
- transform: translateY(-4px);
131
- box-shadow: 0 25px 50px -12px rgba(15,23,42,0.18), 0 12px 24px -8px rgba(15,23,42,0.1);
132
- border-color: var(--color-border-hover);
133
- }
134
- .window-chrome .chrome-dots span { transition: background-color 0.3s ease; }
135
- .window-chrome:hover .chrome-dots .dot-1 { background-color: #fb7185; }
136
- .window-chrome:hover .chrome-dots .dot-2 { background-color: #fbbf24; }
137
- .window-chrome:hover .chrome-dots .dot-3 { background-color: #4ade80; }
138
-
139
- /* ── Terminal Card ───────────────────────────────────── */
140
- .terminal-card {
141
- position: relative;
142
- overflow: hidden;
143
- }
144
- .terminal-card .terminal-header {
145
- background: #1e293b; /* slate-800 */
146
- }
147
- .terminal-card .terminal-body-dark {
148
- background: #0f172a; /* slate-900 */
149
- color: #e2e8f0;
150
- }
151
- .terminal-card::after {
152
- content: '';
153
- position: absolute;
154
- inset: 0;
155
- pointer-events: none;
156
- opacity: 0;
157
- background: repeating-linear-gradient(
158
- 0deg, transparent, transparent 2px,
159
- rgba(148, 163, 184, 0.03) 2px, rgba(148, 163, 184, 0.03) 4px
160
- );
161
- transition: opacity 0.3s ease;
162
- }
163
- .terminal-card:hover::after { opacity: 1; }
164
-
165
- /* ── Block Progress Bar ──────────────────────────────── */
166
- /* Usage: <span class="block-progress"><span class="filled-high">████████</span><span class="empty">░░</span> <span class="score">0.82</span></span> */
167
- .block-progress {
168
- font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;
169
- font-size: 14px;
170
- }
171
- .block-progress .filled-high { color: var(--color-primary-500); } /* ≥ 0.8 */
172
- .block-progress .filled-mid { color: #f59e0b; } /* ≥ 0.6 */
173
- .block-progress .filled-low { color: #f43f5e; } /* < 0.6 */
174
- .block-progress .empty { color: #e2e8f0; }
175
- .block-progress .score { font-size: 12px; color: #94a3b8; margin-left: 8px; }
176
-
177
- /* ── Sidebar Navigation ──────────────────────────────── */
178
- /* When a sidebar is rendered (#report-sidebar), the body shifts right on lg+
179
- to make room. Styling matches the corner-accent / mono-label language. */
180
- @media (min-width: 1024px) {
181
- body:has(#report-sidebar) { padding-left: 15rem; }
182
- }
183
- .sidebar-link {
184
- border-left: 2px solid transparent;
185
- }
186
- .sidebar-link.active {
187
- color: var(--color-primary-500);
188
- background-color: var(--color-primary-50);
189
- border-left-color: var(--color-primary-500);
190
- font-weight: 600;
191
- }
192
-
193
- /* ── Insight Box ─────────────────────────────────────── */
194
- .insight-box {
195
- background: var(--color-card);
196
- border: 1px solid var(--color-border);
197
- /* No border-radius — uses corner-accents class */
198
- }
199
-
200
- /* ── Section Textures ────────────────────────────────── */
201
- /* Apply to <section>: combine one pattern + noise, e.g. class="texture-dots texture-noise" */
202
- /* Textures should be felt, not seen — low opacity, faded edges */
203
-
204
- /* ── Fade-In ─────────────────────────────────────────── */
205
- .fade-in {
206
- opacity: 0;
207
- transform: translateY(20px);
208
- transition: opacity 0.7s ease-out, transform 0.7s ease-out;
209
- }
210
- .fade-in-visible {
211
- opacity: 1;
212
- transform: translateY(0);
213
- }
214
- /* Stagger with data-delay attribute: <div class="fade-in" data-delay="100"> */`;
215
- /**
216
- * The registered name of the ECharts theme. The skeleton registers the theme under this name, and the
217
- * bootstrap reads the same name. One source prevents a silent mismatch between the two sites.
218
- */
219
- export const ECHARTS_THEME_NAME = "inflexa";
220
- /**
221
- * The ECharts theme as a plain object. The renderer serializes this object and registers it, thus the
222
- * theme never rides a JSON file and no run-time read is necessary. The palette and the axis styles mirror
223
- * the light design tokens of the page.
224
- */
225
- export const ECHARTS_THEME = {
226
- color: ["#576dea", "#ef4444", "#22c55e", "#a78bfa", "#f59e0b", "#ec4899", "#06b6d4", "#f97316", "#8b5cf6", "#14b8a6"],
227
- backgroundColor: "transparent",
228
- textStyle: {
229
- fontFamily: "'Space Grotesk Variable', system-ui, sans-serif",
230
- fontSize: 12,
231
- color: "#64748b",
232
- },
233
- title: {
234
- show: false,
235
- },
236
- legend: {
237
- bottom: 0,
238
- textStyle: { fontSize: 11, color: "#64748b" },
239
- itemGap: 16,
240
- itemWidth: 12,
241
- itemHeight: 12,
242
- },
243
- grid: {
244
- left: 60,
245
- right: 24,
246
- top: 24,
247
- bottom: 48,
248
- containLabel: false,
249
- },
250
- tooltip: {
251
- backgroundColor: "#ffffff",
252
- borderColor: "#e2e8f0",
253
- textStyle: { color: "#334155", fontSize: 12 },
254
- extraCssText: "border-radius: 4px; box-shadow: 0 4px 12px rgba(15,23,42,0.1);",
255
- },
256
- toolbox: {
257
- feature: {
258
- saveAsImage: { title: "Save", pixelRatio: 2 },
259
- },
260
- iconStyle: { borderColor: "#94a3b8" },
261
- right: 16,
262
- top: 0,
263
- },
264
- xAxis: {
265
- axisLine: { lineStyle: { color: "#e2e8f0" } },
266
- axisTick: { lineStyle: { color: "#e2e8f0" } },
267
- axisLabel: { color: "#64748b", fontSize: 11 },
268
- splitLine: { lineStyle: { color: "#f1f5f9", type: "dashed" } },
269
- },
270
- yAxis: {
271
- axisLine: { show: false },
272
- axisTick: { show: false },
273
- axisLabel: { color: "#64748b", fontSize: 11 },
274
- splitLine: { lineStyle: { color: "#f1f5f9", type: "dashed" } },
275
- },
276
- };
14
+ export const ASSET_HEAD = `<script src="${assetSource(ECHARTS_ASSET)}"></script>`;
277
15
  /**
278
16
  * The name of the readiness event, and the name of the window sentinel that guards a late listener. The
279
17
  * bootstrap below emits both names, thus this module owns them. A capture that waits for the page reads the
@@ -288,6 +26,24 @@ export const THEME_READY_SENTINEL = "__inflexaThemeReady";
288
26
  */
289
27
  export const PAGE_NAV_TIMEOUT_MS = 20_000;
290
28
  export const PAGE_READY_TIMEOUT_MS = 8_000;
29
+ /**
30
+ * The name of the reveal gate on the window object. The fade-in script installs the gate, and the chart
31
+ * bootstrap reads it. Both scripts read this one constant, thus the installer and the reader cannot
32
+ * disagree over a rename.
33
+ *
34
+ * The gate takes one callback. It runs the callback when the first reveal pass completes, and immediately
35
+ * when that pass already completed. A page that carries no gate runs the callback at once, thus the
36
+ * bootstrap stays correct on its own.
37
+ */
38
+ const REVEAL_GATE = "__inflexaWhenRevealed";
39
+ /**
40
+ * The budget of the wait for the first observer callback, in milliseconds.
41
+ *
42
+ * The browser delivers that callback in the first rendering update after the observer registers. A page
43
+ * that updates no rendering, for example a hidden tab, delivers nothing. The budget releases the readiness
44
+ * signal in that condition, thus such a page still signals and the capture returns on the signal.
45
+ */
46
+ const REVEAL_SETTLE_TIMEOUT_MS = 2_000;
291
47
  /**
292
48
  * The page-side script that wires each chart. It finds every chart container, reads the option JSON from
293
49
  * the sibling `<script type="application/json">` element, and initializes ECharts with the registered
@@ -300,6 +56,9 @@ export const PAGE_READY_TIMEOUT_MS = 8_000;
300
56
  * not at a timeout. The sentinel guards a listener that registers after the dispatch, thus a late listener
301
57
  * still resolves.
302
58
  *
59
+ * The signal passes through the reveal gate of the fade-in script. Thus the signal comes after the first
60
+ * reveal pass, and a capture reads a settled page instead of a page in the middle of a transition.
61
+ *
303
62
  * Each chart initializes inside a guard. One malformed option is exactly the fault that a look must
304
63
  * diagnose, thus it must never stop a sibling chart and it must never withhold the readiness signal.
305
64
  */
@@ -308,8 +67,16 @@ export const CHART_BOOTSTRAP = `(function () {
308
67
  window.${THEME_READY_SENTINEL} = true;
309
68
  document.dispatchEvent(new Event(${JSON.stringify(THEME_READY_EVENT)}));
310
69
  }
70
+ function whenRevealed(done) {
71
+ var gate = window.${REVEAL_GATE};
72
+ if (typeof gate === "function") {
73
+ gate(done);
74
+ return;
75
+ }
76
+ done();
77
+ }
311
78
  if (typeof echarts === "undefined") {
312
- signalReady();
79
+ whenRevealed(signalReady);
313
80
  return;
314
81
  }
315
82
  var containers = document.querySelectorAll("[data-echarts-id]");
@@ -336,5 +103,95 @@ export const CHART_BOOTSTRAP = `(function () {
336
103
  }
337
104
  }
338
105
  });
339
- signalReady();
106
+ whenRevealed(signalReady);
107
+ })();`;
108
+ /**
109
+ * The page-side script that reveals each `fade-in` element as it enters the viewport. The `data-delay`
110
+ * attribute holds a stagger in milliseconds, and the observer releases each element one time.
111
+ *
112
+ * The threshold is zero, thus one visible pixel reveals an element. A band is a whole top-level section,
113
+ * and a tall band can never cover a fraction of the viewport. A threshold above zero leaves such a band at
114
+ * zero opacity for the whole life of the page.
115
+ *
116
+ * The first callback of the observer is the first reveal pass. It reveals what the viewport already holds,
117
+ * and it drops the transition for that reveal with the `fade-in-instant` class. Thus the page settles in
118
+ * one frame. An element that arrives later keeps the transition and the stagger.
119
+ *
120
+ * The script installs the reveal gate on the window object before it observes anything. The chart bootstrap
121
+ * runs after this script and it signals readiness through that gate. Thus the readiness signal always comes
122
+ * after the first reveal pass. A page with no `fade-in` element, and a browser with no
123
+ * `IntersectionObserver`, opens the gate at once.
124
+ *
125
+ * A browser with no `IntersectionObserver` reveals every element at once. Thus the content is visible in
126
+ * each browser, and the reveal is decoration alone. The print rules and the reduced-motion rules show every
127
+ * element without this script.
128
+ */
129
+ export const FADE_IN_OBSERVER = `(function () {
130
+ var waiting = [];
131
+ var settled = false;
132
+ function settle() {
133
+ if (settled) {
134
+ return;
135
+ }
136
+ settled = true;
137
+ for (var w = 0; w < waiting.length; w++) {
138
+ waiting[w]();
139
+ }
140
+ waiting = [];
141
+ }
142
+ window.${REVEAL_GATE} = function (done) {
143
+ if (settled) {
144
+ done();
145
+ return;
146
+ }
147
+ waiting.push(done);
148
+ };
149
+ function reveal(node) {
150
+ node.classList.add("fade-in-visible");
151
+ }
152
+ function revealSettled(node) {
153
+ node.classList.add("fade-in-instant");
154
+ node.classList.add("fade-in-visible");
155
+ }
156
+ function start() {
157
+ var nodes = document.querySelectorAll(".fade-in");
158
+ if (typeof IntersectionObserver === "undefined" || nodes.length === 0) {
159
+ for (var i = 0; i < nodes.length; i++) {
160
+ revealSettled(nodes[i]);
161
+ }
162
+ settle();
163
+ return;
164
+ }
165
+ var firstPass = true;
166
+ var observer = new IntersectionObserver(function (entries) {
167
+ var instant = firstPass;
168
+ firstPass = false;
169
+ for (var j = 0; j < entries.length; j++) {
170
+ var entry = entries[j];
171
+ if (!entry.isIntersecting) {
172
+ continue;
173
+ }
174
+ var target = entry.target;
175
+ observer.unobserve(target);
176
+ if (instant) {
177
+ revealSettled(target);
178
+ continue;
179
+ }
180
+ var delay = parseInt(target.getAttribute("data-delay") || "0", 10) || 0;
181
+ setTimeout(reveal.bind(null, target), delay);
182
+ }
183
+ if (instant) {
184
+ settle();
185
+ }
186
+ }, { threshold: 0 });
187
+ for (var k = 0; k < nodes.length; k++) {
188
+ observer.observe(nodes[k]);
189
+ }
190
+ setTimeout(settle, ${REVEAL_SETTLE_TIMEOUT_MS});
191
+ }
192
+ if (document.readyState === "loading") {
193
+ document.addEventListener("DOMContentLoaded", start);
194
+ } else {
195
+ start();
196
+ }
340
197
  })();`;