Fossil Forum

ArchieT 2 months ago

Post: getting AI to generate sensible Pikchr

I've distilled this documentation to get AI to stop failing at Pikchr generation. The end of the document has specific prompts that may be more focused for particular issues. Maybe the official Fossil docs may be enhanced with some of this.


Pikchr distilled

1. Core model and script structure

Pikchr is a PIC-like DSL that emits SVG, and a script is a flat sequence of statements separated by newlines or ;. The language is single-pass, so an object can only refer to previously defined objects, and its geometry is fixed when the statement ends except for grouped container behavior.

Example:

right
line
box "Hello," "World!"
arrow

2. Shapes and object classes

Main built-ins include box, circle, ellipse, oval, cylinder, file, diamond, plus path-like objects such as line, arrow, spline, and arc, and point/text objects such as dot and text.

Representative example from the manual family of examples:

box "box"
circle "circle" at 1 right of previous
ellipse "ellipse" at 1 right of previous
oval "oval" at 1 right of previous
cylinder "cylinder" at .8 below first box
file "file" at 1 right of previous
diamond "diamond" at 1 right of previous

3. Automatic layout and move

Automatic layout with right, down, left, and up is useful for small diagrams. For larger technical diagrams, explicit placement is usually more reliable than long chains of implicit stacking.

Safe use of move:

right
box "A"
move
circle "B"
move
cylinder "C"

4. Anchors, coordinates, and placement

Every block object has the nine primary anchors .n, .ne, .e, .se, .s, .sw, .w, .nw, and .c. These anchors should be used explicitly for nontrivial connections because they encode the intended relation directly.

Anchor example:

A: box

dot color red at A.nw ".nw " rjust above
dot same at A.w  ".w "  rjust
dot same at A.sw ".sw " rjust below
dot same at A.s  ".s"   below
dot same at A.se " .se" ljust below
dot same at A.e  " .e"  ljust
dot same at A.ne " .ne" ljust above
dot same at A.n  ".n"   above
dot same at A.c  " .c"  ljust

Correct placement patterns:

For serious diagrams, a coordinate-based form is often clearer:

A: box "A" fit at (0,0)
B: box "B" fit at (3cm,0)
arrow from A.e to B.w

5. Relative movement for paths

Paths can use from, to, up, down, left, right, then, and until even with. The reliable pattern is to anchor the start explicitly and then write the route explicitly.

Example:

B1: box "SRC" fit at (0,0)
C1: box "DST" fit at (4cm,-2cm)

arrow from B1.s \
      down 1cm \
      then right until even with C1 \
      then to C1.w

6. Sizing and fit

Objects support wid, ht, rad, and diameter, and fit resizes a block to contain its text. This is usually the right default for boxes and circles carrying labels.

boxwid = 0
boxht  = 0

box "Hello"
move
box "A longer label" "with more text"

Variable names should begin with a lowercase letter, $, or @, so boxwid is valid but BoxWid is not.

7. Stroke, fill, and color

Useful style controls include thickness, thick, thin, invisible, solid, color, and fill.

box "normal"
move
box "thin" thin
move
box "thick" thick
move
box "filled" color White fill RoyalBlue fit

8. Text annotations

Objects can carry up to five strings, with modifiers such as above, below, ljust, rjust, small, big, bold, italic, and mono.

line wid 200% \
     "ljust" ljust above \
     "rjust" rjust below

dot color red at previous.c

9. Labels and robustness

Labels are strongly preferred over ordinal references such as last circle because they remain stable as the script evolves. A label starts with a capital letter and prefixes the object definition directly.

Do not try to reposition an already-created object later using a standalone Label: at ... form; attach at ... to the object definition itself.

10. Practical technical patterns

Simple pipeline / dataflow

A: box "Sensor" fit at (0,0)
B: box "MCU"    fit at (3cm,0)
C: box "Host"   fit at (6cm,0)

arrow from A.e to B.w
arrow from B.e to C.w

Bus with branch arrows

Bus: line from (-3cm,0) to (3cm,0) "I2C bus" above

Device1: box "Device 1" fit at (-1.5cm,1.6cm)
Device2: box "Device 2" fit at ( 1.5cm,1.6cm)

arrow from Device1.s to (-1.5cm,0)
arrow from Device2.s to ( 1.5cm,0)

Routed L-shaped connection

SRC: box "SRC" fit at (0,0)
DST: box "DST" fit at (4cm,-2cm)

arrow from SRC.e \
      right 1cm \
      then down 2cm \
      then to DST.n

Dimensioned side-by-side objects

B1: box "A" fit at (0,0)
C1: circle "B" fit at (2cm,0)

X1: line thin color gray from (B1.w.x,-0.8cm) to (B1.w.x,-0.2cm)
X2: line thin color gray from (C1.e.x,-0.8cm) to (C1.e.x,-0.2cm)
arrow <-> thin color gray from (B1.w.x,-0.5cm) to (C1.e.x,-0.5cm) "2cm" above

Radial hub-and-spoke pattern

Hub: box "Hub RAM" "Shared" fit at (0,0)

Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

11. Embedding note

In Markdown and Fossil workflows, Pikchr is typically embedded as a fenced code block and rendered by the host environment.

