astro-archify 0.3.4 → 0.3.5
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/README.md +70 -4
- package/package.json +6 -3
- package/vendor/archify/NOTICE.md +8 -5
- package/vendor/archify/assets/template.html +1 -1
- package/vendor/archify/renderers/architecture/render-architecture.mjs +1 -12
- package/vendor/archify/renderers/dataflow/render-dataflow.mjs +16 -15
- package/vendor/archify/renderers/lifecycle/render-lifecycle.mjs +1 -10
- package/vendor/archify/renderers/sequence/render-sequence.mjs +1 -16
- package/vendor/archify/renderers/shared/cli.mjs +1 -3
- package/vendor/archify/renderers/shared/diagnostics.mjs +17 -6
- package/vendor/archify/renderers/shared/generated-validators.mjs +2 -2
- package/vendor/archify/renderers/shared/geometry.mjs +129 -40
- package/vendor/archify/renderers/workflow/render-workflow.mjs +21 -735
- package/vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# astro-archify
|
|
2
2
|
|
|
3
|
-
An Astro integration for rendering [Archify](https://github.com/tt-a1i/archify) system diagrams — architecture, workflow, sequence, data flow, and lifecycle — from JSON IR code blocks in your markdown/MDX content.
|
|
3
|
+
An Astro integration for rendering [Archify](https://github.com/tt-a1i/archify) system diagrams — architecture, workflow, sequence, data flow, and lifecycle — from JSON IR code blocks in your markdown/MDX content. Works with standalone Astro projects and documentation frameworks like [Starlight](https://starlight.astro.build/).
|
|
4
|
+
|
|
5
|
+
## Live demos
|
|
6
|
+
|
|
7
|
+
| Demo | URL | Description |
|
|
8
|
+
|------|-----|-------------|
|
|
9
|
+
| **Starlight** | [astro-archify-starlight-demo.netlify.app](https://astro-archify-starlight-demo.netlify.app/) | Documentation site with Starlight |
|
|
10
|
+
| **Standalone Astro** | [astro-archify-astro-demo.netlify.app](https://astro-archify-astro-demo.netlify.app/) | Pure Astro project template |
|
|
11
|
+
|
|
12
|
+
See also [`starlight-demo/`](./starlight-demo/) and [`astro-demo/`](./astro-demo/) in this repo.
|
|
13
|
+
|
|
14
|
+
Listed in the [Astro integrations directory](https://astro.build/integrations/?search=archify).
|
|
4
15
|
|
|
5
16
|
Archify turns a typed JSON intermediate representation (IR) into a fully self-contained, already-interactive HTML artifact — inline SVG plus a small pan/zoom/focus viewer, with every script and its ~4800 lines of CSS inlined (the one exception is a Google Fonts `<link>`, which degrades gracefully if it can't load). This integration renders that artifact **at build time** using Archify's own renderer — bundled into this package, see [Attribution](#attribution) — and embeds it as a sandboxed `<iframe>`, so you get Archify's real viewer, not a re-implementation of it.
|
|
6
17
|
|
|
@@ -115,6 +126,54 @@ archify({
|
|
|
115
126
|
})
|
|
116
127
|
```
|
|
117
128
|
|
|
129
|
+
There is no `width` option. The iframe is always `width: 100%` and fills whatever container your layout gives it. Use `height`/`minHeight`/`maxHeight` to control vertical sizing; control horizontal sizing in your site CSS (see [Layout and width](#layout-and-width) below).
|
|
130
|
+
|
|
131
|
+
## Layout and width
|
|
132
|
+
|
|
133
|
+
Diagram width is owned by your page layout, not by `archify()`. Each fence becomes a wrapper (`.archify-diagram` by default, or your custom `className`) containing a full-width iframe — so whatever box that wrapper sits in is the diagram's width.
|
|
134
|
+
|
|
135
|
+
In a plain Astro project, that usually means your layout's content column — e.g. a `max-width` on `<main>` in your layout component.
|
|
136
|
+
|
|
137
|
+
In [Starlight](https://starlight.astro.build/), the prose column is controlled by `--sl-content-width` (default ~45rem). Three common approaches:
|
|
138
|
+
|
|
139
|
+
**Widen all docs content** — add a custom CSS file via Starlight's `customCss` option:
|
|
140
|
+
|
|
141
|
+
```css
|
|
142
|
+
/* src/styles/custom.css */
|
|
143
|
+
:root {
|
|
144
|
+
--sl-content-width: 60rem;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Widen only diagrams** — leave prose at the default and target the embed wrapper:
|
|
149
|
+
|
|
150
|
+
```css
|
|
151
|
+
/* src/styles/custom.css */
|
|
152
|
+
.archify-diagram {
|
|
153
|
+
width: calc(100% + 8rem);
|
|
154
|
+
max-width: 80rem;
|
|
155
|
+
margin-inline: -4rem;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Tune the values to taste. This is useful when body text should stay narrow but architecture or workflow diagrams need more room.
|
|
160
|
+
|
|
161
|
+
**Per-page width** — in MDX, wrap a fence in a styled container:
|
|
162
|
+
|
|
163
|
+
````mdx
|
|
164
|
+
<div class="diagram-wide">
|
|
165
|
+
|
|
166
|
+
```archify
|
|
167
|
+
{ ... }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
</div>
|
|
171
|
+
````
|
|
172
|
+
|
|
173
|
+
Then style `.diagram-wide` in your custom CSS.
|
|
174
|
+
|
|
175
|
+
If you push `--sl-content-width` well past ~60rem, Starlight's table-of-contents layout can overflow at some viewport sizes — see [Starlight issue #3513](https://github.com/withastro/starlight/issues/3513) for workarounds.
|
|
176
|
+
|
|
118
177
|
## Astro Compatibility
|
|
119
178
|
|
|
120
179
|
`astro-archify` follows the same markdown-engine detection as [astro-mermaid](https://github.com/joesaby/astro-mermaid) to work across Astro 4 through 7:
|
|
@@ -142,7 +201,7 @@ Static builds (`output: 'static'`, the default) are fully supported. Under SSR (
|
|
|
142
201
|
|
|
143
202
|
## Attribution
|
|
144
203
|
|
|
145
|
-
Archify's own renderer and viewer are vendored into this package at [`vendor/archify/`](./vendor/archify) — copied from [tt-a1i/archify](https://github.com/tt-a1i/archify) (MIT licensed) at commit `
|
|
204
|
+
Archify's own renderer and viewer are vendored into this package at [`vendor/archify/`](./vendor/archify) — copied from [tt-a1i/archify](https://github.com/tt-a1i/archify) (MIT licensed) at commit `39a2113`, and used unmodified. See [`vendor/archify/NOTICE.md`](./vendor/archify/NOTICE.md) for exactly what was copied, why, and how to update it.
|
|
146
205
|
|
|
147
206
|
To be clear about the boundary: **everything under `vendor/archify/` is Archify's own code**, doing Archify's own layout, rendering, and the entire interactive viewer. Everything else in this repository — the remark/Sätteri plugin glue that finds `archify` code fences, spawning the renderer as a subprocess, content-addressed caching, serving artifacts from their own URLs, the iframe embedding and its auto-resize bridge, the Astro markdown-engine compatibility shim, the tests, and the demo — is original to `astro-archify` (the markdown-engine detection follows the same pattern used in [astro-mermaid](https://github.com/joesaby/astro-mermaid), also by this author).
|
|
148
207
|
|
|
@@ -150,9 +209,16 @@ To be clear about the boundary: **everything under `vendor/archify/` is Archify'
|
|
|
150
209
|
|
|
151
210
|
Archify inlines its entire viewer stylesheet (~4800 lines) into every artifact — the only external stylesheet is a Google Fonts `<link>`, which degrades gracefully if it can't load. Combined with the iframe embedding, this means a diagram always renders pixel-identical to opening the artifact standalone: your site's CSS can never leak into it, and its CSS can never leak into your site.
|
|
152
211
|
|
|
153
|
-
|
|
212
|
+
You can still style the **wrapper** around the iframe from your site CSS — margins, borders, and width (see [Layout and width](#layout-and-width)). The `className` option is there if you need a project-specific hook.
|
|
213
|
+
|
|
214
|
+
## Demos
|
|
215
|
+
|
|
216
|
+
| Demo | URL | Source |
|
|
217
|
+
|------|-----|--------|
|
|
218
|
+
| Starlight | [astro-archify-starlight-demo.netlify.app](https://astro-archify-starlight-demo.netlify.app/) | [`starlight-demo/`](./starlight-demo/) |
|
|
219
|
+
| Standalone Astro | [astro-archify-astro-demo.netlify.app](https://astro-archify-astro-demo.netlify.app/) | [`astro-demo/`](./astro-demo/) |
|
|
154
220
|
|
|
155
|
-
|
|
221
|
+
For Starlight, list `archify()` before `starlight()` in `astro.config.mjs` (see [Integration Order](#integration-order-important)).
|
|
156
222
|
|
|
157
223
|
## Supported Diagram Types
|
|
158
224
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-archify",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "An Astro integration that renders Archify system diagrams (architecture, workflow, sequence, dataflow, lifecycle) from JSON IR code blocks at build time, with Archify's renderer bundled in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./astro-archify-integration.js",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"keywords": [
|
|
21
21
|
"astro",
|
|
22
22
|
"astro-integration",
|
|
23
|
+
"withastro",
|
|
24
|
+
"utility",
|
|
23
25
|
"archify",
|
|
24
26
|
"diagrams",
|
|
25
27
|
"markdown",
|
|
@@ -51,13 +53,14 @@
|
|
|
51
53
|
"test": "vitest",
|
|
52
54
|
"test:ui": "vitest --ui",
|
|
53
55
|
"test:coverage": "vitest --coverage",
|
|
54
|
-
"update:vendor": "node scripts/update-vendor.mjs"
|
|
56
|
+
"update:vendor": "node scripts/update-vendor.mjs",
|
|
57
|
+
"vendor:verify": "node scripts/vendor-verify.mjs"
|
|
55
58
|
},
|
|
56
59
|
"repository": {
|
|
57
60
|
"type": "git",
|
|
58
61
|
"url": "git+https://github.com/joesaby/astro-archify.git"
|
|
59
62
|
},
|
|
60
|
-
"homepage": "https://
|
|
63
|
+
"homepage": "https://astro-archify-starlight-demo.netlify.app/",
|
|
61
64
|
"bugs": {
|
|
62
65
|
"url": "https://github.com/joesaby/astro-archify/issues"
|
|
63
66
|
}
|
package/vendor/archify/NOTICE.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
This directory contains source code copied verbatim from [tt-a1i/archify](https://github.com/tt-a1i/archify), used under its MIT license (see `LICENSE` in this directory).
|
|
4
4
|
|
|
5
5
|
- **Source**: https://github.com/tt-a1i/archify
|
|
6
|
-
- **Pinned commit**: `
|
|
7
|
-
- **Upstream version**: `2.16.0
|
|
8
|
-
- **Vendored on**: 2026-08-
|
|
6
|
+
- **Pinned commit**: `39a21139a4661203888049d44e3b8c0da13fa576`
|
|
7
|
+
- **Upstream version**: `2.16.0` (per `archify/package.json` at that commit)
|
|
8
|
+
- **Vendored on**: 2026-08-30
|
|
9
9
|
|
|
10
10
|
## What was copied, and why
|
|
11
11
|
|
|
@@ -15,6 +15,8 @@ This directory contains source code copied verbatim from [tt-a1i/archify](https:
|
|
|
15
15
|
renderers/architecture/render-architecture.mjs
|
|
16
16
|
renderers/architecture/grid.mjs
|
|
17
17
|
renderers/workflow/render-workflow.mjs
|
|
18
|
+
renderers/workflow/workflow-compiler.mjs
|
|
19
|
+
renderers/workflow/workflow-migration-geometry.mjs
|
|
18
20
|
renderers/sequence/render-sequence.mjs
|
|
19
21
|
renderers/dataflow/render-dataflow.mjs
|
|
20
22
|
renderers/lifecycle/render-lifecycle.mjs
|
|
@@ -41,8 +43,9 @@ This re-traces the same import graph used to vendor these files originally (see
|
|
|
41
43
|
```bash
|
|
42
44
|
git diff vendor/archify/ # review what actually changed upstream
|
|
43
45
|
# update the Pinned commit / Upstream version / Vendored on fields above
|
|
44
|
-
npm
|
|
45
|
-
cd demo && npm install && npm run build # rebuild and spot-check a page
|
|
46
|
+
npm run vendor:verify
|
|
46
47
|
```
|
|
47
48
|
|
|
49
|
+
`vendor:verify` runs the full test suite and rebuilds both demos. For a quicker check while iterating, `npm test -- --run` is enough.
|
|
50
|
+
|
|
48
51
|
The script only touches files under `vendor/archify/` — it never edits this file, `git`, or anything outside `vendor/`.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta name="generator" content="archify 2.16.0
|
|
6
|
+
<meta name="generator" content="archify 2.16.0">
|
|
7
7
|
<title>[PROJECT NAME] Architecture Diagram</title>
|
|
8
8
|
<script>
|
|
9
9
|
// Resolve the theme before first paint so light-preference users don't
|
|
@@ -340,15 +340,6 @@ function validateArchitecture() {
|
|
|
340
340
|
problems.push(resolvedBoundaryTitles.readabilityProblem);
|
|
341
341
|
}
|
|
342
342
|
const requiresNestedBoundaryMembership = arch.meta?.engineering_profile === 'deployment-ownership';
|
|
343
|
-
if (arch.schema_version !== 1) problems.push('Architecture files must set "schema_version": 1.');
|
|
344
|
-
if (arch.diagram_type !== 'architecture') problems.push('Architecture files must set "diagram_type": "architecture".');
|
|
345
|
-
if (!arch.meta?.title) problems.push('Architecture files must include meta.title.');
|
|
346
|
-
if (!Array.isArray(arch.components) || arch.components.length < 1) {
|
|
347
|
-
problems.push('Architecture diagrams need at least one component.');
|
|
348
|
-
}
|
|
349
|
-
if (arch.connections !== undefined && !Array.isArray(arch.connections)) problems.push('Architecture "connections" must be an array.');
|
|
350
|
-
if (arch.boundaries !== undefined && !Array.isArray(arch.boundaries)) problems.push('Architecture "boundaries" must be an array.');
|
|
351
|
-
if (arch.cards !== undefined && !Array.isArray(arch.cards)) problems.push('Architecture "cards" must be an array.');
|
|
352
343
|
if (components.size !== asArray(arch.components).length) problems.push('Component ids must be unique.');
|
|
353
344
|
if (grid) {
|
|
354
345
|
validateGridPlacement(arch, grid, problems);
|
|
@@ -527,13 +518,11 @@ function validateArchitecture() {
|
|
|
527
518
|
}));
|
|
528
519
|
problems.push(...cleanFlowProblems({
|
|
529
520
|
relations: arch.connections,
|
|
530
|
-
endpointIds: new Set(components.keys()),
|
|
531
521
|
obstacles: components.values(),
|
|
532
522
|
pathFor,
|
|
533
523
|
diagramType: 'architecture',
|
|
534
524
|
relationCollection: 'connections',
|
|
535
525
|
obstacleKind: 'component',
|
|
536
|
-
profile: arch.meta?.quality_profile,
|
|
537
526
|
routeHint: 'adjust fromSide/toSide, set route/via, or move the component'
|
|
538
527
|
}));
|
|
539
528
|
problems.push(...cleanCrossingProblems({
|
|
@@ -1046,7 +1035,7 @@ function renderLegend() {
|
|
|
1046
1035
|
}
|
|
1047
1036
|
|
|
1048
1037
|
function renderSvg() {
|
|
1049
|
-
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(arch.meta
|
|
1038
|
+
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(arch.meta)}>
|
|
1050
1039
|
${svgAccessibleText(arch.meta, 'architecture')}
|
|
1051
1040
|
${renderDefinitions()}
|
|
1052
1041
|
|
|
@@ -117,17 +117,6 @@ for (const [index, node] of asArray(dataflow.nodes).entries()) {
|
|
|
117
117
|
|
|
118
118
|
function validateDataflow() {
|
|
119
119
|
const problems = [];
|
|
120
|
-
if (dataflow.schema_version !== 1) problems.push('Data-flow files must set "schema_version": 1.');
|
|
121
|
-
if (dataflow.diagram_type !== 'dataflow') problems.push('Data-flow files must set "diagram_type": "dataflow".');
|
|
122
|
-
if (!dataflow.meta?.title) problems.push('Data-flow files must include meta.title.');
|
|
123
|
-
if (!Array.isArray(dataflow.stages) || dataflow.stages.length < 2) {
|
|
124
|
-
problems.push('Data-flow diagrams need at least two stages.');
|
|
125
|
-
}
|
|
126
|
-
if (!Array.isArray(dataflow.nodes) || dataflow.nodes.length < 2) {
|
|
127
|
-
problems.push('Data-flow diagrams need at least two nodes.');
|
|
128
|
-
}
|
|
129
|
-
if (!Array.isArray(dataflow.flows)) problems.push('Data-flow diagrams must include a flows array.');
|
|
130
|
-
if (dataflow.cards !== undefined && !Array.isArray(dataflow.cards)) problems.push('Data-flow "cards" must be an array.');
|
|
131
120
|
if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.');
|
|
132
121
|
|
|
133
122
|
const stageCount = asArray(dataflow.stages).length;
|
|
@@ -215,13 +204,11 @@ function validateDataflow() {
|
|
|
215
204
|
}));
|
|
216
205
|
problems.push(...cleanFlowProblems({
|
|
217
206
|
relations: dataflow.flows,
|
|
218
|
-
endpointIds: new Set(nodes.keys()),
|
|
219
207
|
obstacles: nodes.values(),
|
|
220
208
|
pathFor,
|
|
221
209
|
diagramType: 'dataflow',
|
|
222
210
|
relationCollection: 'flows',
|
|
223
211
|
obstacleKind: 'node',
|
|
224
|
-
profile: dataflow.meta?.quality_profile,
|
|
225
212
|
routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the node to another stage/row'
|
|
226
213
|
}));
|
|
227
214
|
problems.push(...cleanCrossingProblems({
|
|
@@ -355,7 +342,21 @@ function pathFor(flow) {
|
|
|
355
342
|
const { fromSide, toSide } = flowSides(flow);
|
|
356
343
|
const start = ports?.from || anchor(from, fromSide);
|
|
357
344
|
const end = ports?.to || anchor(to, toSide);
|
|
358
|
-
|
|
345
|
+
// Drop consecutive duplicate points so a purely vertical (or horizontal)
|
|
346
|
+
// auto-route never emits a zero-length final segment — SVG derives
|
|
347
|
+
// marker-end orientation from the last segment, and a degenerate segment
|
|
348
|
+
// leaves the arrowhead angle undefined (see #169).
|
|
349
|
+
const rawPoints = [start, ...routeVia(flow, from, to, start, end), end];
|
|
350
|
+
const points = [];
|
|
351
|
+
for (const p of rawPoints) {
|
|
352
|
+
const prev = points.at(-1);
|
|
353
|
+
if (!prev || Math.abs(p[0] - prev[0]) > 0.0001 || Math.abs(p[1] - prev[1]) > 0.0001) {
|
|
354
|
+
points.push(p);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Guard against an all-degenerate route (e.g. start === end): keep both
|
|
358
|
+
// endpoints so the path is still well-formed even if the marker is hidden.
|
|
359
|
+
if (points.length < 2) points.push(end);
|
|
359
360
|
const routed = { d: polylinePath(points), points };
|
|
360
361
|
pathCache.set(flow, routed);
|
|
361
362
|
return routed;
|
|
@@ -447,7 +448,7 @@ function renderLegend() {
|
|
|
447
448
|
}
|
|
448
449
|
|
|
449
450
|
function renderSvg() {
|
|
450
|
-
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(dataflow.meta
|
|
451
|
+
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(dataflow.meta)}>
|
|
451
452
|
${svgAccessibleText(dataflow.meta, 'dataflow')}
|
|
452
453
|
${renderDefinitions()}
|
|
453
454
|
|
|
@@ -140,13 +140,6 @@ for (const [index, state] of asArray(lifecycle.states).entries()) {
|
|
|
140
140
|
|
|
141
141
|
function validateLifecycle() {
|
|
142
142
|
const problems = [];
|
|
143
|
-
if (lifecycle.schema_version !== 1) problems.push('Lifecycle files must set "schema_version": 1.');
|
|
144
|
-
if (lifecycle.diagram_type !== 'lifecycle') problems.push('Lifecycle files must set "diagram_type": "lifecycle".');
|
|
145
|
-
if (!lifecycle.meta?.title) problems.push('Lifecycle files must include meta.title.');
|
|
146
|
-
if (!Array.isArray(lifecycle.lanes) || lifecycle.lanes.length < 1) problems.push('Lifecycle diagrams need at least one lane.');
|
|
147
|
-
if (!Array.isArray(lifecycle.states) || lifecycle.states.length < 2) problems.push('Lifecycle diagrams need at least two states.');
|
|
148
|
-
if (!Array.isArray(lifecycle.transitions)) problems.push('Lifecycle diagrams must include a transitions array.');
|
|
149
|
-
if (lifecycle.cards !== undefined && !Array.isArray(lifecycle.cards)) problems.push('Lifecycle "cards" must be an array.');
|
|
150
143
|
if (states.size !== asArray(lifecycle.states).length) problems.push('State ids must be unique.');
|
|
151
144
|
|
|
152
145
|
// The three bands are fixed at y=112/264/436. Preserve the original
|
|
@@ -246,13 +239,11 @@ function validateLifecycle() {
|
|
|
246
239
|
}));
|
|
247
240
|
problems.push(...cleanFlowProblems({
|
|
248
241
|
relations: lifecycle.transitions,
|
|
249
|
-
endpointIds: new Set(states.keys()),
|
|
250
242
|
obstacles: states.values(),
|
|
251
243
|
pathFor,
|
|
252
244
|
diagramType: 'lifecycle',
|
|
253
245
|
relationCollection: 'transitions',
|
|
254
246
|
obstacleKind: 'state',
|
|
255
|
-
profile: lifecycle.meta?.quality_profile,
|
|
256
247
|
routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the state with col/yOffset'
|
|
257
248
|
}));
|
|
258
249
|
problems.push(...cleanCrossingProblems({
|
|
@@ -532,7 +523,7 @@ function renderLifecycleRail() {
|
|
|
532
523
|
}
|
|
533
524
|
|
|
534
525
|
function renderSvg() {
|
|
535
|
-
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(lifecycle.meta
|
|
526
|
+
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(lifecycle.meta)}>
|
|
536
527
|
${svgAccessibleText(lifecycle.meta, 'lifecycle')}
|
|
537
528
|
${renderDefinitions()}
|
|
538
529
|
|
|
@@ -134,20 +134,7 @@ function messagePath(message) {
|
|
|
134
134
|
|
|
135
135
|
function validateSequence() {
|
|
136
136
|
const problems = [];
|
|
137
|
-
if (sequence.schema_version !== 1) problems.push('Sequence files must set "schema_version": 1.');
|
|
138
|
-
if (sequence.diagram_type !== 'sequence') problems.push('Sequence files must set "diagram_type": "sequence".');
|
|
139
|
-
if (!sequence.meta?.title) problems.push('Sequence files must include meta.title.');
|
|
140
|
-
if (!Array.isArray(sequence.participants) || sequence.participants.length < 2) {
|
|
141
|
-
problems.push('Sequence diagrams need at least two participants.');
|
|
142
|
-
}
|
|
143
137
|
if (participants.size !== asArray(sequence.participants).length) problems.push('Participant ids must be unique.');
|
|
144
|
-
if (!Array.isArray(sequence.messages) || sequence.messages.length < 1) {
|
|
145
|
-
problems.push('Sequence diagrams need at least one message.');
|
|
146
|
-
}
|
|
147
|
-
if (sequence.cards !== undefined && !Array.isArray(sequence.cards)) problems.push('Sequence "cards" must be an array.');
|
|
148
|
-
for (const arr of ['segments', 'activations']) {
|
|
149
|
-
if (sequence[arr] !== undefined && !Array.isArray(sequence[arr])) problems.push(`Sequence "${arr}" must be an array.`);
|
|
150
|
-
}
|
|
151
138
|
|
|
152
139
|
if (layout.lifelineBottom - layout.lifelineTop < 120) {
|
|
153
140
|
problems.push(`viewBox height ${viewBox[1]} leaves under 120px of timeline — set meta.viewBox[1] to at least ${layout.lifelineTop + 120 + 65}.`);
|
|
@@ -188,13 +175,11 @@ function validateSequence() {
|
|
|
188
175
|
// segment bands remain intentional pass-through geometry and are excluded.
|
|
189
176
|
problems.push(...cleanFlowProblems({
|
|
190
177
|
relations: sequence.messages,
|
|
191
|
-
endpointIds: new Set(participants.keys()),
|
|
192
178
|
obstacles: participants.values(),
|
|
193
179
|
pathFor: messagePath,
|
|
194
180
|
diagramType: 'sequence',
|
|
195
181
|
relationCollection: 'messages',
|
|
196
182
|
obstacleKind: 'participant header',
|
|
197
|
-
profile: sequence.meta?.quality_profile,
|
|
198
183
|
clearance: 0,
|
|
199
184
|
routeHint: 'move the message y below the participant headers or reorder participants'
|
|
200
185
|
}));
|
|
@@ -427,7 +412,7 @@ function renderLegend() {
|
|
|
427
412
|
|
|
428
413
|
function renderSvg() {
|
|
429
414
|
const participantList = [...participants.values()];
|
|
430
|
-
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(sequence.meta
|
|
415
|
+
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(sequence.meta)}>
|
|
431
416
|
${svgAccessibleText(sequence.meta, 'sequence')}
|
|
432
417
|
${renderDefinitions()}
|
|
433
418
|
|
|
@@ -26,8 +26,6 @@ export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = p
|
|
|
26
26
|
validateEngineeringProfile(diagramType, diagram);
|
|
27
27
|
const sourceEvidence = verifyRepositoryEvidence(diagramType, diagram, process.env.ARCHIFY_REPO_ROOT);
|
|
28
28
|
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
|
|
29
|
-
// Optional chaining: in degraded mode (no ajv) malformed input must still
|
|
30
|
-
// reach the renderer's friendly layout checks instead of crashing here.
|
|
31
29
|
const outputRequest = {
|
|
32
30
|
requestedOutput: argv[3],
|
|
33
31
|
authoredOutput: diagram.meta?.output,
|
|
@@ -148,7 +146,7 @@ export function validateGuidedViews(diagramType, diagram) {
|
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
// Accessible name for the generated diagram SVG.
|
|
151
|
-
export function svgRootAttrs(meta
|
|
149
|
+
export function svgRootAttrs(meta) {
|
|
152
150
|
const animation = meta.animation === 'trace' ? ' data-animation="trace"' : '';
|
|
153
151
|
const preset = ` data-preset="${esc(meta.visual_preset || 'classic')}"`;
|
|
154
152
|
const engineeringProfile = meta.engineering_profile
|
|
@@ -5,6 +5,7 @@ const DIAGNOSTIC_MODE = process.env.ARCHIFY_DIAGNOSTIC_FORMAT === 'json';
|
|
|
5
5
|
const recorded = [];
|
|
6
6
|
const recordedMessages = new Set();
|
|
7
7
|
const boundaryKey = Symbol.for('archify.renderer-diagnostic-boundary');
|
|
8
|
+
let recordingSuppressionDepth = 0;
|
|
8
9
|
|
|
9
10
|
function plainObject(value) {
|
|
10
11
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
@@ -22,17 +23,29 @@ function normalizedDiagnostic(diagnostic) {
|
|
|
22
23
|
supportedFixes: Array.isArray(diagnostic?.supportedFixes)
|
|
23
24
|
? [...new Set(diagnostic.supportedFixes.map((fix) => String(fix).trim()).filter(Boolean))]
|
|
24
25
|
: [],
|
|
26
|
+
...(Array.isArray(diagnostic?.suppresses) ? {
|
|
27
|
+
suppresses: [...new Set(diagnostic.suppresses.map((code) => String(code).trim()).filter(Boolean))],
|
|
28
|
+
} : {}),
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export function recordDiagnostic(diagnostic) {
|
|
29
|
-
if (!DIAGNOSTIC_MODE) return;
|
|
33
|
+
if (!DIAGNOSTIC_MODE || recordingSuppressionDepth > 0) return;
|
|
30
34
|
const normalized = normalizedDiagnostic(diagnostic);
|
|
31
35
|
if (recordedMessages.has(normalized.message)) return;
|
|
32
36
|
recordedMessages.add(normalized.message);
|
|
33
37
|
recorded.push(normalized);
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
export function withDiagnosticRecordingSuppressed(callback) {
|
|
41
|
+
recordingSuppressionDepth += 1;
|
|
42
|
+
try {
|
|
43
|
+
return callback();
|
|
44
|
+
} finally {
|
|
45
|
+
recordingSuppressionDepth -= 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
export function throwDiagnosticError(message, diagnostics) {
|
|
37
50
|
for (const diagnostic of diagnostics || []) recordDiagnostic(diagnostic);
|
|
38
51
|
const error = new Error(message);
|
|
@@ -42,17 +55,15 @@ export function throwDiagnosticError(message, diagnostics) {
|
|
|
42
55
|
|
|
43
56
|
export function throwDiagnosticProblems(prefix, problems, { code = 'layout/constraint', subject = {} } = {}) {
|
|
44
57
|
const messages = (problems || []).map((problem) => String(problem));
|
|
45
|
-
|
|
46
|
-
recordDiagnostic({
|
|
58
|
+
const diagnostics = messages.map((message) => normalizedDiagnostic({
|
|
47
59
|
code,
|
|
48
60
|
severity: 'error',
|
|
49
61
|
message,
|
|
50
62
|
subject,
|
|
51
63
|
evidence: {},
|
|
52
64
|
supportedFixes: [],
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
throw new Error(`${prefix}:\n- ${messages.join('\n- ')}`);
|
|
65
|
+
}));
|
|
66
|
+
throwDiagnosticError(`${prefix}:\n- ${messages.join('\n- ')}`, diagnostics);
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
function fallbackDiagnostic(error) {
|