VS Code extension that previews Archify diagrams from their JSON sources (live, as you type) and opens rendered Archify HTML in a viewer tab. Bundles the Archify 3.0.1 renderer and runs it on VS Code's Node runtime. Adds validation diagnostics, JSON schema help, source-link navigation, export saving, render-to-file and open-in-browser commands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
302 lines
15 KiB
Markdown
302 lines
15 KiB
Markdown
# Workflow Renderer
|
||
|
||
Render `diagram_type: "workflow"` JSON files into the standard Archify HTML
|
||
template.
|
||
|
||
```bash
|
||
node archify/renderers/workflow/render-workflow.mjs input.workflow.json output.html
|
||
```
|
||
|
||
The renderer validates input against `archify/schemas/workflow.schema.json`
|
||
with the bundled standalone validator. No dependency installation is required.
|
||
|
||
If `output.html` is omitted, the renderer uses the required `meta.output` value
|
||
from the JSON file.
|
||
|
||
After rendering, run the artifact checker:
|
||
|
||
```bash
|
||
node archify/scripts/check-render-output.mjs output.html
|
||
```
|
||
|
||
It catches final-SVG issues that are easiest to see in a browser: non-finite
|
||
SVG values, accidental two-point diagonal arrows, and arrows crossing the
|
||
legend.
|
||
|
||
## Input
|
||
|
||
Workflow JSON files must set:
|
||
|
||
```json
|
||
{
|
||
"schema_version": 2,
|
||
"diagram_type": "workflow",
|
||
"meta": {
|
||
"title": "Agent Tool Call Workflow",
|
||
"output": "agent-tool-call.html"
|
||
},
|
||
"lanes": [],
|
||
"phases": [],
|
||
"groups": [],
|
||
"mainPath": [],
|
||
"nodes": [],
|
||
"edges": [],
|
||
"cards": []
|
||
}
|
||
```
|
||
|
||
Use `schema_version: 2` for new workflows. Its readable layout compiler treats
|
||
every `col` as a logical rank in `0..5` and derives geometry from the measured
|
||
document. `schema_version: 1` remains the fixed legacy contract for existing
|
||
sources; valid v1 output is preserved byte-for-byte and never silently
|
||
reinterpreted as v2.
|
||
|
||
Omit `meta.viewBox` for the common v2 case so the compiler can use intrinsic
|
||
measured bounds. In v1, the omitted width remains fixed at 720 and height is
|
||
derived from lane count. A complete worked example lives at
|
||
`archify/examples/agent-tool-call.workflow.json`; its `schema_version` selects
|
||
the applicable contract.
|
||
|
||
The schema lives at:
|
||
|
||
```text
|
||
archify/schemas/workflow.schema.json
|
||
```
|
||
|
||
## Migration and layout receipt
|
||
|
||
Migrate an existing v1 source into a separate v2 file:
|
||
|
||
```bash
|
||
node archify/bin/archify.mjs migrate workflow old.json new.json --to-schema 2 --json
|
||
```
|
||
|
||
Running the command again with its schema-v2 output as the new source is an
|
||
idempotent verification pass: the destination bytes and geometry stay unchanged.
|
||
|
||
If a legacy v1 source is blocked solely because `meta.output` is missing or no
|
||
longer portable, supply its replacement for the separate v2 destination:
|
||
|
||
```bash
|
||
node archify/bin/archify.mjs migrate workflow old.json new.json --to-schema 2 --output reports/workflow.html --json
|
||
```
|
||
|
||
`--output` must itself be a portable POSIX-relative `.html` path. It updates
|
||
only the verified destination candidate; the source bytes remain unchanged and
|
||
all non-output schema and compiler diagnostics still block migration.
|
||
|
||
The command never overwrites the source by default. It maps absolute
|
||
`via[*][0]`, `labelAt[0]`, and `channelX` values from legacy to solved rank
|
||
space, preserves y coordinates unless a reported vertical constraint needs
|
||
author input, expands an explicit viewBox only for an unambiguous containment
|
||
repair, and writes the destination only after v2 compilation and artifact
|
||
checks pass. Ambiguous explicit pins fail without producing the destination.
|
||
|
||
Inspect the stable author-facing v2 plan with:
|
||
|
||
```bash
|
||
node archify/bin/archify.mjs validate workflow input.workflow.json --layout-json
|
||
```
|
||
|
||
The receipt reports the selected contract, measured `viewBox` and
|
||
`requiredViewBox`, solved columns, nodes, edges, labels, and causal diagnostics.
|
||
It deliberately omits solver iterations and candidate scores.
|
||
|
||
## Legend
|
||
|
||
The default legend derives component kinds from `nodes[].type`. Supported
|
||
`meta.legend.entries` keys, in stable order, are `frontend`, `backend`,
|
||
`security`, `messagebus`, `database`, `cloud`, and `external`. Labels and
|
||
visibility may be overridden through the shared legend contract; only kinds
|
||
backed by rendered nodes receive Semantic Legend controls.
|
||
|
||
## Layout contracts
|
||
|
||
### Fixed v1
|
||
|
||
| Constant | Value |
|
||
|----------|-------|
|
||
| viewBox | default `[720, auto]` — auto height = 52 + lanes×104 + (lanes−1)×20 + 124 |
|
||
| Lane frame | x 40, width 640, height 104, gap 20; first lane top at y 52 |
|
||
| Lane title strip | top 30px of each lane; node boxes must stay below it |
|
||
| Column centers (`col` 0–5) | x = 88, 220, 300, 430, 500, 625 |
|
||
| Phase headers | Optional `phases[]` render above the first lane, spanning `fromCol..toCol` |
|
||
| Lane groups | Optional `groups[]` frame parallel work or branch work inside one lane |
|
||
| Exception lanes | Set `lane.variant: "exception"` for retry, denial, fallback, or failure paths |
|
||
| Main path lint | Optional `mainPath[]` checks that happy-path steps have matching edges and do not move backward |
|
||
| Default node | 92×52 (height 68 when `tag` is set) |
|
||
| Node spacing | ≥8px between nodes in the same lane |
|
||
| Edge length | straight segments must span ≥28px |
|
||
| Legend row | y = lane bottom + 44; viewBox height must be ≥ legend y + 18 |
|
||
|
||
Column-center gaps are 132 / 80 / 130 / 70 / 125 px: columns 1↔2 (80px) and
|
||
3↔4 (70px) cannot both hold default-width 92px nodes in the same lane. Such an
|
||
invalid v1 source receives one causal `workflow/column-capacity` diagnostic and
|
||
a verified migration-to-v2 repair; v1 never falls through to adaptive layout.
|
||
|
||
### Readable v2
|
||
|
||
| Invariant | Contract |
|
||
|----------|----------|
|
||
| Logical columns | `col` is an integer in `0..5`; pixel centers are measured output |
|
||
| Adjacent-rank baseline | 120px center distance before document-specific constraints |
|
||
| Same-lane node clearance | ≥8px when vertical node intervals overlap |
|
||
| Facing direct edge | clear gap ≥`max(28px, measured label mask width + 8px)` |
|
||
| Automatic route rhythm | direct segment ≥28px; endpoint stub ≥8px; interior turn segment ≥16px |
|
||
| Implicit viewBox | intrinsic content bounds plus contract padding |
|
||
| Explicit viewBox | containment capacity; too-small input reports exact `requiredViewBox` and contributors |
|
||
| Lane measurement | A same-column vertical stack (two or more distinct `yOffset` values) opts an implicit, unpinned workflow into per-lane measurement. Explicit `meta.viewBox`, `via`, `labelAt`, `channelX`, or `channelY`, and workflows without a stack retain shared-height v2 geometry for compatibility. |
|
||
|
||
The compiler applies constraints only to actual related or overlapping
|
||
same-lane nodes, so a wide node in an unrelated lane does not expand every
|
||
rank. Legacy centers are a soft preference after correctness constraints, not
|
||
a geometry promise. Phase and group frames derive from the solved rank bands.
|
||
Automatic routes are normalized once and the same final scene drives
|
||
validation and SVG serialization. Long automatic labels compare direct-gutter
|
||
growth with a legal channel instead of widening every downstream rank. Measured
|
||
multi-row legends participate in intrinsic height and explicit viewBox
|
||
capacity.
|
||
|
||
The Issue #250 shape already has a v2 representation without an authored lane
|
||
size. Keep the three stages in one grouped lane, omit `meta.viewBox`, and center
|
||
their offsets around zero:
|
||
|
||
```json
|
||
{
|
||
"schema_version": 2,
|
||
"diagram_type": "workflow",
|
||
"meta": { "title": "stack", "output": "stack.html" },
|
||
"lanes": [{ "id": "cage", "label": "one cage" }],
|
||
"groups": [{ "id": "g", "label": "cage", "lane": "cage", "fromCol": 1, "toCol": 3 }],
|
||
"nodes": [
|
||
{ "id": "a", "lane": "cage", "col": 2, "type": "security", "label": "stageA", "yOffset": -90 },
|
||
{ "id": "b", "lane": "cage", "col": 2, "type": "security", "label": "stageB", "yOffset": 0 },
|
||
{ "id": "c", "lane": "cage", "col": 2, "type": "security", "label": "stageC", "yOffset": 90 }
|
||
],
|
||
"edges": [
|
||
{ "from": "a", "to": "b", "fromSide": "bottom", "toSide": "top" },
|
||
{ "from": "b", "to": "c", "fromSide": "bottom", "toSide": "top" }
|
||
]
|
||
}
|
||
```
|
||
|
||
An implicit readable-v2 vertical stack whose measured lane height exceeds the
|
||
104px baseline opts into the desktop Viewer's height budget. This decision
|
||
comes from compiled geometry, not an authored sizing field. The Viewer changes only
|
||
the outer reader width so the complete lane remains on screen; canonical SVG
|
||
geometry and explicit `meta.viewBox` workflows retain their authored contracts.
|
||
When necessary, the Viewer may scale below the intrinsic 1:1 width only as far
|
||
as the 6px projected node-text floor. If the complete workflow still cannot fit
|
||
at that readable scale, `visual-check` reports the remaining viewport overflow
|
||
instead of clipping or introducing an internal scroller.
|
||
|
||
Authored `via`, `labelAt`, `channelX`, and `channelY` are absolute hard pins in
|
||
v2; an infeasible pin returns `workflow/explicit-pin-conflict` rather than being
|
||
silently moved. `fromSide` and `toSide` remain direction constraints. A route
|
||
preset restricts the automatic candidate family but is not itself an absolute
|
||
coordinate pin. When either endpoint side is omitted, the v2 compiler chooses
|
||
a feasible side; an authored side restricts that endpoint to the named port.
|
||
|
||
## Design Rules
|
||
|
||
- Use lanes for ownership or runtime boundaries.
|
||
- Use phase headers for high-level story beats such as Intake, Plan, Execute, and Report.
|
||
- Use groups for parallel checks, branch handling, or bounded work within a lane; every group must contain at least one node.
|
||
- For sequential stages stacked inside one container, use one v2 lane and one group, keep the stages in one column, and omit `meta.viewBox`. `yOffset` is relative to the center of the lane's content area, so center a three-stage stack with `-90 / 0 / 90` rather than `0 / 90 / 180`. With compiler-owned routes and canvas, the compiler expands only that lane.
|
||
- Use `lane.variant: "exception"` for human wait, denial, retry, fallback, and failure lanes instead of mixing those paths into the happy path.
|
||
- Set `mainPath` when the diagram has a clear happy path; the renderer validates that consecutive ids have matching edges and move left-to-right.
|
||
- Place nodes with lane IDs and `col` indexes in `0..5`, not raw SVG coordinates.
|
||
- Preserve semantic edge labels. Readable v2 allocates measured label clearance;
|
||
when a label does not fit, repair the reported capacity or route constraint
|
||
instead of deleting meaning.
|
||
- Use labels for decisions, approvals, protocols, async traces, return paths,
|
||
and any other relationship meaning not fully implied by its endpoints.
|
||
- Prefer route presets — `drop` (bend between lanes; `bias` 0–1 picks where),
|
||
`outside-right`, `return-left`, `bottom-channel`, and `up-channel` — before
|
||
using raw `via` points. `straight` and the default `auto` cover the rest.
|
||
- Keep workflow examples compact enough to render well in narrow chat/browser
|
||
previews.
|
||
|
||
### Optional semantic checks
|
||
|
||
Layout validation cannot infer domain truth from labels or cards. When source
|
||
evidence establishes roots, terminals, mandatory direct relationships, or
|
||
mandatory directed reachability, encode those facts in `semanticChecks`:
|
||
|
||
```json
|
||
"semanticChecks": {
|
||
"allowedRoots": ["request", "resource_catalog"],
|
||
"allowedTerminals": ["reply", "audit_log"],
|
||
"requiredEdges": [
|
||
{ "from": "dispatch", "to": "dispatch_ledger" }
|
||
],
|
||
"requiredPaths": [
|
||
{ "from": "event_ledger", "to": "runtime_host" }
|
||
]
|
||
}
|
||
```
|
||
|
||
When `allowedRoots` or `allowedTerminals` is present, it is the complete allow
|
||
list for zero-incoming or zero-outgoing nodes respectively. `requiredEdges`
|
||
requires one exact authored direction; `requiredPaths` permits intermediate
|
||
nodes but follows authored edge direction. These checks run before layout, do
|
||
not alter SVG or receipt bytes, and must not be weakened merely to resolve a
|
||
route or composition diagnostic. Omit fields whose domain facts are unknown.
|
||
|
||
Schema violations exit non-zero with path-prefixed messages annotated with the
|
||
element's id or label. The renderer additionally fails when it can detect
|
||
layout problems, including node overlap, nodes outside their lanes, invalid
|
||
phase/group column ranges, empty groups, broken `mainPath` steps, unknown edge
|
||
targets, labels colliding with nodes or other labels, labels wider than their
|
||
node, legends outside the viewBox, or straight arrows that are too short to
|
||
read cleanly. The shared Clean Flow Gate also rejects edges crossing unrelated
|
||
nodes with 2px clearance; lanes, phases, and groups remain intentional
|
||
pass-through containers. Text width is estimated CJK-aware: fullwidth glyphs
|
||
count as two units.
|
||
|
||
Diagnostics are causal: a rank-capacity failure suppresses derivative short
|
||
edge, endpoint-direction, and label-overlap findings. Every
|
||
`supportedFixes[]` entry is verified by replanning the proposed edit, and a
|
||
diagnostic never proposes removing a semantic label when label presence does
|
||
not cause the failed invariant.
|
||
|
||
Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
|
||
X crossings then fail with `composition/proper-crossing`; default `standard`
|
||
keeps them as artifact-receipt warnings. Collinear lane corridors are outside
|
||
the proper-X rule, but a separate gate warns in `standard` and fails in
|
||
`showcase` when unrelated edges overlap for at least 8px. V1 keeps its authored
|
||
shared-endpoint contract. V2 also checks shared endpoints, including explicitly
|
||
controlled routes: long or mixed-style trunks, counterflow, proper interior X
|
||
crossings and overlapping independent arrowheads are not exempt. Pins are
|
||
preserved, not silently repaired; unresolved v2 corridor/arrowhead collisions
|
||
warn in `standard` and fail in `showcase`.
|
||
|
||
V2 permits a same-direction shared terminal stub of at most 24 SVG units only
|
||
when both relationships share the actual source or target port, effective
|
||
variant, stroke width and role. A nonterminal overlap is never such a stub;
|
||
forward-collinear waypoints do not split a long trunk into permitted pieces.
|
||
Compatible short merges may share their terminal arrowhead. Automatic routes
|
||
consider separate ports, reserve absolute routes at contested nodes, and prefer
|
||
clear paths over shorter ambiguous ones. Crowded automatic corridors can use a
|
||
bounded local adjustment without moving nodes or changing explicit coordinates.
|
||
|
||
The SVG carries `data-layout-contract="readable-v2"` and each edge's role so
|
||
artifact checks apply the same classification to actual visible path geometry,
|
||
not stale composition-point metadata. These internal output attributes do not
|
||
add authoring schema fields. Other diagram types retain their existing rules.
|
||
V2 proper-crossing diagnostics retain the relationship IDs, intersection point,
|
||
and supported fixes in both compiler/layout-JSON receipts and final HTML checks;
|
||
`standard` reports warnings while `showcase` rejects the crossing. Both analyses
|
||
merge forward-collinear waypoints without rewriting authored paths; real bends
|
||
and reversals remain endpoint touches rather than being merged into an X.
|
||
|
||
The older per-edge `data-composition-routing="workflow-v2-auto"` marker remains
|
||
for compatibility with first-round exported HTML that has no root layout
|
||
contract and with older artifact checkers. Marker-only artifacts retain their
|
||
narrower automatic-pair crossing/counterflow policy; the root `readable-v2`
|
||
contract is authoritative when present and also checks explicit routes. Do not
|
||
remove the marker-only path as dead code without retiring that export format.
|
||
Showcase also
|
||
rejects any route segment below 8px and any interior turn segment below 16px;
|
||
ordinary 8–15px endpoint stubs remain valid for fixed lane gaps.
|