``` pikchr
A: box "Input" fit at (0,0)
B: box "Formatter" fit at (3cm,0)
arrow from A.e to B.w
# Pikchr templates

## 1. Core syntax tokens

| Token / form | Role | Minimal example |
| --- | --- | --- |
| `box` | Rectangular node | `box "MCU" fit`   |
| `circle` | Circle node | `circle "IRQ" fit`   |
| `ellipse` | Elliptical node | `ellipse "State" fit`   |
| `oval` | Oval node | `oval "Loop" fit`   |
| `cylinder` | DB/storage | `cylinder "DB" fit`   |
| `file` | File/doc node | `file "config.json" fit`   |
| `diamond` | Decision node | `diamond "?" fit`   |
| `line` | Line segment/polyline | `line right 2cm`   |
| `arrow` | Line with arrow head | `arrow from A.e to B.w`   |
| `spline` | Curved path | `spline from A.e go right 2cm`   |
| `arc` | Circular arc | `arc from 1cm right of A.e`   |
| `dot` | Point marker | `dot at A.c`   |
| `text` | Stand-alone text | `text "Note" at 5mm above A.n`   |
| `right` | Set flow dir to right | `right; box "A"; box "B"`   |
| `left` | Set flow dir to left | `left; box "A"; box "B"`   |
| `down` | Set flow dir to down | `down; box "A"; box "B"`   |
| `up` | Set flow dir to up | `up; box "A"; box "B"`   |
| `move` | Invisible spacer | `box; move; box`   |
| `from` | Start of path | `arrow from A.e to B.w`   |
| `to` | End of path | `arrow from A.s to B.n`   |
| `then` | Continue path | `arrow from A.e right 1cm then down 1cm then to B.n`   |
| `go` | First segment in path | `line from A.e go right 2cm`   |
| `heading` | Polar direction | `line go 3cm heading 150`   |
| `until even with` | Axis alignment helper | `right until even with B`   |
| `at` | Position current object | `box "A" at (0,0)`   |
| `with .anchor` | Position by anchor | `circle with .w at 1cm right of B.e`   |
| `.n/.s/.e/.w` | Cardinal anchors | `arrow from A.e to B.w`   |
| `.ne/.nw/.se/.sw` | Diagonal anchors | `arrow from A.ne to B.sw`   |
| `.c` | Center anchor | `dot at A.c`   |
| `.start/.end` | Path endpoints | `text "len" at <L.start,L.end>`   |
| `wid` / `width` | Object width | `box "A" wid 2cm`   |
| `ht` / `height` | Object height | `box "A" ht 1cm`   |
| `rad` / `radius` | Corner radius | `box "A" rad 4pt`   |
| `diameter` | Circle size | `circle diameter 1cm`   |
| `%` on size | Relative resize | `box wid 125% ht 80%`   |
| `fit` | Auto-size to text | `box "Label" fit`   |
| `"text"` | Annotation string | `box "CPU" "Core0" fit`   |
| `above` | Text above object | `arrow "IRQ" above`   |
| `below` | Text below object | `arrow "ack" below`   |
| `ljust` | Left-justify text | `box "Left" ljust fit`   |
| `rjust` | Right-justify text | `box "Right" rjust fit`   |
| `small` / `big` | Font size hints | `text "note" small`   |
| `bold` | Bold text | `box "CPU" bold fit`   |
| `italic` | Italic text | `text "async" italic`   |
| `mono` | Monospace | `box "0x10" mono fit`   |
| `color` | Stroke color | `box "A" color CadetBlue`   |
| `fill` | Fill color | `box "A" fill Bisque`   |
| `thickness` | Stroke width | `box thickness 2pt`   |
| `thick` | Thicker stroke | `box thick`   |
| `thin` | Thinner stroke | `box thin`   |
| `invis` / `invisible` | Hide stroke | `box invis`   |
| `solid` | Restore stroke | `box invis; box solid`   |
| `close` | Close path | `line go 2cm heading 0 then 2cm heading 120 close fill 0x006000`   |
| Label `A:` | Name object | `A: box "CPU" fit`   |
| `A.n` | Labeled anchor | `arrow from A.s to 1cm below A.s`   |
| `previous` / `last` | Last object | `circle at 1cm right of previous`   |
| `first box` | First of class | `arrow from first box.e to last box.w`   |
| `<P,Q>` | Interpolate point | `dot at 0.5<Bus.start,Bus.end>`   |
| `(x,y)` | Literal coords | `dot at (0,0)`   |
| Units `cm/in/mm/pt/px` | Length units | `box wid 1.5cm ht 0.8cm`   |

## 2. Template: simple state machine

This version keeps the geometry explicit enough that the back-edge is predictable. 

```pikchr
# Simple state machine
S0: box "IDLE"  fit at (0, 0)
S1: box "RUN"   fit at (0,-2cm)
S2: box "FAULT" fit at (0,-4cm)

arrow from S0.s to S1.n "start" above
arrow from S1.s to S2.n "error" above

# Back edge RUN -> IDLE with explicit route
arrow from S1.w left 1.2cm then up 2cm then to S0.w "stop" above

3. Template: request/response timing

This corrected version avoids mixed implicit layout and uses fixed x positions for lifelines.

# Request/response over time
A: box "Client" fit at (0,0)
B: box "Server" fit at (5cm,0)

