Skip to content

Commit 64b5001

Browse files
feat(cmd/crucible): machine visualizer — scoped, granular, forge-themed render (D2) (#186)
* feat(cmd/crucible): machine visualizer — scoped, granular, forge-themed render Project the IR through a view-model at a chosen scope and detail granularity and render it as a themed SVG via the embedded D2 engine (pure Go, no Chromium, no external Graphviz). - Scope: -from/-to (path A→X), -from (reachable-from-A), or whole machine; -mode shortest|all|trace selects the path view. - Detail: -detail outline|guards|actions|lifecycle|full (default actions), a cumulative ladder refined by repeatable -show/-hide <dimension>. Lifecycle detail (entry/exit/invoke) renders as in-node compartments. - Distinct shapes per kind (initial dot, atomic 3D steel box, invoke hexagon, history circle, final double-ring, composite/parallel containers); the active path is highlighted in ember over cold-steel context. - Theme: -theme file.json overlays the embedded "forge" default palette. - New internal packages: viewmodel (projection), query (path/scope BFS over the IR), render (D2 emit + in-process SVG + post-process). Replaces the previous WebAssembly Graphviz svg/png backend with D2 and removes that dependency. `render -format png` now errors with guidance to render -format svg and convert with resvg/rsvg-convert; dot/mermaid output is unchanged. * fix(cmd/crucible): quote D2 labels (guard-only/bracketed labels crashed render) An edge whose label starts with `[` (e.g. an eventless transition carrying only a guard, rendered as `[hasStock]`) made D2 parse the value as an array and fail to compile ("edges cannot be assigned arrays"), so RenderSVG errored. The emitter's quote() used a too-narrow blacklist that left such labels bare. Replace it with a whitelist — bare only for ^[A-Za-z_][A-Za-z0-9_]*$, everything else double-quoted (escaping backslash then quote). quote() is the single helper used at every label/name/title/compartment site, so this fixes the whole special-char class. Adds emit + end-to-end RenderSVG regression tests; existing goldens unchanged (a new guard_only_edge golden covers the quoted forms). Found by the granularity showcase (eventless+guard transition).
1 parent 9e8efd5 commit 64b5001

28 files changed

Lines changed: 5085 additions & 376 deletions

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ same rule. Defaults are no-ops, nothing third-party is forced on the consumer.
3535

3636
This "stdlib-only" guarantee is about the library you import: the `state` engine
3737
and its seams pull in nothing third-party. The standalone `crucible` CLI is a
38-
leaf tool, not a library, and is the one exception — it embeds a pure-Go
39-
(WebAssembly) Graphviz **only** for `render -format svg|png`, so you can render
40-
images without installing Graphviz. That convenience lives entirely in the CLI
41-
binary; it never enters the `state` engine or any module you import. See
38+
leaf tool, not a library, and is the one exception — it embeds
39+
[D2](https://d2lang.com) (MPL-2.0, pure Go, no Chromium) **only** for
40+
`render -format svg`, so you can render diagrams without installing Graphviz.
41+
That dependency lives entirely in the CLI binary; it never enters the `state`
42+
engine or any module you import. See
4243
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for attribution.
4344

4445
## Documentation

THIRD_PARTY_NOTICES.md

Lines changed: 21 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Third-Party Notices
22

33
The `crucible` command-line tool (`cmd/crucible`) embeds third-party software so
4-
that `crucible render -format svg|png` can produce images without requiring an
4+
that `crucible render -format svg` can produce diagrams without requiring an
55
external Graphviz installation. The crucible `state` engine and its seams do
66
**not** depend on any of the software listed here; these notices apply only to
77
the CLI binary.
@@ -11,68 +11,32 @@ with each dependency, which remain authoritative.
1111

1212
---
1313

14-
## github.com/goccy/go-graphviz
14+
## oss.terrastruct.com/d2
1515

16-
Pure-Go bindings that run Graphviz compiled to WebAssembly via
17-
[wazero](https://github.com/tetratelabs/wazero). Used by the CLI to render DOT
18-
to SVG/PNG in-process.
16+
The D2 diagramming engine. Used by the CLI to lay out and render the machine
17+
diagram to SVG in-process — pure Go, with no Chromium and no external Graphviz
18+
install. The distributed `crucible` binary therefore contains D2.
1919

20-
- License: MIT
21-
- Project: https://github.com/goccy/go-graphviz
20+
- License: Mozilla Public License, Version 2.0 (MPL-2.0)
21+
- Project: https://github.com/terrastruct/d2
22+
- License text: https://www.mozilla.org/en-US/MPL/2.0/ (and the `LICENSE`
23+
shipped within the `oss.terrastruct.com/d2` module)
2224

23-
```
24-
MIT License
25-
26-
Copyright (c) 2020 Masaaki Goshima
27-
28-
Permission is hereby granted, free of charge, to any person obtaining a copy
29-
of this software and associated documentation files (the "Software"), to deal
30-
in the Software without restriction, including without limitation the rights
31-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32-
copies of the Software, and to permit persons to whom the Software is
33-
furnished to do so, subject to the following conditions:
34-
35-
The above copyright notice and this permission notice shall be included in all
36-
copies or substantial portions of the Software.
37-
38-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44-
SOFTWARE.
45-
```
46-
47-
---
48-
49-
## Graphviz (bundled as WebAssembly by go-graphviz)
50-
51-
`go-graphviz` embeds the [Graphviz](https://graphviz.org/) graph-layout and
52-
rendering engine compiled to WebAssembly. The distributed `crucible` binary
53-
therefore contains Graphviz.
54-
55-
- License: Eclipse Public License, Version 1.0 (EPL-1.0)
56-
- Project: https://graphviz.org/
57-
- License text: https://graphviz.org/license/ (and the `LICENSE` shipped within
58-
the go-graphviz module under `vendor`/embedded WebAssembly assets)
59-
60-
Graphviz is distributed under the Eclipse Public License, Version 1.0. The full
61-
license text is available at https://www.eclipse.org/legal/epl-v10.html. The
62-
program source for the embedded Graphviz is available from the Graphviz project
63-
at https://gitlab.com/graphviz/graphviz.
25+
MPL-2.0 is a file-level copyleft license. The full license text is authoritative
26+
and is shipped with the module in the Go module cache.
6427

6528
---
6629

67-
## Transitive image-encoding dependencies
30+
## Transitive dependencies
6831

69-
`go-graphviz` pulls in pure-Go image-encoding libraries used during PNG/JPEG
70-
rendering. Their licenses are reproduced in their respective module directories
71-
in the Go module cache and in `cmd/crucible/go.sum`:
32+
D2 pulls in supporting libraries during layout and SVG rendering. Their licenses
33+
are reproduced in their respective module directories in the Go module cache and
34+
recorded in `cmd/crucible/go.sum`. Notable entries:
7235

73-
- `github.com/tetratelabs/wazero` — Apache License 2.0 (WebAssembly runtime)
74-
- `github.com/disintegration/imaging` — MIT
75-
- `github.com/fogleman/gg` — MIT
36+
- `github.com/dop251/goja` — MIT (JavaScript engine used by D2 layout)
37+
- `oss.terrastruct.com/util-go` — MPL-2.0
38+
- `github.com/alecthomas/chroma/v2` — MIT
39+
- `github.com/PuerkitoBio/goquery` — BSD-3-Clause
40+
- `github.com/lucasb-eyer/go-colorful` — MIT
7641
- `github.com/golang/freetype` — FreeType License / GNU GPL (dual)
77-
- `github.com/flopp/go-findfont` — MIT
78-
- `golang.org/x/image`, `golang.org/x/text` — BSD-3-Clause
42+
- `golang.org/x/image`, `golang.org/x/net`, `golang.org/x/text` — BSD-3-Clause

cmd/crucible/CHANGELOG.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ versioned independently of the `state` module.
99

1010
### Added
1111

12-
- `render -format svg|png` renders the machine directly to a themed image via an
13-
embedded, pure-Go (WebAssembly) Graphviz — no external Graphviz install is
14-
required. The image carries the Crucible brand palette. `-o file` writes the
15-
output to a file instead of stdout (the norm for binary `png`); `mermaid` and
16-
`dot` output is unchanged.
12+
- Machine visualizer: `render -format svg` renders the machine directly to a
13+
themed, scalable SVG via the embedded D2 engine (pure Go, no Chromium, no
14+
external Graphviz install). The pipeline supports scope and detail projection:
15+
`-from`/`-to` with `-mode shortest|all|trace` select a whole / reachable-from
16+
/ path scope, and `-detail outline|guards|actions|lifecycle|full` (default
17+
`actions`) sets a cumulative detail ladder, refined by repeatable
18+
`-show`/`-hide <dimension>`. `-o file` writes the SVG to a file instead of
19+
stdout.
20+
- `render -theme file.json` overlays a JSON theme onto the embedded default
21+
forge palette; omitted fields keep their defaults.
1722
- `lint -format` selects the output format: `text` (default), `json`, or
1823
`sarif` (SARIF 2.1.0) for machine-readable CI ingestion.
1924
- `diff -format` selects `text` (default) or `json` output.
@@ -25,6 +30,21 @@ versioned independently of the `state` module.
2530
guards default to false). `-initial` overrides the IR's declared start state.
2631
`-format` selects `text` (default) or `json` output.
2732

33+
### Changed
34+
35+
- The `render` SVG backend now uses the embedded D2 engine
36+
(`oss.terrastruct.com/d2`) instead of the previous WebAssembly Graphviz
37+
backend. SVG output is themed with the Crucible forge palette and rendered
38+
in-process.
39+
40+
### Removed
41+
42+
- The previous WebAssembly Graphviz rendering dependency (and its bundled
43+
Graphviz engine) is removed in favor of D2.
44+
- `render -format png` no longer renders directly; it now exits with a usage
45+
error hinting to render `-format svg` and convert with `resvg` or
46+
`rsvg-convert`.
47+
2848
## [0.1.0] - 2026-06-13
2949

3050
Initial release.

cmd/crucible/README.md

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,87 @@ physical location unless the IR was read from stdin (`-`).
3232
### render
3333

3434
```
35-
crucible render <ir.json> [-format mermaid|dot|svg|png] [-o outfile]
35+
crucible render <ir.json> [-format mermaid|dot|svg] [-o outfile] \
36+
[-from state] [-to state] [-mode shortest|all|trace] \
37+
[-detail outline|guards|actions|lifecycle|full] \
38+
[-show dim]... [-hide dim]... [-theme file.json]
3639
```
3740

38-
Renders the machine as a Mermaid `stateDiagram-v2` (the default), Graphviz DOT,
39-
or a themed `svg`/`png` image. The `svg` and `png` formats are rendered directly
40-
by an embedded, pure-Go (WebAssembly) Graphviz, so **no external Graphviz
41-
install is required** and the image carries the Crucible brand palette.
41+
Renders the machine as a diagram. `-format` selects the output:
4242

43-
`-o` writes the output to a file instead of stdout; it is the norm for `png`,
44-
whose bytes are binary. `mermaid`/`dot` remain text and stream to stdout
45-
unchanged (the historical `crucible render m.json -format dot | dot -Tsvg`
46-
pipeline still works for callers who prefer their own Graphviz).
43+
- `mermaid` (the default) — a Mermaid `stateDiagram-v2`, streamed to stdout.
44+
- `dot` — Graphviz DOT, streamed to stdout (the historical
45+
`crucible render m.json -format dot | dot -Tsvg` pipeline still works for
46+
callers who prefer their own Graphviz).
47+
- `svg` — a themed, scalable SVG rendered in-process by the embedded D2 engine
48+
(pure Go, no Chromium and no external Graphviz install). The SVG carries the
49+
Crucible forge palette.
50+
51+
There is no `png` format: `-format png` exits with a usage error pointing you
52+
at the conversion path. SVG is the scalable raster-free output; for a PNG,
53+
render `-format svg` and convert it, e.g.:
54+
55+
```
56+
crucible render m.json -format svg -o m.svg
57+
resvg m.svg m.png # recommended
58+
# or: rsvg-convert m.svg -o m.png
59+
```
60+
61+
`-o` writes the output to a file instead of stdout; it is the norm for `svg`.
62+
63+
#### Scope and detail
64+
65+
The SVG pipeline projects the machine along two independent axes: **scope**
66+
(how much of the graph to keep) and **detail** (how much of each
67+
state/transition to show).
68+
69+
**Scope** is chosen from `-from`/`-to`/`-mode`:
70+
71+
- No `-from`: the **whole** machine.
72+
- `-from A` only: the subgraph **reachable from A**.
73+
- `-from A -to X`: a **path** from A to X. `-mode` shapes it:
74+
- `shortest` (default) keeps the whole reachable subgraph but highlights the
75+
single shortest A→X path (off-path elements stay, dimmed).
76+
- `all` keeps the union of all simple A→X paths, all highlighted.
77+
- `trace` keeps **only** the shortest A→X path, dropping everything else.
78+
79+
`-to` requires `-from`; a non-default `-mode` requires `-from`. Endpoints are
80+
bare state names (composite names resolve to their region).
81+
82+
**Detail** is a cumulative ladder set by `-detail` (default `actions`); each
83+
level implies all the levels below it:
84+
85+
| Level | Adds |
86+
|-------------|---------------------------------------------------|
87+
| `outline` | states and transitions only |
88+
| `guards` | + transition guards |
89+
| `actions` | + effects and assigns (the default) |
90+
| `lifecycle` | + entry/exit actions and invocations |
91+
| `full` | + delays, descriptions, data-flow, context schema, source |
92+
93+
`-show <dimension>` and `-hide <dimension>` (both repeatable) override the
94+
ladder per dimension; `-show` wins when both name the same one. Dimensions:
95+
`guards`, `effects`, `assigns`, `entry-exit`, `invoke`, `delays`,
96+
`descriptions`, `data-flow`, `context-schema`, `source`.
97+
98+
#### Theme
99+
100+
`-theme file.json` overlays a JSON theme onto the embedded default forge
101+
palette; fields you omit keep their defaults. Without `-theme`, the embedded
102+
default theme is used.
103+
104+
#### Examples
105+
106+
```
107+
# Whole machine, default detail, as Mermaid (to stdout):
108+
crucible render m.json
109+
110+
# Just the shortest path from cart to done, nothing else, as SVG:
111+
crucible render m.json -format svg -mode trace -from cart -to done -o path.svg
112+
113+
# Reachable-from-A view with full detail but guards suppressed:
114+
crucible render m.json -format svg -from active -detail full -hide guards -o active.svg
115+
```
47116

48117
### diff
49118

0 commit comments

Comments
 (0)