@anselmdk/feature-spec-md 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,243 @@
1
+ import { appendFile } from "node:fs/promises";
2
+ import { ftpConnectionConfig, listRemoteDirectory, pathJoin, runCurl, } from "./githubActionFtp.js";
3
+ export async function runFtpMaintenance(options) {
4
+ const config = ftpConnectionConfig(options);
5
+ const mode = maintenanceMode(options.mode ?? process.env.FEATURE_SPEC_FTP_MODE ?? "report");
6
+ const keepBuilds = positiveInteger(options["keep-builds"] ?? process.env.FEATURE_SPEC_FTP_KEEP_BUILDS, 10);
7
+ const requestedPaths = csv(options.paths ?? process.env.FEATURE_SPEC_FTP_PATHS);
8
+ const inventory = await inventoryRemoteDirectory(config.remoteDir, config);
9
+ const freeBytes = await remoteFreeBytes(config.remoteDir, config);
10
+ const cleanupPaths = mode === "report"
11
+ ? []
12
+ : cleanupCandidates(inventory, config.remoteDir, keepBuilds, requestedPaths);
13
+ if (mode === "cleanup" && !requestedPaths.length && keepBuilds < 1) {
14
+ throw new Error("Cleanup requires --keep-builds of at least 1 or --paths.");
15
+ }
16
+ if (mode === "cleanup") {
17
+ await deletePaths(cleanupPaths, inventory, config);
18
+ }
19
+ const afterInventory = mode === "cleanup"
20
+ ? await inventoryRemoteDirectory(config.remoteDir, config)
21
+ : inventory;
22
+ const summary = formatSummary({
23
+ config,
24
+ mode,
25
+ keepBuilds,
26
+ requestedPaths,
27
+ inventory,
28
+ cleanupPaths,
29
+ freeBytes,
30
+ afterInventory,
31
+ });
32
+ await writeSummary(summary, options);
33
+ console.log(summary);
34
+ return { summary, inventory, cleanupPaths, freeBytes };
35
+ }
36
+ export function parseFtpSizeResponse(response) {
37
+ const match = response.match(/(?:^|\s)(\d+)\s*$/m);
38
+ return match ? Number(match[1]) : undefined;
39
+ }
40
+ export function parseFtpHeadSize(response) {
41
+ const match = response.match(/^Size:\s*(\d+)\s*$/im);
42
+ return match ? Number(match[1]) : parseFtpSizeResponse(response);
43
+ }
44
+ async function inventoryRemoteDirectory(remoteDir, config) {
45
+ const entries = [];
46
+ await visit(remoteDir);
47
+ return entries.sort((a, b) => a.path.localeCompare(b.path));
48
+ async function visit(directory) {
49
+ const listing = await listRemoteDirectory(directory, config);
50
+ const names = listing
51
+ .split(/\r?\n/)
52
+ .map((line) => line.trim())
53
+ .filter(Boolean)
54
+ .map((line) => line.split(/\s+/).at(-1) ?? "")
55
+ .filter((name) => name && name !== "." && name !== "..");
56
+ for (const name of new Set(names)) {
57
+ const child = pathJoin(directory, name);
58
+ try {
59
+ await listRemoteDirectory(child, config);
60
+ entries.push({ path: child, kind: "directory", sizeBytes: 0 });
61
+ await visit(child);
62
+ }
63
+ catch {
64
+ const sizeBytes = (await remoteFileSize(child, config)) ?? 0;
65
+ entries.push({ path: child, kind: "file", sizeBytes });
66
+ }
67
+ }
68
+ }
69
+ }
70
+ async function remoteFileSize(remotePath, config) {
71
+ try {
72
+ const response = await runCurl(ftpArgs(config, ["--head"], remotePath));
73
+ return parseFtpHeadSize(response);
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ async function remoteFreeBytes(remoteDir, config) {
80
+ for (const command of ["SITE AVAIL", "STAT -f"]) {
81
+ try {
82
+ const response = await runCurl(ftpArgs(config, ["--quote", command, "--head"], remoteDir));
83
+ const size = parseFtpSizeResponse(response);
84
+ if (size !== undefined)
85
+ return size;
86
+ }
87
+ catch {
88
+ // FTP has no portable free-space command; unsupported servers are normal.
89
+ }
90
+ }
91
+ return undefined;
92
+ }
93
+ function cleanupCandidates(inventory, root, keepBuilds, requestedPaths) {
94
+ const exact = requestedPaths.map((item) => safeChildPath(root, item));
95
+ const buildRoot = pathJoin(root, "build");
96
+ const builds = inventory
97
+ .filter((entry) => entry.kind === "directory" && entry.path.startsWith(`${buildRoot}/`))
98
+ .map((entry) => entry.path.slice(buildRoot.length + 1).split("/")[0])
99
+ .filter((name) => /^\d+$/.test(name));
100
+ const oldBuilds = Array.from(new Set(builds))
101
+ .sort((a, b) => Number(b) - Number(a))
102
+ .slice(keepBuilds)
103
+ .map((name) => pathJoin(buildRoot, name));
104
+ return Array.from(new Set([...oldBuilds, ...exact]));
105
+ }
106
+ async function deletePaths(paths, inventory, config) {
107
+ for (const target of paths) {
108
+ const descendants = inventory
109
+ .filter((entry) => entry.path === target || entry.path.startsWith(`${target}/`))
110
+ .sort((a, b) => b.path.length - a.path.length);
111
+ for (const entry of descendants) {
112
+ await deleteRemote(entry.path, entry.kind, config);
113
+ }
114
+ if (!descendants.length) {
115
+ await deleteRemote(target, "file", config);
116
+ }
117
+ }
118
+ }
119
+ async function deleteRemote(remotePath, kind, config) {
120
+ const command = kind === "directory" ? "RMD" : "DELE";
121
+ await runCurl(ftpArgs(config, ["--quote", `${command} ${remotePath}`, "--list-only"], config.remoteDir));
122
+ }
123
+ function formatSummary(input) {
124
+ const lines = [
125
+ "## FTP storage diagnostic",
126
+ "",
127
+ `- Mode: **${input.mode}**`,
128
+ `- Root: \`${input.config.remoteDir || "/"}\``,
129
+ `- Files: **${input.inventory.filter((entry) => entry.kind === "file").length}**`,
130
+ `- Used: **${formatBytes(totalBytes(input.inventory))}**`,
131
+ `- Available: **${input.freeBytes === undefined ? "not reported by server" : formatBytes(input.freeBytes)}**`,
132
+ "",
133
+ "### Directory usage",
134
+ "",
135
+ "| Directory | Files | Size |",
136
+ "| --- | ---: | ---: |",
137
+ ...directoryRows(input.inventory, input.config.remoteDir),
138
+ "",
139
+ "### Cleanup",
140
+ "",
141
+ input.cleanupPaths.length
142
+ ? `The following paths ${input.mode === "cleanup" ? "were deleted" : "would be deleted"}:`
143
+ : "No cleanup paths selected.",
144
+ ...input.cleanupPaths.map((path) => `- \`${path}\``),
145
+ ];
146
+ if (input.mode === "cleanup") {
147
+ lines.push("", `After cleanup: **${formatBytes(totalBytes(input.afterInventory))}** used across **${input.afterInventory.filter((entry) => entry.kind === "file").length}** files.`);
148
+ }
149
+ else {
150
+ lines.push("", `Build retention: keep the newest **${input.keepBuilds}** numbered builds.`);
151
+ }
152
+ return lines.join("\n");
153
+ }
154
+ function directoryRows(inventory, root) {
155
+ const rows = new Map();
156
+ for (const entry of inventory.filter((item) => item.kind === "file")) {
157
+ const relative = entry.path.startsWith(`${root}/`)
158
+ ? entry.path.slice(root.length + 1)
159
+ : entry.path;
160
+ const parts = relative.split("/");
161
+ parts.pop();
162
+ for (let index = 0; index <= parts.length; index += 1) {
163
+ const directory = index
164
+ ? `${root}/${parts.slice(0, index).join("/")}`
165
+ : root || "/";
166
+ const current = rows.get(directory) ?? { files: 0, bytes: 0 };
167
+ current.files += 1;
168
+ current.bytes += entry.sizeBytes;
169
+ rows.set(directory, current);
170
+ }
171
+ }
172
+ return Array.from(rows.entries())
173
+ .sort(([, a], [, b]) => b.bytes - a.bytes)
174
+ .map(([directory, value]) => `| \`${directory || "/"}\` | ${value.files} | ${formatBytes(value.bytes)} |`);
175
+ }
176
+ function totalBytes(inventory) {
177
+ return inventory.reduce((sum, entry) => sum + (entry.kind === "file" ? entry.sizeBytes : 0), 0);
178
+ }
179
+ function ftpArgs(config, args, remotePath) {
180
+ const protocol = config.secure ? "ftps" : "ftp";
181
+ const port = config.port ? `:${config.port}` : "";
182
+ const encodedPath = remotePath
183
+ .split("/")
184
+ .filter(Boolean)
185
+ .map(encodeURIComponent)
186
+ .join("/");
187
+ return [
188
+ "--silent",
189
+ "--show-error",
190
+ "--fail",
191
+ "-u",
192
+ `${config.user}:${config.password}`,
193
+ ...args,
194
+ `${protocol}://${config.host}${port}/${encodedPath}`,
195
+ ];
196
+ }
197
+ function safeChildPath(root, value) {
198
+ if (!value || value.startsWith("/") || value.split("/").includes("..")) {
199
+ throw new Error(`Cleanup path must be relative to the FTP root: ${value}`);
200
+ }
201
+ return pathJoin(root, value);
202
+ }
203
+ function maintenanceMode(value) {
204
+ if (value === "report" || value === "dry-run" || value === "cleanup")
205
+ return value;
206
+ throw new Error(`Unknown FTP maintenance mode: ${value ?? ""}`);
207
+ }
208
+ function positiveInteger(value, fallback) {
209
+ if (value === undefined)
210
+ return fallback;
211
+ if (!/^\d+$/.test(value))
212
+ throw new Error(`Expected a non-negative integer: ${value}`);
213
+ return Number(value);
214
+ }
215
+ function csv(value) {
216
+ return (value ?? "")
217
+ .split(",")
218
+ .map((item) => item.trim())
219
+ .filter(Boolean);
220
+ }
221
+ function formatBytes(bytes) {
222
+ if (bytes < 1024)
223
+ return `${bytes} B`;
224
+ const units = ["KiB", "MiB", "GiB", "TiB"];
225
+ let value = bytes;
226
+ let unit = "B";
227
+ for (const next of units) {
228
+ value /= 1024;
229
+ unit = next;
230
+ if (value < 1024)
231
+ break;
232
+ }
233
+ return `${value.toFixed(value >= 10 ? 1 : 2)} ${unit}`;
234
+ }
235
+ async function writeSummary(summary, options) {
236
+ const file = options["summary-file"] ?? process.env.GITHUB_STEP_SUMMARY;
237
+ if (file)
238
+ await appendFile(file, `${summary}\n`);
239
+ const output = options["output-file"] ?? process.env.GITHUB_OUTPUT;
240
+ if (output)
241
+ await appendFile(output, `ftp-summary<<FEATURE_SPEC_FTP_SUMMARY\n${summary}\nFEATURE_SPEC_FTP_SUMMARY\n`);
242
+ }
243
+ //# sourceMappingURL=githubActionFtpMaintenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"githubActionFtpMaintenance.js","sourceRoot":"","sources":["../src/githubActionFtpMaintenance.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"githubActionReport.d.ts","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAK9B,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAO5D,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,yBAAyB,iBA8DnC;AAqFD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,UAE9C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;EAK/D"}
1
+ {"version":3,"file":"githubActionReport.d.ts","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAM9B,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAO5D,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,yBAAyB,iBA8DnC;AAqFD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,UAE9C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;EAK/D"}
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { tmpdir } from "node:os";
4
4
  import { ftpConfig, listBuildNumbers, pathJoin, publicUrl, uploadDirectory, uploadFile, } from "./githubActionFtp.js";
5
5
  import { writeGithubOutput, writeGithubSummary } from "./githubActionOutput.js";
6
+ import { formatGeneratedAt, renderGeneratedAt } from "./reportDate.js";
6
7
  import { writePublishedFeatureSpecFiles } from "./reportArtifacts.js";
7
8
  import { renderHtmlPage } from "./reportHtml.js";
8
9
  const defaultSpecPattern = "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md";
@@ -92,7 +93,7 @@ li { margin: 0.35rem 0; }
92
93
  `,
93
94
  body: `
94
95
  <h1>Feature spec build reports</h1>
95
- <p>Generated ${escapeHtml(generatedAt)}.</p>
96
+ <p ${renderGeneratedAt(generatedAt)}>Generated ${escapeHtml(formatGeneratedAt(generatedAt))}.</p>
96
97
  <p><a href="${escapeHtml(latestReportUrl(baseUrl))}"><strong>Open the latest report</strong></a></p>
97
98
  <ol>
98
99
  ${rows}
@@ -1 +1 @@
1
- {"version":3,"file":"githubActionReport.js","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"githubActionReport.js","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,8 @@
1
1
  export declare function pngsHaveIdenticalPixels(previousPath: string, currentPath: string): Promise<boolean | undefined>;
2
+ /**
3
+ * Treats tiny rasterisation noise as visually equivalent while retaining a
4
+ * conservative guard against hiding real UI changes. The thresholds are based
5
+ * on decoded RGBA pixels, so PNG compression and metadata do not affect them.
6
+ */
7
+ export declare function pngsAreVisuallyEquivalent(previousPath: string, currentPath: string): Promise<boolean | undefined>;
2
8
  //# sourceMappingURL=imageComparison.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"imageComparison.d.ts","sourceRoot":"","sources":["../src/imageComparison.ts"],"names":[],"mappings":"AAGA,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,gCAoBpB"}
1
+ {"version":3,"file":"imageComparison.d.ts","sourceRoot":"","sources":["../src/imageComparison.ts"],"names":[],"mappings":"AAGA,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,gCAoBpB;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,gCAiCpB"}
@@ -18,6 +18,40 @@ export async function pngsHaveIdenticalPixels(previousPath, currentPath) {
18
18
  return undefined;
19
19
  }
20
20
  }
21
+ /**
22
+ * Treats tiny rasterisation noise as visually equivalent while retaining a
23
+ * conservative guard against hiding real UI changes. The thresholds are based
24
+ * on decoded RGBA pixels, so PNG compression and metadata do not affect them.
25
+ */
26
+ export async function pngsAreVisuallyEquivalent(previousPath, currentPath) {
27
+ try {
28
+ const [previous, current] = await Promise.all([
29
+ readPng(previousPath),
30
+ readPng(currentPath),
31
+ ]);
32
+ if (previous.width !== current.width ||
33
+ previous.height !== current.height) {
34
+ return false;
35
+ }
36
+ let changedPixels = 0;
37
+ let totalDifference = 0;
38
+ for (let offset = 0; offset < previous.data.length; offset += 4) {
39
+ let pixelDifference = 0;
40
+ for (let channel = 0; channel < 4; channel += 1) {
41
+ pixelDifference += Math.abs(previous.data[offset + channel] - current.data[offset + channel]);
42
+ }
43
+ totalDifference += pixelDifference;
44
+ if (pixelDifference > 8)
45
+ changedPixels += 1;
46
+ }
47
+ const pixelCount = previous.width * previous.height;
48
+ return (changedPixels / pixelCount <= 0.001 &&
49
+ totalDifference / (pixelCount * 4) <= 1.5);
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ }
21
55
  async function readPng(filePath) {
22
56
  return PNG.sync.read(await readFile(filePath));
23
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"imageComparison.js","sourceRoot":"","sources":["../src/imageComparison.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,YAAoB,EACpB,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC5C,OAAO,CAAC,YAAY,CAAC;YACrB,OAAO,CAAC,WAAW,CAAC;SACrB,CAAC,CAAC;QACH,IACE,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK;YAChC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAClC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,QAAgB;IACrC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"imageComparison.js","sourceRoot":"","sources":["../src/imageComparison.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,YAAoB,EACpB,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC5C,OAAO,CAAC,YAAY,CAAC;YACrB,OAAO,CAAC,WAAW,CAAC;SACrB,CAAC,CAAC;QACH,IACE,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK;YAChC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAClC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,YAAoB,EACpB,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC5C,OAAO,CAAC,YAAY,CAAC;YACrB,OAAO,CAAC,WAAW,CAAC;SACrB,CAAC,CAAC;QACH,IACE,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK;YAChC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAClC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;YAChE,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;gBAChD,eAAe,IAAI,IAAI,CAAC,GAAG,CACzB,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CACjE,CAAC;YACJ,CAAC;YACD,eAAe,IAAI,eAAe,CAAC;YACnC,IAAI,eAAe,GAAG,CAAC;gBAAE,aAAa,IAAI,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;QACpD,OAAO,CACL,aAAa,GAAG,UAAU,IAAI,KAAK;YACnC,eAAe,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,GAAG,CAC1C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,QAAgB;IACrC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function formatGeneratedAt(value: string | undefined): string;
2
+ export declare function renderGeneratedAt(value: string | undefined): string;
3
+ //# sourceMappingURL=reportDate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportDate.d.ts","sourceRoot":"","sources":["../src/reportDate.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,UAqB1D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,UAG1D"}
@@ -0,0 +1,40 @@
1
+ import { html } from "./html.js";
2
+ export function formatGeneratedAt(value) {
3
+ const date = value ? new Date(value) : new Date();
4
+ if (Number.isNaN(date.getTime()))
5
+ return value ?? "";
6
+ const day = date.getDate();
7
+ const month = [
8
+ "January",
9
+ "February",
10
+ "March",
11
+ "April",
12
+ "May",
13
+ "June",
14
+ "July",
15
+ "August",
16
+ "September",
17
+ "October",
18
+ "November",
19
+ "December",
20
+ ][date.getMonth()];
21
+ const hours = String(date.getHours()).padStart(2, "0");
22
+ const minutes = String(date.getMinutes()).padStart(2, "0");
23
+ return `${day}${ordinalSuffix(day)} ${month} ${date.getFullYear()} at ${hours}:${minutes}`;
24
+ }
25
+ export function renderGeneratedAt(value) {
26
+ const timestamp = value ?? new Date().toISOString();
27
+ return `data-generated-at="${html(timestamp)}"`;
28
+ }
29
+ function ordinalSuffix(day) {
30
+ if (day >= 11 && day <= 13)
31
+ return "th";
32
+ if (day % 10 === 1)
33
+ return "st";
34
+ if (day % 10 === 2)
35
+ return "nd";
36
+ if (day % 10 === 3)
37
+ return "rd";
38
+ return "th";
39
+ }
40
+ //# sourceMappingURL=reportDate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportDate.js","sourceRoot":"","sources":["../src/reportDate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IAClD,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,KAAK,IAAI,EAAE,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG;QACZ,SAAS;QACT,UAAU;QACV,OAAO;QACP,OAAO;QACP,KAAK;QACL,MAAM;QACN,MAAM;QACN,QAAQ;QACR,WAAW;QACX,SAAS;QACT,UAAU;QACV,UAAU;KACX,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,OAAO,GAAG,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,MAAM,SAAS,GAAG,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpD,OAAO,sBAAsB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,GAAG,GAAG,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,GAAG,GAAG,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,GAAG,GAAG,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"reportHtml.d.ts","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAM7B,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAW,EACX,OAAY,EACZ,QAAQ,EACR,QAAwC,GACzC,EAAE,eAAe,UAqGjB"}
1
+ {"version":3,"file":"reportHtml.d.ts","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAM7B,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAW,EACX,OAAY,EACZ,QAAQ,EACR,QAAwC,GACzC,EAAE,eAAe,UAuGjB"}
@@ -10,7 +10,7 @@ export function renderHtmlPage({ title, body, styles = "", scripts = "", maxWidt
10
10
  <meta charset="utf-8">
11
11
  <meta name="viewport" content="width=device-width, initial-scale=1">
12
12
  <title>${html(title)}</title>
13
- <script>(function(){var saved;try{saved=localStorage.getItem("feature-spec-md-theme");}catch(error){}var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.dataset.theme=saved|| (dark?"dark":"light");})();</script>
13
+ <script>(function(){var saved;try{saved=localStorage.getItem("feature-spec-md-theme");}catch(error){}var explicit=saved==="dark"||saved==="light"?saved:null;var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.dataset.theme=explicit|| (dark?"dark":"light");})();</script>
14
14
  <style>
15
15
  :root {
16
16
  color-scheme: light;
@@ -78,6 +78,8 @@ ${scripts ? indentTemplateBlock(scripts, 2) : ""}
78
78
  </dialog>
79
79
  <script>
80
80
  (function(){
81
+ function formatGeneratedAt(value){var date=new Date(value);if(Number.isNaN(date.getTime()))return value;var day=date.getDate();var suffix=day>=11&&day<=13?"th":day%10===1?"st":day%10===2?"nd":day%10===3?"rd":"th";var months=["January","February","March","April","May","June","July","August","September","October","November","December"];var hours=String(date.getHours()).padStart(2,"0");var minutes=String(date.getMinutes()).padStart(2,"0");return day+suffix+" "+months[date.getMonth()]+" "+date.getFullYear()+" at "+hours+":"+minutes;}
82
+ document.querySelectorAll("[data-generated-at]").forEach(function(item){item.textContent="Generated "+formatGeneratedAt(item.getAttribute("data-generated-at"))+".";});
81
83
  var root=document.documentElement;
82
84
  var themeButton=document.querySelector(".theme-toggle");
83
85
  function updateThemeButton(){var dark=root.dataset.theme==="dark";themeButton.textContent=dark?"☀":"☾";themeButton.title=dark?"Use light mode":"Use dark mode";}
@@ -1 +1 @@
1
- {"version":3,"file":"reportHtml.js","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EACL,oBAAoB,GAErB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AAWrC,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAM,GAAG,EAAE,EACX,OAAO,GAAG,EAAE,EACZ,QAAQ,EACR,QAAQ,GAAG,2BAA2B,EAAE,GACxB;IAChB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG;;;;;WAKJ,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAwCH,QAAQ,CAAC,QAAQ;;;;;;;;;;;;;;;;EAgBlC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;;;;;EAK1C,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;EAC5B,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B/C,CAAC;IAEA,OAAO,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"reportHtml.js","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EACL,oBAAoB,GAErB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AAWrC,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAM,GAAG,EAAE,EACX,OAAO,GAAG,EAAE,EACZ,QAAQ,EACR,QAAQ,GAAG,2BAA2B,EAAE,GACxB;IAChB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG;;;;;WAKJ,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAwCH,QAAQ,CAAC,QAAQ;;;;;;;;;;;;;;;;EAgBlC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;;;;;EAK1C,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;EAC5B,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B/C,CAAC;IAEA,OAAO,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"reportTemplate.d.ts","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,eAAe,EACf,UAAU,EAEV,WAAW,EAEX,SAAS,EACT,WAAW,EACX,cAAc,EACd,SAAS,EAET,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAwCF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,GAAE,aAAkB,UAe5B"}
1
+ {"version":3,"file":"reportTemplate.d.ts","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,eAAe,EACf,UAAU,EAEV,WAAW,EAEX,SAAS,EACT,WAAW,EACX,cAAc,EACd,SAAS,EAET,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAwCF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,GAAE,aAAkB,UAe5B"}
@@ -1,4 +1,5 @@
1
1
  import { html } from "./html.js";
2
+ import { formatGeneratedAt, renderGeneratedAt } from "./reportDate.js";
2
3
  import { renderHtmlPage } from "./reportHtml.js";
3
4
  import { screenshotKey } from "./screenshots.js";
4
5
  const extensionDefinitions = [
@@ -26,17 +27,18 @@ export function renderHtmlReport(specs, options = {}) {
26
27
  }
27
28
  function featureReportBody({ specs, options, evidence, sourceLinks, title, }) {
28
29
  const documents = allReportDocuments(specs, options);
30
+ const generatedAt = options.generatedAt ?? new Date().toISOString();
29
31
  if (options.layers?.length) {
30
32
  return `
31
33
  <h1>${renderReportTitle(title, options.repositoryUrl)}</h1>
32
- <p>Generated ${html(formatGeneratedAt(options.generatedAt))}.</p>
34
+ <p ${renderGeneratedAt(generatedAt)}>Generated ${html(formatGeneratedAt(generatedAt))}.</p>
33
35
  ${renderIssues(options.validationIssues ?? [])}
34
36
  ${renderLayeredDocuments(documents, options, evidence, sourceLinks)}
35
37
  `;
36
38
  }
37
39
  return `
38
40
  <h1>${renderReportTitle(title, options.repositoryUrl)}</h1>
39
- <p>Generated ${html(formatGeneratedAt(options.generatedAt))}.</p>
41
+ <p ${renderGeneratedAt(generatedAt)}>Generated ${html(formatGeneratedAt(generatedAt))}.</p>
40
42
  ${renderOpenQuestionsAndAssumptions(documents, sourceLinks)}
41
43
  ${renderJourneyOverview(specs, options.coverage, sourceLinks)}
42
44
  ${renderIssues(options.validationIssues ?? [])}
@@ -84,7 +86,7 @@ function featureReportStyles() {
84
86
  .report-navigator{position:fixed;z-index:19;right:12px;top:12px;width:min(320px,calc(100vw - 24px));border:1px solid var(--border);border-radius:10px;background:var(--surface);box-shadow:0 8px 24px rgba(31,35,40,.16)}
85
87
  .navigator-trigger{appearance:none;display:flex;align-items:center;width:100%;border:0;border-radius:10px;background:transparent;color:var(--fg);cursor:pointer;font:inherit;padding:9px 12px;text-align:left}.navigator-trigger:hover{background:var(--surface-hover)}.navigator-trigger:focus-visible,.navigator-link:focus-visible,.navigator-scenarios:focus-visible{outline:2px solid var(--link);outline-offset:-2px}
86
88
  .navigator-current{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:650}
87
- .navigator-menu{display:none;border-top:1px solid var(--border);padding:10px}.report-navigator[data-open="true"] .navigator-menu{display:block}
89
+ .navigator-menu{display:none;border-top:1px solid var(--border);padding:10px}.report-navigator[data-open="true"] .navigator-menu{display:block}.navigator-search{width:100%;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--fg);font:inherit;font-size:13px;padding:6px 8px;margin-bottom:8px}.navigator-search:focus-visible{outline:2px solid var(--link);outline-offset:1px}.navigator-no-results{color:var(--muted);font-size:13px;padding:8px}
88
90
  .navigator-links{max-height:calc(100vh - 100px);overflow:auto;padding:2px}.navigator-layer{margin:6px 0 2px}.navigator-row{display:flex;align-items:center;gap:4px}.navigator-link{appearance:none;display:flex;align-items:center;gap:7px;width:100%;min-width:0;border:0;border-radius:5px;background:transparent;color:var(--fg);cursor:pointer;font:inherit;font-size:13px;padding:6px 8px;text-align:left}.navigator-link:hover,.navigator-scenarios:hover{background:var(--surface-hover)}.navigator-link[aria-current="location"]{background:var(--surface-muted);box-shadow:inset 3px 0 var(--link);font-weight:650}.navigator-toggle-state{flex:0 0 auto;width:13px;color:var(--muted);font-size:10px;text-align:center}.navigator-link[aria-pressed="true"]>.navigator-toggle-state::before{content:"●"}.navigator-link[aria-pressed="false"]>.navigator-toggle-state::before{content:"○"}.navigator-link-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.navigator-document{padding-left:22px;color:var(--muted)}.navigator-document[aria-current="location"]{color:var(--fg)}.navigator-scenarios{appearance:none;flex:0 0 auto;border:1px solid var(--border);border-radius:999px;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-size:11px;padding:3px 7px}.navigator-scenarios[aria-pressed="true"]{background:var(--surface-muted);color:var(--fg)}
89
91
  .feature-policy{display:flex;gap:8px;flex-wrap:wrap;margin:-4px 0 12px}.feature-policy .badge{display:inline-flex;gap:5px;align-items:center}
90
92
  .scenario{border:1px solid var(--border);border-radius:8px;margin:12px 0;background:var(--surface)}
@@ -192,7 +194,10 @@ function initializeReportNavigator() {
192
194
  if (!(navigator instanceof HTMLElement)) return;
193
195
  const trigger = navigator.querySelector("[data-navigator-trigger]");
194
196
  const currentLabel = navigator.querySelector("[data-navigator-current]");
197
+ const search = navigator.querySelector("[data-navigator-search]");
198
+ const noResults = navigator.querySelector("[data-navigator-no-results]");
195
199
  const entries = Array.from(navigator.querySelectorAll("[data-navigator-target]"));
200
+ const layers = Array.from(navigator.querySelectorAll("[data-navigator-layer]"));
196
201
  const scenarioButtons = Array.from(navigator.querySelectorAll("[data-navigator-scenarios-target]"));
197
202
  const targets = entries
198
203
  .map((entry) => document.getElementById(entry.dataset.navigatorTarget))
@@ -205,6 +210,25 @@ function initializeReportNavigator() {
205
210
  trigger?.setAttribute("aria-expanded", String(open));
206
211
  }
207
212
 
213
+ function updateSearch() {
214
+ const query = search instanceof HTMLInputElement ? search.value.trim().toLocaleLowerCase() : "";
215
+ let matches = 0;
216
+ layers.forEach((layer) => {
217
+ const layerEntry = layer.querySelector("[data-navigator-layer-target]");
218
+ const layerMatches = layerEntry?.textContent?.trim().toLocaleLowerCase().includes(query) ?? false;
219
+ let layerHasMatch = false;
220
+ layer.querySelectorAll(".navigator-row").forEach((row) => {
221
+ const entry = row.querySelector("[data-navigator-target]");
222
+ const matchesEntry = layerMatches || entry?.textContent?.trim().toLocaleLowerCase().includes(query) || false;
223
+ row.hidden = !matchesEntry;
224
+ if (matchesEntry) layerHasMatch = true;
225
+ });
226
+ layer.hidden = !layerHasMatch;
227
+ if (layerHasMatch) matches += 1;
228
+ });
229
+ if (noResults instanceof HTMLElement) noResults.hidden = matches > 0;
230
+ }
231
+
208
232
  function updateActiveSection() {
209
233
  frame = 0;
210
234
  const visible = targets.filter((target) => target.getClientRects().length > 0);
@@ -256,6 +280,7 @@ function initializeReportNavigator() {
256
280
  }
257
281
 
258
282
  trigger?.addEventListener("click", () => setMenuOpen(navigator.dataset.open !== "true"));
283
+ search?.addEventListener("input", updateSearch);
259
284
  entries.forEach((entry, index) => entry.addEventListener("click", (event) => {
260
285
  event.preventDefault();
261
286
  toggleAndJumpTo(index);
@@ -289,6 +314,7 @@ function initializeReportNavigator() {
289
314
  });
290
315
  document.addEventListener("toggle", requestActiveUpdate, true);
291
316
  updateActiveSection();
317
+ updateSearch();
292
318
  }
293
319
  document.addEventListener("DOMContentLoaded", initializeReportNavigator);
294
320
  ${closeTag}
@@ -328,40 +354,6 @@ function renderReportTitle(title, repositoryUrl) {
328
354
  }
329
355
  return `<a ${attributes}>${html(title)}</a>`;
330
356
  }
331
- function formatGeneratedAt(value) {
332
- const date = value ? new Date(value) : new Date();
333
- if (Number.isNaN(date.getTime()))
334
- return value ?? "";
335
- const day = date.getDate();
336
- const month = [
337
- "January",
338
- "February",
339
- "March",
340
- "April",
341
- "May",
342
- "June",
343
- "July",
344
- "August",
345
- "September",
346
- "October",
347
- "November",
348
- "December",
349
- ][date.getMonth()];
350
- const hours = String(date.getHours()).padStart(2, "0");
351
- const minutes = String(date.getMinutes()).padStart(2, "0");
352
- return `${day}${ordinalSuffix(day)} ${month} ${date.getFullYear()} at ${hours}:${minutes}`;
353
- }
354
- function ordinalSuffix(day) {
355
- if (day >= 11 && day <= 13)
356
- return "th";
357
- if (day % 10 === 1)
358
- return "st";
359
- if (day % 10 === 2)
360
- return "nd";
361
- if (day % 10 === 3)
362
- return "rd";
363
- return "th";
364
- }
365
357
  function renderLayeredDocuments(documents, options, evidence, sourceLinks) {
366
358
  const configured = options.layers ?? [];
367
359
  const configuredIds = new Set(configured.map((layer) => layer.id));
@@ -389,8 +381,8 @@ function renderLayeredDocuments(documents, options, evidence, sourceLinks) {
389
381
  }
390
382
  function renderReportNavigator(layers) {
391
383
  const entries = layers
392
- .map((layer) => `<div class="navigator-layer">
393
- <div class="navigator-row">${renderNavigatorLink(`layer-${layer.id}`, layer.title, "navigator-layer-link")}</div>
384
+ .map((layer) => `<div class="navigator-layer" data-navigator-layer>
385
+ <div class="navigator-row">${renderNavigatorLink(`layer-${layer.id}`, layer.title, "navigator-layer-link", true)}</div>
394
386
  ${layer.documents
395
387
  .map((document) => `<div class="navigator-row">${renderNavigatorLink(document.frontmatter.id.toLowerCase(), document.title, "navigator-document")}${renderNavigatorScenarioButton(document)}</div>`)
396
388
  .join("")}
@@ -402,12 +394,15 @@ function renderReportNavigator(layers) {
402
394
  <span class="navigator-current" data-navigator-current>${html(firstTitle)}</span>
403
395
  </button>
404
396
  <div class="navigator-menu">
397
+ <input class="navigator-search" type="search" data-navigator-search aria-label="Search report navigation" placeholder="Search context" autocomplete="off">
405
398
  <div class="navigator-links">${entries}</div>
399
+ <div class="navigator-no-results" data-navigator-no-results hidden>No matching context</div>
406
400
  </div>
407
401
  </nav>`;
408
402
  }
409
- function renderNavigatorLink(id, title, className) {
410
- return `<button class="navigator-link ${className}" type="button" data-navigator-target="${html(id)}" aria-pressed="false"><span class="navigator-toggle-state" aria-hidden="true"></span><span class="navigator-link-label">${html(title)}</span></button>`;
403
+ function renderNavigatorLink(id, title, className, layer = false) {
404
+ const targetAttribute = layer ? " data-navigator-layer-target" : "";
405
+ return `<button class="navigator-link ${className}" type="button" data-navigator-target="${html(id)}"${targetAttribute} aria-pressed="false"><span class="navigator-toggle-state" aria-hidden="true"></span><span class="navigator-link-label">${html(title)}</span></button>`;
411
406
  }
412
407
  function renderNavigatorScenarioButton(document) {
413
408
  if (document.kind !== "feature" && document.kind !== undefined)