LA: line from A.s down 4cm
LB: line from B.s down 4cm

arrow from (A.e.x, -1.0cm) to (B.w.x, -1.0cm) "REQ" above
arrow from (B.w.x, -2.4cm) to (A.e.x, -2.4cm) "RESP" above

4. Template: simple multi-core block diagram

The earlier example mixed fragile placement with a bus line. This version is explicit and anchor-driven.

# Multi-core block diagram
MCU:   box "MCU"    fit at (0,0)
Core0: box "Core 0" fit at (-2.2cm,-2.0cm)
Core1: box "Core 1" fit at ( 0.0cm,-2.0cm)
Core2: box "Core 2" fit at ( 2.2cm,-2.0cm)

Bus: line from (-3.2cm,-3.8cm) to (3.2cm,-3.8cm) "APB" above

arrow from MCU.s   to Core1.n
arrow from Core0.s to (-2.2cm,-3.8cm)
arrow from Core1.s to ( 0.0cm,-3.8cm)
arrow from Core2.s to ( 2.2cm,-3.8cm)

5. Template: bus with multiple devices

This corrected version uses explicit stub endpoints on the bus instead of relying on object-as-place shortcuts.

# Horizontal bus with devices
Master: box "Master" fit at (-5cm,0)
Bus: line from (-2.5cm,0) to (3.5cm,0) "I2C" above

Dev1: box "Dev 1" fit at (-1.5cm, 1.8cm)
Dev2: box "Dev 2" fit at ( 0.5cm, 1.8cm)
Dev3: box "Dev 3" fit at ( 2.5cm, 1.8cm)

arrow from Master.e to Bus.start
arrow from Dev1.s to (-1.5cm,0)
arrow from Dev2.s to ( 0.5cm,0)
arrow from Dev3.s to ( 2.5cm,0)

6. Template: minimal component with ports

This keeps the port geometry explicit and adds the actual connection stubs.

# Component with ports
C: box "Component" fit at (0,0)

P1: dot at (-1.6cm, 0.4cm)
P2: dot at (-1.6cm,-0.4cm)
Q1: dot at ( 1.6cm, 0.0cm)

line from P1 to C.w + (0, 0.4cm)
line from P2 to C.w + (0,-0.4cm)
line from C.e to Q1

text "IN1" at 4mm left of P1
text "IN2" at 4mm left of P2
text "OUT" at 4mm right of Q1

7. Template: radial technical layout

Hub: box "Hub RAM" "Shared" fit at (0,0)

Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

Pikchr addendum for diagram generation

This addendum is a practical companion to the official Pikchr manual. It is meant to load fast into context and prevent common failures when generating technical diagrams. Pikchr is a PIC-like diagram language that emits SVG, and its documented model is single-pass, object-oriented, and heavily based on explicit placement, anchors, and relative geometry rather than any automatic graph layout.

Working model

Pikchr should be treated as a precise drawing language, not as an autorouter or graph-layout engine. A good generation strategy is to decide the geometry first, place objects second, and only then add connections.

The language fixes an object's geometry when its statement ends, except for grouped container behavior using [], so late correction by "nudging" earlier objects is the wrong mental model. When a diagram matters, layout should be explicit enough that every important object position and every important connection endpoint can be predicted before rendering.

Non-negotiable rules

  • Place major objects explicitly with at (...) or equivalent documented relative placement forms.
  • Use labels for every object that will be referenced later, because ordinal references like last box are fragile when the script changes.
  • Choose arrow endpoints from named anchors explicitly: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c.
  • Treat hub-and-spoke, bus, timing, and state diagrams as geometry problems, not prose-generation problems.
  • Prefer deterministic scripts over compact clever scripts.
  • Use fit for text-bearing objects unless fixed dimensions are semantically important.
  • Keep titles and captions outside the active geometry envelope so they do not collide with arrows or nodes.
  • Use lowercase-leading variable names for variables; uppercase-leading names are suitable for object labels, not variables.

Anchor discipline

Every block object has nine primary anchors: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c. These are the first choice for diagram connectivity because they encode intent directly in the source.

Examples:

Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog1: circle "COG1" fit at ( 0.00cm,  3.20cm)
arrow from Hub.ne to Cog1.sw

This is better than vague placement because the relation is visible in the code itself. For radial topologies, use matching opposing anchors; for left-to-right flows, use .e -> .w; for top-to-bottom flows, use .s -> .n.

Preferred design workflow

1. Decide topology class first

Choose one of these before writing Pikchr:

  • Linear pipeline
  • Hub and spoke
  • Bus with taps
  • State machine
  • Sequence / timing lanes
  • Layered stack
  • Matrix / grid

Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.

2. Place nodes explicitly

For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:

Hub:  box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)

Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.

3. Add connections last

After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.

4. Only then add labels and styling

Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.

Placement patterns that work

Radial pattern

Use explicit coordinates or explicit heading placement from a stable center when building rings.

Safe pattern, Use explicit anchor pairs for relations:

Hub:  box "Hub" fit at (0,0)
Cog0: circle "0" fit at ( 0.0cm,  3.0cm)
Cog1: circle "1" fit at ( 2.1cm,  2.1cm)
Cog2: circle "2" fit at ( 3.0cm,  0.0cm)
arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w

Left-to-right flow

Use .e -> .w consistently.

