agentic-mermaid 0.1.1 → 0.2.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.
Files changed (340) hide show
  1. package/AGENT_NATIVE.md +155 -91
  2. package/CHANGELOG.md +163 -4
  3. package/Instructions_for_agents.md +10 -10
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +31 -38
  6. package/THIRD_PARTY_NOTICES.md +29 -0
  7. package/assets/hero.png +0 -0
  8. package/dist/agent-core.d.ts +463 -0
  9. package/dist/agent-core.js +397 -0
  10. package/dist/agent.d.ts +73 -1868
  11. package/dist/agent.js +336 -37325
  12. package/dist/agentic-mermaid-mcp.js +7 -38399
  13. package/dist/am.js +33626 -40446
  14. package/dist/chunk-2P5FKUQC.js +3485 -0
  15. package/dist/chunk-2S3VSS6I.js +149 -0
  16. package/dist/chunk-2SILJDW2.js +37 -0
  17. package/dist/chunk-BJYMBT4I.js +61963 -0
  18. package/dist/chunk-FDM43XU7.js +615 -0
  19. package/dist/chunk-MYPDVZCO.js +4702 -0
  20. package/dist/index-DWPa2MJs.d.ts +6780 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +310 -33014
  23. package/dist/mcp-cli-5J47WEY3.js +17 -0
  24. package/docs/README.md +39 -31
  25. package/docs/agent-api-cookbook.md +44 -35
  26. package/docs/agent-mutation-policy.md +1 -1
  27. package/docs/agent-workflow-examples.md +2 -2
  28. package/docs/api.md +284 -77
  29. package/docs/ascii.md +26 -16
  30. package/docs/code-mode-impact.md +214 -0
  31. package/docs/comparison.md +23 -11
  32. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  33. package/docs/config.md +26 -10
  34. package/docs/custom-fonts.md +265 -0
  35. package/docs/custom-style-cookbook.md +130 -9
  36. package/docs/diagram-families.md +48 -23
  37. package/docs/features.md +59 -52
  38. package/docs/fork-differences.md +6 -3
  39. package/docs/getting-started.md +23 -16
  40. package/docs/issue-derived-test-cases.md +29 -22
  41. package/docs/mcp-code-mode-rationale.md +13 -6
  42. package/docs/mcp-http-transport.md +20 -13
  43. package/docs/mermaid-layout-complaints.md +66 -52
  44. package/docs/mutation-testing.md +145 -66
  45. package/docs/quality.md +23 -14
  46. package/docs/react.md +2 -2
  47. package/docs/schemas/style-spec.schema.json +1221 -30
  48. package/docs/style-authoring.md +241 -38
  49. package/docs/svg-semantic-contract.md +49 -0
  50. package/docs/testing-strategy.md +166 -58
  51. package/docs/theming.md +19 -32
  52. package/examples/styles/catalog.json +54 -0
  53. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  54. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  55. package/examples/styles/cupertino-prototype.mmd +10 -0
  56. package/examples/styles/cupertino-prototype.style.json +63 -0
  57. package/examples/styles/mid-century-report.style.json +2 -2
  58. package/examples/styles/star-chart-atlas.style.json +2 -1
  59. package/examples/styles/transit-route-map.style.json +2 -2
  60. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  61. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  62. package/package.json +85 -45
  63. package/server.json +2 -2
  64. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  65. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  66. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  67. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  68. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  69. package/TODO.md +0 -684
  70. package/dist/index-wjRjbsP-.d.ts +0 -1102
  71. package/docs/assets/improvements/box-start-connector.png +0 -0
  72. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  73. package/docs/assets/improvements/er-content-loss.png +0 -0
  74. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  75. package/docs/assets/improvements/gantt-family.png +0 -0
  76. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  77. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  78. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  79. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  80. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  81. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  82. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  83. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  84. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  85. package/docs/contributing/adding-diagram-types.md +0 -144
  86. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  87. package/docs/contributing/diagram-family-citizenship.md +0 -82
  88. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  89. package/docs/contributing/lessons-learned.md +0 -276
  90. package/docs/contributing/releasing.md +0 -82
  91. package/docs/contributing/visual-review-evidence.md +0 -53
  92. package/docs/design/families/README.md +0 -26
  93. package/docs/design/families/architecture-beta.md +0 -48
  94. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  95. package/docs/design/families/gantt-research.md +0 -137
  96. package/docs/design/families/gantt.md +0 -329
  97. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  98. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  99. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  100. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  101. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  102. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  103. package/docs/design/families/journey-migration-parity.md +0 -439
  104. package/docs/design/families/journey-section-overlap-after.png +0 -0
  105. package/docs/design/families/journey-section-overlap-before.png +0 -0
  106. package/docs/design/families/journey-usage-research.md +0 -423
  107. package/docs/design/families/journey.md +0 -153
  108. package/docs/design/families/xychart.md +0 -85
  109. package/docs/design/system/README.md +0 -45
  110. package/docs/design/system/abstraction-audit.md +0 -224
  111. package/docs/design/system/abstraction-recommendations.md +0 -655
  112. package/docs/design/system/architecture.mmd +0 -29
  113. package/docs/design/system/architecture.svg +0 -137
  114. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  115. package/docs/design/system/layout-rubric.md +0 -138
  116. package/docs/design/system/route-contracts.md +0 -848
  117. package/docs/design/system/source-preservation-ladder.md +0 -52
  118. package/docs/design/system/styles-rollout.md +0 -216
  119. package/docs/design/system/ugly-layouts.md +0 -119
  120. package/docs/layout-characterization/README.md +0 -245
  121. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  122. package/docs/layout-characterization/contact-sheet.md +0 -638
  123. package/docs/layout-characterization/properties.md +0 -281
  124. package/docs/layout-characterization/visual-quality.md +0 -160
  125. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  126. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  127. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  128. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  129. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  130. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  131. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  132. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  133. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  134. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  135. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  136. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  137. package/docs/project/divergences.md +0 -98
  138. package/docs/project/dogfooding-docs-strategy.md +0 -66
  139. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  140. package/docs/project/lessons-learned.md +0 -1019
  141. package/docs/project/remove-role-styling-plan.md +0 -176
  142. package/examples/agent-improve-auth-flow.ts +0 -206
  143. package/examples/agent-loop.ts +0 -53
  144. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  145. package/llms.txt +0 -104
  146. package/skill-evals/README.md +0 -77
  147. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  148. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  149. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  150. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  151. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  152. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  153. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  154. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  155. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  156. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  157. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  158. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  159. package/skill-evals/shared-benchmark.json +0 -510
  160. package/src/agent/analyze.ts +0 -157
  161. package/src/agent/apply.ts +0 -146
  162. package/src/agent/architecture-body.ts +0 -458
  163. package/src/agent/certificates.ts +0 -110
  164. package/src/agent/class-body.ts +0 -319
  165. package/src/agent/core.ts +0 -207
  166. package/src/agent/create.ts +0 -106
  167. package/src/agent/describe.ts +0 -368
  168. package/src/agent/er-body.ts +0 -224
  169. package/src/agent/facts.ts +0 -360
  170. package/src/agent/families-builtin.ts +0 -920
  171. package/src/agent/families.ts +0 -232
  172. package/src/agent/family-layouts.ts +0 -832
  173. package/src/agent/flowchart-body.ts +0 -372
  174. package/src/agent/flowchart-unsupported.ts +0 -243
  175. package/src/agent/font-coverage.ts +0 -288
  176. package/src/agent/gantt-body.ts +0 -495
  177. package/src/agent/index.ts +0 -10
  178. package/src/agent/journey-body.ts +0 -374
  179. package/src/agent/label-metrics.ts +0 -28
  180. package/src/agent/layout-to-rendered.ts +0 -63
  181. package/src/agent/mutate.ts +0 -97
  182. package/src/agent/mutation-ops.ts +0 -51
  183. package/src/agent/op-schema.ts +0 -443
  184. package/src/agent/parse.ts +0 -223
  185. package/src/agent/pie-body.ts +0 -270
  186. package/src/agent/png.ts +0 -171
  187. package/src/agent/quadrant-body.ts +0 -336
  188. package/src/agent/quality.ts +0 -363
  189. package/src/agent/readability-audit.ts +0 -92
  190. package/src/agent/rendered-route-audit.ts +0 -61
  191. package/src/agent/sequence-body.ts +0 -345
  192. package/src/agent/serialize.ts +0 -214
  193. package/src/agent/state-body.ts +0 -589
  194. package/src/agent/structural-count.ts +0 -150
  195. package/src/agent/timeline-body.ts +0 -280
  196. package/src/agent/trace-log.ts +0 -43
  197. package/src/agent/types.ts +0 -995
  198. package/src/agent/verify.ts +0 -603
  199. package/src/agent/xychart-body.ts +0 -472
  200. package/src/architecture/config.ts +0 -282
  201. package/src/architecture/layout.ts +0 -496
  202. package/src/architecture/parser.ts +0 -375
  203. package/src/architecture/renderer.ts +0 -611
  204. package/src/architecture/types.ts +0 -113
  205. package/src/ascii/ansi.ts +0 -423
  206. package/src/ascii/architecture.ts +0 -210
  207. package/src/ascii/canvas.ts +0 -443
  208. package/src/ascii/class-diagram.ts +0 -697
  209. package/src/ascii/converter.ts +0 -475
  210. package/src/ascii/draw.ts +0 -1693
  211. package/src/ascii/edge-bundling.ts +0 -328
  212. package/src/ascii/edge-routing.ts +0 -330
  213. package/src/ascii/er-diagram.ts +0 -439
  214. package/src/ascii/gantt.ts +0 -276
  215. package/src/ascii/grid.ts +0 -747
  216. package/src/ascii/index.ts +0 -196
  217. package/src/ascii/journey.ts +0 -169
  218. package/src/ascii/meta.ts +0 -244
  219. package/src/ascii/multiline-utils.ts +0 -80
  220. package/src/ascii/pathfinder.ts +0 -301
  221. package/src/ascii/pie.ts +0 -67
  222. package/src/ascii/quadrant.ts +0 -158
  223. package/src/ascii/reverse.ts +0 -216
  224. package/src/ascii/sequence.ts +0 -499
  225. package/src/ascii/shapes/circle.ts +0 -27
  226. package/src/ascii/shapes/corners.ts +0 -143
  227. package/src/ascii/shapes/diamond.ts +0 -27
  228. package/src/ascii/shapes/hexagon.ts +0 -27
  229. package/src/ascii/shapes/index.ts +0 -106
  230. package/src/ascii/shapes/rectangle.ts +0 -168
  231. package/src/ascii/shapes/rounded.ts +0 -27
  232. package/src/ascii/shapes/special.ts +0 -332
  233. package/src/ascii/shapes/stadium.ts +0 -110
  234. package/src/ascii/shapes/state.ts +0 -192
  235. package/src/ascii/shapes/types.ts +0 -73
  236. package/src/ascii/timeline.ts +0 -141
  237. package/src/ascii/types.ts +0 -312
  238. package/src/ascii/validate.ts +0 -120
  239. package/src/ascii/width.ts +0 -81
  240. package/src/ascii/wrap.ts +0 -131
  241. package/src/ascii/xychart.ts +0 -919
  242. package/src/browser.ts +0 -29
  243. package/src/class/layout.ts +0 -240
  244. package/src/class/parser.ts +0 -332
  245. package/src/class/renderer.ts +0 -638
  246. package/src/class/types.ts +0 -129
  247. package/src/cli/agent-instructions.ts +0 -1
  248. package/src/cli/am-bin.ts +0 -6
  249. package/src/cli/exit-codes.ts +0 -18
  250. package/src/cli/index.ts +0 -1132
  251. package/src/cli/init-agent.ts +0 -147
  252. package/src/cli/run-entrypoint.ts +0 -10
  253. package/src/color-resolver.ts +0 -152
  254. package/src/elk-instance.ts +0 -114
  255. package/src/er/layout.ts +0 -187
  256. package/src/er/parser.ts +0 -226
  257. package/src/er/renderer.ts +0 -792
  258. package/src/er/types.ts +0 -99
  259. package/src/family-rubric.ts +0 -425
  260. package/src/font-manifest.ts +0 -31
  261. package/src/gantt/layout.ts +0 -365
  262. package/src/gantt/parser.ts +0 -282
  263. package/src/gantt/pipeline.ts +0 -27
  264. package/src/gantt/renderer.ts +0 -414
  265. package/src/gantt/schedule.ts +0 -513
  266. package/src/gantt/types.ts +0 -255
  267. package/src/index.ts +0 -368
  268. package/src/journey/layout.ts +0 -554
  269. package/src/journey/parse-core.ts +0 -251
  270. package/src/journey/parser.ts +0 -101
  271. package/src/journey/renderer.ts +0 -917
  272. package/src/journey/types.ts +0 -144
  273. package/src/layout/geometry.ts +0 -182
  274. package/src/layout/pass.ts +0 -142
  275. package/src/layout/passes/index.ts +0 -3916
  276. package/src/layout-engine.ts +0 -1565
  277. package/src/layout-rubric.ts +0 -659
  278. package/src/mcp/artifacts.ts +0 -137
  279. package/src/mcp/dynamic-harness.ts +0 -31
  280. package/src/mcp/dynamic-modules.d.ts +0 -7
  281. package/src/mcp/facade.ts +0 -608
  282. package/src/mcp/harness-runtime.ts +0 -203
  283. package/src/mcp/hosted-server.ts +0 -468
  284. package/src/mcp/mcp-bin.ts +0 -3
  285. package/src/mcp/mcp-cli.ts +0 -82
  286. package/src/mcp/protocol.ts +0 -13
  287. package/src/mcp/sandbox.ts +0 -195
  288. package/src/mcp/sdk-decl.ts +0 -391
  289. package/src/mcp/server.ts +0 -362
  290. package/src/mcp/tool-surface.ts +0 -146
  291. package/src/mermaid-source.ts +0 -655
  292. package/src/multiline-utils.ts +0 -228
  293. package/src/parser.ts +0 -1031
  294. package/src/pie/layout.ts +0 -168
  295. package/src/pie/parser.ts +0 -136
  296. package/src/pie/renderer.ts +0 -234
  297. package/src/pie/types.ts +0 -82
  298. package/src/quadrant/layout.ts +0 -191
  299. package/src/quadrant/parser.ts +0 -183
  300. package/src/quadrant/renderer.ts +0 -322
  301. package/src/quadrant/types.ts +0 -113
  302. package/src/render-family-hooks.ts +0 -244
  303. package/src/renderer.ts +0 -1099
  304. package/src/route-contracts.ts +0 -2723
  305. package/src/scene/backend.ts +0 -76
  306. package/src/scene/fidelity.ts +0 -183
  307. package/src/scene/hybrid-backend.ts +0 -174
  308. package/src/scene/ir.ts +0 -181
  309. package/src/scene/marks.ts +0 -127
  310. package/src/scene/rough-backend.ts +0 -378
  311. package/src/scene/seed.ts +0 -46
  312. package/src/scene/style-registry.ts +0 -556
  313. package/src/sequence/layout.ts +0 -456
  314. package/src/sequence/parser.ts +0 -255
  315. package/src/sequence/renderer.ts +0 -604
  316. package/src/sequence/types.ts +0 -154
  317. package/src/shape-clipping.ts +0 -351
  318. package/src/shared/accessibility-directives.ts +0 -22
  319. package/src/shared/batched.ts +0 -61
  320. package/src/shared/color-math.ts +0 -93
  321. package/src/shared/svg-a11y.ts +0 -29
  322. package/src/shared/syntax-error.ts +0 -17
  323. package/src/shared/unicode-ranges.ts +0 -135
  324. package/src/styles.ts +0 -318
  325. package/src/svg-paths.ts +0 -28
  326. package/src/text-metrics.ts +0 -248
  327. package/src/theme.ts +0 -710
  328. package/src/timeline/layout.ts +0 -381
  329. package/src/timeline/parser.ts +0 -215
  330. package/src/timeline/renderer.ts +0 -538
  331. package/src/timeline/types.ts +0 -105
  332. package/src/types.ts +0 -462
  333. package/src/website-worker-modules.d.ts +0 -18
  334. package/src/xychart/axis-utils.ts +0 -109
  335. package/src/xychart/colors.ts +0 -124
  336. package/src/xychart/config.ts +0 -88
  337. package/src/xychart/layout.ts +0 -656
  338. package/src/xychart/parser.ts +0 -504
  339. package/src/xychart/renderer.ts +0 -640
  340. package/src/xychart/types.ts +0 -282
