figdown 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SHOWCASE.md ADDED
@@ -0,0 +1,421 @@
1
+ # FigDown capability showcase — one source, two readers
2
+
3
+ A FigDown figure is a single `.fd` text: a human opens the rendered SVG and
4
+ *sees* it; an agent opens the same text and *reads* it — answering questions
5
+ about widths, bit positions, message order, and decision logic without a
6
+ pixel. These six classic networking figures each demonstrate one advantage as
7
+ an agent skill: the agent writes the `.fd` (not brittle ASCII art), the human
8
+ gets a clean SVG, and a second agent reads the text back to answer questions
9
+ that would otherwise need OCR. The sixth — the complete TCP state machine —
10
+ is the stress test: every state and every labelled transition of RFC 9293
11
+ Figure 5 on one canvas.
12
+
13
+ Each entry: the rendered SVG, the full `.fd`, one line on what the human sees,
14
+ and questions an agent answers **from the text alone**. Every colour and
15
+ dash is carried by a `class` whose label states its *meaning*; no meaning rides
16
+ on geometry (`tools/r25-check.js --strict`) or presentation.
17
+
18
+ Two of these figures — the TCP handshake and ARP resolution — are deliberately
19
+ **mixed documents**: a scene block *and* a `table` block in one `.fd`, composed
20
+ in document order (spec §8). This is the project's ruled answer for content the
21
+ scene genre cannot yet carry first-class (per-endpoint lifeline state; a local
22
+ cache transition): a second typed block carries it machine-readably, rather than
23
+ letting it ride on geometry or absence.
24
+
25
+ ---
26
+
27
+ ## 1. TCP header — bit-exact machine-readable layout (`bitfield`)
28
+
29
+ ![TCP header](examples/showcase/tcp-header.svg)
30
+
31
+ ```figdown
32
+ figdown 0.1 bitfield
33
+ title TCP Header
34
+
35
+ bitfield tcp "TCP Header (RFC 9293)" unit=32 numbering=msb0
36
+
37
+ field "Source Port" 16
38
+ field "Destination Port" 16
39
+ field "Sequence Number" 32
40
+ field "Acknowledgment Number" 32
41
+ field "Data Offset" 4 note="header length in 32-bit words; min 5 (=20 bytes), max 15 (=60 bytes)"
42
+ field Reserved 4 note="must be zero"
43
+ field CWR 1
44
+ field ECE 1
45
+ field URG 1
46
+ field ACK 1
47
+ field PSH 1
48
+ field RST 1
49
+ field SYN 1 color=#fee2e2
50
+ field FIN 1 color=#fee2e2
51
+ field Window 16 note="receive window size in bytes (subject to window scaling option)"
52
+ field Checksum 16
53
+ field "Urgent Pointer" 16
54
+ field Options 32 optional note="present iff Data Offset > 5; padded to a 32-bit boundary"
55
+ field Data * note="variable-length payload; length from the IP datagram, not the TCP header"
56
+ ```
57
+
58
+ **Human sees:** the RFC-style header — a 32-bit ruler, every field to width,
59
+ the eight flags as individual bit cells, Options dashed (optional).
60
+
61
+ **An agent answers from the text alone:**
62
+
63
+ - *Q: Which bits carry the window size?* A: bits 112–127 — the sum of preceding
64
+ widths (96 for rows 1–3, then Data Offset 4 + Reserved 4 + eight 1-bit flags)
65
+ places `Window` next. Derived from field order/width, not the drawing.
66
+ - *Q: Is SYN one bit or a byte?* A: one bit (`field SYN 1`); each of the eight
67
+ flags is a distinct, individually addressable 1-bit field.
68
+ - *Q: When are Options present; what is the minimum header length?* A: present
69
+ iff Data Offset > 5 (`optional` + note); minimum header = 5 × 32-bit words =
70
+ 20 bytes (Data Offset note).
71
+
72
+ ---
73
+
74
+ ## 2. TCP three-way handshake — labelled directed exchange (`topology`)
75
+
76
+ ![TCP handshake](examples/showcase/tcp-handshake.svg)
77
+
78
+ ```figdown
79
+ figdown 0.1 topology
80
+ title TCP Three-Way Handshake
81
+
82
+ class setup "Connection-setup segment — carries SYN and/or ACK control flags" color=#2563eb
83
+ class data "Segment that also carries application data (piggybacked on the ACK)" color=#16a34a
84
+
85
+ node client "Client (active open)"
86
+ node server "Server (passive open, LISTEN)"
87
+
88
+ flow right
89
+
90
+ edge client -[1: SYN seq=x]-> server class=setup
91
+ edge server -[2: SYN,ACK seq=y ack=x+1]-> client class=setup
92
+ edge client -[3: ACK ack=y+1 (+ data)]-> server class=data
93
+
94
+ table state "Endpoint state after each segment (RFC 9293 §3.3.2 state names)"
95
+ | Segment | Client state | Server state |
96
+ |--------------------|-----------------------|---------------------------|
97
+ | start | CLOSED | LISTEN |
98
+ | 1: SYN → | SYN-SENT | SYN-RECEIVED on receipt |
99
+ | 2: SYN,ACK ← | ESTABLISHED on receipt| SYN-RECEIVED |
100
+ | 3: ACK → | ESTABLISHED | ESTABLISHED on receipt |
101
+ ```
102
+
103
+ This is a **mixed document**: a `topology` scene *and* a `table` in one `.fd`,
104
+ composed in document order (spec §8). The canonical RFC 9293 figure carries the
105
+ per-endpoint state on the lifelines; the topology genre has no lifeline-state
106
+ construct, so the companion table carries those states machine-readably —
107
+ composition is the ruled interim until the sequence genre lands.
108
+
109
+ **Human sees:** two endpoints and three numbered segments — SYN out, SYN-ACK
110
+ back, ACK (with piggybacked data) out — with a state table below tracking each
111
+ endpoint's TCP state segment by segment.
112
+
113
+ **An agent answers from the text alone:**
114
+
115
+ - *Q: Which flags are set in segment 2?* A: SYN and ACK — from label `2: SYN,ACK`.
116
+ - *Q: Which segment carries data, in which direction?* A: segment 3,
117
+ client → server — the only `data`-classed edge; its operator gives direction.
118
+ - *Q: What ack number does the client send last?* A: `ack=y+1`, acknowledging
119
+ the server's SYN — from the segment-3 label.
120
+ - *Q: What state is the server in after receiving segment 1?* A: SYN-RECEIVED —
121
+ row 2 of the state table (`1: SYN →`, Server state). Answerable only because
122
+ the companion table carries the RFC 9293 lifeline states.
123
+
124
+ > **Honest limit (stated in the source):** message *order* rides on the 1/2/3
125
+ > label ordinals, not a first-class construct — R37 does not treat numbering as
126
+ > semantics. The v0.2 sequence genre (OQ-S18) closes this; see
127
+ > [EXPRESSING.md](EXPRESSING.md), "strict message ordering".
128
+
129
+ ---
130
+
131
+ ## 3. L2 switch forwarding decision — protocol logic, readable (`flowchart`)
132
+
133
+ ![L2 forwarding logic](examples/showcase/l2-forwarding-logic.svg)
134
+
135
+ ```figdown
136
+ figdown 0.1 flowchart
137
+ title L2 Switch Forwarding Decision
138
+
139
+ class forward "Forward — send the frame out the single learned egress port" color=#16a34a
140
+ class flood "Flood — send out every port in the VLAN except the ingress port" color=#2563eb
141
+ class filter "Filter — discard; destination is out the same port it arrived on" color=#dc2626
142
+
143
+ boundary wire "from ingress port"
144
+
145
+ node learn "Learn source MAC:\nupdate MAC table\n(SA -> ingress port)"
146
+ node lookup "Look up destination MAC\nin MAC table"
147
+ node hit "Entry found?" shape=diamond
148
+ node same "Egress port ==\ningress port?" shape=diamond
149
+ node fwd "Forward out\nlearned port" shape=rounded class=forward
150
+ node fld "Flood to all ports\nin VLAN except ingress" shape=rounded class=flood
151
+ node flt "Filter (drop):\nsame-port destination" shape=rounded class=filter
152
+
153
+ flow down
154
+
155
+ edge wire -> learn
156
+ edge learn -> lookup
157
+ edge lookup -> hit
158
+ edge hit -[no]-> fld
159
+ edge hit -[yes]-> same
160
+ edge same -[yes]-> flt
161
+ edge same -[no]-> fwd
162
+ ```
163
+
164
+ **Human sees:** a frame from the ingress port → source-MAC learning →
165
+ destination lookup → two decisions branching into three colour-coded outcomes.
166
+
167
+ **An agent answers from the text alone:**
168
+
169
+ - *Q: When does the switch flood?* A: on a lookup miss — `hit -[no]-> fld`, and
170
+ `fld` is the `flood` outcome (unknown-unicast and broadcast both take it).
171
+ - *Q: A hit whose destination is behind the ingress port?* A: filtered
172
+ (dropped) — `same -[yes]-> flt`, class `filter`.
173
+ - *Q: Does learning happen before or after lookup?* A: before — the chain is
174
+ `wire -> learn -> lookup`.
175
+
176
+ Closed-world: the three `class`-marked terminals are the *only* outcomes; there
177
+ is no unstated fourth path.
178
+
179
+ ---
180
+
181
+ ## 4. Ethernet II frame — byte order carried by syntax (`table`)
182
+
183
+ ![Ethernet frame](examples/showcase/ethernet-frame.svg)
184
+
185
+ ```figdown
186
+ figdown 0.1 table
187
+ title Ethernet II Frame — Untagged vs 802.1Q Tagged
188
+
189
+ class tag "802.1Q VLAN tag (4 bytes), inserted after Source MAC; absent in an untagged frame" color=#dbeafe
190
+
191
+ table eth "Ethernet II frame — column order = wire order (byte counts in the second header tier)"
192
+ | Variant | Destination MAC | Source MAC | 802.1Q Tag | EtherType | Payload | FCS |
193
+ | (frame) | (6 bytes) | (6 bytes) | (4 bytes) | (2 bytes) | (46-1500 bytes) | (4 bytes) |
194
+ |----------|-----------------|------------|-------------|-----------|-----------------|-----------|
195
+ | Untagged | DA | SA | (none) | Type | Data | CRC-32 |
196
+ | Tagged | DA | SA | TPID 0x8100 + PCP/DEI/VID | Type | Data | CRC-32 |
197
+ colw 90 auto auto auto auto auto auto
198
+ cell 1,4 class=tag
199
+ cell 2,4 class=tag
200
+ ```
201
+
202
+ **Human sees:** the frame in wire order left-to-right, two variants (untagged /
203
+ 802.1Q-tagged), the tag column highlighted at its insertion point.
204
+
205
+ **An agent answers from the text alone:**
206
+
207
+ - *Q: What field follows Source MAC on a tagged frame?* A: the 802.1Q Tag
208
+ (column 4, `class=tag`) — column order is wire order.
209
+ - *Q: How many bytes does the tag add?* A: 4 (header tier `(4 bytes)` + class
210
+ label).
211
+ - *Q: Untagged vs tagged?* A: the tag column is `(none)` vs
212
+ `TPID 0x8100 + PCP/DEI/VID` — absent, not zeroed (class label).
213
+
214
+ The OQ-S24 sanctioned pattern: a byte-unit packet is a single-row `table`, so
215
+ byte order lives in cell order (strip test survives), not a bit ruler.
216
+
217
+ **One fact this figure cannot draw:** the FCS (CRC-32) is computed over
218
+ Destination MAC through Payload inclusive — a coverage span across a
219
+ *contiguous run of columns*. FigDown has no construct to annotate such a span
220
+ (curly-brace grouping over a contiguous cell range is **OQ-S16**, v0.2), so the
221
+ fact is stated in the `.fd` source comment and here in prose, never on geometry.
222
+
223
+ ---
224
+
225
+ ## 5. ARP resolution — boundary + class discipline (`topology`)
226
+
227
+ ![ARP resolution](examples/showcase/arp-resolution.svg)
228
+
229
+ ```figdown
230
+ figdown 0.1 topology
231
+ title ARP Resolution
232
+
233
+ class bcast "Broadcast — delivered to every host on the LAN (dst MAC ff:ff:ff:ff:ff:ff)" color=#dc2626 style=dashed
234
+ class ucast "Unicast — delivered only to the requester (dst MAC = A's MAC)" color=#16a34a
235
+
236
+ node a "Host A\nwants MAC for B's IP"
237
+ node b "Host B\nowns the target IP"
238
+
239
+ boundary lan "rest of the LAN\n(hosts C, D, ...)"
240
+
241
+ flow right
242
+
243
+ edge a -[1: who-has B? (broadcast)]-> b class=bcast
244
+ edge a -[1: heard, ignored]-> lan class=bcast
245
+ edge b -[2: B is at aa:bb:cc:... A caches it]-> a class=ucast
246
+
247
+ table cache "Host A's ARP cache — before and after the exchange"
248
+ | Moment | Entry |
249
+ |--------------------|-----------------------------------------|
250
+ | before the exchange| (no entry for B's IP) |
251
+ | after segment 2 | B's IP → aa:bb:cc:... , learned from the reply |
252
+ ```
253
+
254
+ This too is a **mixed document** (scene + table in one `.fd`, document order):
255
+ the cache *transition* is the figure's actual outcome, but it is a local state
256
+ change on A rather than a message, so the companion table carries the
257
+ before/after cache state machine-readably.
258
+
259
+ **Human sees:** A broadcasts who-has (reaching B; the rest of the LAN hears it
260
+ and ignores it), B replies by unicast and A caches the binding — broadcast
261
+ dashed-red, unicast green, "rest of the LAN" an open boundary — with a cache
262
+ table below showing the before/after state. The cache update is a local action
263
+ on A, not a message, so it rides on the reply label, the cache table, and this
264
+ prose rather than on an edge of its own.
265
+
266
+ **An agent answers from the text alone:**
267
+
268
+ - *Q: Broadcast or unicast request, and how do you know?* A: broadcast — the
269
+ request edges are `class=bcast` ("delivered to every host … dst MAC ff:…ff").
270
+ The dashed red is only presentation; the meaning is the class label.
271
+ - *Q: Who receives the reply, and what does A do with it?* A: only Host A —
272
+ reply edge `class=ucast`, directed `b -> a`; the label states A caches the
273
+ IP → MAC binding on receipt.
274
+ - *Q: Is "rest of the LAN" a participating host?* A: no — a `boundary`, stating
275
+ only that the broadcast crosses the figure's edge.
276
+ - *Q: What does A's cache hold before the exchange?* A: nothing — row 1 of the
277
+ cache table (`before the exchange` → `(no entry for B's IP)`). Answerable only
278
+ because the companion table carries the before-state explicitly.
279
+
280
+ > **Honest limit:** step order (1/2) rides on label ordinals, pending the v0.2
281
+ > sequence genre (OQ-S18).
282
+
283
+ ---
284
+
285
+ ## 6. TCP connection state machine — every state, every transition (`flowchart`)
286
+
287
+ ![TCP state machine](examples/showcase/tcp-state-machine.svg)
288
+
289
+ ```figdown
290
+ figdown 0.1 flowchart
291
+ title TCP Connection State Machine (RFC 9293, Figure 5)
292
+
293
+ class states "A node IS a TCP state (RFC 9293 §3.3.2); the two CLOSED nodes are one state — no node-identity construct yet (OQ-S26)" color=#eef2ff
294
+ class setup "Connection-setup transition — opening the connection (OPEN, SYN exchange, first ACK)" color=#2563eb
295
+ class client "Active-close path (typically the client) — calls CLOSE first: FIN-WAIT-1 → FIN-WAIT-2 / CLOSING → TIME-WAIT → CLOSED" color=#dc2626
296
+ class server "Passive-close path (typically the server) — receives the peer's FIN first: CLOSE-WAIT → LAST-ACK → CLOSED" color=#16a34a
297
+ class rare "Rare / simultaneous transition — simultaneous open or close, or a reset/abort (RST, close from a half-open state)" color=#9333ea style=dashed
298
+
299
+ node closed "CLOSED" shape=rounded class=states
300
+ node listen "LISTEN" shape=rounded class=states
301
+ node synsent "SYN-SENT" shape=rounded class=states
302
+ node synrcvd "SYN-RECEIVED" shape=rounded class=states
303
+ node estab "ESTABLISHED" shape=rounded class=states
304
+ node fw1 "FIN-WAIT-1" shape=rounded class=states
305
+ node fw2 "FIN-WAIT-2" shape=rounded class=states
306
+ node closing "CLOSING" shape=rounded class=states
307
+ node closewait "CLOSE-WAIT" shape=rounded class=states
308
+ node lastack "LAST-ACK" shape=rounded class=states
309
+ node timewait "TIME-WAIT" shape=rounded class=states
310
+ node closed2 "CLOSED" shape=rounded class=states
311
+
312
+ flow down
313
+
314
+ edge closed -[passive OPEN / create TCB]-> listen class=setup
315
+ edge closed -[active OPEN / create TCB, snd SYN]-> synsent class=setup
316
+ edge listen -[SEND / snd SYN]-> synsent class=setup
317
+ edge listen -[rcv SYN / snd SYN,ACK]-> synrcvd class=setup
318
+ edge synsent -[rcv SYN / snd SYN,ACK]-> synrcvd class=rare
319
+ edge synsent -[rcv SYN,ACK / snd ACK]-> estab class=setup
320
+ edge synrcvd -[rcv ACK of SYN / x]-> estab class=setup
321
+
322
+ edge listen -[CLOSE / delete TCB]-> closed class=rare
323
+ edge synsent -[CLOSE / delete TCB]-> closed class=rare
324
+ edge synrcvd -[rcv RST (note 1) / x]-> listen class=rare
325
+
326
+ edge synrcvd -[CLOSE / snd FIN]-> fw1 class=client
327
+ edge estab -[CLOSE / snd FIN]-> fw1 class=client
328
+ edge fw1 -[rcv ACK of FIN / x]-> fw2 class=client
329
+ edge fw1 -[rcv FIN / snd ACK]-> closing class=client
330
+ edge fw1 -[rcv FIN,ACK / snd ACK]-> timewait class=rare
331
+ edge fw2 -[rcv FIN / snd ACK]-> timewait class=client
332
+ edge closing -[rcv ACK of FIN / x]-> timewait class=client
333
+ edge timewait -[Timeout=2MSL / delete TCB]-> closed2 class=client
334
+
335
+ edge estab -[rcv FIN / snd ACK]-> closewait class=server
336
+ edge closewait -[CLOSE / snd FIN]-> lastack class=server
337
+ edge lastack -[rcv ACK of FIN / x]-> closed2 class=server
338
+
339
+ render
340
+ pin closed at=394,20
341
+ pin listen at=394,150
342
+ pin synrcvd at=152,280
343
+ pin synsent at=607,280
344
+ pin estab at=376,410
345
+ pin fw1 at=159,540
346
+ pin closewait at=600,540
347
+ pin fw2 at=159,670
348
+ pin closing at=311,670
349
+ pin lastack at=607,670
350
+ pin timewait at=304,800
351
+ pin closed2 at=614,800
352
+ route closed -> synsent via=790,38;790,298
353
+ route listen -> closed via=300,105
354
+ route synsent -> closed via=640,120
355
+ ```
356
+
357
+ The `pin`/`route` block reproduces the canonical Figure 5 arrangement so a
358
+ reader who knows the RFC diagram recognises it — but it is **presentation
359
+ only** (`r25-check --strict` passes): strip every line after `render` and all
360
+ 11 states, all 21 transitions and their `event / action` labels survive on the
361
+ `node`/`edge`/`class` lines. All meaning is in the edges; the pins are layout.
362
+
363
+ **Human sees:** the whole RFC 9293 state diagram — CLOSED at top, the SYN pair
364
+ flanking, ESTABLISHED centred, the active-close column falling left and the
365
+ passive-close column right — four colour-coded transition families (blue setup,
366
+ red active-close, green passive-close, purple rare/simultaneous dashed), every
367
+ arrow carrying its `event / action`.
368
+
369
+ **An agent answers from the text alone:**
370
+
371
+ - *Q: What event takes ESTABLISHED to CLOSE-WAIT, and what does the endpoint
372
+ send?* A: `rcv FIN / snd ACK` — one edge label
373
+ (`edge estab -[rcv FIN / snd ACK]-> closewait`).
374
+ - *Q: From FIN-WAIT-1, how many ways lead to TIME-WAIT and under what
375
+ conditions?* A: two. Directly, `rcv FIN,ACK / snd ACK` (the RFC Note-2
376
+ transition, `fw1 -> timewait`); and via FIN-WAIT-2, `rcv ACK of FIN / x` then
377
+ `rcv FIN / snd ACK` (`fw1 -> fw2 -> timewait`). A third path runs via CLOSING
378
+ (`rcv FIN / snd ACK` then `rcv ACK of FIN / x`). All read off the edges.
379
+ - *Q: How long does TIME-WAIT last and what happens after?* A: `Timeout=2MSL`,
380
+ then `delete TCB` → CLOSED (`timewait -[Timeout=2MSL / delete TCB]-> closed2`).
381
+ - *Q: Which transitions belong to the typical server (passive-close) path?*
382
+ A: the three `class=server` edges — `ESTABLISHED → CLOSE-WAIT`,
383
+ `CLOSE-WAIT → LAST-ACK`, `LAST-ACK → CLOSED` — enumerated by querying the
384
+ class, whose label spells the path out.
385
+
386
+ > **Honest limit (stated in the source):** a node *is* a state is not yet a
387
+ > machine-declarable fact — FigDown has no state genre (the R49 §6 candidate),
388
+ > so "this node is a protocol state" rides on the `states` class label, interim.
389
+ > And CLOSED is drawn **twice** (top origin, bottom terminus) because FigDown
390
+ > has no node-identity/alias construct (**OQ-S26**): the two `closed`/`closed2`
391
+ > nodes are declared one state only by the shared class label, not machine-
392
+ > readably. Everything else — all 11 states, all 21 labelled transitions
393
+ > including the one the canonical figure omits in its own Note 2 — the edges
394
+ > carry in full.
395
+
396
+ ---
397
+
398
+ ## The honest limits, in one paragraph
399
+
400
+ Two figures depend on a convention the language does not yet make first-class:
401
+ **message/step ordering** in the handshake and ARP is carried by numbering edge
402
+ labels `1:`/`2:`/`3:`, which R37 treats as naming, not semantics — the v0.2
403
+ sequence genre (OQ-S18) will fix it, and the sources say so. Relatedly, the RFC
404
+ 9293 handshake carries **per-endpoint lifeline state** (LISTEN, SYN-SENT, …)
405
+ that the topology genre has no construct for; the companion state table carries
406
+ it as composition (a ruled interim, again pending the sequence genre, OQ-S18).
407
+ The Ethernet frame uses the **OQ-S24** byte-unit workaround (single-row `table`,
408
+ not `bitfield`) so byte order rides on cell order, and one of its facts — the
409
+ FCS **coverage span** over a contiguous run of columns (DA through Payload) —
410
+ has no annotation construct yet (**OQ-S16**), so it rides on a source comment
411
+ and prose. The TCP state machine meets two more: **state-ness is not first-class**
412
+ — a node *is* a state carries as the `states` class label until the state genre
413
+ lands (the R49 §6 candidate) — and CLOSED is drawn **twice** because FigDown has
414
+ no **node-identity/alias** construct (**OQ-S26**) to declare two nodes the same
415
+ entity; the shared class label asserts it instead. Every state and every labelled
416
+ transition still rides on an edge, so those two are the *only* facts the figure
417
+ cannot carry that the canonical drawing implies. All are documented in
418
+ [EXPRESSING.md](EXPRESSING.md)'s "Known limits".
419
+ That a figure format states its own limits — and that a reading agent recovers
420
+ exactly what the text declares and nothing the geometry merely implies — is the
421
+ claim these figures make.