A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w

Top-to-bottom flow

Use .s -> .n consistently.

S0: box "Idle" fit at (0,0)
S1: box "Run" fit at (0,-2cm)
arrow from S0.s to S1.n

Bus with taps

Draw the bus once, then attach devices by anchor.

Bus: line from (-3cm,0) to (3cm,0) "I2C" above
Dev1: box "Dev1" fit at (-2cm,1.5cm)
Dev2: box "Dev2" fit at ( 0cm,1.5cm)
Dev3: box "Dev3" fit at ( 2cm,1.5cm)
arrow from Dev1.s to (-2cm,0)
arrow from Dev2.s to ( 0cm,0)
arrow from Dev3.s to ( 2cm,0)

Connection routing policy

Pikchr should not be asked to "find a clean route". Instead, choose one of these explicit routing modes:

  • Direct radial or axial segment
  • Manhattan route with then
  • Dedicated bus or backbone plus short stubs
  • Multi-diagram decomposition when one drawing becomes semantically dense

Example Manhattan route:

A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
arrow from A.e right 1cm then down 1.5cm then to B.n

Use this when a direct segment would cross important geometry.

When to split a diagram

Split a diagram when any of these happen:

  • More than one semantic layer competes in the same central area.
  • Arrow crossings obscure labels.
  • The code starts depending on trial-and-error shifts.
  • The relation is easier to explain as two drawings than one.

For example, Hub <-> Cogs and Cogs <-> Smart Pins can be separate diagrams if the combined version becomes unreadable.

Common failure modes

1. Invalid variable names

Variables must begin with a lowercase letter, $, or @, so names like CogRadius are invalid variable names in Pikchr grammar. Use cogRadius, $cogRadius, or pinr instead.

2. Trying to reposition an already-created object

Label: at ... is not a standalone object-reposition statement in the way one might expect from a retained-mode graphics system. Put at ... on the object definition itself, such as Cog0: circle "COG0" fit at (...).

3. Overusing implicit layout

Default stacking directions are useful for small diagrams, but they become fragile for serious technical drawings. Once a diagram has more than a few nodes or has crossing semantics, switch to explicit coordinates or explicit relation-based placement.

4. Overdrawing the center

If many arrows terminate on a small central object, readability collapses even when the code is technically correct. Solutions are: enlarge the central object, route through a bus/backbone, or split the diagram.

5. Letting captions invade geometry

A title or caption belongs outside the connection field. Place it explicitly with coordinates beyond the node envelope.

Prompt aids for future AI use

The following prompt snippets are designed to force correct Pikchr behavior.

Prompt aid: strict placement

Generate Pikchr as explicit geometry. Do not rely on automatic layout, implicit stacking, or guessed routing. Place every major object with an explicit `at (...)` coordinate or a clearly documented relative placement. Label every referenced object.

Prompt aid: strict anchors

For every arrow, choose endpoints explicitly from named anchors such as `.n`, `.ne`, `.e`, `.se`, `.s`, `.sw`, `.w`, `.nw`, or `.c`. Do not use vague or implicit connection endpoints.

Prompt aid: topology-first

Before writing Pikchr, state the topology class: pipeline, hub-and-spoke, bus, state machine, timing lanes, layered stack, or grid. Then assign coordinates or a deterministic placement scheme.

Prompt aid: no autorouter fantasy

Treat Pikchr as a deterministic drawing language, not an autorouter. If a connection would be messy, route it explicitly with `from ... then ... then to ...`, or redesign the geometry.

Prompt aid: readability over compactness

Prefer longer but deterministic Pikchr over compact clever Pikchr. Hard-code coordinates when necessary. The source should make the geometry obvious before rendering.

Prompt aid: final verification checklist

Before presenting the Pikchr, verify:
1. Every referenced object has a label.
2. Every major object has explicit placement.
3. Every arrow names both endpoints explicitly.
4. Anchor choices match the intended relation.
5. Title and caption are outside the active geometry.
6. Variable names are valid in Pikchr.
7. No connection depends on hoping the renderer makes it look good.

Compact AI memory block

Use this as a short context injection block:

Pikchr memory block:
- Pikchr is single-pass; geometry should be deliberate.
- Use labels for all referenced objects.
- Use explicit `at (...)` placement for nontrivial diagrams.
- Use anchors explicitly: .n .ne .e .se .s .sw .w .nw .c.
- Write arrows as explicit anchor-to-anchor relations.
- Use Manhattan routing with `then` when direct lines would cross important geometry.
- Prefer deterministic coordinates over clever compact code.
- Variable names start with lowercase, `$`, or `@`.
- Do not try to reposition an already-created object with a later `at` statement.
- If the center gets crowded, enlarge it, use a bus, or split the diagram.

Example canonical pattern

This is a good default pattern for a radial technical diagram because it is explicit, stable, and readable:

Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

This pattern encodes the relations directly in the source and uses the documented anchor model exactly as intended.

ArchieT 1 month, 4 weeks ago

I've distilled this documentation to get AI to stop failing at Pikchr generation. The end of the document has specific prompts that may be more focused for particular issues


Pikchr distilled

1. Core model and script structure

Pikchr is a PIC-like DSL that emits SVG, and a script is a flat sequence of statements separated by newlines or ;. The language is single-pass, so an object can only refer to previously defined objects, and its geometry is fixed when the statement ends except for grouped container behavior.

