Style guide
Rules distilled from real presentation rounds. Each one exists because a presenter or a reviewer had to ask for it.
Structure first: areas a person would draw
Before writing boxes, decide the 5–9 areas (zones) that are ours (plus the outside areas around them) as a person would explain the thing on a whiteboard to the
audience in the config, and get them approved when workflow.confirm_structure is on. Areas are named after what
they do for someone (“Who uses it”, “Access & identity”, “One API for every model”, “Guardrails”, “Where models run”),
not after the repository layout or the order of the docs. A reviewer compared a hand-made diagram with a generated
one: the hand-made one read as a product, the generated one as an implementation inventory. The difference was the
area structure and the naming, not the number of boxes.
Viewpoint
meta.viewpoint comes from the config and changes what goes into every box and panel:
| product (default for executives, business, customers, investors) | mixed | technical (architects, engineers) | |
|---|---|---|---|
| areas | capabilities and who they serve | capabilities, technology underneath | layers, runtimes, deployment units |
| box title | the capability (“Guardrails on every request”) | capability | the component (“Guardrail hook”) |
| box sub | what it does for whom | technology in a few words | technology, protocol, version |
what / why |
outcome and value | both | mechanism, failure modes, limits |
pick / alt |
pick at most (panel.tech: pick or off) |
pick |
pick + alt with the exit path (panel.tech: full) |
| personas | yes, with paths | yes | optional |
| external parties | always marked | always marked | always marked |
The same sources can produce both; when the config asks for two audiences, make two diagrams in one file
(diagrams: list) rather than one diagram that tries to be both.
Boundaries: what is ours and what isn’t
- Every zone that is not built and run by the product/organisation gets a
boundary:externalfor third-party services and the users’ own applications,partnerfor partners,customerfor things the customer owns and runs (their data centre, their VPC, their identity provider). The engine draws them dashed/dotted with a badge and adds them to the legend, so nobody mistakes an external API for a component of the product. - Which boundary? Apps the customer builds and runs, their identity provider, their hardware →
customer; third-party SaaS and public APIs →external; a named partner’s system →partner. - Users and client applications sit on the left, external providers on the right, and both are outside the frame. The automatic frame wraps only the zones that are ours; keep other zones out of its rectangle (the check warns).
Content
- Sourced, not invented. Every component, number, name and claim comes from the sources in the config. If the
sources don’t say it, leave it out or list it as a gap in the report. Put file references in
sources. - Planned vs. real. Never present planned things as built. Use
statusfor it, and say “intended” / “planned” in the hero footnote whenproject.maturityisplanned. Withderive_status_from, a component counts as running only if a manifest actually deploys it. - Write for the audience in the config. Executive tone: outcomes and value first, product names second, no protocol soup. Technical tone: name the protocols, ports, data formats and failure modes.
- Panel text:
what= 1–3 sentences.why= 2–4 bullets, each a benefit, not a feature restated.pick= the chosen technology + the one reason it won.alt= real alternatives + the exit path. - Text budget per box. Room for text = box width − 76 px (normal box) or − 90 px (big box). A title character takes
about 8 px (normal, 14.5 px font) or 10 px (big); a subtitle character about 6 px. So a 240 px box fits ~20 title and
~27 subtitle characters, a 200 px box (two columns) ~15 and ~20. Normal boxes cut the subtitle with …; big boxes wrap
it to two lines. A zone header fits its title (13 px capitals, ~10 px per character) + subtitle + badge in the zone
width minus ~40 px. Product name in the title only if the product is the component; otherwise put it in
sub. - Decision ids and question ids from the docs (DL7, Q4, C2) belong in
badge,subor the text, so people can find the source. - Doc inconsistencies are findings. Resolve by
sources.priority, then list them in the report. Don’t silently pick one. - Name rules from
branding.name_rulesapply to every string, including the hero and overlays. - No ambiguous jargon in titles. A word that means something else to part of the audience gets replaced or
qualified: “Edge” (reads as edge computing) → “Access layer” or “Entry point”; “Agent” (software agent or a person?),
“Pipeline”, “Platform” alone. If the sources use the ambiguous term, keep it in
subor the panel text. - Control points are on the path. If the sources say requests pass a control (guardrails, policy check, approval, audit, budget check), the main flow edges must go through those boxes in the map and in every tab and persona path that shows that flow. A guardrail that is only mentioned in a panel reads as optional.
Diagram types
Pick meta.type from the config. The engine is the same; what changes is what a zone, a box and an edge mean,
and the reading direction.
| type | zones are | boxes are | edges are | layout |
|---|---|---|---|---|
| architecture | layers / areas of the system | components | calls, data, control | users left, system middle (framed), providers right, foundation bottom |
| data-flow | stages (ingest → process → store → serve) | jobs, topics, stores | data movement, labelled with format/volume | sources left → consumers right, one column per stage |
| process | phases, numbered in the title (“1 · Cart”) | steps | sequence (“then”), hand-offs | phases left → right, external parties as a bottom row; status = manual/automated or owner |
| journey | journey stages | touchpoints, channels | the customer’s path | stages left → right; pains/opportunities in panel extra sections |
| ecosystem | domains or partner groups | products, partners, platforms | contracts, integrations, money | “us” in the framed middle, partners around it |
| org | units / departments | teams or roles | reporting, collaboration, services | top-down by hierarchy, or grouped by value stream |
| custom | as the config says | follow the config’s map notes |
Tabs for non-architecture types ask the same kind of question: process → “happy path”, “exceptions”, “manual steps”; journey → “typical path”, “time to value”, “pain points” (a list, one item per stage), “outside parties”; org → “who owns X”. The tour follows the reading direction.
- Journey and process: stages/phases are columns left → right; external parties and customer-owned systems go in a
row along the bottom (
boundaryset), so each stage can connect straight down to them.statusfor a journey is who drives the touchpoint (self-serve / human-led) or the channel; for a process manual / automated or the owner. Box counts are smaller than for architecture: about 2–4 per stage (a 7-stage journey ≈ 15–25 boxes, whatevermap.detailsays). - Aspect ratio: aim for roughly 16:9 overall. A single long row of 7+ stages gets tiny in the overview (the check warns); use two rows, or narrower columns.
Map layout
- Reading order is the main flow: requests/data enter on the left, the system is in the middle, external providers/targets are on the right, shared foundations run along the bottom.
- Use
settings.layout.gridfor zones: columns of 280–640 px, rows sized to their content, gap 40. A world around 2000–2400 × 1300–1600 px fits a 16:9 screen well. - 3–8 components per zone. Prefer automatic node placement (
zone.layout.columns), explicitx/yonly to fix a specific arrangement. Start nodes ≥ 55 px below the zone top so the header stays clear. size: "big"for the 1–2 most important components of a zone (110 px tall), normal for the rest (64 px).- Put components that talk to each other a lot next to each other; avoid edges that cross the whole map. A hub with many spokes (a gateway calling detectors) reads best with the spokes stacked in one column next to it.
- Inside a zone, order boxes along their internal chain (a → b → c top to bottom), so connections between them
are short and never skip over a box. A connection that must pass several boxes is better attached to the zone
(
"zone:<id>"). The build warns when a connection runs through a box highlighted together with it. boundaryfor everything that isn’t ours (see above);base: truefor a foundation layer.- Every zone colour is distinct and readable on dark and light themes (Tailwind 400–500 shades work well; the engine
darkens titles automatically on light themes). On light themes (
light,paper) prefer the 500–600 shades for zones and edges, so thin lines stay visible. External zones read best in grey (#94a3b8;#64748bon light themes). - Automatic placement uses zone padding 20, gap 16 and the first box 55 px below the zone top (
zone.layoutpadding,gap,top); use the same numbers when you place a box by hand. - A zone that is ours but should sit outside the frame (e.g. replica servers, a second site) gets
"frame": false.
Edges
- Every edge has ≥ 1 group. Groups are the kinds of flow (
req,data,gov,ops,deploy…), 5–10 in total. - Label only edges where the label adds information (“OIDC / JWT”, “append-only”, “U2”); 2–4 words. Labels need room: a label of 2–3 words needs a connection of about 80–120 px, so label connections that cross between zones, not ones between boxes stacked 16 px apart or zones 40 px apart (the check warns).
settings.edges.mainFlow= the group that tells the core story; it is drawn brighter with moving dots in the overview. On dense maps (40+ boxes) setsettings.edges.overview: "main"so the overview shows only that story.dashed: truefor optional, planned or one-off connections.
Tabs (lenses)
- 4–8 tabs including the overview; keep titles short (1–3 words) so they fit the tab bar (the check reports it). Each tab answers one question (“How does a request flow?”, “Who governs what?”).
- A tab lights up only what belongs to it:
nodes/zones+ its edgegroups. Clicking a box inside the tab then shows only that tab’s kinds of connections (edges.filterByContext). - Use a
listwhen the question has alternatives (usage patterns, deployment options, tiers): each item highlights its own path.
Personas
- If the sources name users, roles, actors or personas, add
personas(3–7). Each gets apath: the boxes they touch, in the order they touch them (typically: their app or console → access → the capability they came for → what records it). 3–8 steps. The engine numbers the boxes and lights the connections between consecutive steps, so consecutive steps should be connected by an edge where the sources say they talk to each other. text: their goal and their main pain, and which parts serve them (1–3 sentences). Mark unvalidated personas as hypotheses in the text when the sources say so.- When the config sets
personas.on_map, give each persona azone: the area with the applications or channels they use (often a “Who uses it” or “Applications” area on the left), so the path visibly starts at the person. Make that zone tall enough for the extra boxes, or uselayout.columns: 2. Best is a column of its own to the left of the zones where their paths start (people → their apps → the system): the connection from a persona’s box then runs sideways. A personas zone directly above or below the apps makes those connections run through the other persona boxes. Persona titles are often long: give that column ~300 px, and setboxSubto a short subtitle. Otherwise keep personas off the map (the Personas tab is enough) and never draw them as ordinary boxes. - No roles in the sources (common for technical diagrams of a piece of software): no personas. Never make them up; say so in the report.
Tour
- 12–22 steps. Opening: the problem or the vision. Middle: one step per area or per stage of the story. End: roadmap,
options, open questions, then a closing “why this” with a
summaryof 4–6 tiles. - One idea per step, up to ~110 words (shorter is fine), active voice. Title = the takeaway (“Guardrails in the request path, not in a document”), not the component name.
- Each step highlights only what the text talks about (≤ ~15 boxes) and sets
edgesto the relevant groups, or["none"]for summary steps. The camera zooms to the highlight, so a tight highlight = a readable zoom. fit: "all"for intro and closing steps and for steps about the whole map.- Use
summarytiles for lists of 3–6 parallel points instead of a long sentence.
Overlays
- Use an overlay for content that is a table or a set of options, not a place on the map: pilot options, pricing
or sizing tiers, the full roadmap. Cards: 2–4, the recommended one
main: true. Add anotewith the recommendation. - Reference an overlay from the tour with
"overlay": "<id>". In the tour the sheet must fit one screen: show only the essential blocks with"blocks": [0, 1]or mark the rest"tour": false; the full sheet stays on its button.
Visual check (what went wrong before)
- Text that doesn’t fit its box →
#checkflags it; shorten or widen. Never leave a cut-off title. - Zone headers overlapping their first component → start nodes lower.
- Labels on top of boxes → move or drop the label; the engine already avoids collisions where it can.
- A tour step that zooms to a sliver → its highlight is too spread out; narrow it or use
fit: "all". - Two panels at once (list panel + node panel) is expected only outside the tour; the sidebar tour shows lists inline.
#checkreports all of the above automatically;node tools/check.mjs <file> --explainshows what each view highlights; the editor’s Check tab (press E) runs both for every tab and step.