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,365 +0,0 @@
1
- // ============================================================================
2
- // Gantt layout — schedule → deterministic geometry (docs/design/families/gantt.md §4).
3
- //
4
- // One layout model feeds both renderers: the SVG renderer consumes the pixel
5
- // geometry directly; the ASCII renderer reuses tick *selection* (the resolved
6
- // tick instants) and the row model with its own column math. Bars never
7
- // leave the plot area; `vert` markers consume no task row; compact mode packs
8
- // non-overlapping tasks of a section into shared rows deterministically
9
- // (first-fit by source order).
10
- // ============================================================================
11
-
12
- import type {
13
- GanttModel, GanttSchedule, GanttLayoutResult, GanttBarLayout, GanttRowLayout,
14
- GanttSectionBand, GanttVertLayout, GanttTick, GanttTickUnit, EpochMs,
15
- } from './types.ts'
16
- import { DAY_MS, dayOfWeek, WEEKDAY_INDEX, formatGanttInstant, startOfDay } from './schedule.ts'
17
- import { applyTextTransform, estimateTextWidth, resolveRenderStyle, STROKE_WIDTHS } from '../styles.ts'
18
- import type { RenderStyleDefaults, ResolvedRenderStyle } from '../styles.ts'
19
- import type { RenderOptions } from '../types.ts'
20
-
21
- export const GANTT_MAX_TICKS = 120
22
-
23
- export interface GanttLayoutOptions {
24
- /** Total drawing width; the plot shrinks to fit. */
25
- width?: number
26
- compact?: boolean
27
- barHeight?: number
28
- /** Resolved clock instant (schedule.today). */
29
- today?: EpochMs
30
- /** Render options whose style roles affect label and axis geometry. */
31
- renderOptions?: RenderOptions
32
- }
33
-
34
- const GL = {
35
- plotWidth: 600,
36
- padding: 16,
37
- titleHeight: 34,
38
- axisHeight: 24,
39
- rowGap: 8,
40
- sectionGap: 6,
41
- barHeight: 22,
42
- labelGap: 12,
43
- labelFontSize: 13,
44
- sectionFontSize: 13,
45
- } as const
46
-
47
- export const GANTT_STYLE_DEFAULTS: RenderStyleDefaults = {
48
- nodeLabelFontSize: GL.labelFontSize,
49
- edgeLabelFontSize: 11,
50
- groupHeaderFontSize: GL.sectionFontSize,
51
- nodeLabelFontWeight: 500,
52
- edgeLabelFontWeight: 500,
53
- groupHeaderFontWeight: 600,
54
- nodePaddingX: 0,
55
- nodePaddingY: 0,
56
- nodeLineWidth: STROKE_WIDTHS.innerBox,
57
- edgeLineWidth: STROKE_WIDTHS.connector,
58
- groupCornerRadius: 0,
59
- groupPaddingX: 0,
60
- groupPaddingY: 0,
61
- groupLineWidth: STROKE_WIDTHS.outerBox,
62
- }
63
-
64
- const TICK_UNIT_MS: Record<GanttTickUnit, number> = {
65
- millisecond: 1,
66
- second: 1000,
67
- minute: 60_000,
68
- hour: 3_600_000,
69
- day: DAY_MS,
70
- week: 7 * DAY_MS,
71
- month: 30 * DAY_MS, // month ticks step by calendar months; this is the sizing estimate
72
- }
73
-
74
- // Auto-tick candidates: (stepMs, defaultFormat) pairs from minutes to years.
75
- const AUTO_TICKS: Array<{ unit: GanttTickUnit; count: number; format: string }> = [
76
- { unit: 'minute', count: 1, format: '%H:%M' },
77
- { unit: 'minute', count: 15, format: '%H:%M' },
78
- { unit: 'hour', count: 1, format: '%H:%M' },
79
- { unit: 'hour', count: 6, format: '%H:%M' },
80
- { unit: 'day', count: 1, format: '%m-%d' },
81
- { unit: 'week', count: 1, format: '%m-%d' },
82
- { unit: 'week', count: 2, format: '%m-%d' },
83
- { unit: 'month', count: 1, format: '%b %Y' },
84
- { unit: 'month', count: 3, format: '%b %Y' },
85
- { unit: 'month', count: 12, format: '%Y' },
86
- ]
87
-
88
- interface TickPlan {
89
- unit: GanttTickUnit
90
- count: number
91
- format: string
92
- }
93
-
94
- const INLINE_FORMAT_TAG = /<\/?(?:b|strong|i|em|u|s|del)\s*>/gi
95
-
96
- export function resolveGanttRenderStyle(options: RenderOptions = {}): ResolvedRenderStyle {
97
- return resolveRenderStyle(options, GANTT_STYLE_DEFAULTS)
98
- }
99
-
100
- export function ganttTitleFontSize(style: ResolvedRenderStyle): number {
101
- return Math.max(17, style.groupHeaderFontSize)
102
- }
103
-
104
- export function ganttTitleY(style: ResolvedRenderStyle): number {
105
- const titleHeight = ganttTitleHeight(style)
106
- return titleHeight === GL.titleHeight ? 18 : titleHeight / 2
107
- }
108
-
109
- export function ganttAxisLabelOffset(style: ResolvedRenderStyle): number {
110
- return Math.max(10, style.edgeLabelFontSize * 0.75)
111
- }
112
-
113
- export function ganttMeasureTextWidth(
114
- text: string,
115
- fontSize: number,
116
- fontWeight: number,
117
- letterSpacing = 0,
118
- ): number {
119
- const plain = text.replace(INLINE_FORMAT_TAG, '')
120
- const codepoints = [...plain].length
121
- const tracking = Math.max(0, codepoints - 1) * letterSpacing
122
- return Math.max(0, estimateTextWidth(plain, fontSize, fontWeight) + tracking)
123
- }
124
-
125
- function ganttTitleHeight(style: ResolvedRenderStyle): number {
126
- return Math.max(GL.titleHeight, Math.ceil(ganttTitleFontSize(style) * 1.3) + 8)
127
- }
128
-
129
- function ganttAxisHeight(style: ResolvedRenderStyle): number {
130
- return Math.max(GL.axisHeight, Math.ceil(ganttAxisLabelOffset(style) + style.edgeLabelFontSize * 0.65 + 4))
131
- }
132
-
133
- function ganttRowHeight(style: ResolvedRenderStyle, barHeight: number): number {
134
- const labelLineHeight = Math.max(style.nodeLabelFontSize, style.groupHeaderFontSize) * 1.3
135
- return Math.ceil(Math.max(barHeight, labelLineHeight)) + GL.rowGap
136
- }
137
-
138
- /**
139
- * Pick the tick plan: the explicit `tickInterval` when it stays under the
140
- * GANTT_MAX_TICKS bound (mermaid PR #7197 regression: tiny intervals must not
141
- * generate unbounded ticks), otherwise the smallest auto candidate that fits.
142
- */
143
- export function planTicks(schedule: GanttSchedule, model: GanttModel, targetTicks = 8): TickPlan {
144
- const span = schedule.timeMax - schedule.timeMin
145
- if (model.tickInterval) {
146
- const step = TICK_UNIT_MS[model.tickInterval.unit] * model.tickInterval.count
147
- if (span / step <= GANTT_MAX_TICKS) {
148
- return { ...model.tickInterval, format: model.axisFormat ?? defaultFormatFor(model.tickInterval.unit) }
149
- }
150
- }
151
- for (const cand of AUTO_TICKS) {
152
- const step = TICK_UNIT_MS[cand.unit] * cand.count
153
- if (span / step <= targetTicks) {
154
- return { unit: cand.unit, count: cand.count, format: model.axisFormat ?? cand.format }
155
- }
156
- }
157
- const last = AUTO_TICKS[AUTO_TICKS.length - 1]!
158
- // Beyond the largest candidate: scale year ticks up until bounded.
159
- let count = last.count
160
- while ((span / (TICK_UNIT_MS.month * count)) > GANTT_MAX_TICKS) count *= 2
161
- return { unit: 'month', count, format: model.axisFormat ?? last.format }
162
- }
163
-
164
- function defaultFormatFor(unit: GanttTickUnit): string {
165
- switch (unit) {
166
- case 'millisecond': case 'second': return '%H:%M:%S'
167
- case 'minute': case 'hour': return '%H:%M'
168
- case 'day': case 'week': return '%m-%d'
169
- case 'month': return '%b %Y'
170
- }
171
- }
172
-
173
- /** First tick instant at or before timeMin, aligned to the unit (weeks align
174
- * to the model's weekStart; months to the 1st; days to midnight UTC). */
175
- function alignTickStart(timeMin: EpochMs, plan: TickPlan, weekStart: number): EpochMs {
176
- if (plan.unit === 'week') {
177
- let t = startOfDay(timeMin)
178
- let steps = 0
179
- while (dayOfWeek(t) !== weekStart && steps++ < 7) t -= DAY_MS
180
- return t
181
- }
182
- if (plan.unit === 'month') {
183
- const d = new Date(timeMin)
184
- return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)
185
- }
186
- if (plan.unit === 'day') return startOfDay(timeMin)
187
- const step = TICK_UNIT_MS[plan.unit] * plan.count
188
- return Math.floor(timeMin / step) * step
189
- }
190
-
191
- function nextTick(t: EpochMs, plan: TickPlan): EpochMs {
192
- if (plan.unit === 'month') {
193
- const d = new Date(t)
194
- return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + plan.count, 1)
195
- }
196
- return t + TICK_UNIT_MS[plan.unit] * plan.count
197
- }
198
-
199
- /**
200
- * Resolve the tick instants for a schedule (shared by SVG and ASCII).
201
- * Bounded by GANTT_MAX_TICKS regardless of inputs.
202
- */
203
- export function resolveTicks(schedule: GanttSchedule, model: GanttModel, targetTicks = 8): Array<{ time: EpochMs; label: string }> {
204
- const plan = planTicks(schedule, model, targetTicks)
205
- const weekStart = WEEKDAY_INDEX[model.weekStart]
206
- const out: Array<{ time: EpochMs; label: string }> = []
207
- let t = alignTickStart(schedule.timeMin, plan, weekStart)
208
- let guard = 0
209
- while (t <= schedule.timeMax && guard++ <= GANTT_MAX_TICKS) {
210
- if (t >= schedule.timeMin) out.push({ time: t, label: formatGanttInstant(t, plan.format) })
211
- t = nextTick(t, plan)
212
- }
213
- // A coarse explicit interval over a short span (e.g. `tickInterval 1month`
214
- // on a 2-day chart) can leave zero in-range ticks; an axis must never be
215
- // empty, so fall back to a single tick at the range start.
216
- if (out.length === 0) {
217
- out.push({ time: schedule.timeMin, label: formatGanttInstant(schedule.timeMin, plan.format) })
218
- }
219
- return out
220
- }
221
-
222
- /**
223
- * Compact-mode lane packing: within one section, tasks (source order) go to
224
- * the first lane whose latest end is <= the task's start. Deterministic and
225
- * overlap-free by construction.
226
- */
227
- export function packCompactLanes(tasks: Array<{ start: EpochMs; end: EpochMs }>): number[] {
228
- const laneEnds: EpochMs[] = []
229
- const laneOf: number[] = []
230
- for (const t of tasks) {
231
- let lane = laneEnds.findIndex(end => end <= t.start)
232
- if (lane === -1) {
233
- lane = laneEnds.length
234
- laneEnds.push(t.end)
235
- } else {
236
- laneEnds[lane] = t.end
237
- }
238
- laneOf.push(lane)
239
- }
240
- return laneOf
241
- }
242
-
243
- export function layoutGantt(model: GanttModel, schedule: GanttSchedule, options: GanttLayoutOptions = {}): GanttLayoutResult {
244
- const style = resolveGanttRenderStyle(options.renderOptions)
245
- const compact = options.compact ?? (model.displayMode === 'compact')
246
- const barHeight = options.barHeight ?? model.barHeight ?? GL.barHeight
247
- const rowH = ganttRowHeight(style, barHeight)
248
-
249
- const vertTasks = schedule.tasks.filter(t => t.tags.includes('vert'))
250
- const rowTasks = schedule.tasks.filter(t => !t.tags.includes('vert'))
251
-
252
- // ---- label column ---------------------------------------------------------
253
- let labelColumnWidth = 0
254
- for (const t of rowTasks) {
255
- const label = applyTextTransform(t.label, style.nodeTextTransform)
256
- labelColumnWidth = Math.max(labelColumnWidth, ganttMeasureTextWidth(label, style.nodeLabelFontSize, style.nodeLabelFontWeight, style.nodeLetterSpacing))
257
- }
258
- for (const s of model.sections) {
259
- if (s.label) {
260
- const label = applyTextTransform(s.label, style.groupTextTransform)
261
- labelColumnWidth = Math.max(labelColumnWidth, ganttMeasureTextWidth(label, style.groupHeaderFontSize, style.groupHeaderFontWeight, style.groupLetterSpacing))
262
- }
263
- }
264
- labelColumnWidth = Math.ceil(labelColumnWidth) + GL.labelGap
265
-
266
- const width = options.width ?? (GL.padding * 2 + labelColumnWidth + GL.plotWidth)
267
- const plotX = GL.padding + labelColumnWidth
268
- const plotW = Math.max(120, width - plotX - GL.padding)
269
-
270
- const titleH = model.title ? ganttTitleHeight(style) : 0
271
- const axisH = ganttAxisHeight(style)
272
- const topAxisH = model.topAxis ? axisH : 0
273
- const plotY = GL.padding + titleH + topAxisH
274
-
275
- const span = schedule.timeMax - schedule.timeMin
276
- const xOf = (t: EpochMs): number => plotX + ((t - schedule.timeMin) / span) * plotW
277
-
278
- // ---- rows + bars (vert tasks never consume a row) --------------------------
279
- const bars: GanttBarLayout[] = []
280
- const rows: GanttRowLayout[] = []
281
- const sections: GanttSectionBand[] = []
282
- let y = plotY
283
-
284
- for (let si = 0; si < model.sections.length; si++) {
285
- const section = model.sections[si]!
286
- const sectionTasks = rowTasks.filter(t => t.sectionIndex === si)
287
- if (sectionTasks.length === 0 && section.label === undefined) continue
288
- // Bars/rows reference the BAND index in the emitted sections array (empty
289
- // implicit sections are skipped, so it can differ from the model index).
290
- const bandIndex = sections.length
291
- const bandStartY = y
292
- const rowStart = rows.length
293
- if (section.label !== undefined) y += rowH // the section header row
294
-
295
- const lanes = compact ? packCompactLanes(sectionTasks) : sectionTasks.map((_, i) => i)
296
- const laneCount = sectionTasks.length === 0 ? 0 : Math.max(...lanes) + 1
297
- const laneRows: GanttRowLayout[] = []
298
- for (let li = 0; li < laneCount; li++) {
299
- laneRows.push({ barIndexes: [], sectionIndex: bandIndex, y: y + li * rowH, h: barHeight })
300
- }
301
- sectionTasks.forEach((t, i) => {
302
- const lane = lanes[i]!
303
- const row = laneRows[lane]!
304
- const x = xOf(t.start)
305
- const w = Math.max(t.end > t.start ? 2 : 0, xOf(t.end) - x)
306
- const isMilestone = t.tags.includes('milestone')
307
- // Milestone diamonds extend ±barHeight/2 around their center; clamp the
308
- // center so a milestone at the range edge stays inside the plot band
309
- // (the ASCII renderer clamps its grid column the same way — issue #26:
310
- // shared geometry, not per-renderer patch-ups).
311
- let milestoneX: number | undefined
312
- if (isMilestone) {
313
- const r = barHeight / 2
314
- const lo = plotX + r
315
- const hi = Math.max(lo, plotX + plotW - r)
316
- milestoneX = Math.min(hi, Math.max(lo, x + w / 2))
317
- }
318
- bars.push({
319
- taskIndex: t.index, id: t.id, label: t.label, tags: t.tags, sectionIndex: bandIndex,
320
- x, y: row.y, w, h: barHeight,
321
- milestoneX,
322
- rowIndex: rowStart + (section.label !== undefined ? 1 : 0) + lane,
323
- start: t.start, end: t.end,
324
- })
325
- row.barIndexes.push(bars.length - 1)
326
- })
327
- rows.push(...laneRows)
328
- y += laneCount * rowH
329
- sections.push({
330
- label: section.label, y: bandStartY, h: y - bandStartY,
331
- rowStart, rowEnd: rows.length,
332
- })
333
- y += GL.sectionGap
334
- }
335
-
336
- const plotH = Math.max(rowH, y - plotY)
337
- const height = plotY + plotH + axisH + GL.padding
338
-
339
- // ---- ticks / markers --------------------------------------------------------
340
- const ticks: GanttTick[] = resolveTicks(schedule, model).map(t => ({
341
- time: t.time, x: xOf(t.time), label: t.label,
342
- }))
343
-
344
- const verts: GanttVertLayout[] = vertTasks.map(t => ({
345
- taskIndex: t.index, label: t.label,
346
- x: xOf(t.start + (t.end - t.start) / 2), time: t.start,
347
- }))
348
-
349
- const todayX = options.today !== undefined && options.today >= schedule.timeMin && options.today <= schedule.timeMax
350
- ? xOf(options.today)
351
- : undefined
352
-
353
- return {
354
- title: model.title,
355
- width, height,
356
- plot: { x: plotX, y: plotY, w: plotW, h: plotH },
357
- labelColumnWidth,
358
- rows, sections, bars, verts, ticks,
359
- topAxis: model.topAxis,
360
- todayX,
361
- timeMin: schedule.timeMin, timeMax: schedule.timeMax,
362
- dateOnly: schedule.dateOnly,
363
- compact, barHeight,
364
- }
365
- }
@@ -1,282 +0,0 @@
1
- // ============================================================================
2
- // Gantt syntax parser — Mermaid-compatible `gantt` source → GanttModel.
3
- //
4
- // Renderer-grade parser with error semantics (docs/design/families/gantt.md §Parser
5
- // rules): never silently drop a line; duplicate task ids are errors; invalid
6
- // directives/tasks are errors with line numbers. The agent-grade structured-
7
- // or-opaque parser lives in src/agent/gantt-body.ts and shares the task-line
8
- // helpers exported here so the two cannot drift.
9
- //
10
- // Input is the normalized line list from mermaid-source.ts (trimmed, comments
11
- // stripped, header first). Frontmatter-derived config (displayMode, barHeight)
12
- // is merged by the caller via applyGanttFrontmatterConfig.
13
- // ============================================================================
14
-
15
- import type {
16
- GanttModel, GanttModelTask, GanttModelSection, GanttTaskTag,
17
- GanttCalendarToken, GanttWeekday, GanttStartExpr, GanttEndExpr, GanttTickUnit,
18
- } from './types.ts'
19
- import { GanttError } from './types.ts'
20
- import type { MermaidFrontmatterMap } from '../mermaid-source.ts'
21
- import { getFrontmatterMap, getFrontmatterScalar } from '../mermaid-source.ts'
22
-
23
- export const GANTT_TASK_TAGS: readonly GanttTaskTag[] = ['active', 'done', 'crit', 'milestone', 'vert']
24
-
25
- const WEEKDAYS: readonly GanttWeekday[] = [
26
- 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday',
27
- ]
28
-
29
- export const GANTT_DURATION_RE = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w|M|y)$/
30
- const TICK_INTERVAL_RE = /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/
31
-
32
- export interface ParsedTaskMeta {
33
- tags: GanttTaskTag[]
34
- id?: string
35
- start?: GanttStartExpr
36
- end: GanttEndExpr
37
- }
38
-
39
- /**
40
- * Parse the metadata half of a task line (after the `:`), following Mermaid's
41
- * comma-split convention: leading items are status tags; the remainder is
42
- * `[id,] [start,] end`. One item = end only (start inherited from the previous
43
- * task); two = start + end; three = id + start + end. Returns null with no
44
- * side effects when the shape is invalid — callers decide error vs opaque.
45
- */
46
- export function parseGanttTaskMeta(rawMeta: string): ParsedTaskMeta | null {
47
- const items = rawMeta.split(',').map(s => s.trim())
48
- if (items.some(s => s.length === 0)) return null
49
- const tags: GanttTaskTag[] = []
50
- let i = 0
51
- while (i < items.length && (GANTT_TASK_TAGS as readonly string[]).includes(items[i]!)) {
52
- const tag = items[i]! as GanttTaskTag
53
- if (tags.includes(tag)) return null // repeated tag is malformed
54
- tags.push(tag)
55
- i++
56
- }
57
- const rest = items.slice(i)
58
- if (rest.length === 0 || rest.length > 3) return null
59
-
60
- const classifyStart = (raw: string): GanttStartExpr => {
61
- const after = raw.match(/^after\s+(.+)$/)
62
- if (after) return { kind: 'after', refs: after[1]!.split(/\s+/).filter(Boolean) }
63
- return { kind: 'date', raw }
64
- }
65
- const classifyEnd = (raw: string): GanttEndExpr => {
66
- const until = raw.match(/^until\s+(.+)$/)
67
- if (until) return { kind: 'until', refs: until[1]!.split(/\s+/).filter(Boolean) }
68
- if (GANTT_DURATION_RE.test(raw)) return { kind: 'duration', raw }
69
- return { kind: 'date', raw }
70
- }
71
-
72
- if (rest.length === 1) return { tags, end: classifyEnd(rest[0]!) }
73
- if (rest.length === 2) return { tags, start: classifyStart(rest[0]!), end: classifyEnd(rest[1]!) }
74
- // Three items: Mermaid treats the first as the task id unconditionally.
75
- const id = rest[0]!
76
- if (!/^[\w-]+$/.test(id)) return null
77
- return { tags, id, start: classifyStart(rest[1]!), end: classifyEnd(rest[2]!) }
78
- }
79
-
80
- /** Canonical re-emission of a parsed task metadata (used by the agent body
81
- * serializer and the parser↔body differential tests). */
82
- export function renderGanttTaskMeta(meta: ParsedTaskMeta): string {
83
- const parts: string[] = [...meta.tags]
84
- if (meta.id !== undefined) parts.push(meta.id)
85
- if (meta.start) parts.push(meta.start.kind === 'after' ? `after ${meta.start.refs.join(' ')}` : meta.start.raw)
86
- parts.push(meta.end.kind === 'until' ? `until ${meta.end.refs.join(' ')}` : meta.end.raw)
87
- return parts.join(', ')
88
- }
89
-
90
- function parseCalendarTokens(raw: string): GanttCalendarToken[] {
91
- // Mermaid accepts comma- or whitespace-separated entries.
92
- return raw.split(/[,\s]+/).filter(Boolean).map((tok): GanttCalendarToken => {
93
- const lower = tok.toLowerCase()
94
- if (lower === 'weekends') return { kind: 'weekends' }
95
- if ((WEEKDAYS as readonly string[]).includes(lower)) return { kind: 'weekday', day: lower as GanttWeekday }
96
- return { kind: 'date', raw: tok }
97
- })
98
- }
99
-
100
- const DIRECTIVE_RES = {
101
- title: /^title\s+(.+)$/i,
102
- dateFormat: /^dateFormat\s+(.+)$/i,
103
- axisFormat: /^axisFormat\s+(.+)$/i,
104
- tickInterval: /^tickInterval\s+(.+)$/i,
105
- excludes: /^excludes\s+(.+)$/i,
106
- includes: /^includes\s+(.+)$/i,
107
- todayMarker: /^todayMarker\s+(.+)$/i,
108
- weekday: /^weekday\s+(.+)$/i,
109
- weekend: /^weekend\s+(.+)$/i,
110
- section: /^section\s+(.+)$/i,
111
- click: /^click\s+([\w-]+)\s+(href|call)\s+(.+)$/i,
112
- accTitle: /^accTitle\s*:\s*(.+)$/i,
113
- accDescrInline: /^accDescr\s*:\s*(.+)$/i,
114
- accDescrBlock: /^accDescr\s*\{\s*$/i,
115
- } as const
116
-
117
- /**
118
- * Parse normalized Mermaid gantt lines (header included) into a GanttModel.
119
- * Throws GanttError with a code and 1-based line number on the first invalid
120
- * construct. Lines is the normalized list, so `lineNo` refers to it.
121
- */
122
- export function parseGanttModel(lines: string[]): GanttModel {
123
- const header = (lines[0] ?? '').trim()
124
- if (!/^gantt\s*$/i.test(header)) {
125
- throw new GanttError('GANTT_BAD_DIRECTIVE', `Expected "gantt" header, got "${header}"`, 1)
126
- }
127
-
128
- const model: GanttModel = {
129
- dateFormat: 'YYYY-MM-DD',
130
- inclusiveEndDates: false,
131
- topAxis: false,
132
- excludes: [],
133
- includes: [],
134
- weekendStart: 'saturday',
135
- weekStart: 'sunday',
136
- sections: [{ taskIndexes: [] }],
137
- tasks: [],
138
- clicks: [],
139
- }
140
- const seenIds = new Set<string>()
141
- let currentSection = 0
142
- let inAccDescr = false
143
- const accDescrBuf: string[] = []
144
-
145
- for (let i = 1; i < lines.length; i++) {
146
- const line = lines[i]!.trim()
147
- const lineNo = i + 1
148
- if (!line || line.startsWith('%%')) continue
149
-
150
- if (inAccDescr) {
151
- if (/^\}\s*$/.test(line)) {
152
- model.accDescr = accDescrBuf.join(' ').trim()
153
- inAccDescr = false
154
- } else {
155
- accDescrBuf.push(line)
156
- }
157
- continue
158
- }
159
-
160
- let m: RegExpMatchArray | null
161
-
162
- if ((m = line.match(DIRECTIVE_RES.title))) { model.title = m[1]!.trim(); continue }
163
- if ((m = line.match(DIRECTIVE_RES.dateFormat))) { model.dateFormat = m[1]!.trim(); continue }
164
- if ((m = line.match(DIRECTIVE_RES.axisFormat))) { model.axisFormat = m[1]!.trim(); continue }
165
- if ((m = line.match(DIRECTIVE_RES.tickInterval))) {
166
- // Lenient like Mermaid: values outside the documented
167
- // `count(millisecond|second|minute|hour|day|week|month)` shape are
168
- // ignored and the axis falls back to auto ticks — mermaid's own docs
169
- // contain `tickInterval 1decade`. The PR #7197 safety property lives in
170
- // bounded tick GENERATION (layout.ts), not in rejecting the directive.
171
- const tm = m[1]!.trim().match(TICK_INTERVAL_RE)
172
- if (tm) model.tickInterval = { count: Number(tm[1]), unit: tm[2] as GanttTickUnit }
173
- continue
174
- }
175
- if (/^inclusiveEndDates\s*$/i.test(line)) { model.inclusiveEndDates = true; continue }
176
- if (/^topAxis\s*$/i.test(line)) { model.topAxis = true; continue }
177
- if ((m = line.match(DIRECTIVE_RES.excludes))) {
178
- // Multiple excludes lines accumulate (mermaid PR #7772).
179
- model.excludes.push(...parseCalendarTokens(m[1]!.trim()))
180
- continue
181
- }
182
- if ((m = line.match(DIRECTIVE_RES.includes))) {
183
- model.includes.push(...parseCalendarTokens(m[1]!.trim()))
184
- continue
185
- }
186
- if ((m = line.match(DIRECTIVE_RES.todayMarker))) {
187
- const v = m[1]!.trim()
188
- model.todayMarker = v.toLowerCase() === 'off' ? { off: true } : { off: false, style: v }
189
- continue
190
- }
191
- if ((m = line.match(DIRECTIVE_RES.weekday))) {
192
- const day = m[1]!.trim().toLowerCase()
193
- if (!(WEEKDAYS as readonly string[]).includes(day)) {
194
- throw new GanttError('GANTT_BAD_DIRECTIVE', `Invalid weekday "${m[1]!.trim()}"`, lineNo)
195
- }
196
- model.weekStart = day as GanttWeekday
197
- continue
198
- }
199
- if ((m = line.match(DIRECTIVE_RES.weekend))) {
200
- const day = m[1]!.trim().toLowerCase()
201
- if (day !== 'friday' && day !== 'saturday') {
202
- throw new GanttError('GANTT_BAD_DIRECTIVE', `Invalid weekend "${m[1]!.trim()}" (friday or saturday)`, lineNo)
203
- }
204
- model.weekendStart = day
205
- continue
206
- }
207
- if ((m = line.match(DIRECTIVE_RES.click))) {
208
- model.clicks.push({ taskId: m[1]!, action: m[2]!.toLowerCase() as 'href' | 'call', rest: m[3]!.trim(), line: lineNo })
209
- continue
210
- }
211
- if ((m = line.match(DIRECTIVE_RES.accTitle))) { model.accTitle = m[1]!.trim(); continue }
212
- if (DIRECTIVE_RES.accDescrBlock.test(line)) { inAccDescr = true; continue }
213
- if ((m = line.match(DIRECTIVE_RES.accDescrInline))) { model.accDescr = m[1]!.trim(); continue }
214
- if ((m = line.match(DIRECTIVE_RES.section))) {
215
- model.sections.push({ label: m[1]!.trim(), taskIndexes: [], line: lineNo })
216
- currentSection = model.sections.length - 1
217
- continue
218
- }
219
-
220
- // Task line: `<label> : <metadata>` — label is everything before the LAST
221
- // colon-separated metadata block. Mermaid's grammar splits on the first
222
- // colon (labels cannot contain `:`); `#`/`;` are allowed in labels since
223
- // mermaid PR #5095 (our comment stripping only removes `%%` lines).
224
- const colon = line.indexOf(':')
225
- if (colon > 0 && colon < line.length - 1) {
226
- const label = line.slice(0, colon).trim()
227
- const rawMeta = line.slice(colon + 1).trim()
228
- const meta = parseGanttTaskMeta(rawMeta)
229
- if (!meta) throw new GanttError('GANTT_BAD_TASK', `Invalid task metadata "${rawMeta}"`, lineNo)
230
- if (!label) throw new GanttError('GANTT_BAD_TASK', 'Task label is empty', lineNo)
231
- if (meta.id !== undefined) {
232
- if (seenIds.has(meta.id)) throw new GanttError('GANTT_DUPLICATE_TASK_ID', `Duplicate task id "${meta.id}"`, lineNo)
233
- seenIds.add(meta.id)
234
- }
235
- const task: GanttModelTask = {
236
- index: model.tasks.length,
237
- id: meta.id,
238
- label,
239
- tags: meta.tags,
240
- start: meta.start,
241
- end: meta.end,
242
- sectionIndex: currentSection,
243
- line: lineNo,
244
- }
245
- model.tasks.push(task)
246
- model.sections[currentSection]!.taskIndexes.push(task.index)
247
- continue
248
- }
249
-
250
- throw new GanttError('GANTT_BAD_DIRECTIVE', `Unrecognized gantt line "${line}"`, lineNo)
251
- }
252
-
253
- if (inAccDescr) throw new GanttError('GANTT_BAD_DIRECTIVE', 'Unclosed accDescr block')
254
- return model
255
- }
256
-
257
- /**
258
- * Merge frontmatter/init config into the model: Mermaid accepts
259
- * `displayMode: compact` at the top level and `gantt: { displayMode,
260
- * barHeight, topAxis }` under config. mermaid-source.ts already folds the
261
- * `config:` root into the top-level map.
262
- */
263
- export function applyGanttFrontmatterConfig(model: GanttModel, frontmatter: MermaidFrontmatterMap | undefined): GanttModel {
264
- if (!frontmatter) return model
265
- const topLevelMode = getFrontmatterScalar<string>(frontmatter, ['displayMode'])
266
- const ganttMap = getFrontmatterMap(frontmatter, ['gantt'])
267
- const ganttMode = ganttMap ? getFrontmatterScalar<string>(frontmatter, ['gantt', 'displayMode']) : undefined
268
- const mode = ganttMode ?? topLevelMode
269
- if (typeof mode === 'string' && mode.toLowerCase() === 'compact') model.displayMode = 'compact'
270
- const barHeight = getFrontmatterScalar<number>(frontmatter, ['gantt', 'barHeight'])
271
- if (typeof barHeight === 'number' && Number.isFinite(barHeight) && barHeight > 0) model.barHeight = barHeight
272
- const topAxis = getFrontmatterScalar<boolean>(frontmatter, ['gantt', 'topAxis'])
273
- if (topAxis === true) model.topAxis = true
274
- const axisFormat = getFrontmatterScalar<string>(frontmatter, ['gantt', 'axisFormat'])
275
- if (typeof axisFormat === 'string' && model.axisFormat === undefined) model.axisFormat = axisFormat
276
- const tickInterval = getFrontmatterScalar<string>(frontmatter, ['gantt', 'tickInterval'])
277
- if (typeof tickInterval === 'string' && model.tickInterval === undefined) {
278
- const tm = tickInterval.match(TICK_INTERVAL_RE)
279
- if (tm) model.tickInterval = { count: Number(tm[1]), unit: tm[2] as GanttTickUnit }
280
- }
281
- return model
282
- }
@@ -1,27 +0,0 @@
1
- import type { MermaidFrontmatterMap } from '../mermaid-source.ts'
2
- import { applyGanttFrontmatterConfig, parseGanttModel } from './parser.ts'
3
- import { resolveGanttSchedule } from './schedule.ts'
4
- import { layoutGantt, type GanttLayoutOptions } from './layout.ts'
5
- import type { GanttClock, GanttLayoutResult, GanttModel, GanttSchedule } from './types.ts'
6
-
7
- export interface GanttRenderPipelineOptions {
8
- clock?: GanttClock
9
- layout?: Omit<GanttLayoutOptions, 'today'>
10
- }
11
-
12
- export interface GanttRenderPipeline {
13
- model: GanttModel
14
- schedule: GanttSchedule
15
- positioned: GanttLayoutResult
16
- }
17
-
18
- export function buildGanttRenderPipeline(
19
- lines: string[],
20
- frontmatter: MermaidFrontmatterMap | undefined,
21
- options: GanttRenderPipelineOptions = {},
22
- ): GanttRenderPipeline {
23
- const model = applyGanttFrontmatterConfig(parseGanttModel(lines), frontmatter)
24
- const schedule = resolveGanttSchedule(model, options.clock)
25
- const positioned = layoutGantt(model, schedule, { ...options.layout, today: schedule.today })
26
- return { model, schedule, positioned }
27
- }