Example:

right
line
box "Hello," "World!"
arrow

2. Shapes and object classes

Main built-ins include box, circle, ellipse, oval, cylinder, file, diamond, plus path-like objects such as line, arrow, spline, and arc, and point/text objects such as dot and text.

Representative example from the manual family of examples:

box "box"
circle "circle" at 1 right of previous
ellipse "ellipse" at 1 right of previous
oval "oval" at 1 right of previous
cylinder "cylinder" at .8 below first box
file "file" at 1 right of previous
diamond "diamond" at 1 right of previous

3. Automatic layout and move

Automatic layout with right, down, left, and up is useful for small diagrams. For larger technical diagrams, explicit placement is usually more reliable than long chains of implicit stacking.

Safe use of move:

right
box "A"
move
circle "B"
move
cylinder "C"

4. Anchors, coordinates, and placement

Every block object has the nine primary anchors .n, .ne, .e, .se, .s, .sw, .w, .nw, and .c. These anchors should be used explicitly for nontrivial connections because they encode the intended relation directly.

Anchor example:

A: box

dot color red at A.nw ".nw " rjust above
dot same at A.w  ".w "  rjust
dot same at A.sw ".sw " rjust below
dot same at A.s  ".s"   below
dot same at A.se " .se" ljust below
dot same at A.e  " .e"  ljust
dot same at A.ne " .ne" ljust above
dot same at A.n  ".n"   above
dot same at A.c  " .c"  ljust

Correct placement patterns:

For serious diagrams, a coordinate-based form is often clearer:

A: box "A" fit at (0,0)
B: box "B" fit at (3cm,0)
arrow from A.e to B.w

5. Relative movement for paths

Paths can use from, to, up, down, left, right, then, and until even with. The reliable pattern is to anchor the start explicitly and then write the route explicitly.

Example:

B1: box "SRC" fit at (0,0)
C1: box "DST" fit at (4cm,-2cm)

arrow from B1.s \
      down 1cm \
      then right until even with C1 \
      then to C1.w

6. Sizing and fit

Objects support wid, ht, rad, and diameter, and fit resizes a block to contain its text. This is usually the right default for boxes and circles carrying labels.

boxwid = 0
boxht  = 0

box "Hello"
move
box "A longer label" "with more text"

Variable names should begin with a lowercase letter, $, or @, so boxwid is valid but BoxWid is not.

7. Stroke, fill, and color

Useful style controls include thickness, thick, thin, invisible, solid, color, and fill.

box "normal"
move
box "thin" thin
move
box "thick" thick
move
box "filled" color White fill RoyalBlue fit

8. Text annotations

Objects can carry up to five strings, with modifiers such as above, below, ljust, rjust, small, big, bold, italic, and mono.

line wid 200% \
     "ljust" ljust above \
     "rjust" rjust below

dot color red at previous.c

9. Labels and robustness

Labels are strongly preferred over ordinal references such as last circle because they remain stable as the script evolves. A label starts with a capital letter and prefixes the object definition directly.

Do not try to reposition an already-created object later using a standalone Label: at ... form; attach at ... to the object definition itself.

10. Practical technical patterns

Simple pipeline / dataflow

A: box "Sensor" fit at (0,0)
B: box "MCU"    fit at (3cm,0)
C: box "Host"   fit at (6cm,0)

arrow from A.e to B.w
arrow from B.e to C.w

Bus with branch arrows

Bus: line from (-3cm,0) to (3cm,0) "I2C bus" above

Device1: box "Device 1" fit at (-1.5cm,1.6cm)
Device2: box "Device 2" fit at ( 1.5cm,1.6cm)

arrow from Device1.s to (-1.5cm,0)
arrow from Device2.s to ( 1.5cm,0)

Routed L-shaped connection

SRC: box "SRC" fit at (0,0)
DST: box "DST" fit at (4cm,-2cm)

arrow from SRC.e \
      right 1cm \
      then down 2cm \
      then to DST.n

Dimensioned side-by-side objects

B1: box "A" fit at (0,0)
C1: circle "B" fit at (2cm,0)

X1: line thin color gray from (B1.w.x,-0.8cm) to (B1.w.x,-0.2cm)
X2: line thin color gray from (C1.e.x,-0.8cm) to (C1.e.x,-0.2cm)
arrow <-> thin color gray from (B1.w.x,-0.5cm) to (C1.e.x,-0.5cm) "2cm" above

Radial hub-and-spoke pattern

Hub: box "Hub RAM" "Shared" fit at (0,0)

Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

11. Embedding note

In Markdown and Fossil workflows, Pikchr is typically embedded as a fenced code block and rendered by the host environment.

