Skip to content
Docs: Style guide

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: external for third-party services and the users’ own applications, partner for partners, customer for 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 status for it, and say “intended” / “planned” in the hero footnote when project.maturity is planned. With derive_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, sub or 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_rules apply 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 sub or 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.

From code

When the sources are code (alone or with docs), draw the system, not the source tree:

  • Areas are what a person would point at on a whiteboard: the parts of the product, deployable services grouped by job (storefront, ordering, payments), data stores, external services, the platform it runs on. Never one area per folder.
  • Boxes are deployable units and the things they talk to: services, workers, APIs, databases, caches, queues, third-party APIs. Never classes, functions or files. A library shared by several services is a box only if the audience needs it.
  • Connections come from evidence in the code: RPC and HTTP clients (generated gRPC stubs, fetch, client libraries), message topics and queues, database connection strings, environment variables naming another service (CART_SERVICE_ADDR), service names in the deployment manifests. Label them with the protocol or call when it helps (gRPC GetCart, HTTP, Redis). No evidence, no connection. Connections have no sources field: list the evidence for each one in the extraction report. Long RPC names need room: leave about 120–160 px between the box columns a labelled connection crosses, or label the protocol and put the RPCs in the target’s what.
  • Optional parts: a client whose target only exists behind a flag or in an optional component (tracing, a feature add-on) is left out, or drawn only if the config asks; say so in the report.
  • API contracts (OpenAPI, .proto, GraphQL schemas) are the best source for what a service offers: use the RPC names in what and in labels.
  • What runs comes from the deployment files (Kubernetes, Helm, docker-compose, Terraform): replicas, images, the datastore actually deployed. Use them for status when the config asks for it. With several (a kustomization with commented entries, Helm, a release manifest), the default one counts; report the differences.
  • Chosen by configuration: when the code supports several backends (a cart store in Redis, Spanner or memory), draw the default and name the others in alt. Otherwise leave alt out: from code, alternatives exist only when the code or config offers them.
  • Mocks and stubs (a payment service that only pretends) say so in sub (“mock”) so nobody mistakes them for the real thing.
  • Language and framework belong in sub or pick (“Go · gRPC”), not in the title.
  • Sources cite the file and, where useful, the line range: src/cartservice/src/cartstore/RedisCartStore.cs#L20-L60.
  • Big codebases: read the entry points, manifests and API contracts first; hand each service to a sub-agent if you can, asking for: what it does, what it calls (with evidence), what it stores, how it’s deployed.
  • Sources outside the project (a third-party repository you cloned): cite GitHub/GitLab permalinks pinned to the commit you read, and put the commit in the report.
  • Code tells you what and how, rarely why: if the config lists docs or a README too, take the purpose, audience and tour story from them. When the docs and the code disagree about behaviour, the code wins; report it as a conflict. Default to the technical viewpoint unless the config says otherwise.
  • For code, include lists where to look, not a mandate to read every file in those trees.

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 (boundary set), so each stage can connect straight down to them. status for 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, whatever map.detail says).
  • 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.grid for 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), explicit x/y only 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.
  • boundary for everything that isn’t ours (see above); base: true for 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; #64748b on light themes).
  • Automatic placement uses zone padding 20, gap 16 and the first box 55 px below the zone top (zone.layout padding, 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) set settings.edges.overview: "main" so the overview shows only that story.
  • dashed: true for 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 edge groups. Clicking a box inside the tab then shows only that tab’s kinds of connections (edges.filterByContext).
  • Use a list when 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 a path: 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 a zone: 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 use layout.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 set boxSub to 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 summary of 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 edges to 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 summary tiles 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 a note with 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 → #check flags 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.
  • #check reports all of the above automatically; node tools/check.mjs <file> --explain shows what each view highlights; the editor’s Check tab (press E) runs both for every tab and step.