Archify Diagram Viewer 0.1.0

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>
This commit is contained in:
2026-09-30 12:40:55 +03:00
co-authored by Claude Opus 5.5
commit d56e784e97
119 changed files with 66708 additions and 0 deletions
+301
View File
@@ -0,0 +1,301 @@
# 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.