``` pikchr
A: box "Input" fit at (0,0)
B: box "Formatter" fit at (3cm,0)
arrow from A.e to B.w
# Pikchr templates

## 1. Core syntax tokens

| Token / form | Role | Minimal example |
| --- | --- | --- |
| `box` | Rectangular node | `box "MCU" fit`   |
| `circle` | Circle node | `circle "IRQ" fit`   |
| `ellipse` | Elliptical node | `ellipse "State" fit`   |
| `oval` | Oval node | `oval "Loop" fit`   |
| `cylinder` | DB/storage | `cylinder "DB" fit`   |
| `file` | File/doc node | `file "config.json" fit`   |
| `diamond` | Decision node | `diamond "?" fit`   |
| `line` | Line segment/polyline | `line right 2cm`   |
| `arrow` | Line with arrow head | `arrow from A.e to B.w`   |
| `spline` | Curved path | `spline from A.e go right 2cm`   |
| `arc` | Circular arc | `arc from 1cm right of A.e`   |
| `dot` | Point marker | `dot at A.c`   |
| `text` | Stand-alone text | `text "Note" at 5mm above A.n`   |
| `right` | Set flow dir to right | `right; box "A"; box "B"`   |
| `left` | Set flow dir to left | `left; box "A"; box "B"`   |
| `down` | Set flow dir to down | `down; box "A"; box "B"`   |
| `up` | Set flow dir to up | `up; box "A"; box "B"`   |
| `move` | Invisible spacer | `box; move; box`   |
| `from` | Start of path | `arrow from A.e to B.w`   |
| `to` | End of path | `arrow from A.s to B.n`   |
| `then` | Continue path | `arrow from A.e right 1cm then down 1cm then to B.n`   |
| `go` | First segment in path | `line from A.e go right 2cm`   |
| `heading` | Polar direction | `line go 3cm heading 150`   |
| `until even with` | Axis alignment helper | `right until even with B`   |
| `at` | Position current object | `box "A" at (0,0)`   |
| `with .anchor` | Position by anchor | `circle with .w at 1cm right of B.e`   |
| `.n/.s/.e/.w` | Cardinal anchors | `arrow from A.e to B.w`   |
| `.ne/.nw/.se/.sw` | Diagonal anchors | `arrow from A.ne to B.sw`   |
| `.c` | Center anchor | `dot at A.c`   |
| `.start/.end` | Path endpoints | `text "len" at <L.start,L.end>`   |
| `wid` / `width` | Object width | `box "A" wid 2cm`   |
| `ht` / `height` | Object height | `box "A" ht 1cm`   |
| `rad` / `radius` | Corner radius | `box "A" rad 4pt`   |
| `diameter` | Circle size | `circle diameter 1cm`   |
| `%` on size | Relative resize | `box wid 125% ht 80%`   |
| `fit` | Auto-size to text | `box "Label" fit`   |
| `"text"` | Annotation string | `box "CPU" "Core0" fit`   |
| `above` | Text above object | `arrow "IRQ" above`   |
| `below` | Text below object | `arrow "ack" below`   |
| `ljust` | Left-justify text | `box "Left" ljust fit`   |
| `rjust` | Right-justify text | `box "Right" rjust fit`   |
| `small` / `big` | Font size hints | `text "note" small`   |
| `bold` | Bold text | `box "CPU" bold fit`   |
| `italic` | Italic text | `text "async" italic`   |
| `mono` | Monospace | `box "0x10" mono fit`   |
| `color` | Stroke color | `box "A" color CadetBlue`   |
| `fill` | Fill color | `box "A" fill Bisque`   |
| `thickness` | Stroke width | `box thickness 2pt`   |
| `thick` | Thicker stroke | `box thick`   |
| `thin` | Thinner stroke | `box thin`   |
| `invis` / `invisible` | Hide stroke | `box invis`   |
| `solid` | Restore stroke | `box invis; box solid`   |
| `close` | Close path | `line go 2cm heading 0 then 2cm heading 120 close fill 0x006000`   |
| Label `A:` | Name object | `A: box "CPU" fit`   |
| `A.n` | Labeled anchor | `arrow from A.s to 1cm below A.s`   |
| `previous` / `last` | Last object | `circle at 1cm right of previous`   |
| `first box` | First of class | `arrow from first box.e to last box.w`   |
| `<P,Q>` | Interpolate point | `dot at 0.5<Bus.start,Bus.end>`   |
| `(x,y)` | Literal coords | `dot at (0,0)`   |
| Units `cm/in/mm/pt/px` | Length units | `box wid 1.5cm ht 0.8cm`   |

## 2. Template: simple state machine

This version keeps the geometry explicit enough that the back-edge is predictable. 

```pikchr
# Simple state machine
S0: box "IDLE"  fit at (0, 0)
S1: box "RUN"   fit at (0,-2cm)
S2: box "FAULT" fit at (0,-4cm)

arrow from S0.s to S1.n "start" above
arrow from S1.s to S2.n "error" above

# Back edge RUN -> IDLE with explicit route
arrow from S1.w left 1.2cm then up 2cm then to S0.w "stop" above

3. Template: request/response timing

This corrected version avoids mixed implicit layout and uses fixed x positions for lifelines.

# Request/response over time
A: box "Client" fit at (0,0)
B: box "Server" fit at (5cm,0)

LA: line from A.s down 4cm
LB: line from B.s down 4cm

arrow from (A.e.x, -1.0cm) to (B.w.x, -1.0cm) "REQ" above
arrow from (B.w.x, -2.4cm) to (A.e.x, -2.4cm) "RESP" above

4. Template: simple multi-core block diagram

The earlier example mixed fragile placement with a bus line. This version is explicit and anchor-driven.

