@reicek/neataptic-ts 0.1.25 → 0.1.26

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 (210) hide show
  1. package/.github/copilot-instructions.md +11 -0
  2. package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
  3. package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
  4. package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
  5. package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
  6. package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
  7. package/package.json +19 -13
  8. package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
  9. package/plans/README.md +24 -0
  10. package/plans/Roadmap.md +62 -40
  11. package/plans/analyze-trace-solid-split.plans.md +66 -0
  12. package/plans/architecture-solid-split.plans.md +9 -15
  13. package/plans/asciiMaze-typescript-repair.plans.md +1 -1
  14. package/plans/generate-docs-solid-split.plans.md +87 -0
  15. package/plans/methods-docs.plans.md +25 -1
  16. package/plans/methods-solid-split.plans.md +14 -14
  17. package/plans/neat-docs.plans.md +9 -1
  18. package/plans/neat-test-surface-repair.plans.md +1 -1
  19. package/plans/render-docs-html-solid-split.plans.md +68 -0
  20. package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
  21. package/plans/utils-docs.plans.md +6 -1
  22. package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
  23. package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
  24. package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
  25. package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
  26. package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
  27. package/scripts/analyze-trace/analyze-trace.ts +45 -0
  28. package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
  29. package/scripts/assets/theme.css +80 -23
  30. package/scripts/copy-examples.ts +239 -0
  31. package/scripts/export-onnx.ts +223 -0
  32. package/scripts/generate-bench-tables.ts +378 -37
  33. package/scripts/generate-docs/generate-docs.constants.ts +107 -0
  34. package/scripts/generate-docs/generate-docs.order.ts +355 -0
  35. package/scripts/generate-docs/generate-docs.state.ts +31 -0
  36. package/scripts/generate-docs/generate-docs.targets.ts +165 -0
  37. package/scripts/generate-docs/generate-docs.ts +63 -0
  38. package/scripts/generate-docs/generate-docs.types.ts +112 -0
  39. package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
  40. package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
  41. package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
  42. package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
  43. package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
  44. package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
  45. package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
  46. package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
  47. package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
  48. package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
  49. package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
  50. package/scripts/mermaid-cli.mjs +102 -22
  51. package/scripts/mermaid-cli.ts +736 -0
  52. package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
  53. package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
  54. package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
  55. package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
  56. package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
  57. package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
  58. package/scripts/render-docs-html.ts +23 -587
  59. package/scripts/run-docs.ts +238 -0
  60. package/scripts/write-dist-docs-pkg.ts +40 -0
  61. package/src/README.md +75 -75
  62. package/src/architecture/connection/README.md +5 -5
  63. package/src/architecture/layer/README.md +508 -508
  64. package/src/architecture/network/README.md +1458 -1458
  65. package/src/architecture/network/activate/README.md +694 -694
  66. package/src/architecture/network/bootstrap/README.md +77 -77
  67. package/src/architecture/network/connect/README.md +74 -74
  68. package/src/architecture/network/deterministic/README.md +135 -135
  69. package/src/architecture/network/evolve/README.md +364 -364
  70. package/src/architecture/network/gating/README.md +130 -130
  71. package/src/architecture/network/genetic/README.md +399 -399
  72. package/src/architecture/network/mutate/README.md +897 -897
  73. package/src/architecture/network/onnx/README.md +720 -720
  74. package/src/architecture/network/onnx/export/README.md +728 -728
  75. package/src/architecture/network/onnx/export/layers/README.md +450 -450
  76. package/src/architecture/network/onnx/import/README.md +618 -618
  77. package/src/architecture/network/onnx/schema/README.md +32 -32
  78. package/src/architecture/network/prune/README.md +245 -245
  79. package/src/architecture/network/remove/README.md +135 -135
  80. package/src/architecture/network/runtime/README.md +106 -106
  81. package/src/architecture/network/serialize/README.md +542 -542
  82. package/src/architecture/network/slab/README.md +608 -608
  83. package/src/architecture/network/standalone/README.md +212 -212
  84. package/src/architecture/network/stats/README.md +84 -84
  85. package/src/architecture/network/topology/README.md +465 -465
  86. package/src/architecture/network/training/README.md +200 -200
  87. package/src/architecture/node/README.md +5 -5
  88. package/src/architecture/nodePool/README.md +14 -14
  89. package/src/methods/README.md +99 -99
  90. package/src/methods/activation/README.md +189 -189
  91. package/src/methods/cost/README.md +131 -131
  92. package/src/methods/rate/README.md +86 -86
  93. package/src/multithreading/README.md +77 -77
  94. package/src/multithreading/workers/browser/README.md +8 -8
  95. package/src/multithreading/workers/node/README.md +8 -8
  96. package/src/neat/README.md +148 -148
  97. package/src/neat/adaptive/README.md +120 -120
  98. package/src/neat/adaptive/acceptance/README.md +40 -40
  99. package/src/neat/adaptive/complexity/README.md +137 -137
  100. package/src/neat/adaptive/core/README.md +197 -197
  101. package/src/neat/adaptive/lineage/README.md +90 -90
  102. package/src/neat/adaptive/mutation/README.md +284 -284
  103. package/src/neat/compat/README.md +43 -43
  104. package/src/neat/compat/core/README.md +90 -90
  105. package/src/neat/diversity/README.md +35 -35
  106. package/src/neat/diversity/core/README.md +88 -88
  107. package/src/neat/evaluate/README.md +85 -85
  108. package/src/neat/evaluate/auto-distance/README.md +75 -75
  109. package/src/neat/evaluate/entropy-compat/README.md +37 -37
  110. package/src/neat/evaluate/entropy-sharing/README.md +43 -43
  111. package/src/neat/evaluate/fitness/README.md +23 -23
  112. package/src/neat/evaluate/novelty/README.md +120 -120
  113. package/src/neat/evaluate/objectives/README.md +17 -17
  114. package/src/neat/evaluate/shared/README.md +94 -94
  115. package/src/neat/evolve/README.md +96 -96
  116. package/src/neat/evolve/adaptive/README.md +60 -60
  117. package/src/neat/evolve/objectives/README.md +63 -63
  118. package/src/neat/evolve/offspring/README.md +56 -56
  119. package/src/neat/evolve/population/README.md +171 -171
  120. package/src/neat/evolve/runtime/README.md +79 -79
  121. package/src/neat/evolve/speciation/README.md +74 -74
  122. package/src/neat/evolve/warnings/README.md +10 -10
  123. package/src/neat/export/README.md +114 -114
  124. package/src/neat/helpers/README.md +50 -50
  125. package/src/neat/init/README.md +9 -9
  126. package/src/neat/lineage/core/README.md +101 -101
  127. package/src/neat/multiobjective/category/README.md +74 -74
  128. package/src/neat/multiobjective/crowding/README.md +272 -272
  129. package/src/neat/multiobjective/dominance/README.md +171 -171
  130. package/src/neat/multiobjective/fronts/README.md +68 -68
  131. package/src/neat/multiobjective/metrics/README.md +43 -43
  132. package/src/neat/multiobjective/objectives/README.md +31 -31
  133. package/src/neat/multiobjective/shared/README.md +27 -27
  134. package/src/neat/mutation/README.md +97 -97
  135. package/src/neat/mutation/add-conn/README.md +115 -115
  136. package/src/neat/mutation/add-node/README.md +126 -126
  137. package/src/neat/mutation/flow/README.md +149 -149
  138. package/src/neat/mutation/repair/README.md +185 -185
  139. package/src/neat/mutation/select/README.md +117 -117
  140. package/src/neat/mutation/shared/README.md +32 -32
  141. package/src/neat/objectives/README.md +25 -25
  142. package/src/neat/objectives/core/README.md +67 -67
  143. package/src/neat/pruning/README.md +40 -40
  144. package/src/neat/pruning/core/README.md +171 -171
  145. package/src/neat/pruning/facade/README.md +32 -32
  146. package/src/neat/rng/README.md +104 -104
  147. package/src/neat/rng/core/README.md +137 -137
  148. package/src/neat/rng/facade/README.md +50 -50
  149. package/src/neat/selection/README.md +111 -111
  150. package/src/neat/selection/core/README.md +227 -227
  151. package/src/neat/selection/facade/README.md +61 -61
  152. package/src/neat/shared/README.md +163 -163
  153. package/src/neat/speciation/README.md +31 -31
  154. package/src/neat/speciation/threshold/README.md +35 -35
  155. package/src/neat/species/README.md +25 -25
  156. package/src/neat/species/core/README.md +20 -20
  157. package/src/neat/species/core/shared/README.md +18 -18
  158. package/src/neat/species/history/context/README.md +22 -22
  159. package/src/neat/telemetry/accessors/README.md +58 -58
  160. package/src/neat/telemetry/exports/README.md +233 -233
  161. package/src/neat/telemetry/facade/README.md +252 -252
  162. package/src/neat/telemetry/facade/archive/README.md +57 -57
  163. package/src/neat/telemetry/facade/buffer/README.md +43 -43
  164. package/src/neat/telemetry/facade/lineage/README.md +12 -12
  165. package/src/neat/telemetry/facade/objectives/README.md +44 -44
  166. package/src/neat/telemetry/facade/runtime/README.md +26 -26
  167. package/src/neat/telemetry/facade/species/README.md +27 -27
  168. package/src/neat/telemetry/metrics/README.md +696 -696
  169. package/src/neat/telemetry/recorder/README.md +57 -57
  170. package/src/neat/telemetry/types/README.md +32 -32
  171. package/src/neat/topology-intent/README.md +75 -75
  172. package/src/utils/README.md +193 -193
  173. package/test/examples/asciiMaze/browser-entry/README.md +92 -92
  174. package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
  175. package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
  176. package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
  177. package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
  178. package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
  179. package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
  180. package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
  181. package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
  182. package/test/examples/flappy_bird/browser-entry/README.md +508 -508
  183. package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
  184. package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
  185. package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
  186. package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
  187. package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
  188. package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
  189. package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
  190. package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
  191. package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
  192. package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
  193. package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
  194. package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
  195. package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
  196. package/test/examples/flappy_bird/constants/README.md +1070 -1070
  197. package/test/examples/flappy_bird/environment/README.md +22 -22
  198. package/test/examples/flappy_bird/evaluation/README.md +32 -32
  199. package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
  200. package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
  201. package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
  202. package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
  203. package/test/examples/flappy_bird/trainer/README.md +325 -325
  204. package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
  205. package/scripts/analyze-trace.ts +0 -590
  206. package/scripts/copy-examples.mjs +0 -114
  207. package/scripts/export-onnx.mjs +0 -86
  208. package/scripts/generate-bench-tables.mjs +0 -182
  209. package/scripts/generate-docs.ts +0 -2900
  210. package/scripts/write-dist-docs-pkg.mjs +0 -16
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Static asset bootstrapping for generated HTML docs.
3
+ *
4
+ * Theme CSS and the landing-page hero image are stable assets that should be
5
+ * prepared before markdown pages are rendered. Keeping that work here prevents
6
+ * the root renderer from mixing file-copy policy with page assembly.
7
+ */
8
+
9
+ import path from 'path';
10
+ import fs from 'fs-extra';
11
+
12
+ import { DOCS_DIR } from './render-docs-html.shared.js';
13
+
14
+ const THEME_CSS_SOURCE_PATH = path.resolve('scripts', 'assets', 'theme.css');
15
+ const THEME_CSS_OUTPUT_PATH = path.join(DOCS_DIR, 'assets', 'theme.css');
16
+ const NN_IMAGE_SOURCE_PATH = path.resolve('nn.jpg');
17
+ const NN_IMAGE_FALLBACK_SOURCE_PATH = path.resolve(
18
+ 'scripts',
19
+ 'assets',
20
+ 'nn.jpg',
21
+ );
22
+ const NN_IMAGE_OUTPUT_PATH = path.join(DOCS_DIR, 'nn.jpg');
23
+
24
+ /** Ensures static non-Mermaid assets exist under the generated docs tree. */
25
+ export async function ensureRenderDocsAssets(): Promise<void> {
26
+ await ensureThemeCss();
27
+ await ensureRootHeroImage();
28
+ }
29
+
30
+ /** Copies the docs theme stylesheet into the published assets tree. */
31
+ async function ensureThemeCss(): Promise<void> {
32
+ await fs.ensureDir(path.dirname(THEME_CSS_OUTPUT_PATH));
33
+ await fs.copyFile(THEME_CSS_SOURCE_PATH, THEME_CSS_OUTPUT_PATH);
34
+ }
35
+
36
+ /**
37
+ * Copies the root README hero image so `/docs/README.md` can keep using the
38
+ * same `nn.jpg` reference after publication.
39
+ */
40
+ async function ensureRootHeroImage(): Promise<void> {
41
+ const hasRootImage = await fs.pathExists(NN_IMAGE_SOURCE_PATH);
42
+ const hasFallbackImage = await fs.pathExists(NN_IMAGE_FALLBACK_SOURCE_PATH);
43
+ const sourcePath = hasRootImage
44
+ ? NN_IMAGE_SOURCE_PATH
45
+ : hasFallbackImage
46
+ ? NN_IMAGE_FALLBACK_SOURCE_PATH
47
+ : undefined;
48
+
49
+ if (!sourcePath) {
50
+ return;
51
+ }
52
+
53
+ await fs.copyFile(sourcePath, NN_IMAGE_OUTPUT_PATH);
54
+ }
@@ -0,0 +1,245 @@
1
+ /*
2
+ * Mermaid asset bootstrapping and validation for generated HTML docs.
3
+ *
4
+ * This chapter owns both sides of Mermaid handling: copying the browser bundle
5
+ * needed at runtime and treating invalid fenced diagrams as a build failure
6
+ * during docs generation.
7
+ */
8
+
9
+ import { spawn } from 'node:child_process';
10
+ import { mkdtemp, rm } from 'node:fs/promises';
11
+ import os from 'node:os';
12
+ import path from 'path';
13
+ import fs from 'fs-extra';
14
+
15
+ import { escapeHtml, DOCS_DIR } from './render-docs-html.shared.js';
16
+ import type { MermaidBlockReference } from './render-docs-html.types.js';
17
+
18
+ const MERMAID_MODULE_SOURCE_PATH = path.resolve(
19
+ 'node_modules',
20
+ 'mermaid',
21
+ 'dist',
22
+ 'mermaid.esm.min.mjs',
23
+ );
24
+ const MERMAID_DIST_SOURCE_DIR = path.resolve('node_modules', 'mermaid', 'dist');
25
+ const MERMAID_DIST_OUTPUT_DIR = path.join(DOCS_DIR, 'assets', 'vendor');
26
+ const MERMAID_CLI_SCRIPT_PATH = path.resolve(
27
+ 'dist-docs',
28
+ 'scripts',
29
+ 'mermaid-cli.js',
30
+ );
31
+ const MERMAID_VALIDATION_CONCURRENCY = Math.max(
32
+ 1,
33
+ Math.min(
34
+ 4,
35
+ typeof os.availableParallelism === 'function'
36
+ ? os.availableParallelism() - 1
37
+ : 4,
38
+ ),
39
+ );
40
+
41
+ interface MermaidValidationTask {
42
+ mermaidBlock: MermaidBlockReference;
43
+ tempInputPath: string;
44
+ }
45
+
46
+ /** Ensures the Mermaid browser bundle is copied into the published docs tree. */
47
+ export async function ensureMermaidBrowserAssets(): Promise<void> {
48
+ const hasMermaidModule = await fs.pathExists(MERMAID_MODULE_SOURCE_PATH);
49
+ if (!hasMermaidModule) {
50
+ return;
51
+ }
52
+
53
+ await fs.ensureDir(MERMAID_DIST_OUTPUT_DIR);
54
+ await fs.copy(MERMAID_DIST_SOURCE_DIR, MERMAID_DIST_OUTPUT_DIR, {
55
+ overwrite: true,
56
+ });
57
+ }
58
+
59
+ /** Builds the client-side Mermaid bootstrap script for one generated page. */
60
+ export function buildMermaidBootstrapScript(relToRoot: string): string {
61
+ const mermaidModuleHref =
62
+ (relToRoot ? `${relToRoot}/` : '') + 'assets/vendor/mermaid.esm.min.mjs';
63
+
64
+ return `<script type="module">
65
+ import mermaid from "${mermaidModuleHref}";
66
+
67
+ mermaid.initialize({
68
+ startOnLoad: false,
69
+ securityLevel: 'loose',
70
+ theme: 'base',
71
+ themeVariables: {
72
+ darkMode: true,
73
+ background: '#121a24',
74
+ primaryColor: '#102131',
75
+ primaryTextColor: '#d7e4f3',
76
+ primaryBorderColor: '#5ec8ff',
77
+ secondaryColor: '#0f1722',
78
+ secondaryTextColor: '#d7e4f3',
79
+ secondaryBorderColor: '#5ec8ff',
80
+ tertiaryColor: '#16283a',
81
+ tertiaryTextColor: '#d7e4f3',
82
+ tertiaryBorderColor: '#ffbf69',
83
+ mainBkg: '#0f1722',
84
+ nodeBorder: '#5ec8ff',
85
+ clusterBkg: '#102131',
86
+ clusterBorder: '#5ec8ff',
87
+ lineColor: '#5ec8ff',
88
+ edgeLabelBackground: '#0f1722',
89
+ textColor: '#d7e4f3',
90
+ fontFamily: 'Open Sans, sans-serif'
91
+ }
92
+ });
93
+
94
+ try {
95
+ await mermaid.run({ querySelector: '.mermaid-diagram' });
96
+ } catch (error) {
97
+ console.error('[docs] Mermaid render failed.', error);
98
+ }
99
+ </script>`;
100
+ }
101
+
102
+ /** Collects Mermaid fenced blocks from one markdown page. */
103
+ export function collectMermaidBlocks(
104
+ markdown: string,
105
+ readmePath: string,
106
+ ): MermaidBlockReference[] {
107
+ const mermaidBlocks: MermaidBlockReference[] = [];
108
+ const mermaidFencePattern = /^```mermaid[^\n]*\r?\n([\s\S]*?)^```\s*$/gm;
109
+ let mermaidMatch = mermaidFencePattern.exec(markdown);
110
+
111
+ while (mermaidMatch) {
112
+ mermaidBlocks.push({
113
+ readmePath,
114
+ blockNumber: mermaidBlocks.length + 1,
115
+ diagram: mermaidMatch[1].trim(),
116
+ });
117
+ mermaidMatch = mermaidFencePattern.exec(markdown);
118
+ }
119
+
120
+ return mermaidBlocks;
121
+ }
122
+
123
+ /** Validates every collected Mermaid block before HTML generation proceeds. */
124
+ export async function validateMermaidBlocks(
125
+ mermaidBlocks: readonly MermaidBlockReference[],
126
+ ): Promise<void> {
127
+ if (mermaidBlocks.length === 0) {
128
+ return;
129
+ }
130
+
131
+ const uniqueMermaidBlocks = resolveUniqueMermaidBlocks(mermaidBlocks);
132
+ const tempDirectoryPath = await mkdtemp(
133
+ path.join(os.tmpdir(), 'neatapticts-docs-mermaid-'),
134
+ );
135
+
136
+ try {
137
+ const mermaidValidationTasks = uniqueMermaidBlocks.map(
138
+ (mermaidBlock, blockIndex) => ({
139
+ mermaidBlock,
140
+ tempInputPath: path.join(
141
+ tempDirectoryPath,
142
+ `diagram-${blockIndex + 1}.mmd`,
143
+ ),
144
+ }),
145
+ );
146
+
147
+ await Promise.all(
148
+ mermaidValidationTasks.map((mermaidValidationTask) =>
149
+ fs.writeFile(
150
+ mermaidValidationTask.tempInputPath,
151
+ mermaidValidationTask.mermaidBlock.diagram,
152
+ 'utf8',
153
+ ),
154
+ ),
155
+ );
156
+ await runMermaidValidationPool(mermaidValidationTasks);
157
+ } finally {
158
+ await rm(tempDirectoryPath, { recursive: true, force: true });
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Resolves one representative block for each unique Mermaid source string.
164
+ *
165
+ * Many generated README surfaces repeat the same diagrams, so validating each
166
+ * unique source only once avoids redundant parser work.
167
+ *
168
+ * @param mermaidBlocks - Mermaid blocks collected from generated docs surfaces.
169
+ * @returns Representative Mermaid blocks keyed by unique diagram text.
170
+ */
171
+ function resolveUniqueMermaidBlocks(
172
+ mermaidBlocks: readonly MermaidBlockReference[],
173
+ ): MermaidBlockReference[] {
174
+ const uniqueMermaidBlocksByDiagram = new Map<string, MermaidBlockReference>();
175
+
176
+ for (const mermaidBlock of mermaidBlocks) {
177
+ if (!uniqueMermaidBlocksByDiagram.has(mermaidBlock.diagram)) {
178
+ uniqueMermaidBlocksByDiagram.set(mermaidBlock.diagram, mermaidBlock);
179
+ }
180
+ }
181
+
182
+ return [...uniqueMermaidBlocksByDiagram.values()];
183
+ }
184
+
185
+ /** Validates Mermaid diagrams through a bounded parallel worker pool. */
186
+ async function runMermaidValidationPool(
187
+ mermaidValidationTasks: readonly MermaidValidationTask[],
188
+ ): Promise<void> {
189
+ let nextTaskIndex = 0;
190
+ const workerCount = Math.min(
191
+ MERMAID_VALIDATION_CONCURRENCY,
192
+ mermaidValidationTasks.length,
193
+ );
194
+
195
+ await Promise.all(
196
+ Array.from({ length: workerCount }, async () => {
197
+ while (nextTaskIndex < mermaidValidationTasks.length) {
198
+ const mermaidValidationTask = mermaidValidationTasks[nextTaskIndex];
199
+ nextTaskIndex += 1;
200
+ await runMermaidValidation(mermaidValidationTask);
201
+ }
202
+ }),
203
+ );
204
+ }
205
+
206
+ /** Runs the Mermaid CLI validator for one diagram source file. */
207
+ async function runMermaidValidation(
208
+ mermaidValidationTask: MermaidValidationTask,
209
+ ): Promise<void> {
210
+ await new Promise<void>((resolve, reject) => {
211
+ const childProcess = spawn(
212
+ process.execPath,
213
+ [
214
+ MERMAID_CLI_SCRIPT_PATH,
215
+ 'validate',
216
+ '--input',
217
+ mermaidValidationTask.tempInputPath,
218
+ ],
219
+ { stdio: 'inherit' },
220
+ );
221
+
222
+ childProcess.once('exit', (exitCode, signal) => {
223
+ if (exitCode === 0) {
224
+ resolve();
225
+ return;
226
+ }
227
+
228
+ const relativeReadmePath = path.relative(
229
+ process.cwd(),
230
+ mermaidValidationTask.mermaidBlock.readmePath,
231
+ );
232
+ reject(
233
+ new Error(
234
+ `Invalid Mermaid diagram in ${relativeReadmePath} (block ${mermaidValidationTask.mermaidBlock.blockNumber}). Mermaid CLI exited with code ${exitCode ?? 'null'}${signal ? ` and signal ${signal}` : ''}.`,
235
+ ),
236
+ );
237
+ });
238
+ childProcess.once('error', reject);
239
+ });
240
+ }
241
+
242
+ /** Renders Mermaid fenced blocks as safe placeholder HTML before client boot. */
243
+ export function renderMermaidDiagramPlaceholder(diagram: string): string {
244
+ return `<pre class="mermaid mermaid-diagram">${escapeHtml(diagram)}</pre>`;
245
+ }
@@ -1,13 +1,17 @@
1
1
  /*
2
2
  * Owns the generated docs sidebar contract.
3
3
  *
4
- * The main HTML renderer only needs to decide which page is being emitted and
5
- * which docs directories were published. This helper turns that data into the
6
- * left-nav learning path: examples first for onboarding, grouped docs pages for
7
- * reference reading, and a depth-aware rail that stays visually continuous as
8
- * readers move between overview pages and nested deep dives.
4
+ * This chapter turns the published docs page set into the left-nav learning
5
+ * path: examples first for onboarding, grouped docs pages for reference
6
+ * reading, and a depth-aware rail that remains visually continuous.
9
7
  */
10
8
 
9
+ import type {
10
+ DocsSidebarRenderContext,
11
+ PageMeta,
12
+ SidebarRenderContext,
13
+ } from './render-docs-html.types.js';
14
+
11
15
  interface ExampleNavLink {
12
16
  dir: string;
13
17
  label: string;
@@ -30,34 +34,6 @@ interface PipeNavEntry {
30
34
  cssClass?: string;
31
35
  }
32
36
 
33
- interface SidebarRenderHelpers {
34
- generatedPageDirectories: ReadonlySet<string>;
35
- hasPublishedDocsPage: (
36
- relDir: string,
37
- generatedPageDirectories: ReadonlySet<string>,
38
- ) => boolean;
39
- buildRelativeDocsHref: (currentDir: string, targetDir: string) => string;
40
- escapeHtml: (value: string) => string;
41
- }
42
-
43
- /** Minimal page shape needed to assemble the docs sidebar groups. */
44
- export interface SidebarPageMeta {
45
- relDir: string;
46
- }
47
-
48
- /**
49
- * Shared render context for sidebar fragments that need publication checks and
50
- * stable relative links.
51
- */
52
- export interface SidebarRenderContext extends SidebarRenderHelpers {
53
- currentDir: string;
54
- }
55
-
56
- /** Render context for the full docs sidebar, including the grouped page set. */
57
- export interface DocsSidebarRenderContext extends SidebarRenderContext {
58
- pages: readonly SidebarPageMeta[];
59
- }
60
-
61
37
  const EXAMPLES_OVERVIEW_DIR = 'examples';
62
38
  const SIDEBAR_GROUP_ORDER = [
63
39
  'root',
@@ -216,16 +192,7 @@ const EXAMPLE_DEMOS: readonly ExampleNavDemo[] = [
216
192
  },
217
193
  ];
218
194
 
219
- /**
220
- * Render the compact examples list used on the root-page table of contents.
221
- *
222
- * This keeps the landing page aligned with the learning-path plan: readers see
223
- * the examples overview first, then the strongest demo entrypoints, without
224
- * needing the full sidebar card treatment.
225
- *
226
- * @param context - Link and publication helpers for the page being rendered.
227
- * @returns A compact pipe-nav list for published example docs, or an empty list.
228
- */
195
+ /** Renders the compact examples list used on the root-page table of contents. */
229
196
  export function buildExamplesTocLinksHtml(
230
197
  context: SidebarRenderContext,
231
198
  ): string {
@@ -262,25 +229,18 @@ export function buildExamplesTocLinksHtml(
262
229
  return buildPipeNavListHtml(context, entries, 'pipe-nav-list-compact');
263
230
  }
264
231
 
265
- /**
266
- * Render the full left docs sidebar with the examples-first learning path.
267
- *
268
- * The sidebar owns the teaching order for generated docs pages. Runnable
269
- * examples stay at the top because they are the fastest route to first success,
270
- * while the remaining pages are grouped into reference sections that make sense
271
- * once a reader is already oriented.
272
- *
273
- * @param context - The published page set, current page, and link helpers.
274
- * @returns The full sidebar section list for the current docs page.
275
- */
232
+ /** Renders the full left docs sidebar with the examples-first learning path. */
276
233
  export function buildDocsSidebarHtml(
277
234
  context: DocsSidebarRenderContext,
278
235
  ): string {
279
- const groupsMap = new Map<string, { name: string; items: SidebarPageMeta[] }>();
236
+ const groupsMap = new Map<string, { name: string; items: PageMeta[] }>();
280
237
 
281
238
  for (const page of context.pages) {
282
239
  const segment = page.relDir.split('/')[0] || 'root';
283
- if (segment === 'examples') continue;
240
+ if (segment === 'examples') {
241
+ continue;
242
+ }
243
+
284
244
  if (!groupsMap.has(segment)) {
285
245
  groupsMap.set(segment, { name: segment, items: [] });
286
246
  }
@@ -298,8 +258,11 @@ export function buildDocsSidebarHtml(
298
258
  rightGroup.name as (typeof SIDEBAR_GROUP_ORDER)[number],
299
259
  );
300
260
  const leftRank = leftOrder === -1 ? Number.MAX_SAFE_INTEGER : leftOrder;
301
- const rightRank = rightOrder === -1 ? Number.MAX_SAFE_INTEGER : rightOrder;
302
- return leftRank - rightRank || leftGroup.name.localeCompare(rightGroup.name);
261
+ const rightRank =
262
+ rightOrder === -1 ? Number.MAX_SAFE_INTEGER : rightOrder;
263
+ return (
264
+ leftRank - rightRank || leftGroup.name.localeCompare(rightGroup.name)
265
+ );
303
266
  })
304
267
  .map((group) => {
305
268
  const items = group.items.toSorted((leftPage, rightPage) =>
@@ -317,7 +280,7 @@ export function buildDocsSidebarHtml(
317
280
  }
318
281
 
319
282
  return `<li class="group"><div class="g-head">${context.escapeHtml(
320
- group.name,
283
+ formatSidebarGroupName(group.name),
321
284
  )}</div>${buildPipeNavListHtml(
322
285
  context,
323
286
  buildGroupPipeEntries(items, context.currentDir),
@@ -328,16 +291,7 @@ export function buildDocsSidebarHtml(
328
291
  return `<ul class="sidebar-sections">${examplesSidebarHtml}${groupsHtml}</ul>`;
329
292
  }
330
293
 
331
- /**
332
- * Render the example showcase cards that anchor the sidebar learning path.
333
- *
334
- * Each demo keeps a recommended reading order and a separate deep-dive list so
335
- * the sidebar can teach progression without flattening every page into one long
336
- * undifferentiated navigation block.
337
- *
338
- * @param context - Link and publication helpers for the page being rendered.
339
- * @returns The examples-first sidebar section, or an empty string when hidden.
340
- */
294
+ /** Renders the example showcase cards that anchor the sidebar learning path. */
341
295
  function buildExamplesSidebarHtml(context: SidebarRenderContext): string {
342
296
  const examplesOverviewLink = buildPipeNavListHtml(
343
297
  context,
@@ -358,64 +312,98 @@ function buildExamplesSidebarHtml(context: SidebarRenderContext): string {
358
312
  'pipe-nav-list-priority',
359
313
  );
360
314
 
361
- const demoCardsHtml = EXAMPLE_DEMOS.map((demo) => {
362
- const readingOrderLinksHtml = buildPipeNavListHtml(
363
- context,
364
- demo.readingOrder
365
- .filter((entry) =>
366
- context.hasPublishedDocsPage(entry.dir, context.generatedPageDirectories),
367
- )
368
- .map((entry) => ({
369
- label: entry.label,
370
- targetDir: entry.dir,
315
+ const showExpandedExampleShowcase =
316
+ context.currentDir === '' ||
317
+ context.currentDir === EXAMPLES_OVERVIEW_DIR ||
318
+ context.currentDir.startsWith('examples/');
319
+ const compactExamplesLinksHtml = showExpandedExampleShowcase
320
+ ? ''
321
+ : buildPipeNavListHtml(
322
+ context,
323
+ EXAMPLE_DEMOS.filter((demo) =>
324
+ context.hasPublishedDocsPage(
325
+ `${demo.dir}/docs`,
326
+ context.generatedPageDirectories,
327
+ ),
328
+ ).map((demo) => ({
329
+ label: demo.label,
330
+ targetDir: `${demo.dir}/docs`,
371
331
  depth: 0,
372
- isCurrent: context.currentDir === entry.dir,
373
- })),
374
- );
375
- const deepDiveLinksHtml = buildPipeNavListHtml(
376
- context,
377
- demo.deepDives
378
- .filter((entry) =>
379
- context.hasPublishedDocsPage(entry.dir, context.generatedPageDirectories),
380
- )
381
- .map((entry) => ({
382
- label: entry.label,
383
- targetDir: entry.dir,
384
- depth: 1,
385
- isCurrent: context.currentDir === entry.dir,
332
+ isCurrent: context.currentDir === `${demo.dir}/docs`,
386
333
  })),
387
- );
388
- const isCurrentDemo =
389
- context.currentDir === demo.dir ||
390
- context.currentDir.startsWith(`${demo.dir}/`) ||
391
- context.currentDir.startsWith(`${demo.dir}/docs`);
392
-
393
- if (!readingOrderLinksHtml && !deepDiveLinksHtml) {
394
- return '';
395
- }
334
+ 'pipe-nav-list-compact',
335
+ );
396
336
 
397
- return `<li class="nav-demo-card${isCurrentDemo ? ' is-current-demo' : ''}"><div class="nav-demo-meta"><div class="nav-demo-eyebrow">${context.escapeHtml(
398
- demo.eyebrow,
399
- )}</div><div class="nav-demo-title">${context.escapeHtml(
400
- demo.label,
401
- )}</div><p class="nav-demo-description">${context.escapeHtml(
402
- demo.description,
403
- )}</p></div><div class="nav-demo-cluster"><div class="nav-cluster-title">Recommended path</div>${readingOrderLinksHtml}</div>${
404
- deepDiveLinksHtml
405
- ? `<div class="nav-demo-cluster nav-demo-cluster-secondary"><div class="nav-cluster-title">Deep dives</div>${deepDiveLinksHtml}</div>`
406
- : ''
407
- }</li>`;
408
- })
409
- .filter(Boolean)
410
- .join('');
337
+ const demoCardsHtml = showExpandedExampleShowcase
338
+ ? EXAMPLE_DEMOS.map((demo) => {
339
+ const readingOrderLinksHtml = buildPipeNavListHtml(
340
+ context,
341
+ demo.readingOrder
342
+ .filter((entry) =>
343
+ context.hasPublishedDocsPage(
344
+ entry.dir,
345
+ context.generatedPageDirectories,
346
+ ),
347
+ )
348
+ .map((entry) => ({
349
+ label: entry.label,
350
+ targetDir: entry.dir,
351
+ depth: 0,
352
+ isCurrent: context.currentDir === entry.dir,
353
+ })),
354
+ );
355
+ const deepDiveLinksHtml = buildPipeNavListHtml(
356
+ context,
357
+ demo.deepDives
358
+ .filter((entry) =>
359
+ context.hasPublishedDocsPage(
360
+ entry.dir,
361
+ context.generatedPageDirectories,
362
+ ),
363
+ )
364
+ .map((entry) => ({
365
+ label: entry.label,
366
+ targetDir: entry.dir,
367
+ depth: 1,
368
+ isCurrent: context.currentDir === entry.dir,
369
+ })),
370
+ );
371
+ const isCurrentDemo =
372
+ context.currentDir === demo.dir ||
373
+ context.currentDir.startsWith(`${demo.dir}/`) ||
374
+ context.currentDir.startsWith(`${demo.dir}/docs`);
375
+
376
+ if (!readingOrderLinksHtml && !deepDiveLinksHtml) {
377
+ return '';
378
+ }
379
+
380
+ return `<li class="nav-demo-card${isCurrentDemo ? ' is-current-demo' : ''}"><div class="nav-demo-meta"><div class="nav-demo-eyebrow">${context.escapeHtml(
381
+ demo.eyebrow,
382
+ )}</div><div class="nav-demo-title">${context.escapeHtml(
383
+ demo.label,
384
+ )}</div><p class="nav-demo-description">${context.escapeHtml(
385
+ demo.description,
386
+ )}</p></div><div class="nav-demo-cluster"><div class="nav-cluster-title">Recommended path</div>${readingOrderLinksHtml}</div>${
387
+ deepDiveLinksHtml
388
+ ? `<div class="nav-demo-cluster nav-demo-cluster-secondary"><div class="nav-cluster-title">Deep dives</div>${deepDiveLinksHtml}</div>`
389
+ : ''
390
+ }</li>`;
391
+ })
392
+ .filter(Boolean)
393
+ .join('')
394
+ : '';
411
395
 
412
- if (!examplesOverviewLink && !demoCardsHtml) return '';
396
+ if (!examplesOverviewLink && !compactExamplesLinksHtml && !demoCardsHtml) {
397
+ return '';
398
+ }
413
399
 
414
- return `<li class="group group-priority"><div class="g-head">Start With Examples</div><p class="group-copy">The demos are the fastest way to learn the library in the order a first-time reader can actually absorb.</p><div class="nav-priority-links">${examplesOverviewLink}</div><ul class="nav-demo-showcase">${demoCardsHtml}</ul></li>`;
400
+ return `<li class="group group-priority"><div class="g-head">Start With Examples</div><p class="group-copy">The demos are the fastest way to learn the library in the order a first-time reader can actually absorb.</p><div class="nav-priority-links">${examplesOverviewLink}${compactExamplesLinksHtml}</div>${
401
+ demoCardsHtml ? `<ul class="nav-demo-showcase">${demoCardsHtml}</ul>` : ''
402
+ }</li>`;
415
403
  }
416
404
 
417
405
  function buildGroupPipeEntries(
418
- items: readonly SidebarPageMeta[],
406
+ items: readonly PageMeta[],
419
407
  currentDir: string,
420
408
  ): PipeNavEntry[] {
421
409
  return items
@@ -425,7 +413,7 @@ function buildGroupPipeEntries(
425
413
  .map((page) => {
426
414
  const relativeSegments = page.relDir.split('/').slice(1);
427
415
  return {
428
- label: relativeSegments.at(-1) ?? 'Overview',
416
+ label: formatGroupEntryLabel(relativeSegments),
429
417
  targetDir: page.relDir,
430
418
  depth: Math.max(0, relativeSegments.length - 1),
431
419
  isCurrent: page.relDir === currentDir,
@@ -434,17 +422,35 @@ function buildGroupPipeEntries(
434
422
  }
435
423
 
436
424
  /**
437
- * Render the fixed-width rail cells that anchor one sidebar row.
438
- *
439
- * Depth is represented by layout boxes rather than text glyph stacks. That
440
- * gives CSS a stable column contract for the vertical rail and connector line,
441
- * which prevents the left path from breaking when fonts, line-height, or
442
- * example cluster boundaries change.
443
- *
444
- * @param depth - Logical nesting depth for the entry being rendered.
445
- * @param isFooter - Whether the cells belong to the footer row that closes the list.
446
- * @returns The rail-cell markup for one sidebar row.
425
+ * Formats sidebar labels so overview pages read clearly and deep pages are not
426
+ * reduced to ambiguous repeated leaf names.
447
427
  */
428
+ function formatGroupEntryLabel(relativeSegments: readonly string[]): string {
429
+ if (relativeSegments.length === 0) {
430
+ return 'Overview';
431
+ }
432
+
433
+ const leafLabel = formatSidebarGroupName(
434
+ relativeSegments.at(-1) ?? 'Overview',
435
+ );
436
+ if (relativeSegments.length === 1) {
437
+ return leafLabel;
438
+ }
439
+
440
+ const parentLabel = formatSidebarGroupName(relativeSegments.at(-2) ?? '');
441
+ return `${parentLabel} / ${leafLabel}`;
442
+ }
443
+
444
+ /** Formats a sidebar group or segment name into a human-readable label. */
445
+ function formatSidebarGroupName(segment: string): string {
446
+ return segment
447
+ .split(/[-_]/g)
448
+ .filter(Boolean)
449
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
450
+ .join(' ');
451
+ }
452
+
453
+ /** Renders the fixed-width rail cells that anchor one sidebar row. */
448
454
  function buildPipeRailCellsHtml(depth: number, isFooter = false): string {
449
455
  const railDepth = Math.max(0, depth);
450
456
 
@@ -462,24 +468,15 @@ function buildPipeRailCellsHtml(depth: number, isFooter = false): string {
462
468
  }).join('');
463
469
  }
464
470
 
465
- /**
466
- * Render one pipe-nav list using box-based rail columns for visual continuity.
467
- *
468
- * Entries provide a logical depth value rather than nested list markup. That
469
- * keeps the DOM shallow while still giving the theme enough structure to draw a
470
- * continuous rail through overview links, recommended paths, and deep dives.
471
- *
472
- * @param context - Link and publication helpers for the page being rendered.
473
- * @param entries - Sidebar links with the depth contract already resolved.
474
- * @param listClass - Optional modifier class for compact or priority variants.
475
- * @returns One pipe-nav list plus a footer rail that finishes the visual track.
476
- */
471
+ /** Renders one rail-based pipe navigation list. */
477
472
  function buildPipeNavListHtml(
478
473
  context: SidebarRenderContext,
479
474
  entries: readonly PipeNavEntry[],
480
475
  listClass = '',
481
476
  ): string {
482
- if (entries.length === 0) return '';
477
+ if (entries.length === 0) {
478
+ return '';
479
+ }
483
480
 
484
481
  const itemsHtml = entries
485
482
  .map((entry) => {
@@ -510,4 +507,4 @@ function buildPipeNavListHtml(
510
507
  lastEntryDepth,
511
508
  true,
512
509
  )}</span></li></ul>`;
513
- }
510
+ }