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.
Files changed (52) hide show
  1. package/AGENT_NATIVE.md +671 -0
  2. package/CHANGELOG.md +19 -0
  3. package/Instructions_for_agents.md +89 -0
  4. package/README.md +8 -8
  5. package/assets/hero.png +0 -0
  6. package/dist/agent-core.d.ts +2 -2
  7. package/dist/agent-core.js +2 -2
  8. package/dist/agent.d.ts +2 -2
  9. package/dist/agent.js +4 -4
  10. package/dist/agentic-mermaid-mcp.js +5 -5
  11. package/dist/am.js +8 -15
  12. package/dist/{chunk-QKDLMOJJ.js → chunk-2P5FKUQC.js} +7 -9
  13. package/dist/{chunk-WN3YDOBG.js → chunk-2S3VSS6I.js} +4 -4
  14. package/dist/{chunk-CDPKJ2BQ.js → chunk-2SILJDW2.js} +2 -2
  15. package/dist/{chunk-ZYMMKQ3I.js → chunk-BJYMBT4I.js} +299 -973
  16. package/dist/{chunk-WI35L7IR.js → chunk-FDM43XU7.js} +2 -2
  17. package/dist/{chunk-4QECACIA.js → chunk-MYPDVZCO.js} +19 -19
  18. package/dist/{index-D54dqSdC.d.ts → index-DWPa2MJs.d.ts} +7 -4
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/mcp-cli-5J47WEY3.js +17 -0
  22. package/docs/README.md +34 -34
  23. package/docs/agent-api-cookbook.md +317 -0
  24. package/docs/agent-mutation-policy.md +104 -0
  25. package/docs/agent-workflow-examples.md +46 -0
  26. package/docs/api.md +418 -0
  27. package/docs/ascii.md +113 -0
  28. package/docs/code-mode-impact.md +214 -0
  29. package/docs/comparison.md +106 -0
  30. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  31. package/docs/config.md +121 -0
  32. package/docs/custom-fonts.md +265 -0
  33. package/docs/custom-style-cookbook.md +275 -0
  34. package/docs/diagram-families.md +218 -0
  35. package/docs/features.md +154 -0
  36. package/docs/fork-differences.md +133 -0
  37. package/docs/getting-started.md +182 -0
  38. package/docs/issue-derived-test-cases.md +163 -0
  39. package/docs/mcp-code-mode-rationale.md +108 -0
  40. package/docs/mcp-http-transport.md +230 -0
  41. package/docs/mermaid-layout-complaints.md +851 -0
  42. package/docs/mutation-testing.md +285 -0
  43. package/docs/quality.md +224 -0
  44. package/docs/react.md +97 -0
  45. package/docs/style-authoring.md +336 -0
  46. package/docs/svg-semantic-contract.md +49 -0
  47. package/docs/testing-strategy.md +443 -0
  48. package/docs/theming.md +149 -0
  49. package/package.json +21 -6
  50. package/server.json +2 -2
  51. package/skills/agentic-mermaid-live-editor/SKILL.md +1 -1
  52. package/dist/mcp-cli-SBQSTJFC.js +0 -17
