7. Nodes

12 primitives. All accept position (SPEC 5) and paint (SPEC 6); closed primitives also accept multiple, rotate, shadow. Text is not a primitive — it is bare content (SPEC 3); the frameless |block| box (SPEC 8) is what you reach for when text needs an id, a class, a link, or box layout.

Dimensions use width / height, each defaulting to auto (content + padding, border-boxSPEC 5). They are always bbox dimensions: |oval| { width: 60; height: 40 } is an ellipse in a 60×40 box; equal dimensions (or an empty |oval|) make a circle.

PrimitiveRequiredNotes
|block|size (auto)The base rectangle — frameless (no fill/stroke, radius: 0, padding: 0), like a div. It keeps stroke-width: 2 (invisible while stroke: none), so a styled block gets a sensible border. |box| frames + rounds it, |rect| frames it sharp (SPEC 8).
|oval|size (auto)Bbox ellipse; equal width/height = circle.
|hex|size (auto)Regular hex, flat top/bottom.
|slant|size (auto)Parallelogram; top edge shifted tan(skew) × h. skew in degrees, (-89, 89), default 15.
|cyl|size (auto)Cylinder; end ellipses ≈ h/10.
|diamond|size (auto)Rhombus inscribed in the bbox.
|poly|points≥3 points, local (centre-origin) coords. Closed.
|path|pathRaw SVG path. Native top-left coords.
|line|points2+ points. Markers via marker*:.
|icon|symbolA Phosphor icon — symbol: (or the label) names it; paints two-tone like a box (fill body, stroke line, counter-scaled stroke-width). A square that grows with its [ ] text (32 floor); |sign| is the larger preset. See Icons.
|image|src, width, heightA picture — src: a URL, a data URI, or a local path; local files embed (see Images); both dimensions required. fit maps it into the box — auto (default, letterbox), contain, cover, or stretch.
|sketch|drawA pen that folds to a path — profiles drawn call by call, with named points and edges, mirroring, and view breaks (SPEC 15.3). Closed-primitive paint; bbox from the geometry.

radius rounds a rectangle’s corners — |box| defaults to 8, |block| / |rect| to 0. It is honoured on the rectangle (and on a multi-point |line|’s joins); radius on the non-rect primitives (hex / diamond / slant / poly) is deferred (SPEC 24).

Visual modifiers (closed primitives)

PropertyFormsEffect
stroke-stylesolid / dashed / dotted / center / phantomStroke pattern. Default solid. center (dash-dot) and phantom (dash-dot-dot) are the drafting line conventions — axes and alternate positions — valid on shapes and |line|s everywhere (SPEC 15.7); a link’s set stays solid / dashed / dotted / wavy (SPEC 9). wavy is link-only by design — a wire waves, an outline never does.
multipleN / dx dyDraw one offset duplicate behind the node — the “several of these” reading. Scalar N = N -N.
rotateN degreesRotate around the bbox centre (SPEC 5).
shadowN / dx dy / dx dy blur / dx dy blur colorDrop shadow via SVG <filter>. Scalar N = offset N N, blur N; tint defaults to --lini-shadow-color.
PropertyEffect
marker: XBoth ends.
marker-start: XStart end (link source).
marker-end: XEnd end (link target).

Values: none, arrow, dot, circle, diamond, datum (the filled drafting triangle a drawing’s >- leader lowers to — SPEC 15.7), and the ER cardinality setcrow (the “many” foot — many is its alias), one (a bar |), exactly-one (a double bar ), zero-or-one, one-or-many, zero-or-many (a bar or paired with the foot). The compositional operators -+ / -< / -o+ / -+< / -o< / -++ are sugar over this set (SPEC 9). circle is a larger dot — a filled point sized for hovering or reading (on a chart line it marks a data point; SPEC 14). Markers scale with stroke-width (a link’s wire and a shape’s outline alike; the size law is SPEC 19’s); colour follows the stroke. |line| is bare by default — write |line| { marker-end: arrow } for a one-shot arrow. For links the operator picks markers (see SPEC 9). Source order wins: marker: arrow; marker-end: dot → start arrow, end dot.

Icons

|icon| draws a Phosphor icon (MIT) as inline SVG paths — themeable, reproducible, and renderer-agnostic (no icon font). The symbol property names it — or, as the smart label, the string does (|icon| "heart" is |icon| { symbol: heart }); everything else paints like a box:

|icon| "bell"                                          // symbol via the label
|icon| { symbol: warning-circle; stroke: --amber-ink } // the longhand
|icon| "heart" { fill: --rose-wash; stroke: --rose-ink }
|icon#tag| "bell" [ "3" ]                              // symbol bell, "3" rides as text
3

Setting the symbol twice — a label and { symbol: … } — is an error; pick one.

Phosphor icons are two-tone (a soft fill behind a line), so an icon wears Lini’s paint roles like any node: fill the body (default the soft grey --icon-fill), stroke the line (default --stroke, matching borders and wires), stroke-width its weight (default 2). A single-tone line icon is fill: none.

stroke-width is counter-scaled: an icon is authored on a 256-unit grid and fit to its box, and the stroke is divided by that scale (baked at compile time), so its line weight holds as the icon resizes.

An icon is a square that grows uniformly with its [ ] text (and padding): the side is a 32 floor (icon-size) over the text + padding on either axis — an empty icon is 32×32; a longer label scales the whole icon up, symbol and all, never distorting it. For a larger stand-alone icon, reach for |sign| (SPEC 8).

fit controls how the symbol fills that box. auto (default) keeps Phosphor’s authored framing — each glyph’s built-in 256-grid margin — so a row of mixed icons reads at an even weight; contain scales the glyph’s own bounds up to meet the box (|sign|’s default); cover scales until the box is covered (may overflow); stretch fits both axes (may distort). The counter-scaled stroke-width follows the resulting scale, so line weight is constant whichever fit you choose.

A missing symbol errors like |poly| without points; an unknown one suggests the nearest name. Only the icons a diagram uses are embedded (a default-on icons feature, SPEC 24).

Images

|image|’s src: takes an HTTP(S) URL, a data: URI, or a local path, resolved against the source .lini file’s directory. A local file’s bytes are read once, at resolve — a missing or unreadable path errors at the src: span — and embedded in the output (SPEC 18): SVG as a nested, id-isolated <svg>; raster (PNG / JPEG / GIF / WebP) as a base64 data URI. Embedding is the one behaviour for a path (there is no opt-out — a self-contained SVG is the output contract) and is deterministic from the bytes: the same file and assets give byte-identical output on every run. The compiler never touches the network — URLs and authored data URIs pass through untouched, so a URL is the authored non-embedded form. Under lini serve, assets resolve inside the served root only (SPEC 20).