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,210 +0,0 @@
1
- // ============================================================================
2
- // ASCII renderer — architecture diagrams
3
- //
4
- // Purpose-built renderer that preserves architecture-specific visual identity:
5
- // group frames with headers, service cards with icon indicators, junction
6
- // markers, and labeled edge connections.
7
- // ============================================================================
8
-
9
- import { parseArchitectureDiagram } from '../architecture/parser.ts'
10
- import type {
11
- ArchitectureChildRef,
12
- ArchitectureDiagram,
13
- ArchitectureEdge,
14
- ArchitectureGroup,
15
- ArchitectureJunction,
16
- ArchitectureService,
17
- } from '../architecture/types.ts'
18
- import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi.ts'
19
- import type { AsciiConfig, AsciiTheme, CharRole, ColorMode } from './types.ts'
20
-
21
- interface StyledSegment {
22
- text: string
23
- role: CharRole | null
24
- }
25
-
26
- function renderStyledLine(
27
- segments: StyledSegment[],
28
- colorMode: ColorMode,
29
- theme: AsciiTheme,
30
- ): string {
31
- const chars: string[] = []
32
- const roles: (CharRole | null)[] = []
33
-
34
- for (const segment of segments) {
35
- for (const char of segment.text) {
36
- chars.push(char)
37
- roles.push(segment.role)
38
- }
39
- }
40
-
41
- return colorizeLine(chars, roles, theme, colorMode)
42
- }
43
-
44
- /**
45
- * Render a Mermaid architecture diagram to ASCII/Unicode text.
46
- */
47
- export function renderArchitectureAscii(
48
- lines: string[],
49
- config: AsciiConfig,
50
- colorMode: ColorMode = 'none',
51
- theme: AsciiTheme = DEFAULT_ASCII_THEME,
52
- ): string {
53
- const diagram = parseArchitectureDiagram(lines)
54
- const useAscii = config.useAscii
55
- const out: string[] = []
56
-
57
- const pushLine = (segments: StyledSegment[] = []): void => {
58
- out.push(segments.length === 0 ? '' : renderStyledLine(segments, colorMode, theme))
59
- }
60
-
61
- const groupsById = new Map(diagram.groups.map((g) => [g.id, g]))
62
- const servicesById = new Map(diagram.services.map((s) => [s.id, s]))
63
- const junctionsById = new Map(diagram.junctions.map((j) => [j.id, j]))
64
-
65
- const tl = useAscii ? '+' : '┌'
66
- const tr = useAscii ? '+' : '┐'
67
- const bl = useAscii ? '+' : '└'
68
- const br = useAscii ? '+' : '┘'
69
- const h = useAscii ? '-' : '─'
70
- const v = useAscii ? '|' : '│'
71
- const arrow = useAscii ? '>' : '►'
72
- const arrowLeft = useAscii ? '<' : '◄'
73
- const dash = useAscii ? '-' : '─'
74
- const junctionMark = useAscii ? '(*)' : '◉'
75
-
76
- renderChildren(diagram.rootChildren, 0)
77
-
78
- if (diagram.edges.length > 0) {
79
- pushLine()
80
- for (const edge of diagram.edges) {
81
- renderEdgeLine(edge)
82
- }
83
- }
84
-
85
- return out.join('\n').trimEnd()
86
-
87
- function renderChildren(children: ArchitectureChildRef[], indent: number): void {
88
- for (let ci = 0; ci < children.length; ci++) {
89
- const child = children[ci]!
90
-
91
- if (child.kind === 'group') {
92
- const group = groupsById.get(child.id)!
93
- renderGroup(group, indent)
94
- } else if (child.kind === 'service') {
95
- const service = servicesById.get(child.id)!
96
- renderService(service, indent)
97
- } else {
98
- const junction = junctionsById.get(child.id)!
99
- renderJunction(junction, indent)
100
- }
101
-
102
- if (ci < children.length - 1) pushLine()
103
- }
104
- }
105
-
106
- function renderGroup(group: ArchitectureGroup, indent: number): void {
107
- const pad = ' '.repeat(indent)
108
- const label = group.label.replace(/\n/g, ' ')
109
- const iconTag = group.icon ? `(${group.icon})` : ''
110
- const header = `${iconTag}${iconTag ? ' ' : ''}${label}`
111
- const innerWidth = Math.max(header.length + 4, 30)
112
- const hBar = h.repeat(innerWidth - 2)
113
-
114
- pushLine([
115
- { text: pad, role: null },
116
- { text: tl + hBar + tr, role: 'border' },
117
- ])
118
- pushLine([
119
- { text: pad, role: null },
120
- { text: v, role: 'border' },
121
- { text: ' ', role: null },
122
- { text: header, role: 'text' },
123
- { text: ' '.repeat(Math.max(0, innerWidth - header.length - 3)), role: null },
124
- { text: v, role: 'border' },
125
- ])
126
- pushLine([
127
- { text: pad, role: null },
128
- { text: v + h.repeat(innerWidth - 2) + v, role: 'border' },
129
- ])
130
-
131
- for (const child of group.children) {
132
- if (child.kind === 'group') {
133
- const nested = groupsById.get(child.id)!
134
- renderGroup(nested, indent + 2)
135
- } else if (child.kind === 'service') {
136
- const service = servicesById.get(child.id)!
137
- renderService(service, indent + 2)
138
- } else {
139
- const junction = junctionsById.get(child.id)!
140
- renderJunction(junction, indent + 2)
141
- }
142
- }
143
-
144
- pushLine([
145
- { text: pad, role: null },
146
- { text: bl + hBar + br, role: 'border' },
147
- ])
148
- }
149
-
150
- function renderService(service: ArchitectureService, indent: number): void {
151
- const pad = ' '.repeat(indent)
152
- const icon = service.icon ? `[${service.icon}]` : ''
153
- const label = service.label.replace(/\n/g, ' ')
154
-
155
- pushLine([
156
- { text: pad, role: null },
157
- ...(icon
158
- ? [
159
- { text: icon, role: 'arrow' as CharRole },
160
- { text: ' ', role: null as CharRole | null },
161
- ]
162
- : []),
163
- { text: label, role: 'text' },
164
- ])
165
- }
166
-
167
- function renderJunction(junction: ArchitectureJunction, indent: number): void {
168
- const pad = ' '.repeat(indent)
169
-
170
- pushLine([
171
- { text: pad, role: null },
172
- { text: junctionMark, role: 'arrow' },
173
- { text: ' ', role: null },
174
- { text: junction.id, role: 'text' },
175
- ])
176
- }
177
-
178
- function renderEdgeLine(edge: ArchitectureEdge): void {
179
- const srcName = itemLabel(edge.source.id)
180
- const tgtName = itemLabel(edge.target.id)
181
- const left = edge.hasArrowStart ? arrowLeft + dash : dash + dash
182
- const right = edge.hasArrowEnd ? dash + arrow : dash + dash
183
- const label = edge.label ? ` ${edge.label.replace(/\n/g, ' ')} ` : ''
184
-
185
- pushLine([
186
- { text: ' ', role: null },
187
- { text: srcName, role: 'text' },
188
- { text: `:${edge.source.side}`, role: 'border' },
189
- { text: ' ', role: null },
190
- { text: left, role: 'line' },
191
- ...(label
192
- ? [
193
- { text: '[', role: 'border' as CharRole },
194
- { text: label.trim(), role: 'text' as CharRole },
195
- { text: ']', role: 'border' as CharRole },
196
- ]
197
- : []),
198
- { text: right, role: 'line' },
199
- { text: ' ', role: null },
200
- { text: `${edge.target.side}:`, role: 'border' },
201
- { text: tgtName, role: 'text' },
202
- ])
203
- }
204
-
205
- function itemLabel(id: string): string {
206
- const service = servicesById.get(id)
207
- if (service) return service.label.replace(/\n/g, ' ')
208
- return id
209
- }
210
- }
@@ -1,443 +0,0 @@
1
- // ============================================================================
2
- // ASCII renderer — 2D text canvas
3
- //
4
- // Ported from AlexanderGrooff/mermaid-ascii cmd/draw.go.
5
- // The canvas is a column-major 2D array of single-character strings.
6
- // canvas[x][y] gives the character at column x, row y.
7
- // ============================================================================
8
-
9
- import type { Canvas, DrawingCoord, RoleCanvas, CharRole, AsciiTheme, ColorMode } from './types.ts'
10
- import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi.ts'
11
- import { charVisualWidth, WIDE_CHAR_CONTINUATION } from './width.ts'
12
-
13
- /**
14
- * Create a blank canvas filled with spaces.
15
- * Dimensions are inclusive: mkCanvas(3, 2) creates a 4x3 grid (indices 0..3, 0..2).
16
- */
17
- export function mkCanvas(x: number, y: number): Canvas {
18
- const canvas: Canvas = []
19
- for (let i = 0; i <= x; i++) {
20
- const col: string[] = []
21
- for (let j = 0; j <= y; j++) {
22
- col.push(' ')
23
- }
24
- canvas.push(col)
25
- }
26
- return canvas
27
- }
28
-
29
- /** Create a blank canvas with the same dimensions as the given canvas. */
30
- export function copyCanvas(source: Canvas): Canvas {
31
- const [maxX, maxY] = getCanvasSize(source)
32
- return mkCanvas(maxX, maxY)
33
- }
34
-
35
- // ============================================================================
36
- // Role canvas creation and management
37
- // ============================================================================
38
-
39
- /**
40
- * Create a blank role canvas filled with nulls.
41
- * Same dimensions as mkCanvas — column-major, roleCanvas[x][y].
42
- */
43
- export function mkRoleCanvas(x: number, y: number): RoleCanvas {
44
- const roleCanvas: RoleCanvas = []
45
- for (let i = 0; i <= x; i++) {
46
- const col: (CharRole | null)[] = []
47
- for (let j = 0; j <= y; j++) {
48
- col.push(null)
49
- }
50
- roleCanvas.push(col)
51
- }
52
- return roleCanvas
53
- }
54
-
55
- /** Create a blank role canvas with the same dimensions as the given role canvas. */
56
- export function copyRoleCanvas(source: RoleCanvas): RoleCanvas {
57
- const maxX = source.length - 1
58
- const maxY = (source[0]?.length ?? 1) - 1
59
- return mkRoleCanvas(maxX, maxY)
60
- }
61
-
62
- /**
63
- * Grow the role canvas to fit at least (newX, newY), preserving existing roles.
64
- * Mutates the role canvas in place and returns it.
65
- */
66
- export function increaseRoleCanvasSize(roleCanvas: RoleCanvas, newX: number, newY: number): RoleCanvas {
67
- const currX = roleCanvas.length - 1
68
- const currY = (roleCanvas[0]?.length ?? 1) - 1
69
- const targetX = Math.max(newX, currX)
70
- const targetY = Math.max(newY, currY)
71
- const grown = mkRoleCanvas(targetX, targetY)
72
- for (let x = 0; x < grown.length; x++) {
73
- for (let y = 0; y < grown[0]!.length; y++) {
74
- if (x < roleCanvas.length && y < roleCanvas[0]!.length) {
75
- grown[x]![y] = roleCanvas[x]![y]!
76
- }
77
- }
78
- }
79
- roleCanvas.length = 0
80
- roleCanvas.push(...grown)
81
- return roleCanvas
82
- }
83
-
84
- /**
85
- * Set a role at a specific coordinate.
86
- * Expands the role canvas if necessary.
87
- */
88
- export function setRole(roleCanvas: RoleCanvas, x: number, y: number, role: CharRole): void {
89
- if (x >= roleCanvas.length || y >= (roleCanvas[0]?.length ?? 0)) {
90
- increaseRoleCanvasSize(roleCanvas, x, y)
91
- }
92
- roleCanvas[x]![y] = role
93
- }
94
-
95
- /**
96
- * Merge role canvases — same logic as mergeCanvases but for roles.
97
- * Non-null roles in overlays overwrite null roles in base.
98
- */
99
- export function mergeRoleCanvases(
100
- base: RoleCanvas,
101
- offset: DrawingCoord,
102
- ...overlays: RoleCanvas[]
103
- ): RoleCanvas {
104
- let maxX = base.length - 1
105
- let maxY = (base[0]?.length ?? 1) - 1
106
-
107
- for (const overlay of overlays) {
108
- const oX = overlay.length - 1
109
- const oY = (overlay[0]?.length ?? 1) - 1
110
- maxX = Math.max(maxX, oX + offset.x)
111
- maxY = Math.max(maxY, oY + offset.y)
112
- }
113
-
114
- const merged = mkRoleCanvas(maxX, maxY)
115
-
116
- // Copy base
117
- for (let x = 0; x <= maxX; x++) {
118
- for (let y = 0; y <= maxY; y++) {
119
- if (x < base.length && y < base[0]!.length) {
120
- merged[x]![y] = base[x]![y]!
121
- }
122
- }
123
- }
124
-
125
- // Apply overlays
126
- for (const overlay of overlays) {
127
- for (let x = 0; x < overlay.length; x++) {
128
- for (let y = 0; y < overlay[0]!.length; y++) {
129
- const role = overlay[x]?.[y]
130
- if (role !== null && role !== undefined) {
131
- const mx = x + offset.x
132
- const my = y + offset.y
133
- merged[mx]![my] = role
134
- }
135
- }
136
- }
137
- }
138
-
139
- return merged
140
- }
141
-
142
- /** Returns [maxX, maxY] — the highest valid indices in each dimension. */
143
- export function getCanvasSize(canvas: Canvas): [number, number] {
144
- return [canvas.length - 1, (canvas[0]?.length ?? 1) - 1]
145
- }
146
-
147
- /**
148
- * Grow the canvas to fit at least (newX, newY), preserving existing content.
149
- * Mutates the canvas in place and returns it.
150
- */
151
- export function increaseSize(canvas: Canvas, newX: number, newY: number): Canvas {
152
- const [currX, currY] = getCanvasSize(canvas)
153
- const targetX = Math.max(newX, currX)
154
- const targetY = Math.max(newY, currY)
155
- const grown = mkCanvas(targetX, targetY)
156
- for (let x = 0; x < grown.length; x++) {
157
- for (let y = 0; y < grown[0]!.length; y++) {
158
- if (x < canvas.length && y < canvas[0]!.length) {
159
- grown[x]![y] = canvas[x]![y]!
160
- }
161
- }
162
- }
163
- // Mutate in place: splice old contents and replace with grown
164
- canvas.length = 0
165
- canvas.push(...grown)
166
- return canvas
167
- }
168
-
169
- // ============================================================================
170
- // Junction merging — Unicode box-drawing character compositing
171
- // ============================================================================
172
-
173
- /** All Unicode box-drawing characters that participate in junction merging. */
174
- const JUNCTION_CHARS = new Set([
175
- '─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', '╴', '╵', '╶', '╷',
176
- ])
177
-
178
- export function isJunctionChar(c: string): boolean {
179
- return JUNCTION_CHARS.has(c)
180
- }
181
-
182
- /** Check if a character is alphanumeric (part of a label). */
183
- function isAlphanumeric(c: string): boolean {
184
- return /^[a-zA-Z0-9]$/.test(c)
185
- }
186
-
187
- /**
188
- * When two junction characters overlap during canvas merging,
189
- * resolve them to the correct combined junction.
190
- * E.g., '─' overlapping '│' becomes '┼'.
191
- */
192
- const JUNCTION_MAP: Record<string, Record<string, string>> = {
193
- '─': { '│': '┼', '┌': '┬', '┐': '┬', '└': '┴', '┘': '┴', '├': '┼', '┤': '┼', '┬': '┬', '┴': '┴' },
194
- '│': { '─': '┼', '┌': '├', '┐': '┤', '└': '├', '┘': '┤', '├': '├', '┤': '┤', '┬': '┼', '┴': '┼' },
195
- '┌': { '─': '┬', '│': '├', '┐': '┬', '└': '├', '┘': '┼', '├': '├', '┤': '┼', '┬': '┬', '┴': '┼' },
196
- '┐': { '─': '┬', '│': '┤', '┌': '┬', '└': '┼', '┘': '┤', '├': '┼', '┤': '┤', '┬': '┬', '┴': '┼' },
197
- '└': { '─': '┴', '│': '├', '┌': '├', '┐': '┼', '┘': '┴', '├': '├', '┤': '┼', '┬': '┼', '┴': '┴' },
198
- '┘': { '─': '┴', '│': '┤', '┌': '┼', '┐': '┤', '└': '┴', '├': '┼', '┤': '┤', '┬': '┼', '┴': '┴' },
199
- '├': { '─': '┼', '│': '├', '┌': '├', '┐': '┼', '└': '├', '┘': '┼', '┤': '┼', '┬': '┼', '┴': '┼' },
200
- '┤': { '─': '┼', '│': '┤', '┌': '┼', '┐': '┤', '└': '┼', '┘': '┤', '├': '┼', '┬': '┼', '┴': '┼' },
201
- '┬': { '─': '┬', '│': '┼', '┌': '┬', '┐': '┬', '└': '┼', '┘': '┼', '├': '┼', '┤': '┼', '┴': '┼' },
202
- '┴': { '─': '┴', '│': '┼', '┌': '┼', '┐': '┼', '└': '┴', '┘': '┴', '├': '┼', '┤': '┼', '┬': '┼' },
203
- }
204
-
205
- export function mergeJunctions(c1: string, c2: string): string {
206
- return JUNCTION_MAP[c1]?.[c2] ?? c1
207
- }
208
-
209
- // ============================================================================
210
- // Canvas merging — composite multiple canvases with offset
211
- // ============================================================================
212
-
213
- /**
214
- * Merge overlay canvases onto a base canvas at the given offset.
215
- * Non-space characters in overlays overwrite the base.
216
- * When both characters are Unicode junction chars, they're merged intelligently.
217
- */
218
- export function mergeCanvases(
219
- base: Canvas,
220
- offset: DrawingCoord,
221
- useAscii: boolean,
222
- ...overlays: Canvas[]
223
- ): Canvas {
224
- let [maxX, maxY] = getCanvasSize(base)
225
- for (const overlay of overlays) {
226
- const [oX, oY] = getCanvasSize(overlay)
227
- maxX = Math.max(maxX, oX + offset.x)
228
- maxY = Math.max(maxY, oY + offset.y)
229
- }
230
-
231
- const merged = mkCanvas(maxX, maxY)
232
-
233
- // Copy base
234
- for (let x = 0; x <= maxX; x++) {
235
- for (let y = 0; y <= maxY; y++) {
236
- if (x < base.length && y < base[0]!.length) {
237
- merged[x]![y] = base[x]![y]!
238
- }
239
- }
240
- }
241
-
242
- // Apply overlays
243
- for (const overlay of overlays) {
244
- for (let x = 0; x < overlay.length; x++) {
245
- for (let y = 0; y < overlay[0]!.length; y++) {
246
- const c = overlay[x]![y]!
247
- if (c !== ' ') {
248
- const mx = x + offset.x
249
- const my = y + offset.y
250
- const current = merged[mx]![my]!
251
- if (!useAscii && isJunctionChar(c) && isJunctionChar(current)) {
252
- merged[mx]![my] = mergeJunctions(current, c)
253
- } else if (isAlphanumeric(current) && isAlphanumeric(c)) {
254
- // Don't overwrite existing label text with new label text
255
- // This prevents label collisions (first label wins)
256
- } else {
257
- merged[mx]![my] = c
258
- }
259
- }
260
- }
261
- }
262
- }
263
-
264
- return merged
265
- }
266
-
267
- // ============================================================================
268
- // Canvas → string conversion
269
- // ============================================================================
270
-
271
- /** Options for converting canvas to string with optional coloring. */
272
- export interface CanvasToStringOptions {
273
- /** Role canvas for applying colors. If not provided, output is plain text. */
274
- roleCanvas?: RoleCanvas
275
- /** Color mode for terminal output. Default: 'none' */
276
- colorMode?: ColorMode
277
- /** Theme colors for ASCII output. Uses default theme if not provided. */
278
- theme?: AsciiTheme
279
- }
280
-
281
- /**
282
- * Convert the canvas to a multi-line string (row by row, left to right).
283
- * Optionally applies ANSI color codes based on character roles.
284
- */
285
- export function canvasToString(canvas: Canvas, options?: CanvasToStringOptions): string {
286
- const [maxX, maxY] = getCanvasSize(canvas)
287
- const lines: string[] = []
288
-
289
- const roleCanvas = options?.roleCanvas
290
- const colorMode = options?.colorMode ?? 'none'
291
- const theme = options?.theme ?? DEFAULT_ASCII_THEME
292
-
293
- for (let y = 0; y <= maxY; y++) {
294
- if (colorMode === 'none' || !roleCanvas) {
295
- // Plain text output — no colors
296
- let line = ''
297
- for (let x = 0; x <= maxX; x++) {
298
- const ch = canvas[x]![y]!
299
- if (ch !== WIDE_CHAR_CONTINUATION) line += ch
300
- }
301
- lines.push(line)
302
- } else {
303
- // Colored output — collect chars and roles for this row
304
- const chars: string[] = []
305
- const roles: (CharRole | null)[] = []
306
- for (let x = 0; x <= maxX; x++) {
307
- const ch = canvas[x]![y]!
308
- if (ch !== WIDE_CHAR_CONTINUATION) {
309
- chars.push(ch)
310
- roles.push(roleCanvas[x]?.[y] ?? null)
311
- }
312
- }
313
- lines.push(colorizeLine(chars, roles, theme, colorMode))
314
- }
315
- }
316
-
317
- return lines.join('\n')
318
- }
319
-
320
- // ============================================================================
321
- // Canvas vertical flip — used for BT (bottom-to-top) direction support.
322
- //
323
- // The ASCII renderer lays out graphs top-down (TD). For BT direction, we
324
- // flip the finished canvas vertically and remap directional characters so
325
- // arrows point upward and corners are mirrored correctly.
326
- // ============================================================================
327
-
328
- /**
329
- * Characters that change meaning when the Y-axis is flipped.
330
- * Symmetric characters (─, │, ├, ┤, ┼) are unchanged.
331
- */
332
- const VERTICAL_FLIP_MAP: Record<string, string> = {
333
- // Unicode arrows
334
- '▲': '▼', '▼': '▲',
335
- '◤': '◣', '◣': '◤',
336
- '◥': '◢', '◢': '◥',
337
- // ASCII arrows
338
- '^': 'v', 'v': '^',
339
- // Unicode corners
340
- '┌': '└', '└': '┌',
341
- '┐': '┘', '┘': '┐',
342
- // Unicode junctions (T-pieces flip vertically)
343
- '┬': '┴', '┴': '┬',
344
- // Box-start junctions (exit points from node boxes)
345
- '╵': '╷', '╷': '╵',
346
- }
347
-
348
- /**
349
- * Flip the canvas vertically (mirror across the horizontal center).
350
- * Reverses row order within each column and remaps directional characters
351
- * (arrows, corners, junctions) so they point the correct way after flip.
352
- *
353
- * Used to transform a TD-rendered canvas into BT output.
354
- * Mutates the canvas in place and returns it.
355
- */
356
- export function flipCanvasVertically(canvas: Canvas): Canvas {
357
- // Reverse each column array (Y-axis flip in column-major layout)
358
- for (const col of canvas) {
359
- col.reverse()
360
- }
361
-
362
- // Remap directional characters that change meaning after vertical flip
363
- for (const col of canvas) {
364
- for (let y = 0; y < col.length; y++) {
365
- const flipped = VERTICAL_FLIP_MAP[col[y]!]
366
- if (flipped) col[y] = flipped
367
- }
368
- }
369
-
370
- return canvas
371
- }
372
-
373
- /**
374
- * Flip the role canvas vertically to match flipCanvasVertically.
375
- * Mutates the role canvas in place and returns it.
376
- */
377
- export function flipRoleCanvasVertically(roleCanvas: RoleCanvas): RoleCanvas {
378
- for (const col of roleCanvas) {
379
- col.reverse()
380
- }
381
- return roleCanvas
382
- }
383
-
384
- /**
385
- * Draw text string onto the canvas starting at the given coordinate.
386
- * By default, preserves existing non-space characters (labels don't overwrite each other).
387
- * Set forceOverwrite=true to always overwrite (for box content).
388
- */
389
- export function drawText(
390
- canvas: Canvas,
391
- start: DrawingCoord,
392
- text: string,
393
- forceOverwrite = false
394
- ): void {
395
- let width = 0
396
- for (const ch of text) width += charVisualWidth(ch)
397
- increaseSize(canvas, start.x + width, start.y)
398
- let offset = 0
399
- for (const ch of text) {
400
- const x = start.x + offset
401
- const current = canvas[x]![start.y]!
402
- // Only write if target is empty or we're forcing overwrite
403
- if (forceOverwrite || current === ' ') {
404
- canvas[x]![start.y] = ch
405
- if (charVisualWidth(ch) === 2 && x + 1 < canvas.length) {
406
- canvas[x + 1]![start.y] = WIDE_CHAR_CONTINUATION
407
- }
408
- }
409
- offset += charVisualWidth(ch)
410
- }
411
- }
412
-
413
- /**
414
- * Set the canvas size to fit all grid columns and rows.
415
- * Called after layout to ensure the canvas covers the full drawing area.
416
- */
417
- export function setCanvasSizeToGrid(
418
- canvas: Canvas,
419
- columnWidth: Map<number, number>,
420
- rowHeight: Map<number, number>,
421
- ): void {
422
- let maxX = 0
423
- let maxY = 0
424
- for (const w of columnWidth.values()) maxX += w
425
- for (const h of rowHeight.values()) maxY += h
426
- increaseSize(canvas, maxX - 1, maxY - 1)
427
- }
428
-
429
- /**
430
- * Set the role canvas size to match the grid dimensions.
431
- * Should be called alongside setCanvasSizeToGrid.
432
- */
433
- export function setRoleCanvasSizeToGrid(
434
- roleCanvas: RoleCanvas,
435
- columnWidth: Map<number, number>,
436
- rowHeight: Map<number, number>,
437
- ): void {
438
- let maxX = 0
439
- let maxY = 0
440
- for (const w of columnWidth.values()) maxX += w
441
- for (const h of rowHeight.values()) maxY += h
442
- increaseRoleCanvasSize(roleCanvas, maxX - 1, maxY - 1)
443
- }