psi-slides · a figure language for lectures

Figures you write

Every lecture needs figures, and you already have a way to make them. PowerPoint, Keynote, Illustrator, Inkscape, TikZ, Mermaid, … – and for pictures that are drawn once and looked at once those are the right tools.

We believe lecture figures are different. They should be built up in front of a room while somebody talks over them, survive the slides being reordered, and come out of the same file as the printed handout. Ideally, changes can be tracked, invisible annotations can be added, edges move with their objects, and an LLM can read and change them.

In psi-slides, a figure is a few lines of text in the lecture’s own Markdown file, and the build – the one command that turns that file into slides – draws it.

Development status: The underlying ::: draw mechanism is available as a preview in this repository; it is not part of version 1.0.0 of psi-slides – how to get it is a clone or the source zip. The graphical editor is experimental and desktop-oriented: it has not yet been tested broadly.

TCP IP Ethernet one frame payload the next hop

Open it as a lecture

Animated Infographics is a complete psi-slides lecture. Its figures live inside its Markdown source, the build places them into the slides, and the same source becomes the projection, the speaker cockpit and both reading views.

Start with the projection. Space walks through the beats inside a figure; S opens a cockpit that follows it.

Figures in psi-slides

A figure is whatever a slide shows in place of plain text – such as an image or a video clip. Our focus is on relational drawings fenced off with ::: draw. They are described with text, rendered when you build the slides, and can optionally be built up in beats while you present.

This is a whole figure just as you would insert it into a slide. Note that the positions of the objects and arrows are not stored as absolute coordinates: Eve is placed against Alice, Bob against Eve. The arrows are described by naming their two ends and they find their own way between them.

plaintext plaintext Alice Eve Bob
::: drawbox alice "Alice"box eve "Eve"  right of alice gap 1.7 {.accent}box bob "Bob"  right of eve gap 1.7edge alice -> eve "plaintext"edge eve -> bob "plaintext":::

Beats let figures come alive

The same three boxes, with two beats added – keyframes, in the sense video editing uses the word. Step through it: the second beat renames Eve, and her box, both arrows and the outline around her move without a line of source mentioning any of them.

Eve's reach message Alice Bob EveEve, on path
::: draw {unit=170x56}box  alice  "Alice"box  bob  "Bob"   right of alice gap 10.3box  eve  "Eve"   between alice,bob offset 0,1.5 {.accent}edge direct alice -> bob "message"edge alice -> eve {.accent}edge eve -> bob {.accent}container zone "Eve's reach" over eve pad 0.45 {.dashed .muted}step intercept  show eve  hide directstep relabel  label eve "Eve, on path"  emph eve:::

Toy examples are nice, but what about real figures?

A passkey registration: four lifelines, nine messages, two notes. The numbering, the spacing and the lifelines are generated. The four beats build it the way it is read to a room, one exchange then the next, and two things arrive without a beat naming them: the brace over the CTAP leg, because a brace is only as visible as what it holds, and the note in the margin, held back a beat so it lands after the exchange it comments on.

on the device, over CTAP clicks Create passkey request registration options registration options CTAP authenticatorMakeCredential prompt: PIN or biometric user verified locally attestation object attestationObject + clientDataJSON verify signature · check origin User Browser Authenticator Relying Party 1 2 3 challenge · rp.id · user.id · algs 4 clientDataHash · rp.id · user · algs CTAP runs over USB, NFC or BLE 5 6 generate key pairbind to SHA-256(rp.id)store privately · emit publicly 7 authData (public key, cred ID) · signature 8 clientDataJSON carries challenge · origin 9 the challenge is what makes it fresh
::: draw {unit=150x40}# The actors are lines of their own because each needs a name later lines can# hold on to and an attribute tail of its own. Everything under them is a# message - an arrow between two names - or a note.sequence wa at 0,0  actor u  "User"  actor br "Browser"  actor au "Authenticator" {.tone-3}  actor rp "Relying Party"  u  -> br "clicks Create passkey"  br -> rp "request registration options"  br <- rp "registration options" "challenge · rp.id · user.id · algs" {.dashed}  br -> au "CTAP authenticatorMakeCredential" "clientDataHash · rp.id · user · algs"  note br,au "CTAP runs over USB, NFC or BLE"  au -> u  "prompt: PIN or biometric"  u  -> au "user verified locally"  note au "generate key pair\nbind to SHA-256(rp.id)\nstore privately · emit publicly"  au -> br "attestation object" "authData (public key, cred ID) · signature" {.dashed}  br -> rp "attestationObject + clientDataJSON" "clientDataJSON carries challenge · origin"  rp -> rp "verify signature · check origin"# Two annotations the statement knows nothing about, hung off generated# names: a brace over three messages and a note beside one of them. A beat# can show or emphasise either one exactly as it would a box.brace ctap over wa-3,wa-4,wa-5 pad 0.3 "on the device, over CTAP" side left {.small .turn}text fresh "the challenge is what makes it fresh" right of wa-2 gap 1.9 {.small .hand} -- wa-2# The beats build the protocol rather than only lighting parts of a finished# one, which is how it is read in front of a room: one exchange, then the next.# Two things arrive on their own and neither is named where it appears. The# brace is never shown by a step - it holds wa-3, wa-4 and wa-5, and a brace is# only as visible as its members, so it comes in with them. The annotation is# the opposite case: it hangs off wa-2 by a leader and would arrive with it, so# an explicit `show` one beat later is what holds it back until the exchange# it comments on has been read.step ask-for-options  show @wa-msg-1, @wa-msg-2  emph @wa-msg-2step the-challenge-is-the-point  show fresh  dim @wa-msg-1, @wa-msg-2step on-the-device  show @wa-msg-3, @wa-msg-4, @wa-msg-5  show wa-note-0, wa-note-1  emph austep back-to-the-relying-party  show @wa-msg-6, @wa-msg-7, @wa-msg-8  dim au  emph @wa-msg-8:::

