executable-stories-formatters 0.16.0 → 1.0.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 +6 -4
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +183 -1
- package/dist/adapters.d.ts +183 -1
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +1945 -15988
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2353 -15773
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -1174
- package/dist/index.d.ts +127 -1174
- package/dist/index.js +2350 -15770
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/schemas/README.md +0 -1
- package/templates/astro-thin/astro.config.mjs +62 -0
- package/templates/astro-thin/executable-stories.config.mjs +47 -0
- package/templates/astro-thin/gitignore +3 -0
- package/templates/astro-thin/package.json +20 -0
- package/templates/astro-thin/reports/sample-run.json +133 -0
- package/templates/astro-thin/src/content/docs/404.md +20 -0
- package/templates/astro-thin/src/content/docs/guides/writing-docs.mdx +7 -0
- package/templates/astro-thin/src/content/docs/index.mdx +44 -0
- package/templates/astro-thin/src/content.config.ts +26 -0
- package/templates/astro-thin/src/styles/stories.css +32 -0
- package/templates/astro-thin/tsconfig.json +5 -0
- package/dist/index-CXrzCk9p.d.cts +0 -628
- package/dist/index-CXrzCk9p.d.ts +0 -628
- package/schemas/story-report-v1.json +0 -456
- package/templates/astro-starlight/astro.config.mjs +0 -57
- package/templates/astro-starlight/gitignore +0 -14
- package/templates/astro-starlight/package.json +0 -20
- package/templates/astro-starlight/public/stories/assets/.gitkeep +0 -0
- package/templates/astro-starlight/public/stories/notes-index.json +0 -4
- package/templates/astro-starlight/public/stories/story-report.json +0 -17
- package/templates/astro-starlight/src/components/ApiOperations.astro +0 -366
- package/templates/astro-starlight/src/components/Checklist.astro +0 -15
- package/templates/astro-starlight/src/components/HealthDashboard.astro +0 -171
- package/templates/astro-starlight/src/components/PageTitle.astro +0 -53
- package/templates/astro-starlight/src/components/VerifiedBy.astro +0 -281
- package/templates/astro-starlight/src/components/VerifiedStep.astro +0 -91
- package/templates/astro-starlight/src/content/docs/examples/example-adr.mdx +0 -45
- package/templates/astro-starlight/src/content/docs/guides/behavior-portal.mdx +0 -41
- package/templates/astro-starlight/src/content/docs/guides/writing-docs.mdx +0 -49
- package/templates/astro-starlight/src/content/docs/index.mdx +0 -49
- package/templates/astro-starlight/src/content/docs/stories/.gitkeep +0 -0
- package/templates/astro-starlight/src/content.config.ts +0 -18
- package/templates/astro-starlight/src/lib/config.ts +0 -50
- package/templates/astro-starlight/src/lib/render-doc-entry.ts +0 -154
- package/templates/astro-starlight/src/lib/report-health.ts +0 -61
- package/templates/astro-starlight/src/lib/verification.ts +0 -247
- package/templates/astro-starlight/src/pages/explorer/explorer.css +0 -729
- package/templates/astro-starlight/src/pages/explorer/index.astro +0 -404
- package/templates/astro-starlight/src/styles/global.css +0 -293
- package/templates/astro-starlight/src/styles/themes/corporate.css +0 -83
- package/templates/astro-starlight/src/styles/themes/dashboard.css +0 -76
- package/templates/astro-starlight/src/styles/themes/default.css +0 -86
- package/templates/astro-starlight/src/styles/themes/minimal.css +0 -87
- package/templates/astro-starlight/src/styles/themes/playful.css +0 -77
- package/templates/astro-starlight/src/styles/themes/terminal.css +0 -77
- package/templates/astro-starlight/tsconfig.json +0 -13
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { NOTES_INDEX_URL, REPORT_URL, SOURCE_BASE_URL } from "../../lib/config";
|
|
3
|
-
// Share the design tokens (--es-*) and theme palette (--sl-*) with the rest of
|
|
4
|
-
// the docs so the explorer reads as one product, not a bolted-on page.
|
|
5
|
-
import "../../styles/themes/default.css";
|
|
6
|
-
import "../../styles/global.css";
|
|
7
|
-
// Explorer styles live in a sibling stylesheet. A page-level CSS import is
|
|
8
|
-
// global, which the runtime-built list/detail markup needs (Astro's scoped
|
|
9
|
-
// styles never reach innerHTML-injected elements).
|
|
10
|
-
import "./explorer.css";
|
|
11
|
-
|
|
12
|
-
const reportPath = REPORT_URL;
|
|
13
|
-
const notesIndexPath = NOTES_INDEX_URL;
|
|
14
|
-
const sourceBaseUrl = SOURCE_BASE_URL;
|
|
15
|
-
const baseUrl = import.meta.env.BASE_URL ?? "/";
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
<!doctype html>
|
|
19
|
-
<html lang="en">
|
|
20
|
-
<head>
|
|
21
|
-
<meta charset="utf-8" />
|
|
22
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
23
|
-
<title>Scenario Explorer</title>
|
|
24
|
-
<script is:inline>
|
|
25
|
-
// Mirror the docs theme: Starlight persists the choice under this key.
|
|
26
|
-
try {
|
|
27
|
-
const t = localStorage.getItem("starlight-theme");
|
|
28
|
-
if (t === "light" || t === "dark") document.documentElement.dataset.theme = t;
|
|
29
|
-
} catch {}
|
|
30
|
-
</script>
|
|
31
|
-
</head>
|
|
32
|
-
<body class="es-tool">
|
|
33
|
-
<header class="topbar">
|
|
34
|
-
<a class="topbar__home" href={baseUrl}>Story Docs</a>
|
|
35
|
-
<span class="topbar__sep">/</span>
|
|
36
|
-
<span class="topbar__here">Scenario Explorer</span>
|
|
37
|
-
</header>
|
|
38
|
-
<main class="shell">
|
|
39
|
-
<aside class="sidebar" aria-label="Scenario navigation">
|
|
40
|
-
<div class="brand">
|
|
41
|
-
<h1>Scenario Explorer</h1>
|
|
42
|
-
<span class="meta" id="run-meta">Loading…</span>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="controls">
|
|
45
|
-
<input
|
|
46
|
-
class="search"
|
|
47
|
-
id="search"
|
|
48
|
-
type="search"
|
|
49
|
-
aria-label="Search scenarios, tags, or file paths"
|
|
50
|
-
placeholder="Search scenarios, tags, or file paths"
|
|
51
|
-
/>
|
|
52
|
-
<select class="select" id="status-filter" aria-label="Filter by status">
|
|
53
|
-
<option value="all">All statuses</option>
|
|
54
|
-
<option value="passed">Passed</option>
|
|
55
|
-
<option value="failed">Failed</option>
|
|
56
|
-
<option value="skipped">Skipped</option>
|
|
57
|
-
<option value="pending">Pending</option>
|
|
58
|
-
</select>
|
|
59
|
-
</div>
|
|
60
|
-
<div class="summary" id="summary" aria-label="Run summary"></div>
|
|
61
|
-
<div class="scenario-list" id="scenario-list"></div>
|
|
62
|
-
</aside>
|
|
63
|
-
<section class="content">
|
|
64
|
-
<article class="scenario-card" id="scenario-detail">
|
|
65
|
-
<div class="empty">Load <code>story-report.json</code> to browse executable stories.</div>
|
|
66
|
-
</article>
|
|
67
|
-
</section>
|
|
68
|
-
</main>
|
|
69
|
-
|
|
70
|
-
<script is:inline define:vars={{ reportPath, notesIndexPath, sourceBaseUrl, baseUrl }}>
|
|
71
|
-
window.__EXPLORER_CONFIG__ = { reportPath, notesIndexPath, sourceBaseUrl, baseUrl };
|
|
72
|
-
</script>
|
|
73
|
-
<script>
|
|
74
|
-
// Rendering libraries are bundled with the site (no CDN), so the Explorer
|
|
75
|
-
// works offline / air-gapped — the enterprise "Confluence replacement"
|
|
76
|
-
// case. Code highlighting, section markdown, and live diagrams all render
|
|
77
|
-
// locally.
|
|
78
|
-
import { marked } from "marked";
|
|
79
|
-
import hljs from "highlight.js/lib/common";
|
|
80
|
-
import mermaid from "mermaid";
|
|
81
|
-
// Pure, unit-tested doc-entry → HTML renderer.
|
|
82
|
-
import { renderDocs, escapeHtml } from "../../lib/render-doc-entry";
|
|
83
|
-
|
|
84
|
-
const { reportPath, notesIndexPath, sourceBaseUrl, baseUrl } = window.__EXPLORER_CONFIG__ ?? {
|
|
85
|
-
reportPath: "/stories/story-report.json",
|
|
86
|
-
notesIndexPath: "/stories/notes-index.json",
|
|
87
|
-
sourceBaseUrl: "",
|
|
88
|
-
baseUrl: "/",
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const libs = { marked, hljs, mermaid };
|
|
92
|
-
{
|
|
93
|
-
const dark =
|
|
94
|
-
document.documentElement.dataset.theme === "dark" ||
|
|
95
|
-
(!document.documentElement.dataset.theme &&
|
|
96
|
-
matchMedia("(prefers-color-scheme: dark)").matches);
|
|
97
|
-
mermaid.initialize({ startOnLoad: false, theme: dark ? "dark" : "default", securityLevel: "loose" });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const state = {
|
|
101
|
-
report: null,
|
|
102
|
-
scenarios: [],
|
|
103
|
-
notesByScenarioId: {},
|
|
104
|
-
selectedId: "",
|
|
105
|
-
query: "",
|
|
106
|
-
status: "all",
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const elements = {
|
|
110
|
-
meta: document.querySelector("#run-meta"),
|
|
111
|
-
search: document.querySelector("#search"),
|
|
112
|
-
status: document.querySelector("#status-filter"),
|
|
113
|
-
summary: document.querySelector("#summary"),
|
|
114
|
-
list: document.querySelector("#scenario-list"),
|
|
115
|
-
detail: document.querySelector("#scenario-detail"),
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const statusClass = (status) => `status status-${status}`;
|
|
119
|
-
const withBase = (value) => {
|
|
120
|
-
const base = (baseUrl || "/").replace(/\/$/, "");
|
|
121
|
-
const normalized = String(value || "").replace(/^\/+/, "");
|
|
122
|
-
return base ? `${base}/${normalized}` : `/${normalized}`;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const flattenReport = (report) =>
|
|
126
|
-
(report.features ?? []).flatMap((feature) =>
|
|
127
|
-
(feature.scenarios ?? []).map((scenario) => ({
|
|
128
|
-
...scenario,
|
|
129
|
-
featureTitle: feature.title,
|
|
130
|
-
sourceFile: scenario.sourceFile ?? feature.sourceFile,
|
|
131
|
-
})),
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const filteredScenarios = () => {
|
|
135
|
-
const q = state.query.trim().toLowerCase();
|
|
136
|
-
return state.scenarios.filter((scenario) => {
|
|
137
|
-
const matchesStatus = state.status === "all" || scenario.status === state.status;
|
|
138
|
-
const haystack = [
|
|
139
|
-
scenario.title,
|
|
140
|
-
scenario.featureTitle,
|
|
141
|
-
scenario.sourceFile,
|
|
142
|
-
...(scenario.tags ?? []),
|
|
143
|
-
...(scenario.covers ?? []),
|
|
144
|
-
]
|
|
145
|
-
.join(" ")
|
|
146
|
-
.toLowerCase();
|
|
147
|
-
return matchesStatus && (!q || haystack.includes(q));
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const renderSummary = () => {
|
|
152
|
-
const summary = state.report?.summary ?? {
|
|
153
|
-
total: 0,
|
|
154
|
-
passed: 0,
|
|
155
|
-
failed: 0,
|
|
156
|
-
skipped: 0,
|
|
157
|
-
pending: 0,
|
|
158
|
-
};
|
|
159
|
-
elements.summary.innerHTML = ["total", "passed", "failed", "skipped", "pending"]
|
|
160
|
-
.map(
|
|
161
|
-
(key) => `
|
|
162
|
-
<div class="metric metric--${key}">
|
|
163
|
-
<strong>${summary[key] ?? 0}</strong>
|
|
164
|
-
<span>${key}</span>
|
|
165
|
-
</div>
|
|
166
|
-
`,
|
|
167
|
-
)
|
|
168
|
-
.join("");
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const renderList = () => {
|
|
172
|
-
const scenarios = filteredScenarios();
|
|
173
|
-
if (!state.selectedId && scenarios[0]) state.selectedId = scenarios[0].id;
|
|
174
|
-
|
|
175
|
-
elements.list.innerHTML = scenarios
|
|
176
|
-
.map(
|
|
177
|
-
(scenario) => `
|
|
178
|
-
<button class="scenario-button" type="button" data-scenario-id="${escapeHtml(
|
|
179
|
-
scenario.id,
|
|
180
|
-
)}" aria-current="${scenario.id === state.selectedId}">
|
|
181
|
-
<span class="row-dot dot-${escapeHtml(scenario.status)}"></span>
|
|
182
|
-
<span class="scenario-title">${escapeHtml(scenario.title)}</span>
|
|
183
|
-
${
|
|
184
|
-
state.notesByScenarioId[scenario.id]
|
|
185
|
-
? '<span class="scenario-context-flag">Context</span>'
|
|
186
|
-
: ""
|
|
187
|
-
}
|
|
188
|
-
<span class="scenario-source">${escapeHtml(scenario.sourceFile ?? "")}${
|
|
189
|
-
scenario.sourceLine ? `:${scenario.sourceLine}` : ""
|
|
190
|
-
}</span>
|
|
191
|
-
</button>
|
|
192
|
-
`,
|
|
193
|
-
)
|
|
194
|
-
.join("");
|
|
195
|
-
|
|
196
|
-
elements.list.querySelectorAll("[data-scenario-id]").forEach((button) => {
|
|
197
|
-
button.addEventListener("click", () => {
|
|
198
|
-
// pushState so the browser Back button steps through visited
|
|
199
|
-
// scenarios instead of ejecting you from the Explorer.
|
|
200
|
-
selectScenario(button.dataset.scenarioId, { push: true });
|
|
201
|
-
const smooth = !matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
202
|
-
elements.detail.scrollIntoView({ block: "start", behavior: smooth ? "smooth" : "auto" });
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// renderDocs / renderDocEntry are imported from ../../lib/render-doc-entry.
|
|
208
|
-
// Run the rendering libraries over the freshly-inserted markup.
|
|
209
|
-
const hydrateDocs = async () => {
|
|
210
|
-
const root = elements.detail;
|
|
211
|
-
if (libs.marked) {
|
|
212
|
-
root.querySelectorAll(".doc-section-content[data-md]").forEach((el) => {
|
|
213
|
-
try {
|
|
214
|
-
el.innerHTML = libs.marked.parse(decodeURIComponent(el.dataset.md));
|
|
215
|
-
el.removeAttribute("data-md");
|
|
216
|
-
} catch {}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
if (libs.hljs) {
|
|
220
|
-
root.querySelectorAll("pre code[class^='language-']").forEach((el) => {
|
|
221
|
-
try {
|
|
222
|
-
libs.hljs.highlightElement(el);
|
|
223
|
-
} catch {}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
if (libs.mermaid) {
|
|
227
|
-
const nodes = [...root.querySelectorAll("pre.mermaid:not([data-processed])")];
|
|
228
|
-
if (nodes.length) {
|
|
229
|
-
try {
|
|
230
|
-
await libs.mermaid.run({ nodes });
|
|
231
|
-
} catch {}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Flag broken images so the layout doesn't show a torn-image icon.
|
|
235
|
-
root.querySelectorAll(".doc-media img").forEach((img) => {
|
|
236
|
-
img.addEventListener("error", () => {
|
|
237
|
-
const wrap = img.closest(".doc-media");
|
|
238
|
-
if (wrap)
|
|
239
|
-
wrap.outerHTML = `<div class="doc-media doc-media--broken">Image unavailable<code>${escapeHtml(
|
|
240
|
-
img.getAttribute("src") ?? "",
|
|
241
|
-
)}</code></div>`;
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const sourceMarkup = (scenario) => {
|
|
247
|
-
const path = scenario.sourceFile;
|
|
248
|
-
if (!path) return "";
|
|
249
|
-
const loc = `${escapeHtml(path)}${scenario.sourceLine ? `:${scenario.sourceLine}` : ""}`;
|
|
250
|
-
if (sourceBaseUrl) {
|
|
251
|
-
const base = sourceBaseUrl.replace(/\/$/, "");
|
|
252
|
-
const href = `${base}/${path}${scenario.sourceLine ? `#L${scenario.sourceLine}` : ""}`;
|
|
253
|
-
return `<a class="source-link" href="${escapeHtml(
|
|
254
|
-
href,
|
|
255
|
-
)}" target="_blank" rel="noopener noreferrer">${loc}</a>`;
|
|
256
|
-
}
|
|
257
|
-
return `<span class="source-text">${loc}</span>`;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const renderDetail = () => {
|
|
261
|
-
const scenario =
|
|
262
|
-
state.scenarios.find((item) => item.id === state.selectedId) ?? filteredScenarios()[0];
|
|
263
|
-
|
|
264
|
-
if (!scenario) {
|
|
265
|
-
elements.detail.innerHTML = '<div class="empty">No scenarios match current filters.</div>';
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const note = state.notesByScenarioId[scenario.id];
|
|
270
|
-
state.selectedId = scenario.id;
|
|
271
|
-
|
|
272
|
-
elements.detail.innerHTML = `
|
|
273
|
-
<header class="scenario-heading">
|
|
274
|
-
<span class="${statusClass(scenario.status)}">${escapeHtml(scenario.status)}</span>
|
|
275
|
-
<h2>${escapeHtml(scenario.title)}</h2>
|
|
276
|
-
<div class="feature-line">
|
|
277
|
-
${scenario.featureTitle ? `<span>${escapeHtml(scenario.featureTitle)}</span><span class="sep">·</span>` : ""}
|
|
278
|
-
${sourceMarkup(scenario)}
|
|
279
|
-
</div>
|
|
280
|
-
${
|
|
281
|
-
note
|
|
282
|
-
? `<div class="context-row"><a class="context-link" href="${escapeHtml(
|
|
283
|
-
withBase(`notes/${note.slug}/`),
|
|
284
|
-
)}">Business context →</a></div>`
|
|
285
|
-
: ""
|
|
286
|
-
}
|
|
287
|
-
${
|
|
288
|
-
(scenario.tags ?? []).length
|
|
289
|
-
? `<div class="tag-row">${scenario.tags
|
|
290
|
-
.map((tag) => `<span class="pill">#${escapeHtml(tag)}</span>`)
|
|
291
|
-
.join("")}</div>`
|
|
292
|
-
: ""
|
|
293
|
-
}
|
|
294
|
-
${
|
|
295
|
-
(scenario.covers ?? []).length
|
|
296
|
-
? `<div class="covers-row">Covers ${scenario.covers
|
|
297
|
-
.map((p) => `<code class="covers-path">${escapeHtml(p)}</code>`)
|
|
298
|
-
.join(" ")}</div>`
|
|
299
|
-
: ""
|
|
300
|
-
}
|
|
301
|
-
</header>
|
|
302
|
-
<section class="section">
|
|
303
|
-
<h3>Steps</h3>
|
|
304
|
-
<ol class="steps">
|
|
305
|
-
${(scenario.steps ?? [])
|
|
306
|
-
.map(
|
|
307
|
-
(step) => `
|
|
308
|
-
<li class="step step--${escapeHtml(step.status)}">
|
|
309
|
-
<span class="keyword">${escapeHtml(step.keyword)}</span>
|
|
310
|
-
<span class="step-text">${escapeHtml(step.text)}</span>
|
|
311
|
-
<span class="step-status">${escapeHtml(step.status)}</span>
|
|
312
|
-
</li>
|
|
313
|
-
`,
|
|
314
|
-
)
|
|
315
|
-
.join("")}
|
|
316
|
-
</ol>
|
|
317
|
-
</section>
|
|
318
|
-
<section class="section">
|
|
319
|
-
<h3>Docs</h3>
|
|
320
|
-
<div class="doc-grid">${renderDocs(scenario.docEntries)}</div>
|
|
321
|
-
</section>
|
|
322
|
-
${
|
|
323
|
-
scenario.errorMessage
|
|
324
|
-
? `<section class="section"><h3>Error</h3><pre class="error">${escapeHtml(
|
|
325
|
-
scenario.errorMessage,
|
|
326
|
-
)}</pre></section>`
|
|
327
|
-
: ""
|
|
328
|
-
}
|
|
329
|
-
`;
|
|
330
|
-
hydrateDocs();
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const render = () => {
|
|
334
|
-
renderSummary();
|
|
335
|
-
renderList();
|
|
336
|
-
renderDetail();
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
// Select a scenario and reflect it in the URL. `push` adds a history entry
|
|
340
|
-
// (clicks) so Back works; otherwise we replace (initial load / popstate).
|
|
341
|
-
const selectScenario = (id, { push = false } = {}) => {
|
|
342
|
-
if (!id) return;
|
|
343
|
-
state.selectedId = id;
|
|
344
|
-
const hash = `#${encodeURIComponent(id)}`;
|
|
345
|
-
if (location.hash !== hash) {
|
|
346
|
-
if (push) history.pushState(null, "", hash);
|
|
347
|
-
else history.replaceState(null, "", hash);
|
|
348
|
-
}
|
|
349
|
-
render();
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
// Apply whatever scenario the URL hash points at (deep-link, Back/Forward,
|
|
353
|
-
// or a hash change while already on the page).
|
|
354
|
-
const selectFromHash = () => {
|
|
355
|
-
const id = decodeURIComponent(location.hash.slice(1));
|
|
356
|
-
if (id && state.scenarios.some((s) => s.id === id)) {
|
|
357
|
-
state.selectedId = id;
|
|
358
|
-
}
|
|
359
|
-
render();
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
window.addEventListener("hashchange", selectFromHash);
|
|
363
|
-
window.addEventListener("popstate", selectFromHash);
|
|
364
|
-
|
|
365
|
-
elements.search.addEventListener("input", (event) => {
|
|
366
|
-
state.query = event.target.value;
|
|
367
|
-
state.selectedId = "";
|
|
368
|
-
render();
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
elements.status.addEventListener("change", (event) => {
|
|
372
|
-
state.status = event.target.value;
|
|
373
|
-
state.selectedId = "";
|
|
374
|
-
render();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const loadJson = (url, { allowMissing = false } = {}) =>
|
|
378
|
-
fetch(url).then((response) => {
|
|
379
|
-
if (!response.ok) {
|
|
380
|
-
if (allowMissing && response.status === 404) return null;
|
|
381
|
-
throw new Error(`Could not load ${url}`);
|
|
382
|
-
}
|
|
383
|
-
return response.json();
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
Promise.all([loadJson(reportPath), loadJson(notesIndexPath, { allowMissing: true })])
|
|
387
|
-
.then(([report, notesIndex]) => {
|
|
388
|
-
state.report = report;
|
|
389
|
-
state.scenarios = flattenReport(report);
|
|
390
|
-
state.notesByScenarioId = Object.fromEntries(
|
|
391
|
-
(notesIndex?.notes ?? []).map((note) => [note.scenarioId, note]),
|
|
392
|
-
);
|
|
393
|
-
state.selectedId = decodeURIComponent(location.hash.slice(1));
|
|
394
|
-
elements.meta.textContent = `${report.summary.total} scenarios`;
|
|
395
|
-
render();
|
|
396
|
-
})
|
|
397
|
-
.catch((error) => {
|
|
398
|
-
elements.meta.textContent = "No report";
|
|
399
|
-
elements.detail.innerHTML = `<pre class="error">${escapeHtml(error.message)}</pre>`;
|
|
400
|
-
renderSummary();
|
|
401
|
-
});
|
|
402
|
-
</script>
|
|
403
|
-
</body>
|
|
404
|
-
</html>
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Shared component overrides — applies to all themes.
|
|
3
|
-
* Color/font variables come from the theme CSS file (themes/*.css).
|
|
4
|
-
* Import in astro.config.mjs: customCss: ['./src/styles/global.css', './src/styles/themes/default.css']
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/* ============================================================================
|
|
8
|
-
Design tokens (--es-*)
|
|
9
|
-
----------------------------------------------------------------------------
|
|
10
|
-
The single source of truth for the custom components (VerifiedBy badge,
|
|
11
|
-
API operations table, Scenario Explorer). Derived from Starlight's semantic
|
|
12
|
-
--sl-color-* / font variables so they adapt to whichever theme is active and
|
|
13
|
-
flip correctly between light and dark. Status + HTTP-method hues are fixed
|
|
14
|
-
(they carry meaning); their "soft" fills are mixed into the page background
|
|
15
|
-
with color-mix, so they read correctly in both modes from one definition.
|
|
16
|
-
============================================================================ */
|
|
17
|
-
|
|
18
|
-
:root {
|
|
19
|
-
/* Surfaces & structure */
|
|
20
|
-
--es-bg: var(--sl-color-black);
|
|
21
|
-
--es-surface: var(--sl-color-gray-7);
|
|
22
|
-
--es-surface-2: var(--sl-color-gray-6);
|
|
23
|
-
--es-line: var(--sl-color-gray-5);
|
|
24
|
-
--es-line-strong: var(--sl-color-gray-4);
|
|
25
|
-
--es-text: var(--sl-color-white);
|
|
26
|
-
--es-text-soft: var(--sl-color-gray-1);
|
|
27
|
-
--es-text-muted: var(--sl-color-gray-3);
|
|
28
|
-
|
|
29
|
-
--es-accent: var(--sl-color-accent);
|
|
30
|
-
--es-accent-high: var(--sl-color-accent-high);
|
|
31
|
-
--es-accent-soft: var(--sl-color-accent-low);
|
|
32
|
-
|
|
33
|
-
--es-mono: var(--sl-font-mono);
|
|
34
|
-
--es-radius: 0.625rem;
|
|
35
|
-
--es-radius-sm: 0.375rem;
|
|
36
|
-
--es-radius-pill: 999px;
|
|
37
|
-
--es-shadow: var(--sl-shadow-lg);
|
|
38
|
-
--es-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06), 0 1px 1px rgb(0 0 0 / 0.04);
|
|
39
|
-
|
|
40
|
-
/* Status hues (meaningful, theme-independent) */
|
|
41
|
-
--es-passed: hsl(145 63% 38%);
|
|
42
|
-
--es-failed: hsl(0 72% 48%);
|
|
43
|
-
--es-skipped: hsl(38 92% 40%);
|
|
44
|
-
--es-pending: hsl(262 55% 55%);
|
|
45
|
-
--es-unverified: hsl(38 92% 40%);
|
|
46
|
-
|
|
47
|
-
/* Soft status fills — mix the hue into the current background */
|
|
48
|
-
--es-passed-soft: color-mix(in oklab, var(--es-passed) 12%, var(--es-bg));
|
|
49
|
-
--es-failed-soft: color-mix(in oklab, var(--es-failed) 12%, var(--es-bg));
|
|
50
|
-
--es-skipped-soft: color-mix(in oklab, var(--es-skipped) 12%, var(--es-bg));
|
|
51
|
-
--es-pending-soft: color-mix(in oklab, var(--es-pending) 12%, var(--es-bg));
|
|
52
|
-
|
|
53
|
-
/* HTTP method hues (API operations) */
|
|
54
|
-
--es-method-get: hsl(145 63% 38%);
|
|
55
|
-
--es-method-post: hsl(212 80% 48%);
|
|
56
|
-
--es-method-put: hsl(33 92% 44%);
|
|
57
|
-
--es-method-patch: hsl(280 55% 54%);
|
|
58
|
-
--es-method-delete: hsl(0 72% 48%);
|
|
59
|
-
--es-method-default: var(--sl-color-gray-3);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/* Brighten status hues a touch in dark mode for legibility on dark fills. */
|
|
63
|
-
[data-theme='dark'] {
|
|
64
|
-
--es-passed: hsl(145 60% 52%);
|
|
65
|
-
--es-failed: hsl(0 78% 64%);
|
|
66
|
-
--es-skipped: hsl(38 92% 56%);
|
|
67
|
-
--es-pending: hsl(262 70% 72%);
|
|
68
|
-
--es-unverified: hsl(38 92% 56%);
|
|
69
|
-
--es-method-get: hsl(145 60% 52%);
|
|
70
|
-
--es-method-post: hsl(212 85% 66%);
|
|
71
|
-
--es-method-put: hsl(38 92% 58%);
|
|
72
|
-
--es-method-patch: hsl(280 70% 72%);
|
|
73
|
-
--es-method-delete: hsl(0 78% 66%);
|
|
74
|
-
}
|
|
75
|
-
@media (prefers-color-scheme: dark) {
|
|
76
|
-
:root:not([data-theme='light']) {
|
|
77
|
-
--es-passed: hsl(145 60% 52%);
|
|
78
|
-
--es-failed: hsl(0 78% 64%);
|
|
79
|
-
--es-skipped: hsl(38 92% 56%);
|
|
80
|
-
--es-pending: hsl(262 70% 72%);
|
|
81
|
-
--es-unverified: hsl(38 92% 56%);
|
|
82
|
-
--es-method-get: hsl(145 60% 52%);
|
|
83
|
-
--es-method-post: hsl(212 85% 66%);
|
|
84
|
-
--es-method-put: hsl(38 92% 58%);
|
|
85
|
-
--es-method-patch: hsl(280 70% 72%);
|
|
86
|
-
--es-method-delete: hsl(0 78% 66%);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/* ============================================================================
|
|
91
|
-
Typography
|
|
92
|
-
============================================================================ */
|
|
93
|
-
|
|
94
|
-
.sl-markdown-content h1,
|
|
95
|
-
.sl-markdown-content h2,
|
|
96
|
-
.sl-markdown-content h3,
|
|
97
|
-
.sl-markdown-content h4 {
|
|
98
|
-
letter-spacing: -0.02em;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.sl-markdown-content > p,
|
|
102
|
-
.sl-markdown-content > ul,
|
|
103
|
-
.sl-markdown-content > ol {
|
|
104
|
-
max-width: 72ch;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* ============================================================================
|
|
108
|
-
Header / Nav
|
|
109
|
-
============================================================================ */
|
|
110
|
-
|
|
111
|
-
.site-title {
|
|
112
|
-
font-weight: 700 !important;
|
|
113
|
-
letter-spacing: -0.02em;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.site-title span {
|
|
117
|
-
color: var(--sl-color-text) !important;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.site-title img {
|
|
121
|
-
width: 1.75rem !important;
|
|
122
|
-
height: 1.75rem !important;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
header.header {
|
|
126
|
-
border-bottom: 1px solid var(--sl-color-hairline) !important;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* ============================================================================
|
|
130
|
-
Sidebar
|
|
131
|
-
============================================================================ */
|
|
132
|
-
|
|
133
|
-
nav.sidebar {
|
|
134
|
-
border-right: 1px solid var(--sl-color-gray-3);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
nav.sidebar a[aria-current='page'] {
|
|
138
|
-
border-left: 2px solid var(--sl-color-accent);
|
|
139
|
-
background: var(--sl-color-accent-low);
|
|
140
|
-
font-weight: 600;
|
|
141
|
-
color: var(--sl-color-accent) !important;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
nav.sidebar a {
|
|
145
|
-
transition: background-color 150ms ease, color 150ms ease;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
nav.sidebar a:hover {
|
|
149
|
-
background: var(--sl-color-accent-low);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* ============================================================================
|
|
153
|
-
Code blocks — theme-aware via Starlight variables
|
|
154
|
-
============================================================================ */
|
|
155
|
-
|
|
156
|
-
.expressive-code .frame {
|
|
157
|
-
border-radius: 0.5rem !important;
|
|
158
|
-
border: 1px solid var(--sl-color-gray-5) !important;
|
|
159
|
-
box-shadow: none !important;
|
|
160
|
-
overflow: hidden;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.expressive-code pre {
|
|
164
|
-
border-radius: 0 !important;
|
|
165
|
-
border: none !important;
|
|
166
|
-
padding: 1rem 1.25rem !important;
|
|
167
|
-
font-family: var(--sl-font-mono);
|
|
168
|
-
font-size: 0.875rem !important;
|
|
169
|
-
line-height: 1.7 !important;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.expressive-code .header {
|
|
173
|
-
border-bottom: 1px solid var(--sl-color-gray-5) !important;
|
|
174
|
-
padding: 8px 14px !important;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.expressive-code .header .title {
|
|
178
|
-
font-size: 0.75rem !important;
|
|
179
|
-
font-family: var(--sl-font-mono) !important;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/* Inline code */
|
|
183
|
-
.sl-markdown-content :not(pre) > code {
|
|
184
|
-
background: var(--sl-color-gray-6);
|
|
185
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
186
|
-
border-radius: 0.25rem;
|
|
187
|
-
padding: 0.125em 0.375em;
|
|
188
|
-
font-size: 0.85em;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* ============================================================================
|
|
192
|
-
Status badges — match report status colors
|
|
193
|
-
Starlight renders <span class="sl-badge success">, so override via the
|
|
194
|
-
--sl-badge-*-bg/border/text CSS custom properties it already defines.
|
|
195
|
-
============================================================================ */
|
|
196
|
-
|
|
197
|
-
:root {
|
|
198
|
-
--sl-badge-success-bg: hsl(145 63% 42%);
|
|
199
|
-
--sl-badge-success-border: hsl(145 63% 35%);
|
|
200
|
-
--sl-badge-success-text: #fff;
|
|
201
|
-
|
|
202
|
-
--sl-badge-danger-bg: hsl(0 72% 51%);
|
|
203
|
-
--sl-badge-danger-border: hsl(0 72% 42%);
|
|
204
|
-
--sl-badge-danger-text: #fff;
|
|
205
|
-
|
|
206
|
-
--sl-badge-caution-bg: hsl(38 92% 50%);
|
|
207
|
-
--sl-badge-caution-border: hsl(38 92% 40%);
|
|
208
|
-
--sl-badge-caution-text: #fff;
|
|
209
|
-
|
|
210
|
-
--sl-badge-note-bg: hsl(262 60% 55%);
|
|
211
|
-
--sl-badge-note-border: hsl(262 60% 45%);
|
|
212
|
-
--sl-badge-note-text: #fff;
|
|
213
|
-
|
|
214
|
-
--sl-badge-tip-bg: hsl(145 63% 42%);
|
|
215
|
-
--sl-badge-tip-border: hsl(145 63% 35%);
|
|
216
|
-
--sl-badge-tip-text: #fff;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
[data-theme='light']:root {
|
|
220
|
-
--sl-badge-success-bg: hsl(145 63% 42%);
|
|
221
|
-
--sl-badge-danger-bg: hsl(0 72% 51%);
|
|
222
|
-
--sl-badge-caution-bg: hsl(38 92% 50%);
|
|
223
|
-
--sl-badge-note-bg: hsl(262 60% 55%);
|
|
224
|
-
--sl-badge-tip-bg: hsl(145 63% 42%);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* ============================================================================
|
|
228
|
-
Cards / content areas
|
|
229
|
-
============================================================================ */
|
|
230
|
-
|
|
231
|
-
.sl-markdown-content details {
|
|
232
|
-
border: 1px solid var(--sl-color-gray-3);
|
|
233
|
-
border-radius: 0.5rem;
|
|
234
|
-
background: var(--sl-color-gray-1);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.sl-markdown-content details[open] > summary {
|
|
238
|
-
border-bottom: 1px solid var(--sl-color-gray-3);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.sl-markdown-content summary {
|
|
242
|
-
font-weight: 500;
|
|
243
|
-
cursor: pointer;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/* ============================================================================
|
|
247
|
-
Tables
|
|
248
|
-
============================================================================ */
|
|
249
|
-
|
|
250
|
-
.sl-markdown-content table {
|
|
251
|
-
border-collapse: collapse;
|
|
252
|
-
width: 100%;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.sl-markdown-content th {
|
|
256
|
-
background: var(--sl-color-gray-2);
|
|
257
|
-
font-weight: 600;
|
|
258
|
-
font-size: 0.8125rem;
|
|
259
|
-
text-transform: uppercase;
|
|
260
|
-
letter-spacing: 0.05em;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.sl-markdown-content th,
|
|
264
|
-
.sl-markdown-content td {
|
|
265
|
-
border: 1px solid var(--sl-color-gray-3);
|
|
266
|
-
padding: 0.5rem 0.75rem;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/* ============================================================================
|
|
270
|
-
Links
|
|
271
|
-
============================================================================ */
|
|
272
|
-
|
|
273
|
-
.sl-markdown-content a {
|
|
274
|
-
color: var(--sl-color-accent);
|
|
275
|
-
text-decoration: underline;
|
|
276
|
-
text-underline-offset: 2px;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.sl-markdown-content a:hover {
|
|
280
|
-
color: var(--sl-color-accent-high);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* ============================================================================
|
|
284
|
-
Search dialog
|
|
285
|
-
============================================================================ */
|
|
286
|
-
|
|
287
|
-
[is='search-dialog']::part(result-link):hover {
|
|
288
|
-
background: var(--sl-color-accent-low);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
[is='search-dialog']::part(result-link)[aria-selected='true'] {
|
|
292
|
-
color: var(--sl-color-accent);
|
|
293
|
-
}
|