Rendering and the viewer¶
SVG (src/render.mjs)¶
renderSvg(model, spec, theme) returns a self-contained SVG:
<defs>hold the arrow marker and one<symbol>per distinct icon (symbol(id, file)inlines the official SVG with its gradient ids prefixed so two icons never collide —finalizefails on duplicate ids).- CSS variables (
THEMES.light|dark) drive colours, so one SVG serves both themes.baseCss()is shared with the sequence renderer. - Structure is semantic and stable, because the viewer and the exporters depend on it:
| Element | Attributes |
|---|---|
g.node |
data-id, data-label, data-service, data-category, plus a <title> |
g.group |
data-id, data-label |
g.edge |
data-from, data-to, data-step, data-label, data-tip (the step description) |
g.badge |
contains a <title> with "N. description" and an enlarged transparent circle.hit |
Never change these names without updating viewer.mjs, finalize.mjs and drawio/export.mjs.
One description for a numbered step¶
stepText(desc, fromLabel, toLabel, label) (exported from render.mjs) is the only place that builds the text of a step: the Flow list in page.mjs, the badge <title>/data-tip, the viewer popup and the draw.io tooltip all call it. A test asserts the equality for architecture and sequence examples.
The page (src/page.mjs, src/report.mjs)¶
renderPage(diagram, wa, theme, cost) returns the whole document. The tabs are plain <div class="tab"> panes switched by report.mjs's REPORT_JS; the active tab can be set by ?tab=. report.mjs holds costTab(cost, note) and waTab(report) — pure string builders over the data structures from estimateCost and reviewWorkload, plus their CSS and JS (ledger filters, cost overlay, row → node jump, finding hover).
Escaping
Every dynamic string goes through esc(). If you add a field to a tab, escape it.
The viewer (src/viewer.mjs)¶
VIEWER_CSS and VIEWER_JS are template-literal strings embedded in every page. The JS is one IIFE that:
- indexes
.node,.edge,.groupintoinfo(by id) andE(edges withfrom,to,step); - builds
out/innadjacency maps fromE— all reachability and routing use these authored edges only; - implements
paint(nodeSet, edgeSet, cls)/clear()for dimming and highlighting,focusOn,reach,route(BFS on directed edges), the Passport panel, the Finder, presentation mode and theme; - wires the numbered-callout popup (
#steptip) to.badgehover events; it removes the native<title>while hovering to avoid a double tooltip and restores it afterwards; - exports (
exptable): SVG (dual-theme), PNG/JPEG/WebP via canvas, clipboard, and.drawiofrom#drawio-data; - on
?check=1writes<pre id="archify-check">with{ ready, nodes, edges, overflowX, minFontPx, symbols, badUse, selftest }.
Deep-link state lives in location.hash (#focus=…&reach=…, #route=a~b) and query (?tab, ?theme, ?present).
Adding a viewer feature¶
- Prefer data attributes on the SVG over reaching into geometry.
- Keep the page free of external requests (
finalizefails onfetch(,XMLHttpRequest,@import, remote<img>/<link>). - Use
$/$$helpers and avoid globals; everything is inside the IIFE. - Add the feature to
references/viewer-runtime.md, the user guide's viewer page, and the in-page Guide (?). - Test in a browser: the existing tests drive headless Chrome via
src/browser.mjs; for interaction, a short Playwright script is fine (seetest/for examples of deep-link tests).
Browser helpers (src/browser.mjs, src/png.mjs)¶
No Playwright dependency: Chrome is launched with --headless --dump-dom (and --screenshot for PNG). dumpDom(file, { search }) returns the DOM after scripts ran plus console errors, which is how finalize runs the self-check. CHROME_PATH, then common install locations, then PLAYWRIGHT_BROWSERS_PATH, are searched.