@@ -0,0 +1,336 @@
1
+ # Authoring styles
2
+
3
+ A **style** is a partial description of how diagrams look. Every field is
4
+ optional: a style that only sets colors is a palette; a style that sets
5
+ stroke character, fills, typography, and a palette is a full look. Styles
6
+ apply uniformly to every built-in family. An extension gets the same contract
7
+ when its `FamilyDescriptor` advertises native Scene execution (`layout` +
8
+ `lowerScene`, exposed as the `scene` capability); discovery reports extensions
9
+ without that capability instead of promising style support they cannot render.
10
+
11
+ Styles compose by **stacking**: `RenderOptions.style` accepts a name, an
12
+ inline spec, or an array of either, merged left → right (later fields win,
13
+ colors merge per channel):
14
+
15
+ ```ts
16
+ import { renderMermaidSVG, registerStyle } from 'agentic-mermaid'
17
+
18
+ // a built-in look
19
+ renderMermaidSVG(source, { style: 'hand-drawn' })
20
+
21
+ // hand-drawn geometry × the dracula Palette
22
+ renderMermaidSVG(source, { style: ['hand-drawn', 'dracula'] })
23
+
24
+ // an inline custom style — no registration needed
25
+ renderMermaidSVG(source, {
26
+ style: {
27
+ colors: { bg: '#fffdf7', fg: '#1c1917', accent: '#e11d48' },
28
+ font: 'Caveat',
29
+ stroke: 'jittered',
30
+ roughness: 0.8,
31
+ fill: 'hachure',
32
+ },
33
+ })
34
+
35
+ // register for reuse by canonical kind:name identity
36
+ registerStyle({ name: 'look:acme-brand', /* … */ })
37
+ renderMermaidSVG(source, { style: ['look:acme-brand', 'palette:nord'] })
38
+ ```
39
+
40
+ You never pick a backend. The engine infers the machinery from what the
41
+ style asks for: `stroke: 'freehand'` or `fill: 'wash'` engages the hybrid
42
+ backend (pressure ribbons, watercolor); `stroke: 'jittered'`, hachure fills,
43
+ sketch parameters, or a page backdrop engage rough.js; anything else renders
44
+ on the crisp default backend with your palette and font.
45
+
46
+ ## Progressive authoring: Levels 0–4
47
+
48
+ All five levels use the same `style` option and the same left-to-right merge
49
+ law. Moving up never requires a new package or family adapter.
50
+
51
+ ```ts
52
+ import { renderMermaidSVG } from 'agentic-mermaid'
53
+ import { verifyMermaid } from 'agentic-mermaid/agent'
54
+
55
+ // Level 0 — preset
56
+ renderMermaidSVG(source, { style: 'publication-figure' })
57
+
58
+ // Level 1 — compose a Look and Palette
59
+ renderMermaidSVG(source, { style: ['hand-drawn', 'nord'] })
60
+
61
+ // Level 2 — reusable ordinary StyleSpec JSON
62
+ const palette = { colors: { bg: '#fffdf7', fg: '#211a1a', accent: '#a33b20' } }
63
+ renderMermaidSVG(source, { style: palette })
64
+
65
+ // Level 3 — semantic SceneRole defaults, shared by layout and rendering
66
+ const roleStyle = {
67
+ roles: {
68
+ node: { fontSize: 16, fontWeight: 700, paddingX: 28, paddingY: 14,
69
+ cornerRadius: 8, lineWidth: 2, fillColor: '#fff7ed' },
70
+ edge: { fontSize: 12, fontWeight: 600, lineWidth: 2, bendRadius: 10 },
71
+ group: { fontSize: 13, fontWeight: 700, paddingX: 22, paddingY: 20 },
72
+ },
73
+ }
74
+
75
+ // Level 4 — ordered equality bindings plus inspect-only constraints
76
+ const policyStyle = {
77
+ ...roleStyle,
78
+ semanticSlots: {
79
+ selected: { fillColor: '#fecdd3', borderColor: '#881337', lineWidth: 4, cue: 'outline' },
80
+ },
81
+ bindings: [
82
+ { channel: 'category', value: 'Pro', slot: 'selected', role: 'pie-slice' },
83
+ ],
84
+ constraints: [
85
+ { kind: 'contrast', action: 'error', minimum: 4.5 },
86
+ { kind: 'accent-area', action: 'warn', maxFraction: 0.25 },
87
+ ],
88
+ }
89
+ const verification = verifyMermaid(source, { renderOptions: { style: policyStyle } })
90
+ // CLI equivalent for a saved record: am verify diagram.mmd --style brand.style.json
91
+ ```
92
+
93
+ Role/slot values are defaults. Concrete Mermaid theme/config and element paint
94
+ remain authoritative. Bindings never target `emphasis`: family syntax such as
95
+ Pie `highlightSlice` owns its target and quantitative geometry. Constraints
96
+ inspect the admitted final Scene and return measurable, unmeasurable, or
97
+ not-applicable evidence; they never repaint or relayout. Transparent output has
98
+ no known host backdrop, so contrast reports unmeasurable rather than inventing
99
+ a ratio.
100
+
101
+ ### Export and edit a built-in
102
+
103
+ Built-ins dogfood this same record format. Export one with `getStyle`, validate
104
+ or save the returned JSON, then pass it inline; selecting the name and importing
105
+ its export are behaviorally equivalent.
106
+
107
+ ```ts
108
+ import { getStyle, validateStyleSpec } from 'agentic-mermaid'
109
+ const exported = getStyle('publication-figure')!
110
+ if (validateStyleSpec(exported).length) throw new Error('invalid exported style')
111
+ renderMermaidSVG(source, { style: exported })
112
+ ```
113
+
114
+ ### Terminal projection
115
+
116
+ ASCII/Unicode receives the same resolved Style and compiled role face. Family
117
+ terminal adapters consume supported semantic projections—the Pie witness turns
118
+ an applicable category cue into a no-color marker while keeping
119
+ `highlightSlice` authoritative. Terminal output preserves hierarchy, emphasis,
120
+ symbols, line patterns, and available color; typography, radius, shared shadow,
121
+ and detailed graphical paint produce explicit projection diagnostics rather
122
+ than pretending pixel parity.
123
+
124
+ ### Why there is no BrandPack yet
125
+
126
+ Level 4 is the active ceiling. No external consumer has shown that ordinary
127
+ version-controlled StyleSpec files are insufficient for repeated distribution,
128
+ exact selection, or installed-resource integrity, so the evidence-gated B4
129
+ BrandPack registry is deliberately **not promoted**. Render-time semver ranges,
130
+ dependency solving, ambient resource loading, and a second inheritance system
131
+ remain absent.
132
+
133
+ The generated [Section B capability report](./project/section-b-capability-report.md)
134
+ accounts for every role leaf, private-face projection, role fallback, admitted
135
+ family role/channel, and built-in export.
136
+ The [all-family evidence sheet](./design/families/section-b-brand-evidence.png)
137
+ shows one deliberately distinctive sentinel plus three holdout styles; its receipt
138
+ and honest hard-error baseline live in `eval/section-b-brand-evidence/`.
139
+
140
+ Every field is optional in a reusable fragment, but the resolved final stack
141
+ must make each renderer parameter applicable. `hachureAngle`, `hachureGap`,
142
+ and `fillWeight` require `fill: 'hachure'`; `washOpacity` and `washEdge`
143
+ require `fill: 'wash'`. Crisp/default rendering does not reinterpret
144
+ `fill: 'none'` or `fill: 'solid'`, so those values must be combined with a
145
+ field that selects the rough or hybrid backend. Rendering fails with a stable
146
+ `Invalid style spec` dependency diagnostic when a final stack would otherwise
147
+ change request identity without realizing that customization.
148
+
149
+ Canonical registered identities say what is being named: `look:hand-drawn`
150
+ and `palette:dracula`. Discovery exposes exactly one `kind` (`look` or
151
+ `palette`), an explicit `isDefault`, and a stable `inputName`. Stable short
152
+ inputs such as `hand-drawn` and `dracula` are descriptor-owned names, not
153
+ aliases. Light Tufte is intentionally one full Look with the canonical-only
154
+ input `look:tufte`; the ambiguous bare `tufte` input and duplicate
155
+ `palette:tufte` are not registered. The distinct `tufte-dark` palette is
156
+ unchanged. Use the stable `crisp` input for the default Look; `default` is not
157
+ accepted as a Style name.
158
+
159
+ ## The contract you get for free
160
+
161
+ - **Any Look × any Palette.** Your `colors` are *defaults*: anything the user
162
+ sets via `RenderOptions` colors or Mermaid `themeVariables` wins. The full
163
+ precedence is one line: `defaults < style stack (left→right) <
164
+ themeVariables < explicit color options`.
165
+ - **Determinism.** All stochastic marks are seeded
166
+ `hash(options.seed, stableNodeId, substream)`. Source, resolved options,
167
+ frozen registry/capability/resource snapshots, runtime contract and seed fix
168
+ the bytes. `seed` re-rolls wobble without moving layout. Opt-in caller or
169
+ system fonts make PNG provenance `host-dependent`; the logical request
170
+ receipt is not a final-byte digest.
171
+ - **Semantics survive the graphical pipeline.** Markers, hit geometry and
172
+ strict-security guarantees pass through every backend. SVG retains
173
+ inspectable `class`/`data-*` identity and ARIA; PNG rasterizes the same
174
+ secured geometry and appearance but cannot embed DOM attributes in pixels.
175
+ Text is drawn last, never perturbed, with a page-colored halo (labels stay
176
+ legible on any fill).
177
+ - **Crisp is the canonical default.** `style: 'crisp'` (or unset) selects the
178
+ precise default backend.
179
+
180
+ ## Field reference
181
+
182
+ <!-- BEGIN GENERATED STYLE SPEC FIELDS -->
183
+ | Group | Field | Type / values | Meaning |
184
+ |---|---|---|---|
185
+ | metadata | `formatVersion` | `1` | Persisted wire-format version. Optional on input and normalized to 1 on output. |
186
+ | metadata | `$schema` | `string` | Optional JSON Schema pointer for file-backed styles; ignored while rendering. |
187
+ | metadata | `name` | `string` | Canonical look:name or palette:name identity; required only when registering a style. |
188
+ | metadata | `blurb` | `string` | Short human-readable description used by discovery surfaces. |
189
+ | palette | `colors` | object: `bg`, `fg`, `line`, `accent`, `muted`, `surface`, `border` | Partial palette of safe, non-fetching CSS color tokens. |
190
+ | typography | `font` | `string` | Safe, non-fetching CSS font family or stack; the rendering environment supplies the font face. |
191
+ | roles | `roles` | object: partial records keyed by exact-style `SceneRole` | Partial semantic SceneRole defaults. Family-authored styling remains authoritative. |
192
+ | policy | `semanticSlots` | `Record<string, RoleStyleSpec>` | Named brand-neutral role-style slots selected by semantic bindings. |
193
+ | policy | `bindings` | `SemanticBinding[]` | Ordered equality bindings from authored/domain meaning to semantic slots. |
194
+ | policy | `constraints` | `BrandConstraint[]` | Closed inspect-only brand constraints with warn or error actions. |
195
+ | stroke | `stroke` | `crisp` \| `jittered` \| `freehand` | Stroke treatment; crisp is the default renderer. |
196
+ | stroke | `roughness` | `number`; minimum 0; maximum 10 | Rough.js stroke irregularity. |
197
+ | stroke | `bowing` | `number`; minimum 0; maximum 10 | Rough.js line bowing. |
198
+ | stroke | `passes` | `integer`; minimum 1; maximum 8 | Number of sketch strokes; 1 is single-pass and 2 is the usual double stroke. |
199
+ | stroke | `strokeWidth` | `number`; greater than 0; maximum 20 | Base stroke width in SVG user units. |
200
+ | fill | `fill` | `none` \| `hachure` \| `solid` \| `wash` | Fill policy for rough/hybrid rendering; none and solid require a final stack that activates one of those backends. |
201
+ | fill | `hachureAngle` | `number`; minimum -360; maximum 360 | Hachure line angle in degrees; requires fill hachure in the final stack. |
202
+ | fill | `hachureGap` | `number`; greater than 0; maximum 100 | Gap between hachure lines; requires fill hachure in the final stack. |
203
+ | fill | `fillWeight` | `number`; greater than 0; maximum 20 | Hachure line weight; requires fill hachure in the final stack. |
204
+ | fill | `washOpacity` | `number`; minimum 0; maximum 1 | Watercolor glaze opacity; requires fill wash in the final stack. |
205
+ | fill | `washEdge` | `number`; minimum 0; maximum 1 | Watercolor edge-darkening opacity; requires fill wash in the final stack. |
206
+ | page | `backdrop` | `plain` \| `paper-ruled` \| `grid` | Flat page furniture drawn behind the diagram. |
207
+ | advisory | `intent` | `premium` \| `draft` \| `lofi` | Advisory intent metadata for pickers and quality tooling. |
208
+ | advisory | `mono` | `boolean` | Advisory monochrome contract: express tone through shading and weight. |
209
+ <!-- END GENERATED STYLE SPEC FIELDS -->
210
+
211
+ The field table is generated from the same descriptors that define the
212
+ TypeScript type, runtime validator, and published JSON Schema. SVG declares a
213
+ style's `font`; PNG resolves verified bundled faces plus caller-supplied
214
+ `fontDirs` and then uses its documented fallback. See
215
+ [Fonts in custom styles](./custom-fonts.md).
216
+
217
+ JSON style records are first-class: `validateStyleSpec(json)` returns a list
218
+ of problems (`[]` means the value is structurally admissible as a fragment;
219
+ final-stack applicability is checked during resolution). Renderer-consumed
220
+ fields are declarative-only and cannot carry markup, scripts, or fetching URLs,
221
+ so validated records are safe to load from files and prompts and compatible with
222
+ `security: 'strict'`.
223
+ `$schema` is ignored metadata and may, intentionally, be a schema URL.
224
+
225
+ For file-backed styles, use the schema at
226
+ [`docs/schemas/style-spec.schema.json`](./schemas/style-spec.schema.json), also
227
+ published as `agentic-mermaid/style-spec.schema.json`. The
228
+ [`custom style cookbook`](./custom-style-cookbook.md) has complete JSON files,
229
+ screenshots, and CLI commands. For font resolution across SVG, PNG, browser,
230
+ and MCP surfaces, see [Fonts in custom styles](./custom-fonts.md).
231
+
232
+ JSON Schema provides editor discovery plus all structurally expressible type,
233
+ enum, and numeric constraints. Runtime `validateStyleSpec` is the normative
234
+ admission boundary and additionally enforces the recursive safe-color grammar;
235
+ the generated schema marks those leaves with
236
+ `x-agentic-mermaid-runtime-validator: safeCssColor` instead of pretending a
237
+ portable regular expression can validate nested CSS color functions.
238
+
239
+ Only a genuinely **new capability** (a new fill algorithm, compositor, or
240
+ layout-aware dialect) justifies code. Trusted hosts can implement and register
241
+ a namespaced `StyleBackend`; selection is an in-process `HostBackendPolicy`,
242
+ not serializable Style data or a CLI/MCP/editor field. Keep the determinism rule
243
+ (seed in, bytes out). Backends never dispatch on diagram family — they see
244
+ semantic scene marks and paint channels. A registered backend also declares
245
+ feature/operation-level `PrimitiveCapabilityClaim`s for every core Scene
246
+ primitive; registration rejects empty, duplicate, or cross-target claims so an
247
+ extension cannot imply support it has not described. Registration then executes
248
+ a frozen document fixture plus one exact witness for each declared first-party
249
+ core primitive/feature/operation claim. It rejects a backend that varies
250
+ identical `drawNode`/`render` calls, emits unsafe or malformed SVG, drops the
251
+ document sentinels, or fails any claim-keyed witness. Namespaced extension claims
252
+ with no core witness remain explicitly `unverified-extension`.
253
+ `runBackendConformance` exposes the immutable report and
254
+ `knownBackendDescriptors()` retains it for discovery.
255
+
256
+ Host backends must opt into both the versioned core lifecycle and Scene wire
257
+ contract explicitly; no built-in compatibility default is borrowed by
258
+ `registerBackend`:
259
+
260
+ ```ts
261
+ registerBackend(backend, {
262
+ version: '1.0.0',
263
+ compatibility: { core: '^0.2.0', scene: '^2.0.0' },
264
+ provenance: { owner: 'acme-diagrams', source: 'application-startup' },
265
+ })
266
+ ```
267
+
268
+ The host validates this identity and both ranges before executing any
269
+ conformance witness. Every external `FamilyDescriptor` declares a compatible
270
+ `core` range; one that provides `lowerScene` must additionally include
271
+ `scene: '^2.0.0'` in `identity.compatibility`. A family that remains
272
+ source-only or uses the direct-SVG extension fallback does not consume Scene.
273
+
274
+ Every external family supplies one bounded canonical `example`.
275
+ `registerFamily` stages the immutable descriptor and runs every declared
276
+ native capability twice through the canonical parse/serialize, layout, strict
277
+ SVG, portable PNG pre-raster, terminal, Scene and verification paths. It rolls
278
+ the candidate back if a hook throws, changes output between runs, mutates the
279
+ registry reentrantly, emits an invalid raster envelope, or fails to witness a
280
+ positive Scene role/primitive declaration. `getFamilyConformanceReport`
281
+ exposes the complete immutable evidence. The bounded `am capabilities --json`
282
+ discovery projection keeps every declared state, status, and diagnostic but
283
+ omits stable witness identifiers; a declaration without a passed witness is
284
+ never projected as `native`.
285
+
286
+ Each admission report is deliberately bounded. Backend admission directly proves SVG behavior
287
+ for the named document and exact core claim witnesses; it does not certify a
288
+ namespaced extension claim or family-scale visual, bounds, hit-testing, and
289
+ performance behavior. Extension authors still need those tests for what they
290
+ advertise. PNG is not a second backend conformance target: it inherits the
291
+ admitted secured SVG through canonical rasterizers, whose own parity and
292
+ color-profile suites are separate.
293
+
294
+ ## The quality rubric — what makes a style GOOD
295
+
296
+ The mechanics above are the easy part. Across the built-in catalog, the
297
+ difference between a cheap style and a premium one was never the wiring —
298
+ always the judgement:
299
+
300
+ 1. **Source it.** Start from real references (photos, the actual product),
301
+ not memory. Name the reference in your blurb. Naive-from-memory looks cheap.
302
+ 2. **Premium defaults.** No pure `#000`/`#fff` — warm or desaturated neutrals.
303
+ A real typeface, not a bare fallback. Refined line weights. ONE accent per
304
+ figure unless the aesthetic is intrinsically polychrome.
305
+ 3. **Monochrome discipline** (`mono: true`): tone comes from hatch density and
306
+ line weight, never from extra hues. And don't shade a region people write
307
+ inside — hand-drawn boxes stay open (`fill: 'none'`).
308
+ 4. **Legibility gates.** Text must clear WCAG 4.5:1 against the page the halo
309
+ reveals; non-text strokes 3:1. Hairline + serif styles collapse at
310
+ thumbnail size — declare `intent` honestly and test small.
311
+ 5. **Diagram-fit, not illustration-fit.** Outline + flat fill beats texture +
312
+ depth; edges are first-class; the style must read at a glance from stroke +
313
+ palette alone.
314
+ 6. **Fidelity, not caricature.** Capture the 2–3 signatures that make the
315
+ reference recognisable and stop. Judge the result against its declared
316
+ `intent` — a lo-fi mock that looks polished has failed, exactly like a
317
+ premium style that looks cheap.
318
+
319
+ Worked example (the clearest lesson from the prototype): "Making Software"
320
+ *before* — pure black on white, generic serif, blue everywhere — read as
321
+ cheap. *After* — warm neutrals (`#fafaf9`/`#0c0a09`), Fraunces, hairlines,
322
+ rounded corners, one blue accent — read as premium. Same mechanics, different
323
+ judgement.
324
+
325
+ ## Verify before you ship
326
+
327
+ ```bash
328
+ bun run style:audit # Style + Palette contract across families
329
+ bun test src/__tests__/styled-output.test.ts --timeout 30000 # determinism + goldens + composition
330
+ bun test src/__tests__/scene-fidelity.test.ts # semantic/backend agreement
331
+ ```
332
+
333
+ Then render your style across every registered diagram type and *look at it small*
334
+ — the poster/contact-sheet harness in `scripts/sketch-prototype/` is the
335
+ visual-review surface. A style ships when it is distinctive at a glance,
336
+ legible at cell size, and honest to its intent.
@@ -0,0 +1,49 @@
1
+ # SVG semantic identity, references, and accessibility
2
+
3
+ Agentic Mermaid exposes one contract across every built-in family. Consumers should inspect these attributes instead of inferring meaning from geometry or renderer-specific CSS selectors.
4
+
5
+ ## Identity
6
+
7
+ Every source-semantic Scene mark emits (nodes, relations, groups, participants,
8
+ class/ER members, chart data marks, sections/tasks/periods/events, and titles):
9
+
10
+ - `data-id`: deterministic and unique within one SVG;
11
+ - `data-role`: a closed `SceneRole` value;
12
+ - `data-from` / `data-to`: normalized source endpoints for relations;
13
+ - authored Mermaid class tokens on `class` where that family supports classes.
14
+
15
+ Source-authored IDs win. Anonymous marks receive semantic IDs derived from labels, endpoints, series/category identity, and a deterministic occurrence suffix. The public Scene model carries the same information as `SvgSemanticIdentity`; the DOM attributes and typed value are generated together. Layout furniture such as grids, ticks, halos, and duplicate visual labels retains typed Scene identity but does not emit a second DOM identity.
16
+
17
+ `data-id` is source-facing identity and is intentionally **not** changed by `RenderOptions.idPrefix`.
18
+
19
+ ## Multi-diagram references
20
+
21
+ `idPrefix` namespaces local SVG `id` declarations and every supported local reference:
22
+
23
+ - `url(#…)` in markers, filters, clip paths, masks, gradients, and paints;
24
+ - `href="#…"` and `xlink:href="#…"`;
25
+ - every token in `aria-labelledby` and `aria-describedby`.
26
+
27
+ References are rewritten only when their target is declared in the same SVG. This prevents accidental rewriting of label text. Two instances rendered with distinct prefixes have no colliding declarations, while their semantic `data-id` values remain comparable. Prefixes accept ASCII letters, digits, `_`, `-`, `.`, and `:`; unsafe attribute/URL characters fail fast.
28
+
29
+ ## Relation accessibility
30
+
31
+ Typed edges, messages, and relationships emit:
32
+
33
+ ```xml
34
+ <… data-role="edge" data-from="API" data-to="DB"
35
+ role="graphics-symbol" aria-roledescription="relation"
36
+ aria-label="API to DB: reads" … />
37
+ ```
38
+
39
+ The Scene node carries `SvgSemanticAccessibility` and `SvgRelationSemantics` with the same endpoint and label values. Decorative label/halo marks do not repeat the relation ARIA node.
40
+
41
+ ## Palette contract
42
+
43
+ For every concrete built-in palette, rendering deterministically raises insufficient colors toward the palette foreground:
44
+
45
+ - normal text roles: WCAG 2.x contrast **≥ 4.5:1**;
46
+ - faint informational text: **≥ 3:1**;
47
+ - relation lines and markers: **≥ 3:1** against the page background.
48
+
49
+ Colors that already pass are byte-preserved. Runtime CSS variables cannot be certified without their eventual background and therefore pass through unchanged.