@@ -1,301 +0,0 @@
1
- // ============================================================================
2
- // ASCII renderer — A* pathfinding for edge routing
3
- //
4
- // Ported from AlexanderGrooff/mermaid-ascii cmd/arrow.go.
5
- // Uses A* search with a corner-penalizing heuristic to find clean
6
- // paths between nodes on the grid. Prefers straight lines over zigzags.
7
- // ============================================================================
8
-
9
- import type { GridCoord, AsciiNode, Direction } from './types.ts'
10
- import { gridKey, gridCoordEquals } from './types.ts'
11
-
12
- // ============================================================================
13
- // Priority queue (min-heap) for A* open set
14
- // ============================================================================
15
-
16
- interface PQItem {
17
- coord: GridCoord
18
- priority: number
19
- /**
20
- * Monotonic insertion sequence. Used as a deterministic FIFO tie-breaker so
21
- * equal-priority nodes pop in insertion order regardless of heap topology
22
- * (upstream lukilabs#113). Without this, the order two equal-priority cells
23
- * are visited depends on incidental heap shape, which let sibling fan-out
24
- * edges pick inconsistent corners and produce L-shaped detours.
25
- */
26
- seq: number
27
- }
28
-
29
- /**
30
- * Simple min-heap priority queue with deterministic FIFO tie-breaking.
31
- * For the grid sizes we handle (~100s of cells), this is more than fast enough.
32
- */
33
- class MinHeap {
34
- private items: PQItem[] = []
35
-
36
- get length(): number {
37
- return this.items.length
38
- }
39
-
40
- push(item: PQItem): void {
41
- this.items.push(item)
42
- this.bubbleUp(this.items.length - 1)
43
- }
44
-
45
- pop(): PQItem | undefined {
46
- if (this.items.length === 0) return undefined
47
- const top = this.items[0]!
48
- const last = this.items.pop()!
49
- if (this.items.length > 0) {
50
- this.items[0] = last
51
- this.sinkDown(0)
52
- }
53
- return top
54
- }
55
-
56
- /**
57
- * Order two heap items: by priority, then by insertion sequence (FIFO).
58
- * Returns true if `a` should sit above `b` in the min-heap.
59
- */
60
- private before(a: PQItem, b: PQItem): boolean {
61
- if (a.priority !== b.priority) return a.priority < b.priority
62
- return a.seq < b.seq
63
- }
64
-
65
- private bubbleUp(i: number): void {
66
- while (i > 0) {
67
- const parent = (i - 1) >> 1
68
- if (this.before(this.items[i]!, this.items[parent]!)) {
69
- ;[this.items[i], this.items[parent]] = [this.items[parent]!, this.items[i]!]
70
- i = parent
71
- } else {
72
- break
73
- }
74
- }
75
- }
76
-
77
- private sinkDown(i: number): void {
78
- const n = this.items.length
79
- while (true) {
80
- let smallest = i
81
- const left = 2 * i + 1
82
- const right = 2 * i + 2
83
- // Upstream lukilabs#113 used `<=` here to break ties; we encode the same
84
- // determinism explicitly via the seq tie-breaker in `before`, which is
85
- // unambiguous regardless of traversal order.
86
- if (left < n && this.before(this.items[left]!, this.items[smallest]!)) {
87
- smallest = left
88
- }
89
- if (right < n && this.before(this.items[right]!, this.items[smallest]!)) {
90
- smallest = right
91
- }
92
- if (smallest !== i) {
93
- ;[this.items[i], this.items[smallest]] = [this.items[smallest]!, this.items[i]!]
94
- i = smallest
95
- } else {
96
- break
97
- }
98
- }
99
- }
100
- }
101
-
102
- // ============================================================================
103
- // A* heuristic
104
- // ============================================================================
105
-
106
- /**
107
- * Manhattan distance with a +1 penalty when both dx and dy are non-zero.
108
- * This encourages the pathfinder to prefer straight lines and minimize corners.
109
- */
110
- export function heuristic(a: GridCoord, b: GridCoord): number {
111
- const absX = Math.abs(a.x - b.x)
112
- const absY = Math.abs(a.y - b.y)
113
- if (absX === 0 || absY === 0) {
114
- return absX + absY
115
- }
116
- return absX + absY + 1
117
- }
118
-
119
- // ============================================================================
120
- // A* pathfinding
121
- // ============================================================================
122
-
123
- /** 4-directional movement (no diagonals in grid pathfinding). */
124
- const MOVE_DIRS: GridCoord[] = [
125
- { x: 1, y: 0 },
126
- { x: -1, y: 0 },
127
- { x: 0, y: 1 },
128
- { x: 0, y: -1 },
129
- ]
130
-
131
- /** Build a move-direction order that tries the edge's initial cardinal direction first. */
132
- function buildMoveDirs(preferredDir?: Direction): GridCoord[] {
133
- if (!preferredDir) return [...MOVE_DIRS]
134
-
135
- // Direction values are 3x3 node attachment offsets, not signed deltas:
136
- // Up={1,0}, Down={1,2}, Left={0,1}, Right={2,1}.
137
- if (preferredDir.x === 2 && preferredDir.y === 1) {
138
- return [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
139
- }
140
- if (preferredDir.x === 0 && preferredDir.y === 1) {
141
- return [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
142
- }
143
- if (preferredDir.x === 1 && preferredDir.y === 2) {
144
- return [{ x: 0, y: 1 }, { x: 0, y: -1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
145
- }
146
- if (preferredDir.x === 1 && preferredDir.y === 0) {
147
- return [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
148
- }
149
-
150
- const dx = preferredDir.x - 1
151
- const dy = preferredDir.y - 1
152
- if (Math.abs(dx) >= Math.abs(dy)) {
153
- return dx >= 0
154
- ? [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
155
- : [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
156
- }
157
- return dy >= 0
158
- ? [{ x: 0, y: 1 }, { x: 0, y: -1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
159
- : [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
160
- }
161
-
162
- /** Check if a grid cell is unoccupied and has non-negative coordinates. */
163
- function isFreeInGrid(grid: Map<string, AsciiNode>, c: GridCoord): boolean {
164
- if (c.x < 0 || c.y < 0) return false
165
- return !grid.has(gridKey(c))
166
- }
167
-
168
-
169
- /**
170
- * Find a path from `from` to `to` on the grid using A*.
171
- * Returns the path as an array of GridCoords, or null if no path exists.
172
- *
173
- * @param preferredDir - Optional exit direction tried first during neighbour
174
- * expansion (see `orderedDirs`). Currently unused by the edge router; kept as
175
- * a tested capability and exercised by ascii-pathfinder-determinism.test.ts.
176
- */
177
- export function getPath(
178
- grid: Map<string, AsciiNode>,
179
- from: GridCoord,
180
- to: GridCoord,
181
- preferredDir?: Direction,
182
- ): GridCoord[] | null {
183
- // #66 (ktrysmt): bound the search. `isFreeInGrid` only guards x/y < 0, so an
184
- // unreachable target (walled off) would let A* explore +x/+y unboundedly →
185
- // OOM/hang. Derive an upper bound from the grid extent + the from/to coords,
186
- // plus a hard iteration cap. On exhaustion we return null and the caller
187
- // falls back to a direct route, rather than exhausting memory.
188
- let maxX = Math.max(from.x, to.x)
189
- let maxY = Math.max(from.y, to.y)
190
- for (const key of grid.keys()) {
191
- const comma = key.indexOf(',')
192
- const kx = Number(key.slice(0, comma))
193
- const ky = Number(key.slice(comma + 1))
194
- if (kx > maxX) maxX = kx
195
- if (ky > maxY) maxY = ky
196
- }
197
- // Margin lets routes detour slightly around obstacles past the extent.
198
- const boundX = maxX + 4
199
- const boundY = maxY + 4
200
- // Iteration cap proportional to the bounded grid area (with a floor).
201
- const maxIterations = Math.max(10_000, (boundX + 1) * (boundY + 1) * 4)
202
-
203
- const moveDirs = buildMoveDirs(preferredDir)
204
-
205
- let seq = 0
206
- const pq = new MinHeap()
207
- pq.push({ coord: from, priority: 0, seq: seq++ })
208
-
209
- const costSoFar = new Map<string, number>()
210
- costSoFar.set(gridKey(from), 0)
211
-
212
- const cameFrom = new Map<string, GridCoord | null>()
213
- cameFrom.set(gridKey(from), null)
214
-
215
- let iterations = 0
216
- while (pq.length > 0) {
217
- if (++iterations > maxIterations) return null // #66 guard: bail to caller's fallback
218
- const current = pq.pop()!.coord
219
-
220
- if (gridCoordEquals(current, to)) {
221
- // Reconstruct path by walking backwards through cameFrom
222
- const path: GridCoord[] = []
223
- let c: GridCoord | null = current
224
- while (c !== null) {
225
- path.unshift(c)
226
- c = cameFrom.get(gridKey(c)) ?? null
227
- }
228
- return path
229
- }
230
-
231
- const currentCost = costSoFar.get(gridKey(current))!
232
-
233
- for (const dir of moveDirs) {
234
- const next: GridCoord = { x: current.x + dir.x, y: current.y + dir.y }
235
-
236
- // #66 guard: never expand past the bounded extent. Without this, an
237
- // unreachable target lets the search wander the unbounded +x/+y plane.
238
- if (next.x > boundX || next.y > boundY) continue
239
-
240
- // Allow moving to the destination even if it's occupied (it's a node boundary)
241
- if (!isFreeInGrid(grid, next) && !gridCoordEquals(next, to)) {
242
- continue
243
- }
244
-
245
- const newCost = currentCost + 1
246
- const nextKey = gridKey(next)
247
- const existingCost = costSoFar.get(nextKey)
248
-
249
- if (existingCost === undefined || newCost < existingCost) {
250
- costSoFar.set(nextKey, newCost)
251
- const priority = newCost + heuristic(next, to)
252
- pq.push({ coord: next, priority, seq: seq++ })
253
- cameFrom.set(nextKey, current)
254
- }
255
- }
256
- }
257
-
258
- return null // No path found
259
- }
260
-
261
- /**
262
- * Simplify a path by removing intermediate waypoints on straight segments.
263
- * E.g., [(0,0), (1,0), (2,0), (2,1)] becomes [(0,0), (2,0), (2,1)].
264
- * This reduces the number of line-drawing operations.
265
- */
266
- export function mergePath(path: GridCoord[]): GridCoord[] {
267
- if (path.length <= 2) return path
268
-
269
- const toRemove = new Set<number>()
270
- let step0 = path[0]!
271
- let step1 = path[1]!
272
-
273
- for (let idx = 2; idx < path.length; idx++) {
274
- const step2 = path[idx]!
275
- const prevDx = step1.x - step0.x
276
- const prevDy = step1.y - step0.y
277
- const dx = step2.x - step1.x
278
- const dy = step2.y - step1.y
279
-
280
- // Same direction — the middle point is redundant
281
- if (prevDx === dx && prevDy === dy) {
282
- // In Go: indexToRemove = append(indexToRemove, idx+1) but idx is 0-based from path[2:]
283
- // which corresponds to index idx in the full path. Go uses idx+1 because idx iterates
284
- // from 0 in the [2:] slice, mapping to full-array index idx+1.
285
- // Actually re-checking Go code: the loop is `for idx, step2 := range path[2:]`
286
- // so idx=0 → path[2], and it removes idx+1 which is index 1 in the full array.
287
- // Wait, that doesn't look right. Let me re-read:
288
- // step0 = path[0], step1 = path[1]
289
- // for idx, step2 := range path[2:] { ... indexToRemove = append(indexToRemove, idx+1) ... }
290
- // When idx=0, step2=path[2], and it removes index 1 (step1 = path[1]) if directions match
291
- // So it removes the middle point (step1) which is at index idx+1 in the original array
292
- // when counting from the 2-ahead loop. Let me just track which middle indices to remove.
293
- toRemove.add(idx - 1) // Remove the middle point (step1's position)
294
- }
295
-
296
- step0 = step1
297
- step1 = step2
298
- }
299
-
300
- return path.filter((_, i) => !toRemove.has(i))
301
- }
package/src/ascii/pie.ts DELETED
@@ -1,67 +0,0 @@
1
- // ============================================================================
2
- // ASCII renderer — Pie chart
3
- //
4
- // A pie chart doesn't map cleanly to a character grid, so we render it as a
5
- // proportional horizontal bar list (the conventional terminal pie rendering):
6
- //
7
- // Pets (title, if present)
8
- // Dogs ████████████████████████ 77.2%
9
- // Cats █████ 17.0%
10
- // Rats █ 5.8%
11
- //
12
- // Each bar's length is proportional to the slice's share of the total. With
13
- // `showData` the raw value is shown alongside the percentage. Bars use █
14
- // (Unicode) or # (ASCII fallback), colored per-slice with the same xychart
15
- // palette as the SVG renderer for cross-format consistency.
16
- // ============================================================================
17
-
18
- import { parsePieChart } from '../pie/parser.ts'
19
- import { formatPieValue, formatPiePercent } from '../pie/layout.ts'
20
- import type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'
21
- import { colorizeText } from './ansi.ts'
22
- import { getSeriesColor, CHART_ACCENT_FALLBACK, isValidHex } from '../xychart/colors.ts'
23
-
24
- /** Maximum bar length in characters (the largest slice fills this). */
25
- const MAX_BAR = 30
26
-
27
- function sliceColor(index: number, theme: AsciiTheme): string {
28
- const accent = theme.accent && isValidHex(theme.accent) ? theme.accent : CHART_ACCENT_FALLBACK
29
- return getSeriesColor(index, accent, theme.bg)
30
- }
31
-
32
- export function renderPieAscii(
33
- lines: string[],
34
- config: AsciiConfig,
35
- colorMode: ColorMode,
36
- theme: AsciiTheme,
37
- ): string {
38
- const chart = parsePieChart(lines)
39
- const barChar = config.useAscii ? '#' : '█'
40
-
41
- const total = chart.entries.reduce((sum, e) => sum + e.value, 0)
42
- if (total <= 0) return ''
43
-
44
- // Column widths.
45
- const labelWidth = Math.max(...chart.entries.map(e => e.label.length))
46
- const maxValue = Math.max(...chart.entries.map(e => e.value))
47
-
48
- const out: string[] = []
49
- if (chart.title) out.push(chart.title)
50
-
51
- chart.entries.forEach((entry, index) => {
52
- const fraction = entry.value / total
53
- // Bar length proportional to the largest slice, min 1 char for nonzero.
54
- const barLen = Math.max(1, Math.round((entry.value / maxValue) * MAX_BAR))
55
- const bar = barChar.repeat(barLen)
56
- const coloredBar = colorMode === 'none' ? bar : colorizeText(bar, sliceColor(index, theme), colorMode)
57
-
58
- const label = entry.label.padEnd(labelWidth)
59
- const barPad = ' '.repeat(MAX_BAR - barLen + 2)
60
- const pct = formatPiePercent(fraction).padStart(6)
61
- const valuePart = chart.showData ? ` [${formatPieValue(entry.value)}]` : ''
62
-
63
- out.push(`${label} ${coloredBar}${barPad}${pct}${valuePart}`)
64
- })
65
-
66
- return out.join('\n')
67
- }
@@ -1,158 +0,0 @@
1
- // ============================================================================
2
- // ASCII renderer — Quadrant chart
3
- //
4
- // Renders a bordered SQUARE grid divided into four quadrants. Quadrant labels
5
- // sit in their regions, points are plotted at scaled positions marked ●
6
- // (Unicode) / * (ASCII), and a legend below lists every point's coordinates.
7
- //
8
- // Mermaid quadrant numbering: 1=top-right, 2=top-left, 3=bottom-left,
9
- // 4=bottom-right. Normalized coords (x,y) ∈ [0,1]², y grows UP. Scaling
10
- // follows the xychart convention: round(t * (extent - 1)).
11
- // ============================================================================
12
-
13
- import { parseQuadrantChart } from '../quadrant/parser.ts'
14
- import type { QuadrantChart } from '../quadrant/types.ts'
15
- import type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'
16
- import { colorizeText } from './ansi.ts'
17
-
18
- /** Interior plot dimensions in cells (odd so the dividers land cleanly). */
19
- const GRID_W = 41
20
- const GRID_H = 21
21
-
22
- const UNI = {
23
- tl: '┌', tr: '┐', bl: '└', br: '┘',
24
- h: '─', v: '│',
25
- tDown: '┬', tUp: '┴', tLeft: '┤', tRight: '├', cross: '┼',
26
- point: '●',
27
- } as const
28
-
29
- const ASC = {
30
- tl: '+', tr: '+', bl: '+', br: '+',
31
- h: '-', v: '|',
32
- tDown: '+', tUp: '+', tLeft: '+', tRight: '+', cross: '+',
33
- point: '*',
34
- } as const
35
-
36
- export function renderQuadrantAscii(
37
- lines: string[],
38
- config: AsciiConfig,
39
- colorMode: ColorMode,
40
- theme: AsciiTheme,
41
- ): string {
42
- const chart = parseQuadrantChart(lines)
43
- const ch = config.useAscii ? ASC : UNI
44
-
45
- // Interior grid (rows × cols of single characters). Row 0 = top.
46
- const grid: string[][] = Array.from({ length: GRID_H }, () => Array.from({ length: GRID_W }, () => ' '))
47
- const midCol = Math.floor(GRID_W / 2)
48
- const midRow = Math.floor(GRID_H / 2)
49
-
50
- // Dividers.
51
- for (let r = 0; r < GRID_H; r++) grid[r]![midCol] = ch.v
52
- for (let c = 0; c < GRID_W; c++) grid[midRow]![c] = ch.h
53
- grid[midRow]![midCol] = ch.cross
54
-
55
- // Points — scaled into the grid. x grows right, y grows up (invert row).
56
- // Plotted first so quadrant labels (decorative) never clobber a data point.
57
- const occupied = new Set<string>()
58
- chart.points.forEach(p => {
59
- const col = Math.round(p.x * (GRID_W - 1))
60
- const row = Math.round((1 - p.y) * (GRID_H - 1))
61
- grid[row]![col] = ch.point
62
- occupied.add(`${row},${col}`)
63
- })
64
-
65
- // Quadrant labels. When points exist, Mermaid renders quadrant text near the
66
- // TOP of each region (so it doesn't fight the data); with no points it sits
67
- // in the center. We follow that, and never overwrite a plotted point.
68
- const top = chart.points.length > 0
69
- placeLabel(grid, occupied, chart.quadrants[1], 0, 0, midRow, midCol, top) // q2 top-left
70
- placeLabel(grid, occupied, chart.quadrants[0], 0, midCol + 1, midRow, GRID_W, top) // q1 top-right
71
- placeLabel(grid, occupied, chart.quadrants[2], midRow + 1, 0, GRID_H, midCol, top) // q3 bottom-left
72
- placeLabel(grid, occupied, chart.quadrants[3], midRow + 1, midCol + 1, GRID_H, GRID_W, top) // q4 bottom-right
73
-
74
- // Frame the grid with a border, with axis labels on the edges.
75
- const out: string[] = []
76
- if (chart.title) out.push(centerText(chart.title, GRID_W + 2))
77
-
78
- out.push(ch.tl + ch.h.repeat(GRID_W) + ch.tr)
79
- for (let r = 0; r < GRID_H; r++) {
80
- // The mid divider row meets the border as ├ … ┤ junctions.
81
- const left = r === midRow ? ch.tRight : ch.v
82
- const right = r === midRow ? ch.tLeft : ch.v
83
- out.push(left + grid[r]!.join('') + right)
84
- }
85
- out.push(ch.bl + ch.h.repeat(GRID_W) + ch.br)
86
-
87
- // x-axis labels under the grid (left + right).
88
- if (chart.xAxis) {
89
- out.push(edgeAxisRow(chart.xAxis.near, chart.xAxis.far, GRID_W + 2))
90
- }
91
- // y-axis labels as a separate annotated line.
92
- if (chart.yAxis) {
93
- const top = chart.yAxis.far ? `top: ${chart.yAxis.far}` : ''
94
- const bottom = `bottom: ${chart.yAxis.near}`
95
- out.push(`y-axis ${bottom}${top ? ` | ${top}` : ''}`)
96
- }
97
-
98
- // Legend — every point with its coordinates.
99
- if (chart.points.length > 0) {
100
- out.push('')
101
- const pointGlyph = colorMode === 'none' ? ch.point : colorizeText(ch.point, pointColor(theme), colorMode)
102
- for (const p of chart.points) {
103
- out.push(`${pointGlyph} ${p.label}: [${fmt(p.x)}, ${fmt(p.y)}]`)
104
- }
105
- }
106
-
107
- return out.join('\n')
108
- }
109
-
110
- function pointColor(theme: AsciiTheme): string {
111
- return theme.accent ?? theme.fg
112
- }
113
-
114
- function fmt(n: number): string {
115
- return Number.isInteger(n) ? String(n) : String(Math.round(n * 100) / 100)
116
- }
117
-
118
- /**
119
- * Place a label horizontally centered within a sub-rectangle of the grid.
120
- * When `top` is set the label sits one row down from the region top; otherwise
121
- * it is vertically centered. Never overwrites a plotted point.
122
- */
123
- function placeLabel(
124
- grid: string[][],
125
- occupied: Set<string>,
126
- label: string | undefined,
127
- rowStart: number,
128
- colStart: number,
129
- rowEnd: number,
130
- colEnd: number,
131
- top: boolean,
132
- ): void {
133
- if (!label) return
134
- const regionW = colEnd - colStart
135
- const regionH = rowEnd - rowStart
136
- if (regionW <= 0 || regionH <= 0) return
137
- const text = label.length > regionW ? label.slice(0, regionW) : label
138
- const row = top ? rowStart + 1 : rowStart + Math.floor(regionH / 2)
139
- const col = colStart + Math.max(0, Math.floor((regionW - text.length) / 2))
140
- for (let i = 0; i < text.length && col + i < colEnd; i++) {
141
- if (occupied.has(`${row},${col + i}`)) continue
142
- grid[row]![col + i] = text[i]!
143
- }
144
- }
145
-
146
- function centerText(text: string, width: number): string {
147
- if (text.length >= width) return text
148
- const pad = Math.floor((width - text.length) / 2)
149
- return ' '.repeat(pad) + text
150
- }
151
-
152
- /** Bottom edge axis row: near label left-aligned, far label right-aligned. */
153
- function edgeAxisRow(near: string, far: string | undefined, width: number): string {
154
- if (!far) return near
155
- const gap = width - near.length - far.length
156
- if (gap < 1) return `${near} ${far}`
157
- return near + ' '.repeat(gap) + far
158
- }