# Multi-core block diagram
MCU:   box "MCU"    fit at (0,0)
Core0: box "Core 0" fit at (-2.2cm,-2.0cm)
Core1: box "Core 1" fit at ( 0.0cm,-2.0cm)
Core2: box "Core 2" fit at ( 2.2cm,-2.0cm)

Bus: line from (-3.2cm,-3.8cm) to (3.2cm,-3.8cm) "APB" above

arrow from MCU.s   to Core1.n
arrow from Core0.s to (-2.2cm,-3.8cm)
arrow from Core1.s to ( 0.0cm,-3.8cm)
arrow from Core2.s to ( 2.2cm,-3.8cm)

5. Template: bus with multiple devices

This corrected version uses explicit stub endpoints on the bus instead of relying on object-as-place shortcuts.

# Horizontal bus with devices
Master: box "Master" fit at (-5cm,0)
Bus: line from (-2.5cm,0) to (3.5cm,0) "I2C" above

Dev1: box "Dev 1" fit at (-1.5cm, 1.8cm)
Dev2: box "Dev 2" fit at ( 0.5cm, 1.8cm)
Dev3: box "Dev 3" fit at ( 2.5cm, 1.8cm)

arrow from Master.e to Bus.start
arrow from Dev1.s to (-1.5cm,0)
arrow from Dev2.s to ( 0.5cm,0)
arrow from Dev3.s to ( 2.5cm,0)

6. Template: minimal component with ports

This keeps the port geometry explicit and adds the actual connection stubs.

# Component with ports
C: box "Component" fit at (0,0)

P1: dot at (-1.6cm, 0.4cm)
P2: dot at (-1.6cm,-0.4cm)
Q1: dot at ( 1.6cm, 0.0cm)

line from P1 to C.w + (0, 0.4cm)
line from P2 to C.w + (0,-0.4cm)
line from C.e to Q1

text "IN1" at 4mm left of P1
text "IN2" at 4mm left of P2
text "OUT" at 4mm right of Q1

7. Template: radial technical layout

Hub: box "Hub RAM" "Shared" fit at (0,0)

Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

Pikchr addendum for diagram generation

This addendum is a practical companion to the official Pikchr manual. It is meant to load fast into context and prevent common failures when generating technical diagrams. Pikchr is a PIC-like diagram language that emits SVG, and its documented model is single-pass, object-oriented, and heavily based on explicit placement, anchors, and relative geometry rather than any automatic graph layout.

Working model

Pikchr should be treated as a precise drawing language, not as an autorouter or graph-layout engine. A good generation strategy is to decide the geometry first, place objects second, and only then add connections.

The language fixes an object's geometry when its statement ends, except for grouped container behavior using [], so late correction by "nudging" earlier objects is the wrong mental model. When a diagram matters, layout should be explicit enough that every important object position and every important connection endpoint can be predicted before rendering.

Non-negotiable rules

  • Place major objects explicitly with at (...) or equivalent documented relative placement forms.
  • Use labels for every object that will be referenced later, because ordinal references like last box are fragile when the script changes.
  • Choose arrow endpoints from named anchors explicitly: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c.
  • Treat hub-and-spoke, bus, timing, and state diagrams as geometry problems, not prose-generation problems.
  • Prefer deterministic scripts over compact clever scripts.
  • Use fit for text-bearing objects unless fixed dimensions are semantically important.
  • Keep titles and captions outside the active geometry envelope so they do not collide with arrows or nodes.
  • Use lowercase-leading variable names for variables; uppercase-leading names are suitable for object labels, not variables.

Anchor discipline

Every block object has nine primary anchors: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c. These are the first choice for diagram connectivity because they encode intent directly in the source.

Examples:

Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog1: circle "COG1" fit at ( 0.00cm,  3.20cm)
arrow from Hub.ne to Cog1.sw

This is better than vague placement because the relation is visible in the code itself. For radial topologies, use matching opposing anchors; for left-to-right flows, use .e -> .w; for top-to-bottom flows, use .s -> .n.

Preferred design workflow

1. Decide topology class first

Choose one of these before writing Pikchr:

  • Linear pipeline
  • Hub and spoke
  • Bus with taps
  • State machine
  • Sequence / timing lanes
  • Layered stack
  • Matrix / grid

Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.

2. Place nodes explicitly

For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:

Hub:  box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)

Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.

3. Add connections last

After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.

4. Only then add labels and styling

Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.

Placement patterns that work

Radial pattern

Use explicit coordinates or explicit heading placement from a stable center when building rings.

Safe pattern, Use explicit anchor pairs for relations:

Hub:  box "Hub" fit at (0,0)
Cog0: circle "0" fit at ( 0.0cm,  3.0cm)
Cog1: circle "1" fit at ( 2.1cm,  2.1cm)
Cog2: circle "2" fit at ( 3.0cm,  0.0cm)
arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w

Left-to-right flow

Use .e -> .w consistently.

A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w

Top-to-bottom flow

Use .s -> .n consistently.

S0: box "Idle" fit at (0,0)
S1: box "Run" fit at (0,-2cm)
arrow from S0.s to S1.n

Bus with taps

Draw the bus once, then attach devices by anchor.

