agentic-mermaid 0.1.2 → 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.
- package/AGENT_NATIVE.md +671 -0
- package/CHANGELOG.md +19 -0
- package/Instructions_for_agents.md +89 -0
- package/README.md +8 -8
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +2 -2
- package/dist/agent-core.js +2 -2
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +4 -4
- package/dist/agentic-mermaid-mcp.js +5 -5
- package/dist/am.js +8 -15
- package/dist/{chunk-QKDLMOJJ.js → chunk-2P5FKUQC.js} +7 -9
- package/dist/{chunk-WN3YDOBG.js → chunk-2S3VSS6I.js} +4 -4
- package/dist/{chunk-CDPKJ2BQ.js → chunk-2SILJDW2.js} +2 -2
- package/dist/{chunk-ZYMMKQ3I.js → chunk-BJYMBT4I.js} +299 -973
- package/dist/{chunk-WI35L7IR.js → chunk-FDM43XU7.js} +2 -2
- package/dist/{chunk-4QECACIA.js → chunk-MYPDVZCO.js} +19 -19
- package/dist/{index-D54dqSdC.d.ts → index-DWPa2MJs.d.ts} +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +34 -34
- package/docs/agent-api-cookbook.md +317 -0
- package/docs/agent-mutation-policy.md +104 -0
- package/docs/agent-workflow-examples.md +46 -0
- package/docs/api.md +418 -0
- package/docs/ascii.md +113 -0
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +106 -0
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +121 -0
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +275 -0
- package/docs/diagram-families.md +218 -0
- package/docs/features.md +154 -0
- package/docs/fork-differences.md +133 -0
- package/docs/getting-started.md +182 -0
- package/docs/issue-derived-test-cases.md +163 -0
- package/docs/mcp-code-mode-rationale.md +108 -0
- package/docs/mcp-http-transport.md +230 -0
- package/docs/mermaid-layout-complaints.md +851 -0
- package/docs/mutation-testing.md +285 -0
- package/docs/quality.md +224 -0
- package/docs/react.md +97 -0
- package/docs/style-authoring.md +336 -0
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +443 -0
- package/docs/theming.md +149 -0
- package/package.json +21 -6
- package/server.json +2 -2
- package/skills/agentic-mermaid-live-editor/SKILL.md +1 -1
- package/dist/mcp-cli-SBQSTJFC.js +0 -17
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Computational-aesthetics prototype plan
|
|
2
|
+
|
|
3
|
+
This is the revised plan for cheap, deterministic computational aesthetics in
|
|
4
|
+
Agentic Mermaid. It covers pure functions over geometry and concrete sRGB. It
|
|
5
|
+
does not turn a rubric into an optimizer, add learned scores, or introduce
|
|
6
|
+
stochastic search.
|
|
7
|
+
|
|
8
|
+
The governing rule is stricter than “the formula is deterministic”:
|
|
9
|
+
|
|
10
|
+
> An aesthetic mechanism is ready only when its domain, runtime, product
|
|
11
|
+
> reachability, and evidence are all explicit.
|
|
12
|
+
|
|
13
|
+
Agentic Mermaid already operationalizes graph-layout research in
|
|
14
|
+
`src/layout-rubric.ts` and `src/family-rubric.ts`. The work below extends that
|
|
15
|
+
discipline to categorical color and whole-canvas composition without pretending
|
|
16
|
+
that one scalar score certifies beauty.
|
|
17
|
+
|
|
18
|
+
## Status
|
|
19
|
+
|
|
20
|
+
- **shipped** — implemented on a product path with discriminating tests.
|
|
21
|
+
- **prototype** — report-only until the evaluation harness justifies a weight.
|
|
22
|
+
- **rejected** — considered, but redundant or insufficiently specified.
|
|
23
|
+
|
|
24
|
+
| # | Idea | Status | Contract / next question |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| 1 | OKLCH constant-lightness categorical ramps | **shipped** | deterministic concrete-sRGB generation |
|
|
27
|
+
| 2 | Minimum ΔE_OK collision floor | **shipped** | hard for 7–24 fills; best-effort and linear-time above 24 |
|
|
28
|
+
| 3 | APCA plus WCAG background-visibility floors | **shipped** | every derived fill clears both for valid concrete backgrounds |
|
|
29
|
+
| 10 | Common-region ownership purity | **shipped** | architecture, class, ER, and timeline ownership frames |
|
|
30
|
+
| 12 | Separate typographic CPL warning | **rejected** | duplicates public universal `LABEL_OVERFLOW`; no independent signal |
|
|
31
|
+
| 4 | Cohen-Or hue-harmony templates | **rejected as a default** | 4/360 cases retain {1,2,3}; experiment remains reproducible |
|
|
32
|
+
| 5 | Purchase-normalized crosslessness `m_c` | prototype | does normalization discriminate across graph sizes? |
|
|
33
|
+
| 6 | Consistent flow-direction metric | prototype | does it catch anything ELK does not already guarantee? |
|
|
34
|
+
| 7 | Whole-canvas composition | prototype | which Ngo terms correlate with preference on our diagrams? |
|
|
35
|
+
| 8 | Crossing-angle / RAC penalty | prototype | incremental value after crossing count |
|
|
36
|
+
| 9 | Spine-straightness continuity | prototype | incremental value after {5,8} |
|
|
37
|
+
| 11 | Proximity / MST purity | prototype | incremental value after common-region and composition metrics |
|
|
38
|
+
| 13 | Information-theoretic order | prototype | is a pinned gzip ratio more than a coarse alignment proxy? |
|
|
39
|
+
|
|
40
|
+
## What the first implementation taught us
|
|
41
|
+
|
|
42
|
+
The audit and controlled rollout changed the plan in twelve ways.
|
|
43
|
+
|
|
44
|
+
1. **Runtime is part of correctness.** A deterministic pairwise repair still
|
|
45
|
+
fails the product if an allowed 1,000-slice input takes seconds. The ΔE
|
|
46
|
+
guarantee is therefore bounded at 24 and the large-count path has expected
|
|
47
|
+
linear work. Portable CI asserts the real branch and its operation counts;
|
|
48
|
+
wall-clock observations live in `eval/palette-performance/report.json` with
|
|
49
|
+
their machine, protocol, input hashes, and limitations. Every prototype must
|
|
50
|
+
declare an input-size budget and a degraded-tail rule.
|
|
51
|
+
|
|
52
|
+
2. **A threshold needs a corpus, not one friendly example.** The 0.10 ΔE claim
|
|
53
|
+
initially failed on shipped themes and custom accent/background pairs. The
|
|
54
|
+
contract now runs across every built-in theme, every count from 7 through
|
|
55
|
+
24, and adversarial saturated backgrounds. Future thresholds need the same
|
|
56
|
+
domain matrix before they can be called guarantees.
|
|
57
|
+
|
|
58
|
+
3. **Use the perceptual quantity that controls the decision.** HSL lightness
|
|
59
|
+
misclassified a bright saturated yellow-green background as dark. Palette
|
|
60
|
+
polarity now uses OKLab lightness, and visibility repair searches both
|
|
61
|
+
lightness directions before falling back to a visible achromatic extreme.
|
|
62
|
+
|
|
63
|
+
4. **A shared waist multiplies risk as well as value.** `pieSliceColors` already
|
|
64
|
+
reaches pie and radar, so one defect reaches both. Migrating four more
|
|
65
|
+
families remains valuable, but only after the waist has an explicit range,
|
|
66
|
+
a hard runtime bound, adversarial tests, and per-family visual review.
|
|
67
|
+
|
|
68
|
+
5. **Reachability precedes novelty.** A test-only finding is not a product
|
|
69
|
+
capability. The proposed `LABEL_LINE_OVERLONG` finding was not surfaced by
|
|
70
|
+
verify, CLI, API, or MCP and overlapped the existing public
|
|
71
|
+
`LABEL_OVERFLOW` warning. It is removed rather than wired twice.
|
|
72
|
+
|
|
73
|
+
6. **Independence applies before implementation too.** A new metric must state
|
|
74
|
+
which existing defect it detects that current metrics miss. Idea #12 fails
|
|
75
|
+
that test. Ideas {7,11} and {5,8,9} must pass it through ablation.
|
|
76
|
+
|
|
77
|
+
7. **Semantic scope must follow every real model.** Common-region purity is not
|
|
78
|
+
architecture-only: class namespaces, ER groups, and timeline sections also
|
|
79
|
+
express ownership. Ancestor walks must be cycle-safe without arbitrary
|
|
80
|
+
depth cutoffs.
|
|
81
|
+
|
|
82
|
+
8. **Visual evidence needs its own reproducibility contract.** The rollout now
|
|
83
|
+
freezes pre-change SVGs, extracts the categorical colors actually serialized
|
|
84
|
+
by each renderer, writes a machine-readable comparison, and hashes every
|
|
85
|
+
source input plus the generated report/contact sheet. `--check` fails when
|
|
86
|
+
code, baseline, metrics, or images drift.
|
|
87
|
+
|
|
88
|
+
9. **A controlled rollout is a compatibility policy, not a global recolor.**
|
|
89
|
+
Counts up to six retain each family's existing derived colors byte-for-byte
|
|
90
|
+
(including Journey's established actor wheel). Authored
|
|
91
|
+
`plotColorPalette`, `actorColours`, `sectionFills`/`sectionColours`, and
|
|
92
|
+
`git0..7` remain authoritative. Only derived high-cardinality peer-category
|
|
93
|
+
colors change.
|
|
94
|
+
|
|
95
|
+
10. **Harmony and categorical distinctness compete at this density.** Across
|
|
96
|
+
20 built-in themes × counts 7..24, the Cohen-Or/Matsuda experiment reduced
|
|
97
|
+
mean harmony loss from 8.8303° to 0.0084°, but reduced mean minimum ΔE_OK
|
|
98
|
+
from 0.1107 to 0.0456. Only 4/360 palettes retained {1,2,3}; no count above
|
|
99
|
+
seven passed. That rejects #4 as a default layer over this palette.
|
|
100
|
+
|
|
101
|
+
11. **A measurement must not claim more than it measures.** The performance
|
|
102
|
+
corpus measures one palette-generation call, not a complete diagram render.
|
|
103
|
+
Most controlled families have one peer-category channel; Journey has two
|
|
104
|
+
independent channels (sections and actors). Following the
|
|
105
|
+
[testing-best-practices guidance](https://github.com/adewale/testing-best-practices/tree/e50479920006aa010850a2c37f9bee1e02b5badf),
|
|
106
|
+
CI gates deterministic complexity and evidence freshness, never a portable
|
|
107
|
+
millisecond threshold.
|
|
108
|
+
|
|
109
|
+
12. **Normalize at the algorithm boundary without erasing compatibility.**
|
|
110
|
+
StyleSpec accepts concrete `#RGB`, named, `rgb()`, and `hsl()` colors, while
|
|
111
|
+
the perceptual math requires six-digit sRGB. The high-count path now
|
|
112
|
+
normalizes every parser-resolvable concrete color before repair. The ≤6
|
|
113
|
+
path deliberately preserves each family's historical spelling or fallback
|
|
114
|
+
policy byte-for-byte; a global normalizer at that boundary would be a
|
|
115
|
+
compatibility regression disguised as cleanup.
|
|
116
|
+
|
|
117
|
+
## Shipped color contract and family reach
|
|
118
|
+
|
|
119
|
+
The color work is a generation constraint, not evidence that the quality
|
|
120
|
+
rubric predicts human preference. The neutral shared waist now reaches pie,
|
|
121
|
+
radar, xychart, journey, mindmap, and gitgraph. SVG and terminal xychart/gitgraph
|
|
122
|
+
use the same derived palette; the other terminal families do not currently
|
|
123
|
+
encode peer categories with color.
|
|
124
|
+
|
|
125
|
+
The scope rule for this rollout, and for any future shared-palette expansion,
|
|
126
|
+
is explicit:
|
|
127
|
+
|
|
128
|
+
> The scope is intentionally limited to families where color identifies peer
|
|
129
|
+
> categories. Semantic colors—status, hierarchy, role, or user-authored
|
|
130
|
+
> meaning—should not be automatically redistributed.
|
|
131
|
+
|
|
132
|
+
| Property | 7–24 fills | More than 24 fills |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| deterministic | hard | hard |
|
|
135
|
+
| valid concrete hex output | hard | hard |
|
|
136
|
+
| WCAG ≥ 1.25 and APCA ≥ 15 vs concrete background | hard | hard |
|
|
137
|
+
| unique fills | hard | best-effort |
|
|
138
|
+
| minimum pairwise ΔE_OK ≥ 0.10 | hard | not claimed |
|
|
139
|
+
| asymptotic repair cost | bounded `O(M·n²)`, with `n ≤ 24` and fixed corpus `M` | expected `O(n)` with average `O(1)` set membership |
|
|
140
|
+
|
|
141
|
+
The established local palette is preserved when it meets the contract. If it
|
|
142
|
+
does not, a deterministic farthest-point packing pass selects background-visible
|
|
143
|
+
concrete sRGB candidates. Above 24, pairwise enforcement is skipped.
|
|
144
|
+
|
|
145
|
+
## Prototype protocol
|
|
146
|
+
|
|
147
|
+
Reuse the existing evaluator; do not create a second quality system.
|
|
148
|
+
|
|
149
|
+
1. **Define the contract first.** State applicable families, input-size range,
|
|
150
|
+
asymptotic and wall-clock budgets, degraded-tail behavior, and exclusions.
|
|
151
|
+
2. **Prove product reachability.** Identify the public result, warning, or
|
|
152
|
+
report that exposes the metric. A private helper plus unit test is not done.
|
|
153
|
+
3. **Report before weighting.** Add the metric to the per-metric tracker with
|
|
154
|
+
zero score weight. A weight is a separate scoring-contract change.
|
|
155
|
+
4. **Add trip fixtures.** Include healthy and deliberately bad examples that
|
|
156
|
+
make the metric move. Zero on every committed fixture is a guard, not a
|
|
157
|
+
demonstrated discriminator.
|
|
158
|
+
5. **Measure the full domain.** Use the Mermaid docs corpus, heuristic tracker,
|
|
159
|
+
deterministic family fuzzers, all built-in themes where color is involved,
|
|
160
|
+
and adversarial boundary inputs.
|
|
161
|
+
6. **Keep visual evidence.** Any generation or geometry change needs
|
|
162
|
+
before/after renders and snapshot review in every affected family. The
|
|
163
|
+
palette rollout operationalizes this rule in
|
|
164
|
+
`scripts/pr-assets/palette-rollout-evidence.ts`.
|
|
165
|
+
7. **Gate determinism and cost.** Preserve byte identity and deterministically
|
|
166
|
+
assert that the intended bounded algorithmic path engaged. Keep p50/p95
|
|
167
|
+
observations over a fixed corpus in a separate, provenance-bound report;
|
|
168
|
+
do not use cross-machine wall time as a CI threshold. Idea #13 must also pin
|
|
169
|
+
codec and level.
|
|
170
|
+
|
|
171
|
+
## Evaluation and graduation
|
|
172
|
+
|
|
173
|
+
For a candidate metric M, record:
|
|
174
|
+
|
|
175
|
+
- **coverage** — fraction of diagrams on which M varies;
|
|
176
|
+
- **discrimination** — LCC/SROCC against a small blinded human A/B set;
|
|
177
|
+
- **independence** — partial correlation after controlling for current metrics;
|
|
178
|
+
- **stability** — sensitivity to harmless translation, scale, and equivalent
|
|
179
|
+
source ordering where those transforms should not matter;
|
|
180
|
+
- **cost** — deterministic operation growth at the declared size boundary,
|
|
181
|
+
plus environment-qualified p50/p95 observations where useful;
|
|
182
|
+
- **reach** — public surfaces and diagram families that consume the result.
|
|
183
|
+
|
|
184
|
+
Graduate a metric from report-only only when it fires on trip fixtures, varies
|
|
185
|
+
on the corpus, adds preference signal after current metrics, and stays inside
|
|
186
|
+
its cost budget. Otherwise remove it or retain it explicitly as a correctness
|
|
187
|
+
guard with no aesthetic weight.
|
|
188
|
+
|
|
189
|
+
For combinations, run small ablations rather than blending everything. Treat
|
|
190
|
+
each combination as a gated ladder: validate the cheaper base term, add one
|
|
191
|
+
term, and stop when independence disappears.
|
|
192
|
+
|
|
193
|
+
- **{1,2,3}:** lightness structure, categorical distance, and background
|
|
194
|
+
visibility are complementary and already ship as one generation contract.
|
|
195
|
+
- **{1,2,3,4}:** the completed experiment found no useful Pareto front at
|
|
196
|
+
8–24 categories: hue-sector contraction almost eliminates harmony loss but
|
|
197
|
+
breaks minimum categorical distance. Do not ship this bundle as a default.
|
|
198
|
+
- **{5,8}:** crossing prevalence and crossing quality should be complementary.
|
|
199
|
+
- **{5,6,8}:** add direction only if #6 catches defects not explained by the
|
|
200
|
+
crossing pair or by ELK's existing directional constraints.
|
|
201
|
+
- **{5,8,9}:** likely redundant; add #9 only if partial correlation survives.
|
|
202
|
+
- **{7,10,11}:** composition, common region, and proximity span figure,
|
|
203
|
+
ownership, and local grouping scales, but need ablation because the signals
|
|
204
|
+
can collapse onto the same cluster structure.
|
|
205
|
+
|
|
206
|
+
## Revised stack rank
|
|
207
|
+
|
|
208
|
+
The rank uses five axes: user-visible value, independence from existing
|
|
209
|
+
signals, product reach, bounded runtime, and validation burden. “Foundation” is
|
|
210
|
+
not optional feature work. An arrow means “validate the new idea alone, then
|
|
211
|
+
admit it to the combination”; it is not permission to ship an opaque aggregate.
|
|
212
|
+
|
|
213
|
+
| Rank | Work package | Form | Why it is here | Stop / go gate |
|
|
214
|
+
|---|---|---|---|---|
|
|
215
|
+
| Foundation | **Keep {1,2,3,10}; reject #12** | shipped contract | Color and ownership have bounded, reachable contracts; the extra label warning is redundant. | keep the regression matrix green |
|
|
216
|
+
| Delivered | **Controlled {1,2,3} family rollout** | shipped combination | 14/14 fixture violations corrected; 8/8 family/theme cases pass; ≤6 colors and authored overrides remain compatible; six rich website examples expose the peer mapping at inspectable size. | palette evidence, complexity, website, and family gates |
|
|
217
|
+
| Rejected | **#4 → {1,2,3,4} color-harmony default** | completed experiment | 4/360 pass rate; mean minimum ΔE_OK falls 0.1107 → 0.0456. | reconsider only with a new constraint-preserving algorithm and blinded preference evidence |
|
|
218
|
+
| Paused 1 | **#5 normalized crosslessness `m_c`** | telemetry, postponed | Still the cheapest next metric experiment, but telemetry work is intentionally deferred. | resume only on explicit telemetry restart |
|
|
219
|
+
| Paused 2 | **#8 → {5,8} crossing-quality bundle** | telemetry, postponed | Clear ablation after #5. | #8 must add signal after #5 |
|
|
220
|
+
| Paused 3 | **#6 → {5,6,8} directional-legibility bundle** | telemetry, postponed | Likely overlaps ELK behavior. | independent trip case first |
|
|
221
|
+
| Paused 4 | **#7 whole-canvas composition** | telemetry, postponed | Potential figure-level value, high calibration burden. | stable, nonzero corpus coverage |
|
|
222
|
+
| Paused 5 | **#11 → {7,10,11} multiscale grouping** | telemetry, postponed | Plausible value with high redundancy risk. | term-by-term ablation |
|
|
223
|
+
| Paused 6 | **#9 → {5,8,9} edge-path bundle** | telemetry, postponed | Costlier and likely redundant. | partial-correlation survival |
|
|
224
|
+
| Paused 7 | **#13 pinned-gzip order** | telemetry, postponed | Codec-sensitive and coarse. | beat simpler direct features |
|
|
225
|
+
|
|
226
|
+
This supersedes the earlier ordering. The highest-ranked product change is now
|
|
227
|
+
delivered and its evidence gate is permanent. The harmony experiment has also
|
|
228
|
+
resolved its decision: it is not merely lower priority; this formulation is a
|
|
229
|
+
bad default. With telemetry postponed, there is no authorized next aesthetic
|
|
230
|
+
metric package. If telemetry resumes, it still starts with #5 and every later
|
|
231
|
+
bundle remains conditional on ablation.
|
|
232
|
+
|
|
233
|
+
## Scope boundaries
|
|
234
|
+
|
|
235
|
+
- This plan covers measurement and closed-form generation, not optimization.
|
|
236
|
+
- Flowchart/state subgraph purity remains in `layout-rubric.ts`; the generic
|
|
237
|
+
#10 implementation covers ownership frames projected by architecture, class,
|
|
238
|
+
ER, and timeline.
|
|
239
|
+
- `LABEL_OVERFLOW` remains the public label-length signal. It applies the
|
|
240
|
+
caller-configurable cap to the longest rendered line after entity decoding,
|
|
241
|
+
line-break normalization, and formatting-tag removal. A second fixed CPL
|
|
242
|
+
warning would be redundant and less reachable.
|
|
243
|
+
- The shared perceptual palette reaches only families where color denotes peer
|
|
244
|
+
categories: pie slices, radar curves, xychart series, journey actors/sections,
|
|
245
|
+
mindmap top-level branches, and gitgraph branches. Structural families use
|
|
246
|
+
color for roles, status, hierarchy, or authored semantics; automatic hue
|
|
247
|
+
spreading there would invent category meaning and can weaken brand/status
|
|
248
|
+
cues. Timeline/gantt/quadrant have family-specific categorical contracts and
|
|
249
|
+
are not silently swept into this rollout.
|
|
250
|
+
- A score of 100 proves only that a renderer clears the measured hygiene floor;
|
|
251
|
+
it does not certify beauty. Each family still needs an aesthetic thesis and
|
|
252
|
+
human visual review.
|
package/docs/config.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Mermaid config support
|
|
2
|
+
|
|
3
|
+
Agentic Mermaid accepts Mermaid-style runtime config through explicit render options, YAML frontmatter, and `%%{init: ...}%%` / `%%{initialize: ...}%%` directives.
|
|
4
|
+
|
|
5
|
+
Round-trip behavior: the agent surface preserves these source wrappers
|
|
6
|
+
byte-verbatim — `parseRegisteredMermaid → serializeMermaid` and every `mutate` re-emit
|
|
7
|
+
the original frontmatter/directives/leading comments untouched, so a
|
|
8
|
+
`config.layout`/`config.look` request written for Mermaid survives an
|
|
9
|
+
Agentic Mermaid edit loop. Canonical wrapper synthesis (config-nested
|
|
10
|
+
frontmatter, directives folded) is opt-in via
|
|
11
|
+
`serializeMermaid(d, { wrapper: 'canonical' })` or `am format
|
|
12
|
+
--canonical-wrapper`.
|
|
13
|
+
|
|
14
|
+
## Explicit config
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { renderMermaidSVG } from 'agentic-mermaid'
|
|
18
|
+
|
|
19
|
+
const svg = renderMermaidSVG(source, {
|
|
20
|
+
mermaidConfig: {
|
|
21
|
+
theme: 'base',
|
|
22
|
+
themeVariables: {
|
|
23
|
+
fontFamily: 'Inter, sans-serif',
|
|
24
|
+
primaryColor: '#eef2ff',
|
|
25
|
+
primaryTextColor: '#111827',
|
|
26
|
+
lineColor: '#64748b',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Explicit options win over source-level config when both specify the same supported field. `RenderOptions.onConfigDiagnostic` can collect warnings for explicit config that is unknown, invalid, or has no faithful effect; without a collector, explicit ineffective State config warns through `console.warn` rather than disappearing silently.
|
|
33
|
+
|
|
34
|
+
## YAML frontmatter
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
---
|
|
38
|
+
config:
|
|
39
|
+
theme: base
|
|
40
|
+
themeVariables:
|
|
41
|
+
primaryColor: '#eef2ff'
|
|
42
|
+
lineColor: '#64748b'
|
|
43
|
+
---
|
|
44
|
+
flowchart TD
|
|
45
|
+
A --> B
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Init directives
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
%%{init: { "theme": "base", "themeVariables": { "fontFamily": "Inter" } }}%%
|
|
52
|
+
flowchart TD
|
|
53
|
+
A --> B
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Loose Mermaid-style object literals are accepted for documented config surfaces where Mermaid permits them.
|
|
57
|
+
|
|
58
|
+
## Supported config surfaces
|
|
59
|
+
|
|
60
|
+
Top-level:
|
|
61
|
+
|
|
62
|
+
- `theme`
|
|
63
|
+
- `themeVariables`
|
|
64
|
+
- `fontFamily`
|
|
65
|
+
- `fontSize`
|
|
66
|
+
|
|
67
|
+
(The composable look system is a *render option*, not Mermaid config:
|
|
68
|
+
`RenderOptions.style` — name | JSON record | stack — and
|
|
69
|
+
`RenderOptions.seed`; see `docs/style-authoring.md`. Mermaid
|
|
70
|
+
`themeVariables` and explicit color options still win over a style's
|
|
71
|
+
palette, preserving Mermaid compatibility.)
|
|
72
|
+
|
|
73
|
+
Family-specific config is **wire-or-warn**: every accepted key either changes
|
|
74
|
+
output or produces `INEFFECTIVE_CONFIG` naming the field. Unknown keys also
|
|
75
|
+
warn, so spelling mistakes cannot disappear silently. This contract covers all
|
|
76
|
+
every registered family and both frontmatter and init directives.
|
|
77
|
+
|
|
78
|
+
Notable wired sections include:
|
|
79
|
+
|
|
80
|
+
- `flowchart`: spacing and measured wrapping;
|
|
81
|
+
- `sequence`: actor/message/note spacing, dimensions, activations, and numbering;
|
|
82
|
+
- `timeline`, `journey`, `class`, `er`, `architecture`, `xyChart`, `pie`,
|
|
83
|
+
`quadrantChart`, `mindmap`, and `gitGraph`: the fields documented in their
|
|
84
|
+
family design notes;
|
|
85
|
+
- `gantt.displayMode` (`compact` packs non-overlapping tasks in a section into
|
|
86
|
+
shared rows).
|
|
87
|
+
|
|
88
|
+
`state` has a typed `StateRuntimeConfig`. `nodeSpacing`, `rankSpacing`,
|
|
89
|
+
`padding`, `radius`, `fontSize`, `compositTitleSize` (upstream spelling),
|
|
90
|
+
`forkWidth`, `forkHeight`, `noteMargin`, and `dividerMargin` are wired to
|
|
91
|
+
State-specific geometry/paint. `defaultRenderer: 'elk'` is satisfied;
|
|
92
|
+
alternate renderer requests, invalid values, and legacy Dagre/fixed-metric
|
|
93
|
+
calibration fields emit qualified `state.*` diagnostics. Unsupported fields
|
|
94
|
+
and wrappers remain source-preserved. The executable all-family audit is
|
|
95
|
+
`src/__tests__/unknown-config-wire-or-warn.test.ts`; the independent State key
|
|
96
|
+
inventory and discriminating geometry probes are in `state-config.test.ts`.
|
|
97
|
+
|
|
98
|
+
## Security
|
|
99
|
+
|
|
100
|
+
Use strict rendering for untrusted source:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
renderMermaidSVG(source, { security: 'strict' })
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Strict mode disables external-fetch references such as remote font imports. Agents should prefer strict mode unless the caller explicitly wants externally loaded fonts.
|
|
107
|
+
|
|
108
|
+
## PNG and config
|
|
109
|
+
|
|
110
|
+
PNG renders from the same SVG pipeline, so supported Mermaid config affects PNG too:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { renderMermaidPNG } from 'agentic-mermaid/agent'
|
|
114
|
+
|
|
115
|
+
const png = renderMermaidPNG(source, {
|
|
116
|
+
fitTo: { width: 1200 },
|
|
117
|
+
background: '#fff',
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For offline/deterministic PNG generation, avoid external font imports by relying on the bundled font fallback or by rendering with strict/offline SVG options where applicable.
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Fonts in custom styles
|
|
2
|
+
|
|
3
|
+
A custom style chooses a font family; the environment that renders the
|
|
4
|
+
diagram supplies the actual face. The public `StyleSpec` deliberately keeps
|
|
5
|
+
those responsibilities separate:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"$schema": "https://agentic-mermaid.dev/schemas/style-spec.schema.json",
|
|
10
|
+
"name": "acme-report",
|
|
11
|
+
"font": "'Acme Sans', Inter, system-ui",
|
|
12
|
+
"colors": {
|
|
13
|
+
"bg": "#fffdf8",
|
|
14
|
+
"fg": "#171512",
|
|
15
|
+
"accent": "#1d4ed8"
|
|
16
|
+
},
|
|
17
|
+
"stroke": "crisp"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`font` is a CSS family name or family stack. It does not load a file, register
|
|
22
|
+
a browser `FontFace`, or tell the PNG rasterizer where to find one. This keeps
|
|
23
|
+
the same style usable across SVG and PNG while each output surface resolves
|
|
24
|
+
fonts in its own way.
|
|
25
|
+
|
|
26
|
+
For the rest of the Style model — inline specs, registration, stacking, and
|
|
27
|
+
validation — see [Authoring styles](./style-authoring.md).
|
|
28
|
+
|
|
29
|
+
## Quick recipe
|
|
30
|
+
|
|
31
|
+
Save the JSON above as `acme-report.style.json`. For an SVG that declares the
|
|
32
|
+
family without making an external font request:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
am render diagram.mmd \
|
|
36
|
+
--format svg \
|
|
37
|
+
--style acme-report.style.json \
|
|
38
|
+
--security strict \
|
|
39
|
+
--output diagram.svg
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For a PNG, point the local rasterizer at a directory containing the matching
|
|
43
|
+
font faces:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
am render diagram.mmd \
|
|
47
|
+
--format png \
|
|
48
|
+
--style acme-report.style.json \
|
|
49
|
+
--font-dirs ./fonts \
|
|
50
|
+
--output diagram.png
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The same workflow through the library is:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
57
|
+
import {
|
|
58
|
+
renderMermaidPNG,
|
|
59
|
+
renderMermaidSVG,
|
|
60
|
+
validateStyleSpec,
|
|
61
|
+
} from 'agentic-mermaid/agent'
|
|
62
|
+
|
|
63
|
+
const source = readFileSync('diagram.mmd', 'utf8')
|
|
64
|
+
const style = JSON.parse(readFileSync('acme-report.style.json', 'utf8'))
|
|
65
|
+
const problems = validateStyleSpec(style)
|
|
66
|
+
if (problems.length) throw new Error(problems.join('\n'))
|
|
67
|
+
|
|
68
|
+
const svg = renderMermaidSVG(source, {
|
|
69
|
+
style,
|
|
70
|
+
security: 'strict',
|
|
71
|
+
})
|
|
72
|
+
writeFileSync('diagram.svg', svg)
|
|
73
|
+
|
|
74
|
+
const png = renderMermaidPNG(source, {
|
|
75
|
+
style,
|
|
76
|
+
fontDirs: ['./fonts'],
|
|
77
|
+
})
|
|
78
|
+
writeFileSync('diagram.png', png)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Which font value wins
|
|
82
|
+
|
|
83
|
+
For `renderMermaidSVG`, font selection follows this precedence, from strongest
|
|
84
|
+
to weakest:
|
|
85
|
+
|
|
86
|
+
1. The render call's explicit `font` option.
|
|
87
|
+
2. Mermaid `fontFamily` configuration.
|
|
88
|
+
3. Mermaid `themeVariables.fontFamily` configuration.
|
|
89
|
+
4. The resolved Style stack's `font` field.
|
|
90
|
+
5. The default, Inter.
|
|
91
|
+
|
|
92
|
+
`renderMermaidPNG` accepts the same shared `font` option and precedence as SVG.
|
|
93
|
+
`fontDirs` makes faces available to the rasterizer but does not choose the
|
|
94
|
+
family.
|
|
95
|
+
|
|
96
|
+
Within a Style stack, later entries win. For example, `[style, 'dracula']`
|
|
97
|
+
keeps the inline/file-loaded Style's font because `dracula` is a palette-only
|
|
98
|
+
Style; a later Style that defines `font` would replace it. A file's `name`
|
|
99
|
+
field does not register it automatically — give it a canonical `look:` or
|
|
100
|
+
`palette:` name and call `registerStyle(style)` before using that identity in a
|
|
101
|
+
library stack.
|
|
102
|
+
|
|
103
|
+
`validateStyleSpec` checks that `font` is a string. It cannot prove that the
|
|
104
|
+
family is installed, present under `fontDirs`, or available to a browser.
|
|
105
|
+
|
|
106
|
+
## Resolution by output surface
|
|
107
|
+
|
|
108
|
+
| Surface | Where the face comes from | Custom-font control |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| SVG library/CLI | Browser, viewing application, or host-page CSS | `font`, `embedFontImport`, `security` |
|
|
111
|
+
| PNG library/CLI | Bundled faces, `fontDirs`, optionally OS fonts | `font`, `fontDirs`, `loadSystemFonts` / `--system-fonts` |
|
|
112
|
+
| Live editor | Faces available to the editor page and browser | Font picker; no `fontDirs` |
|
|
113
|
+
| Local MCP `render_png` | Bundled faces, `fontDirs`, optionally OS fonts | `fontDirs`, `loadSystemFonts` |
|
|
114
|
+
| Hosted MCP `render_png` | Faces bundled into the Worker | No filesystem font input; use local MCP/library/CLI for custom directories |
|
|
115
|
+
| ASCII, Unicode, JSON layout | Terminal/layout output has no font face | Style fonts do not apply |
|
|
116
|
+
|
|
117
|
+
### SVG
|
|
118
|
+
|
|
119
|
+
SVG output always declares the selected family through the root `--font` CSS
|
|
120
|
+
custom property and a `font-family` rule. The SVG renderer does not read
|
|
121
|
+
`fontDirs`.
|
|
122
|
+
|
|
123
|
+
By default, a single plain family name such as `Acme Sans` produces a Google
|
|
124
|
+
Fonts `@import`. Class and ER diagrams can also import JetBrains Mono for their
|
|
125
|
+
monospace labels. Set `embedFontImport: false`, or use `security: 'strict'`, to
|
|
126
|
+
omit all of those requests while keeping the family declaration:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const svg = renderMermaidSVG(source, {
|
|
130
|
+
style: { font: 'Acme Sans' },
|
|
131
|
+
embedFontImport: false,
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
CSS variable references and family stacks are declared as-is and do not
|
|
136
|
+
produce an import for the selected family:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
renderMermaidSVG(source, {
|
|
140
|
+
style: { font: "'Acme Sans', Inter, system-ui" },
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
renderMermaidSVG(source, {
|
|
144
|
+
font: 'var(--brand-font, Inter)',
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The class/ER JetBrains Mono import still applies unless `embedFontImport` is
|
|
149
|
+
false or strict security is enabled.
|
|
150
|
+
|
|
151
|
+
If the selected family is unavailable when the SVG is viewed, normal CSS
|
|
152
|
+
fallback applies. A family stack makes that fallback explicit.
|
|
153
|
+
|
|
154
|
+
Raw SVG declares the family through `var(--font, ...)`. Some static SVG
|
|
155
|
+
rasterizers, including resvg and librsvg, do not resolve CSS custom properties.
|
|
156
|
+
Use `renderMermaidPNG` for Agentic Mermaid's built-in workaround, or resolve
|
|
157
|
+
the font variable to a concrete `font-family` before feeding raw SVG to another
|
|
158
|
+
static rasterizer.
|
|
159
|
+
|
|
160
|
+
### Host-page and browser fonts
|
|
161
|
+
|
|
162
|
+
An inline SVG can use a face registered by its host document. A stylesheet can
|
|
163
|
+
map an installed face:
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
@font-face {
|
|
167
|
+
font-family: "Acme Sans";
|
|
168
|
+
src: local("Acme Sans Regular"), local("AcmeSans-Regular");
|
|
169
|
+
font-weight: 400;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Browser applications can also register bytes selected by the user:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const face = new FontFace('Acme Sans', await file.arrayBuffer(), {
|
|
177
|
+
weight: '400',
|
|
178
|
+
})
|
|
179
|
+
await face.load()
|
|
180
|
+
document.fonts.add(face)
|
|
181
|
+
await document.fonts.load('400 14px "Acme Sans"')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Register one `FontFace` or `@font-face` rule per required weight, or accept the
|
|
185
|
+
browser's synthesized/fallback face for weights that are missing.
|
|
186
|
+
|
|
187
|
+
Register the face in the same document that displays the SVG, and wait for it
|
|
188
|
+
before displaying or raster-capturing the result. A raw SVG export still only
|
|
189
|
+
declares the family; it does not serialize a dynamically registered `FontFace`.
|
|
190
|
+
The public live editor can use faces already available to its page/browser,
|
|
191
|
+
but it has no filesystem `fontDirs` option or arbitrary font-file input.
|
|
192
|
+
|
|
193
|
+
### PNG
|
|
194
|
+
|
|
195
|
+
`renderMermaidPNG` renders offline with `@resvg/resvg-js`. It loads:
|
|
196
|
+
|
|
197
|
+
1. The bundled Inter faces used by the text-metrics model.
|
|
198
|
+
2. DejaVu Sans as a per-glyph fallback for symbols Inter lacks.
|
|
199
|
+
3. The bundled faces referenced by built-in Styles: Caveat, EB Garamond,
|
|
200
|
+
Architects Daughter, and Share Tech Mono.
|
|
201
|
+
4. Any directories supplied through `fontDirs`.
|
|
202
|
+
5. OS-installed fonts only when `loadSystemFonts: true` is requested.
|
|
203
|
+
|
|
204
|
+
The CLI equivalents are `--font-dirs <dir[,dir...]>` and `--system-fonts`.
|
|
205
|
+
Prefer a dedicated `fontDirs` directory when reproducible output matters.
|
|
206
|
+
System-font loading makes the result depend on the machine's installed faces.
|
|
207
|
+
|
|
208
|
+
The family stored inside the font file must match the intended family token in
|
|
209
|
+
the Style's `font` value — normally the first family in its stack. Supply the
|
|
210
|
+
weights the diagram uses; Agentic Mermaid commonly emits 400, 500, 600, and
|
|
211
|
+
700. If an unbundled requested family cannot be resolved, PNG rasterization
|
|
212
|
+
uses Inter, with DejaVu available for per-glyph fallback.
|
|
213
|
+
|
|
214
|
+
## Diagnostics and layout limits
|
|
215
|
+
|
|
216
|
+
`PNG_FONT_COVERAGE` warns when no loaded face contains a character, such as a
|
|
217
|
+
CJK or emoji code point. It is a glyph-coverage check, not a family-presence
|
|
218
|
+
check. If `Acme Sans` is missing but Inter covers every Latin character, the
|
|
219
|
+
render can fall back without a coverage warning. Confirm custom-family output
|
|
220
|
+
visually or with a font-specific render fixture.
|
|
221
|
+
|
|
222
|
+
Diagram geometry uses the deterministic proportional measurement contract
|
|
223
|
+
recorded in `TEXT_MEASUREMENT_CONTRACT`; it never reads ambient system-font
|
|
224
|
+
state. Every continuous painted text advance—bundled, monospace, or custom—gets
|
|
225
|
+
SVG `textLength` with `lengthAdjust="spacingAndGlyphs"`, because loading a font
|
|
226
|
+
resource does not calibrate its metrics to the layout estimator. Independently
|
|
227
|
+
positioned multiline tspans are fitted per line, while nested formatting
|
|
228
|
+
contributes inherited size, weight, and tracking to that line's width. Explicit
|
|
229
|
+
emitter-owned `textLength` remains authoritative. Native and browser PNG consume
|
|
230
|
+
the same secured SVG, so a materially wider face cannot silently overflow its
|
|
231
|
+
measured box. The tradeoff is explicit: unusually wide or narrow glyphs may be
|
|
232
|
+
horizontally fitted rather than changing deterministic layout. Test long labels
|
|
233
|
+
across target families for visual quality, especially for extreme proportions.
|
|
234
|
+
|
|
235
|
+
## Troubleshooting
|
|
236
|
+
|
|
237
|
+
**The custom family is not visible in PNG.** Check the font's internal family
|
|
238
|
+
name, include the directory through `fontDirs`, supply the required weights,
|
|
239
|
+
and make sure Mermaid `fontFamily` configuration in the source is not
|
|
240
|
+
overriding the Style.
|
|
241
|
+
|
|
242
|
+
**The SVG requests a font from the network.** Render with
|
|
243
|
+
`security: 'strict'` or `embedFontImport: false`.
|
|
244
|
+
|
|
245
|
+
**The SVG looks different in another viewer.** The other environment did not
|
|
246
|
+
resolve the same family or selected a different fallback. Use a deliberate
|
|
247
|
+
family stack and make the required face available to that viewer.
|
|
248
|
+
|
|
249
|
+
**PNG warns about CJK or emoji.** Add a face covering those code points through
|
|
250
|
+
`fontDirs`, or opt into `loadSystemFonts` when machine-dependent output is
|
|
251
|
+
acceptable.
|
|
252
|
+
|
|
253
|
+
## Related documentation
|
|
254
|
+
|
|
255
|
+
- [Authoring styles](./style-authoring.md) — Style fields, stacks, registration,
|
|
256
|
+
validation, and the quality rubric.
|
|
257
|
+
- [Custom style cookbook](./custom-style-cookbook.md) — complete JSON examples
|
|
258
|
+
and screenshots.
|
|
259
|
+
- [API reference](./api.md) — `RenderOptions` and `PngOptions`.
|
|
260
|
+
- [Security posture](../SECURITY.md) — strict SVG and external-reference
|
|
261
|
+
guarantees.
|
|
262
|
+
- [PNG determinism](./quality.md#png-determinism) — bundled fonts and the
|
|
263
|
+
reproducibility tradeoff of system fonts.
|
|
264
|
+
- [Bundled font inventory](../assets/fonts/FONT-LICENSES.md) — the faces shipped
|
|
265
|
+
with the package.
|