Four design principles

01

The text is the figure

There is no second copy of it: no drawing file beside the lecture, no exported image one edit behind its source. The figure is in the same file as the prose around it, so it travels when a slide is moved or copied, and a change to it arrives in a diff as the lines that changed.

You can still drag it. The live slides carry a graphical editor, and a drag keeps the relation and changes the number inside it: right of alice gap 1.1 becomes right of alice gap 1.6. Open the slides, bring up a ::: draw figure, click it and press E.

02

Positions are relations, and stay relations

right of alice gap 1.7 is the whole of what the file says about where Eve is, permanently – not shorthand the build resolves once and forgets. Every element but the first is placed against another, so the file is a set of dependencies and the build follows them in whichever order resolves them.

Give Alice a longer label and her box grows; Eve stays 1.7 units to its right, Bob follows Eve, and both arrows find their new ends, with nothing moved by hand. If two relations point back to each other the build stops and names the placement cycle.

03

The cast is written once; the beats say what changes

Every element a figure will ever hold – every box, arrow, label, and outline – is written at the top of the ::: draw block, whether or not it is initially visible when the figure comes up on the projector. A static figure stops there. If the figure has beats, one or more step sections follow; each names a beat and lists what it changes. Pressing the space bar when the figure is shown proceeds to the next beat. A beat can change visibility (show, hide), position (move), labelling (label), prominence (emph, dim, ghost) and styling (style).

An element is hidden at the opening beat exactly when the first thing any step says about it is show, so there is no second list of what starts hidden – one description of the figure, and the changes to it.

04

A beat is the whole figure laid out again

Every beat is the same layout evaluated again with different inputs, not a movement played over a finished picture, and the build works all of them out before the page is written. The browser is handed one drawing and a table of numbers to slide between, with no layout engine to load.

So an arrow stays attached to a box that walks away. The arrow never held a coordinate; it held “the right-hand edge of eve”, and that still means the right-hand edge of eve wherever eve now is.

Dragging a box rewrites the line that placed it

A drag changes the number inside a relation and leaves the relation standing, so a figure can be edited by hand without losing what holds it together. The editor therefore works differently from the ones you know: it shows you the relations, and it asks you to name your elements.

the editor, opened on a figure with beats
The diagram editor open over a lecture slide: a dark canvas holding a CBC decryption figure with one box selected, the relations that place it written on the canvas beside it, a panel on the right describing the beat that is standing, a rail of the figure's beats along the bottom, and a strip of the lecture's other figures under that.

The selected box is c1, and the words drawn beside it – gap 1.15, flush middle, align y middle – are the relations it was written with. A finished drawing cannot show those: a box placed gap 1.15 from its neighbour looks exactly like one that happens to sit 1.15 away, and only one of the two moves when the neighbour does.

The panel on the right describes the beat that is standing, and it describes it by what it does rather than by what it says: the beat is written show @out and dim feed0, feed1, feed2, and the panel names the three boxes that appear, the three arrows that come with their ends because an arrow is only as visible as the two things it joins, and the three lines dimmed under them. A drag at that beat writes a move into the step rather than moving the element.

Opening it, and where the edit goes

Click a figure in the live view and press E, or use the button in the corner of the card the click opens. It is on by default wherever a lecture has a diagram; editor: speaker in the frontmatter keeps it off the projection and editor: none ships the lecture without it.

Build with --watch and the edit goes straight back into source.md: the button in the top right then reads Write to source.md, the server splices the block into the file, and the rebuild that follows reloads every open tab. Without a watch server the same button copies the whole block to the clipboard to paste over the old one. An edit that stops the block compiling is not written anywhere – it is rolled back on the spot and the compiler’s error message is shown.

The gestures, the panel and the rest of it are in the manual. The editor is experimental and made for a desktop-sized authoring screen: it has automated coverage, but has not been tested broadly by people.

What you give up, and what you get

A drawing programPowerPoint, Keynote, Excalidraw An auto-layout languageMermaid, Graphviz, PlantUML This::: draw
Where the figure lives A file of its own, beside the lecture Text, in the lecture or beside it Text, in the lecture file, between the sentences it belongs to
Who decides where things go You, in pixels The layout engine You, in relations between one element and another
Give one box a longer label The box grows; every arrow, outline and note around it is moved by hand The graph is laid out again, and can come back in a different arrangement Whatever was attached to it follows, and nothing else moves
Building it up in front of a room A timeline of appearances and moves, played over the finished picture There is no equivalent; a diagram is one static picture Beats, each of them the whole figure laid out again
The handout A second export, and a second thing to keep current The same picture The last beat of the same figure, carrying the prominence written into it rather than the prominence a beat lent it
Output and export Standalone image, SVG or PDF, depending on the program Usually SVG, PDF or PNG Inline SVG in the lecture HTML. There is no standalone SVG or figure-level PDF export yet; the complete lecture or handout can be printed to PDF
When to reach for it One figure, drawn once and not revised. Nothing to install, no build step, and everyone already has it Forty nodes whose arrangement carries no meaning. It places everything for you, which this will never do When the arrangement is part of the argument, or the figure has to be built up in front of a room

This page argues for the language. Figures You Write is the manual: it builds a figure a line at a time, gives it beats, then lists every class and statement, fifteen design rules and a set of worked examples.