Bus: line from (-3cm,0) to (3cm,0) "I2C" above
Dev1: box "Dev1" fit at (-2cm,1.5cm)
Dev2: box "Dev2" fit at ( 0cm,1.5cm)
Dev3: box "Dev3" fit at ( 2cm,1.5cm)
arrow from Dev1.s to (-2cm,0)
arrow from Dev2.s to ( 0cm,0)
arrow from Dev3.s to ( 2cm,0)

Connection routing policy

Pikchr should not be asked to "find a clean route". Instead, choose one of these explicit routing modes:

  • Direct radial or axial segment
  • Manhattan route with then
  • Dedicated bus or backbone plus short stubs
  • Multi-diagram decomposition when one drawing becomes semantically dense

Example Manhattan route:

A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
arrow from A.e right 1cm then down 1.5cm then to B.n

Use this when a direct segment would cross important geometry.

When to split a diagram

Split a diagram when any of these happen:

  • More than one semantic layer competes in the same central area.
  • Arrow crossings obscure labels.
  • The code starts depending on trial-and-error shifts.
  • The relation is easier to explain as two drawings than one.

For example, Hub <-> Cogs and Cogs <-> Smart Pins can be separate diagrams if the combined version becomes unreadable.

Common failure modes

1. Invalid variable names

Variables must begin with a lowercase letter, $, or @, so names like CogRadius are invalid variable names in Pikchr grammar. Use cogRadius, $cogRadius, or pinr instead.

2. Trying to reposition an already-created object

Label: at ... is not a standalone object-reposition statement in the way one might expect from a retained-mode graphics system. Put at ... on the object definition itself, such as Cog0: circle "COG0" fit at (...).

3. Overusing implicit layout

Default stacking directions are useful for small diagrams, but they become fragile for serious technical drawings. Once a diagram has more than a few nodes or has crossing semantics, switch to explicit coordinates or explicit relation-based placement.

4. Overdrawing the center

If many arrows terminate on a small central object, readability collapses even when the code is technically correct. Solutions are: enlarge the central object, route through a bus/backbone, or split the diagram.

5. Letting captions invade geometry

A title or caption belongs outside the connection field. Place it explicitly with coordinates beyond the node envelope.

Prompt aids for future AI use

The following prompt snippets are designed to force correct Pikchr behavior.

Prompt aid: strict placement

Generate Pikchr as explicit geometry. Do not rely on automatic layout, implicit stacking, or guessed routing. Place every major object with an explicit `at (...)` coordinate or a clearly documented relative placement. Label every referenced object.

Prompt aid: strict anchors

For every arrow, choose endpoints explicitly from named anchors such as `.n`, `.ne`, `.e`, `.se`, `.s`, `.sw`, `.w`, `.nw`, or `.c`. Do not use vague or implicit connection endpoints.

Prompt aid: topology-first

Before writing Pikchr, state the topology class: pipeline, hub-and-spoke, bus, state machine, timing lanes, layered stack, or grid. Then assign coordinates or a deterministic placement scheme.

Prompt aid: no autorouter fantasy

Treat Pikchr as a deterministic drawing language, not an autorouter. If a connection would be messy, route it explicitly with `from ... then ... then to ...`, or redesign the geometry.

Prompt aid: readability over compactness

Prefer longer but deterministic Pikchr over compact clever Pikchr. Hard-code coordinates when necessary. The source should make the geometry obvious before rendering.

Prompt aid: final verification checklist

Before presenting the Pikchr, verify:
1. Every referenced object has a label.
2. Every major object has explicit placement.
3. Every arrow names both endpoints explicitly.
4. Anchor choices match the intended relation.
5. Title and caption are outside the active geometry.
6. Variable names are valid in Pikchr.
7. No connection depends on hoping the renderer makes it look good.

Compact AI memory block

Use this as a short context injection block:

Pikchr memory block:
- Pikchr is single-pass; geometry should be deliberate.
- Use labels for all referenced objects.
- Use explicit `at (...)` placement for nontrivial diagrams.
- Use anchors explicitly: .n .ne .e .se .s .sw .w .nw .c.
- Write arrows as explicit anchor-to-anchor relations.
- Use Manhattan routing with `then` when direct lines would cross important geometry.
- Prefer deterministic coordinates over clever compact code.
- Variable names start with lowercase, `$`, or `@`.
- Do not try to reposition an already-created object with a later `at` statement.
- If the center gets crowded, enlarge it, use a bus, or split the diagram.

Example canonical pattern

This is a good default pattern for a radial technical diagram because it is explicit, stable, and readable:

Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm,  3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm,  2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm,  0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm,  0.00cm)
Cog7: circle "COG7" fit at (-2.30cm,  2.30cm)

arrow from Hub.n  to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e  to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s  to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w  to Cog6.e
arrow from Hub.nw to Cog7.se

This pattern encodes the relations directly in the source and uses the documented anchor model exactly as intended.

Eduardo 1 month, 3 weeks ago

Great work! Thanks!

ArchieT 1 month, 3 weeks ago

this bit I find most useful. even as a human, I've had the problem of not getting good results from the sequential process that mixes these steps together. this outline has improved my process, too.

Preferred design workflow

Decide topology class first

Choose one of these before writing Pikchr:

Linear pipeline, Hub and spoke, Bus with taps, State machine, Sequence / timing lanes, Layered stack, Matrix / grid, Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.

Place nodes explicitly

For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:

Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.

Add connections last

After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.

Only then add labels and styling

Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button