magus v0.4.3 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
4 min read

Writing diagrams

Every architecture figure on this site is a Buzz file in docs/site/diagrams/, drawn by magus/figure, a module embedded in the magus binary. A box is a directory the knowledge graph holds, a group is a set of them, and the edges between code boxes are the imports and declared calls those directories carry. You declare what the figure shows; the module places everything and refuses a figure it cannot draw, naming the call to change.

{"figure":"diagram-pipeline","stamp":"sha256:9556ce1d6ec7c2ae6b083719b6b835cb78df8946a01009ede53b6103a7cc69d9"}One library draws every figure, here and in the consoleA figure in docs/site/diagrams builds its boxes from the knowledge graph's own records, magus\dir, magus\dirs and magus\layer, and draws the imports and declared calls between them. magus/figure lays it out and paints it; diagrams-generate writes the committed light, dark and page copies with a receipt beside each, and the site render inlines the page copy. The server embeds the same module to draw live figures at /api/v1/diagrams, which the console fetches.FIGURESOne library draws every figure, here and in the consoleRECORDSIMPORTSEMBEDSPAINTSPAGE COPYGETBUZZFigure sourcerecords, sets, actorsmagus moduledir, dirs, layer from the graphmagus/figuresets, layout, paintSVGs + receiptsdiagrams-generateSite renderinlines the page copy/api/v1/diagramsthe module in-processConsole viewstatic SVG, then wasmLEGENDTHE ONE LAYOUTREADS THE KNOWLEDGE GRAPHCOMMITTED OUTPUT

What a figure is

A figure makes one claim: the title states it, and the boxes and connectors are the evidence. There are hard budgets: at most 9 boxes, 12 connectors, 3 zones and 2 accented elements. A figure that needs more is two figures, an overview and a detail, each with its own figure\of().

The first figure

namespace cacheRead;

import "magus";
import "magus/figure";

export fun cacheReadFigure() > figure\Figure !> any {
    final cache = magus\dir("internal/cache");
    final run = figure\external("Run target", look: figure\Look.plain);
    return figure\of("cache-read", title: "A hit skips the run")
        .box(cache, label: "Cache key", focal: true)
        .flowOut(cache, dst: run, label: "miss")
        .scope([cache]);
}

magus\dir raises MGS7005 when the graph holds no such directory, naming the nearest one, so a typo fails at the line that made it. Register the figure in docs/site/diagrams/all.buzz (the import and an allFigures() entry), then embed it on a page with <!--diagram:cache-read-->.

Boxes, groups and actors

  • box(dir, label:, sub:, focal:, look:) draws one directory.
  • group(dirs, label:) draws every directory in a list as one box. Take the directories as records: magus\layer("handler").dirs is every directory a declared layer covers, magus\dirs("internal/queue/*") every directory a glob matches. + joins two lists, and figure\without(dirs, drop: [...]) leaves some out. A package added to the layer joins the group with no edit to the figure.
  • figure\external(name, ...) is an actor: a person, a vendor service, a step, a Buzz file or a decision.

The layers this repository declares live under "layers" in the root magusfile.buzz: transport, handler, service, repository and composition.

A directory is drawn once, so a box inside a group's dirs is refused; drop it with figure\without. look: takes figure\Look.focal, store, external, input, optional, decision (actors only) or plain. Add a legend() line for each look you use.

Edges

edgesFromGraph() draws every import and every declared magus:calls marker between two drawn boxes, and a figure with two or more code boxes must call it. Every drawn directory needs a symbol index, so run magus graph build first.

  • hideEdges(src, dst:, why:) drops graph edges from one list of dirs to another, with a reason. A hide that matches nothing is a finding.
  • markEdge(src, dst:, label:, stroke:) labels or strokes one graph edge.
  • flowIn, flowOut and flowAcross are the only hand edges, and each has an actor at one end. An edge between two packages is never drawn by hand.

Scope

scope(dirs) names the directories the figure answers for. Every one must be drawn by a box or a group, or left out with except(dirs, why:), which is for a true exclusion. A figure with no code behind it says so with unscoped(why:).

Findings

Nothing is drawn until the figure is clean. Each finding names the call to change:

  • 10 nodes exceeds the budget of 9; split into overview plus detail, one figure\of() each: cut or fold boxes, or make two figures.
  • "internal/x" is in scope("internal/**") but nothing draws it: draw it, or narrow the scope. A finding names a list of dirs by the deepest directory holding them all, or by its one path.
  • draws 2 boxes of code and no edge between them; call edgesFromGraph().
  • "a" has no symbol index: build it with magus graph build.
  • hideEdges(...) hides no edge the graph draws: the code changed; drop the hide.

Generating the files

magus run diagrams-generate docs lays every figure out from the symbol index and writes into docs/assets/gen/ the dark and light pair the README loads, the page copy the site inlines, and a receipt beside each: the stamp, the claim counts, the findings and the index digest it was drawn at. The stamp is also in each SVG's metadata. It fails when the committed files move; :rw rewrites them. The site render reads only the page copies, so it never needs the index.

The console view

The console's Diagrams page draws the project graph, one project's targets and the import graph from /api/v1/diagrams, each through a lens of scope, focus and depth. The server embeds the same module this site builds with, and boxes link to their source.

Credit

The design system, its palette, role treatments and budgets, is cathrynlavery/diagram-design (MIT).

diagramsfigurebuzzdocsdrift
Last updated (b5647a35)
Earlier changes on this page (1)

Full history ↗ · Blame source ↗

Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Module

A magus stdlib namespace a magusfile imports for host capabilities: filesystem, exec, vcs, and more. See the module reference.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. See services.

Server

The background process a person starts with magus server start. It serves MCP, the console, background jobs and the warm knowledge graph, and adopts nested magus calls into one pool. See server.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs: magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Knowledge graph

The queryable graph of a workspace's spells, targets, docs, and code relationships; query it with magus query/explain/path. See knowledge.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate.

Conventions

Placeholders

Angle brackets mark a value you replace with your own; never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.