figdown 0.4.0 → 0.5.0

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.
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 902 984" width="902" height="984" font-family="system-ui,sans-serif"><defs><marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#555"/></marker><pattern id="hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="6" stroke="#bbb" stroke-width="2"/></pattern></defs><g transform="translate(18,6)"><line data-edge="92" x1="433.1" y1="76" x2="433.1" y2="190" stroke="#2563eb" stroke-width="1.6"/><line data-edge="93" x1="452.7652886772431" y1="73.34906513439799" x2="724.2052886772431" y2="307.34906513439796" stroke="#2563eb" stroke-width="1.6"/><line data-edge="94" x1="465.20000000000005" y1="221.63984674329504" x2="700" y2="311.6015325670498" stroke="#2563eb" stroke-width="1.6"/><line data-edge="95" x1="395.5028227438559" y1="228.0864522267111" x2="216.56282274385592" y2="313.1609371712436" stroke="#2563eb" stroke-width="1.6"/><line data-edge="96" x1="700" y1="328" x2="234.39999999999998" y2="328" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="97" x1="700" y1="344.3984674329502" x2="476.58000000000004" y2="430" stroke="#2563eb" stroke-width="1.6"/><line data-edge="98" x1="215.06" y1="346" x2="391.74" y2="430" stroke="#2563eb" stroke-width="1.6"/><line data-edge="101" x1="426.1" y1="190" x2="426.1" y2="76" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="102" x1="719.6347113227569" y1="312.65093486560204" x2="448.1947113227569" y2="78.65093486560201" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="103" x1="213.5571772561441" y1="306.8390628287564" x2="392.4971772561441" y2="221.76457788422394" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="106" x1="179.4896" y1="346" x2="206.7104" y2="560" stroke="#dc2626" stroke-width="1.6"/><line data-edge="107" x1="399.05538461538464" y1="466" x2="239.54461538461538" y2="560" stroke="#dc2626" stroke-width="1.6"/><line data-edge="108" x1="209" y1="596" x2="209" y2="690" stroke="#dc2626" stroke-width="1.6"/><line data-edge="109" x1="228.55076923076922" y1="596" x2="330.64923076923077" y2="690" stroke="#dc2626" stroke-width="1.6"/><line data-edge="110" x1="218.78923076923076" y1="596" x2="340.6107692307692" y2="820" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="111" x1="228.57846153846154" y1="726" x2="330.82153846153847" y2="820" stroke="#dc2626" stroke-width="1.6"/><line data-edge="112" x1="350.2276923076923" y1="726" x2="350.3723076923077" y2="820" stroke="#dc2626" stroke-width="1.6"/><line data-edge="113" x1="396.79999999999995" y1="838" x2="697" y2="838" stroke="#dc2626" stroke-width="1.6"/><line data-edge="116" x1="472.57846153846157" y1="466" x2="697.0215384615385" y2="560" stroke="#16a34a" stroke-width="1.6"/><line data-edge="117" x1="739.003076923077" y1="596" x2="733.796923076923" y2="690" stroke="#16a34a" stroke-width="1.6"/><line data-edge="118" x1="732.7723076923077" y1="726" x2="732.6276923076923" y2="820" stroke="#16a34a" stroke-width="1.6"/><g data-node="closed" data-x="394" data-y="40" style="cursor:move"><rect x="394" y="40" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="429.6" y="62.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSED</text></g><g data-node="listen" data-x="394" data-y="190" style="cursor:move"><rect x="394" y="190" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="429.6" y="212.55" font-size="13" text-anchor="middle" fill="#1d1d1b">LISTEN</text></g><g data-node="synsent" data-x="700" data-y="310" style="cursor:move"><rect x="700" y="310" width="85.6" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="742.8" y="332.55" font-size="13" text-anchor="middle" fill="#1d1d1b">SYN-SENT</text></g><g data-node="synrcvd" data-x="120" data-y="310" style="cursor:move"><rect x="120" y="310" width="114.4" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="177.2" y="332.55" font-size="13" text-anchor="middle" fill="#1d1d1b">SYN-RECEIVED</text></g><g data-node="estab" data-x="376" data-y="430" style="cursor:move"><rect x="376" y="430" width="107.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="429.6" y="452.55" font-size="13" text-anchor="middle" fill="#1d1d1b">ESTABLISHED</text></g><g data-node="fw1" data-x="159" data-y="560" style="cursor:move"><rect x="159" y="560" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="209" y="582.55" font-size="13" text-anchor="middle" fill="#1d1d1b">FIN-WAIT-1</text></g><g data-node="fw2" data-x="159" data-y="690" style="cursor:move"><rect x="159" y="690" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="209" y="712.55" font-size="13" text-anchor="middle" fill="#1d1d1b">FIN-WAIT-2</text></g><g data-node="closing" data-x="311" data-y="690" style="cursor:move"><rect x="311" y="690" width="78.4" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="350.2" y="712.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSING</text></g><g data-node="closewait" data-x="690" data-y="560" style="cursor:move"><rect x="690" y="560" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="740" y="582.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSE-WAIT</text></g><g data-node="lastack" data-x="690" data-y="690" style="cursor:move"><rect x="690" y="690" width="85.6" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="732.8" y="712.55" font-size="13" text-anchor="middle" fill="#1d1d1b">LAST-ACK</text></g><g data-node="timewait" data-x="304" data-y="820" style="cursor:move"><rect x="304" y="820" width="92.8" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="350.4" y="842.55" font-size="13" text-anchor="middle" fill="#1d1d1b">TIME-WAIT</text></g><g data-node="closed2" data-x="697" data-y="820" style="cursor:move"><rect x="697" y="820" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="732.6" y="842.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSED</text></g><text x="439.1" y="136.3" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">passive OPEN / create TCB</text><text x="439.1" y="136.3" font-size="11" text-anchor="start" fill="#2563eb">passive OPEN / create TCB</text><path d="M433.1 190 L427.5 179.92 L438.70000000000005 179.92 z" fill="#2563eb" stroke="none"/><text x="588.485288677243" y="92.14216858267382" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">active OPEN / create TCB, snd SYN</text><text x="588.485288677243" y="92.14216858267382" font-size="11" text-anchor="middle" fill="#2563eb">active OPEN / create TCB, snd SYN</text><path d="M724.2052886772431 307.34906513439796 L712.9141343807203 305.008929528901 L720.2270581478983 296.52593795897457 z" fill="#2563eb" stroke="none"/><text x="582.6" y="243.43773946360156" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">SEND / snd SYN</text><text x="582.6" y="243.43773946360156" font-size="11" text-anchor="middle" fill="#2563eb">SEND / snd SYN</text><path d="M700 311.6015325670498 L688.5836683972988 313.224422808922 L692.5908047969832 302.76579680574537 z" fill="#2563eb" stroke="none"/><text x="306.03282274385595" y="315.42218915222617" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN / snd SYN,ACK</text><text x="306.03282274385595" y="315.42218915222617" font-size="11" text-anchor="middle" fill="#2563eb">rcv SYN / snd SYN,ACK</text><path d="M216.56282274385592 313.1609371712436 L223.261805406868 303.7753081949488 L228.0708381872069 313.89030714292835 z" fill="#2563eb" stroke="none"/><text x="467.2" y="322.25" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN / snd SYN,ACK</text><text x="467.2" y="322.25" font-size="11" text-anchor="middle" fill="#9333ea">rcv SYN / snd SYN,ACK</text><path d="M234.39999999999998 328 L244.48 322.4 L244.48 333.6 z" fill="#9333ea" stroke="none"/><text x="588.29" y="355.29980842911874" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN,ACK / snd ACK</text><text x="588.29" y="355.29980842911874" font-size="11" text-anchor="middle" fill="#2563eb">rcv SYN,ACK / snd ACK</text><path d="M476.58000000000004 430 L483.9891952030168 421.1642642386956 L487.99633160270133 431.6228902418722 z" fill="#2563eb" stroke="none"/><text x="303.4" y="354.43700475435816" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of SYN / x</text><text x="303.4" y="354.43700475435816" font-size="11" text-anchor="middle" fill="#2563eb">rcv ACK of SYN / x</text><path d="M391.74 430 L380.231984556649 430.7293699716848 L385.041017336988 420.61437102370525 z" fill="#2563eb" stroke="none"/><text x="303.1" y="136.3" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / delete TCB</text><text x="303.1" y="136.3" font-size="11" text-anchor="start" fill="#9333ea">CLOSE / delete TCB</text><path d="M426.1 76 L431.70000000000005 86.08 L420.5 86.08 z" fill="#9333ea" stroke="none"/><text x="524.1979113227569" y="206.95196934836065" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / delete TCB</text><text x="524.1979113227569" y="206.95196934836065" font-size="11" text-anchor="middle" fill="#9333ea">CLOSE / delete TCB</text><path d="M448.1947113227569 78.65093486560201 L459.4858656192797 80.99107047109896 L452.17294185210176 89.47406204102543 z" fill="#9333ea" stroke="none"/><text x="303.0271772561441" y="227.648492305777" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv RST (note 1) / x</text><text x="303.0271772561441" y="227.648492305777" font-size="11" text-anchor="middle" fill="#9333ea">rcv RST (note 1) / x</text><path d="M392.4971772561441 221.76457788422394 L385.79819459313205 231.1502068605187 L380.9891618127931 221.03520791253922 z" fill="#9333ea" stroke="none"/><text x="199.1" y="456.3" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="199.1" y="456.3" font-size="11" text-anchor="start" fill="#dc2626">CLOSE / snd FIN</text><path d="M206.7104 560 L199.88323361107894 550.7071963811783 L210.99371139541864 549.2939436070102 z" fill="#dc2626" stroke="none"/><text x="319.3" y="478.52153218495016" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="319.3" y="478.52153218495016" font-size="11" text-anchor="middle" fill="#dc2626">CLOSE / snd FIN</text><path d="M239.54461538461538 560 L245.38572668223014 550.0577759626491 L251.0719964792192 559.7069384027706 z" fill="#dc2626" stroke="none"/><text x="86" y="646.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="86" y="646.3000000000001" font-size="11" text-anchor="start" fill="#dc2626">rcv ACK of FIN / x</text><path d="M209 690 L203.4 679.92 L214.6 679.92 z" fill="#dc2626" stroke="none"/><text x="302.0616615384615" y="607.0624362606231" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="302.0616615384615" y="607.0624362606231" font-size="11" text-anchor="middle" fill="#dc2626">rcv FIN / snd ACK</text><path d="M330.64923076923077 690 L319.4405255167678 687.2923577482596 L327.02659217035057 679.0527222752913 z" fill="#dc2626" stroke="none"/><text x="300.3185846153846" y="738.1800000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN,ACK / snd ACK</text><text x="300.3185846153846" y="738.1800000000001" font-size="11" text-anchor="start" fill="#9333ea">rcv FIN,ACK / snd ACK</text><path d="M340.6107692307692 820 L330.875382512483 813.820303774018 L340.7144579488292 808.469360440559 z" fill="#9333ea" stroke="none"/><text x="257.2065230769231" y="815.4656152758133" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="257.2065230769231" y="815.4656152758133" font-size="11" text-anchor="middle" fill="#dc2626">rcv FIN / snd ACK</text><path d="M330.82153846153847 820 L319.61092639723944 817.3002635047485 L327.19118005561285 809.0552799101793 z" fill="#dc2626" stroke="none"/><text x="356.29999999999995" y="776.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="356.29999999999995" y="776.3000000000001" font-size="11" text-anchor="start" fill="#dc2626">rcv ACK of FIN / x</text><path d="M350.3723076923077 820 L344.75680664555944 809.9286273033927 L355.9567933911451 809.9113965545532 z" fill="#dc2626" stroke="none"/><text x="546.9" y="832.25" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">Timeout=2MSL / delete TCB</text><text x="546.9" y="832.25" font-size="11" text-anchor="middle" fill="#dc2626">Timeout=2MSL / delete TCB</text><path d="M697 838 L686.92 843.6 L686.92 832.4 z" fill="#dc2626" stroke="none"/><text x="584.8000000000001" y="484.1105025773196" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="584.8000000000001" y="484.1105025773196" font-size="11" text-anchor="middle" fill="#16a34a">rcv FIN / snd ACK</text><path d="M697.0215384615385 560 L685.5607329004544 561.2713519933599 L689.8873234537733 550.9407850106658 z" fill="#16a34a" stroke="none"/><text x="742.4" y="646.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="742.4" y="646.3000000000001" font-size="11" text-anchor="start" fill="#16a34a">CLOSE / snd FIN</text><path d="M733.796923076923 690 L728.7629148866224 679.6257452537551 L739.9457765533873 680.2451037460681 z" fill="#16a34a" stroke="none"/><text x="738.7" y="776.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="738.7" y="776.3000000000001" font-size="11" text-anchor="start" fill="#16a34a">rcv ACK of FIN / x</text><path d="M732.6276923076923 820 L727.0432066088549 809.9113965545532 L738.2431933544406 809.9286273033927 z" fill="#16a34a" stroke="none"/><rect x="0" y="877" width="16" height="11" fill="#eef2ff" stroke="#555"/><text x="21" y="886.5" font-size="11" fill="#1d1d1b">A state IS a TCP connection state (RFC 9293 §3.3.2); the two CLOSED states are one state drawn twice — only this label says so</text><rect x="0" y="897" width="16" height="11" fill="#fff" stroke="#2563eb"/><text x="21" y="906.5" font-size="11" fill="#1d1d1b">Connection-setup transition — opening the connection (OPEN, SYN exchange, first ACK)</text><rect x="0" y="917" width="16" height="11" fill="#fff" stroke="#dc2626"/><text x="21" y="926.5" font-size="11" fill="#1d1d1b">Active-close path (typically the client) — calls CLOSE first: FIN-WAIT-1 → FIN-WAIT-2 / CLOSING → TIME-WAIT → CLOSED</text><rect x="0" y="937" width="16" height="11" fill="#fff" stroke="#16a34a"/><text x="21" y="946.5" font-size="11" fill="#1d1d1b">Passive-close path (typically the server) — receives the peer's FIN first: CLOSE-WAIT → LAST-ACK → CLOSED</text><rect x="0" y="957" width="16" height="11" fill="#fff" stroke="#9333ea" stroke-dasharray="6 4"/><text x="21" y="966.5" font-size="11" fill="#1d1d1b">Rare / simultaneous transition — simultaneous open or close, or a reset/abort (RST, close from a half-open state)</text></g><metadata id="figdown-source" data-sha256="107a61548ef549f76483c5a2836aaf38e1021f8d9a29b82c48bf199d34aa7858" data-engine-version="0.4.0"><![CDATA[
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 902 984" width="902" height="984" font-family="system-ui,sans-serif"><defs><marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#555"/></marker><pattern id="hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="6" stroke="#bbb" stroke-width="2"/></pattern></defs><g transform="translate(18,6)"><line data-edge="92" x1="529.1525051498933" y1="77.63902772944337" x2="442.2325051498932" y2="241.63902772944337" stroke="#2563eb" stroke-width="1.6"/><line data-edge="93" x1="552.1899621776382" y1="73.86919445725937" x2="731.7632955109716" y2="307.86919445725937" stroke="#2563eb" stroke-width="1.6"/><line data-edge="94" x1="465.20000000000005" y1="265.9565772669221" x2="700" y2="318.43422733077904" stroke="#2563eb" stroke-width="1.6"/><line data-edge="95" x1="394.93537507812675" y1="271.24591239739203" x2="235.3353750781267" y2="315.50898688233656" stroke="#2563eb" stroke-width="1.6"/><line data-edge="96" x1="700" y1="328" x2="234.39999999999998" y2="328" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="97" x1="700" y1="344.3984674329502" x2="476.58000000000004" y2="430" stroke="#2563eb" stroke-width="1.6"/><line data-edge="98" x1="215.06" y1="346" x2="391.74" y2="430" stroke="#2563eb" stroke-width="1.6"/><line data-edge="101" x1="436.0474948501069" y1="238.36097227055663" x2="522.9674948501068" y2="74.36097227055663" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="102" x1="726.2100378223618" y1="312.13080554274063" x2="546.6367044890284" y2="78.13080554274063" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="103" x1="233.46462492187325" y1="308.76359631893126" x2="393.06462492187325" y2="264.50052183398674" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="106" x1="179.4896" y1="346" x2="206.7104" y2="560" stroke="#dc2626" stroke-width="1.6"/><line data-edge="107" x1="399.05538461538464" y1="466" x2="239.54461538461538" y2="560" stroke="#dc2626" stroke-width="1.6"/><line data-edge="108" x1="209" y1="596" x2="209" y2="690" stroke="#dc2626" stroke-width="1.6"/><line data-edge="109" x1="259" y1="595.3761946133797" x2="400" y2="644.3770634231104" stroke="#dc2626" stroke-width="1.6"/><line data-edge="110" x1="218.78923076923076" y1="596" x2="340.6107692307692" y2="820" stroke="#9333ea" stroke-width="1.6" stroke-dasharray="6 4"/><line data-edge="111" x1="228.57846153846154" y1="726" x2="330.82153846153847" y2="820" stroke="#dc2626" stroke-width="1.6"/><line data-edge="112" x1="430.32" y1="676" x2="359.28" y2="820" stroke="#dc2626" stroke-width="1.6"/><line data-edge="113" x1="396.79999999999995" y1="838" x2="697" y2="838" stroke="#dc2626" stroke-width="1.6"/><line data-edge="116" x1="472.57846153846157" y1="466" x2="697.0215384615385" y2="560" stroke="#16a34a" stroke-width="1.6"/><line data-edge="117" x1="739.003076923077" y1="596" x2="733.796923076923" y2="690" stroke="#16a34a" stroke-width="1.6"/><line data-edge="118" x1="732.7723076923077" y1="726" x2="732.6276923076923" y2="820" stroke="#16a34a" stroke-width="1.6"/><g data-node="closed" data-x="500" data-y="40" style="cursor:move"><rect x="500" y="40" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="535.6" y="62.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSED</text></g><g data-node="listen" data-x="394" data-y="240" style="cursor:move"><rect x="394" y="240" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="429.6" y="262.55" font-size="13" text-anchor="middle" fill="#1d1d1b">LISTEN</text></g><g data-node="synsent" data-x="700" data-y="310" style="cursor:move"><rect x="700" y="310" width="85.6" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="742.8" y="332.55" font-size="13" text-anchor="middle" fill="#1d1d1b">SYN-SENT</text></g><g data-node="synrcvd" data-x="120" data-y="310" style="cursor:move"><rect x="120" y="310" width="114.4" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="177.2" y="332.55" font-size="13" text-anchor="middle" fill="#1d1d1b">SYN-RECEIVED</text></g><g data-node="estab" data-x="376" data-y="430" style="cursor:move"><rect x="376" y="430" width="107.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="429.6" y="452.55" font-size="13" text-anchor="middle" fill="#1d1d1b">ESTABLISHED</text></g><g data-node="fw1" data-x="159" data-y="560" style="cursor:move"><rect x="159" y="560" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="209" y="582.55" font-size="13" text-anchor="middle" fill="#1d1d1b">FIN-WAIT-1</text></g><g data-node="fw2" data-x="159" data-y="690" style="cursor:move"><rect x="159" y="690" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="209" y="712.55" font-size="13" text-anchor="middle" fill="#1d1d1b">FIN-WAIT-2</text></g><g data-node="closing" data-x="400" data-y="640" style="cursor:move"><rect x="400" y="640" width="78.4" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="439.2" y="662.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSING</text></g><g data-node="closewait" data-x="690" data-y="560" style="cursor:move"><rect x="690" y="560" width="100" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="740" y="582.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSE-WAIT</text></g><g data-node="lastack" data-x="690" data-y="690" style="cursor:move"><rect x="690" y="690" width="85.6" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="732.8" y="712.55" font-size="13" text-anchor="middle" fill="#1d1d1b">LAST-ACK</text></g><g data-node="timewait" data-x="304" data-y="820" style="cursor:move"><rect x="304" y="820" width="92.8" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="350.4" y="842.55" font-size="13" text-anchor="middle" fill="#1d1d1b">TIME-WAIT</text></g><g data-node="closed2" data-x="697" data-y="820" style="cursor:move"><rect x="697" y="820" width="71.2" height="36" rx="14" fill="#eef2ff" stroke="#8a8880" stroke-width="1.8"/><text x="732.6" y="842.55" font-size="13" text-anchor="middle" fill="#1d1d1b">CLOSED</text></g><text x="472.5701051498932" y="199.01902772944337" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">passive OPEN / create TCB</text><text x="472.5701051498932" y="199.01902772944337" font-size="11" text-anchor="start" fill="#2563eb">passive OPEN / create TCB</text><path d="M442.2325051498932 241.63902772944337 L442.0048967708611 230.11016853064166 L451.9009132505192 235.35505726486048 z" fill="#2563eb" stroke="none"/><text x="647.976628844305" y="194.16919445725935" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">active OPEN / create TCB, snd SYN</text><text x="647.976628844305" y="194.16919445725935" font-size="11" text-anchor="start" fill="#2563eb">active OPEN / create TCB, snd SYN</text><path d="M731.7632955109716 307.86919445725937 L721.1839693969908 303.28179225404637 L730.0691816987663 296.4632145172763 z" fill="#2563eb" stroke="none"/><text x="582.6" y="276.2761813537676" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">SEND / snd SYN</text><text x="582.6" y="276.2761813537676" font-size="11" text-anchor="middle" fill="#2563eb">SEND / snd SYN</text><path d="M700 318.43422733077904 L688.9412412620329 321.7007629842558 L691.3841632251031 310.7704321437758 z" fill="#2563eb" stroke="none"/><text x="315.13537507812674" y="324.6557380709261" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN / snd SYN,ACK</text><text x="315.13537507812674" y="324.6557380709261" font-size="11" text-anchor="middle" fill="#2563eb">rcv SYN / snd SYN,ACK</text><path d="M235.3353750781267 315.50898688233656 L243.5521373644276 307.4187942066073 L246.54533761443315 318.21141910805585 z" fill="#2563eb" stroke="none"/><text x="467.2" y="322.25" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN / snd SYN,ACK</text><text x="467.2" y="322.25" font-size="11" text-anchor="middle" fill="#9333ea">rcv SYN / snd SYN,ACK</text><path d="M234.39999999999998 328 L244.48 322.4 L244.48 333.6 z" fill="#9333ea" stroke="none"/><text x="588.29" y="425.69865900383144" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv SYN,ACK / snd ACK</text><text x="588.29" y="425.69865900383144" font-size="11" text-anchor="middle" fill="#2563eb">rcv SYN,ACK / snd ACK</text><path d="M476.58000000000004 430 L483.9891952030168 421.1642642386956 L487.99633160270133 431.6228902418722 z" fill="#2563eb" stroke="none"/><text x="303.4" y="428.16299524564187" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of SYN / x</text><text x="303.4" y="428.16299524564187" font-size="11" text-anchor="middle" fill="#2563eb">rcv ACK of SYN / x</text><path d="M391.74 430 L380.231984556649 430.7293699716848 L385.041017336988 420.61437102370525 z" fill="#2563eb" stroke="none"/><text x="473.50749485010687" y="159.6609722705566" font-size="11" text-anchor="end" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / delete TCB</text><text x="473.50749485010687" y="159.6609722705566" font-size="11" text-anchor="end" fill="#9333ea">CLOSE / delete TCB</text><path d="M522.9674948501068 74.36097227055663 L523.195103229139 85.88983146935831 L513.2990867494809 80.64494273513952 z" fill="#9333ea" stroke="none"/><text x="651.972171155695" y="226.5108055427406" font-size="11" text-anchor="end" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / delete TCB</text><text x="651.972171155695" y="226.5108055427406" font-size="11" text-anchor="end" fill="#9333ea">CLOSE / delete TCB</text><path d="M546.6367044890284 78.13080554274063 L557.2160306030091 82.71820774595366 L548.3308183012336 89.53678548272367 z" fill="#9333ea" stroke="none"/><text x="313.26462492187324" y="262.85511771354294" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv RST (note 1) / x</text><text x="313.26462492187324" y="262.85511771354294" font-size="11" text-anchor="middle" fill="#9333ea">rcv RST (note 1) / x</text><path d="M393.06462492187325 264.50052183398674 L384.84786263557237 272.590714509716 L381.8546623855668 261.79808960826745 z" fill="#9333ea" stroke="none"/><text x="199.1" y="456.3" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="199.1" y="456.3" font-size="11" text-anchor="start" fill="#dc2626">CLOSE / snd FIN</text><path d="M206.7104 560 L199.88323361107894 550.7071963811783 L210.99371139541864 549.2939436070102 z" fill="#dc2626" stroke="none"/><text x="319.3" y="478.52153218495016" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="319.3" y="478.52153218495016" font-size="11" text-anchor="middle" fill="#dc2626">CLOSE / snd FIN</text><path d="M239.54461538461538 560 L245.38572668223014 550.0577759626491 L251.0719964792192 559.7069384027706 z" fill="#dc2626" stroke="none"/><text x="203" y="646.3000000000001" font-size="11" text-anchor="end" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="203" y="646.3000000000001" font-size="11" text-anchor="end" fill="#dc2626">rcv ACK of FIN / x</text><path d="M209 690 L203.4 679.92 L214.6 679.92 z" fill="#dc2626" stroke="none"/><text x="329.5" y="594.9259339704605" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="329.5" y="594.9259339704605" font-size="11" text-anchor="middle" fill="#dc2626">rcv FIN / snd ACK</text><path d="M400 644.3770634231104 L388.6402903993394 646.357820295788 L392.3168692813398 635.778464562832 z" fill="#dc2626" stroke="none"/><text x="258.89926153846153" y="662.0200000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN,ACK / snd ACK</text><text x="258.89926153846153" y="662.0200000000001" font-size="11" text-anchor="start" fill="#9333ea">rcv FIN,ACK / snd ACK</text><path d="M340.6107692307692 820 L330.875382512483 813.820303774018 L340.7144579488292 808.469360440559 z" fill="#9333ea" stroke="none"/><text x="257.2065230769231" y="815.4656152758133" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="257.2065230769231" y="815.4656152758133" font-size="11" text-anchor="middle" fill="#dc2626">rcv FIN / snd ACK</text><path d="M330.82153846153847 820 L319.61092639723944 817.3002635047485 L327.19118005561285 809.0552799101793 z" fill="#dc2626" stroke="none"/><text x="400.79999999999995" y="751.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="400.79999999999995" y="751.3000000000001" font-size="11" text-anchor="start" fill="#dc2626">rcv ACK of FIN / x</text><path d="M359.28 820 L358.7175233490691 808.4826209571293 L368.76174925854934 813.4377724058063 z" fill="#dc2626" stroke="none"/><text x="546.9" y="832.25" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">Timeout=2MSL / delete TCB</text><text x="546.9" y="832.25" font-size="11" text-anchor="middle" fill="#dc2626">Timeout=2MSL / delete TCB</text><path d="M697 838 L686.92 843.6 L686.92 832.4 z" fill="#dc2626" stroke="none"/><text x="584.8000000000001" y="484.1105025773196" font-size="11" text-anchor="middle" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv FIN / snd ACK</text><text x="584.8000000000001" y="484.1105025773196" font-size="11" text-anchor="middle" fill="#16a34a">rcv FIN / snd ACK</text><path d="M697.0215384615385 560 L685.5607329004544 561.2713519933599 L689.8873234537733 550.9407850106658 z" fill="#16a34a" stroke="none"/><text x="742.4" y="646.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">CLOSE / snd FIN</text><text x="742.4" y="646.3000000000001" font-size="11" text-anchor="start" fill="#16a34a">CLOSE / snd FIN</text><path d="M733.796923076923 690 L728.7629148866224 679.6257452537551 L739.9457765533873 680.2451037460681 z" fill="#16a34a" stroke="none"/><text x="738.7" y="776.3000000000001" font-size="11" text-anchor="start" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round">rcv ACK of FIN / x</text><text x="738.7" y="776.3000000000001" font-size="11" text-anchor="start" fill="#16a34a">rcv ACK of FIN / x</text><path d="M732.6276923076923 820 L727.0432066088549 809.9113965545532 L738.2431933544406 809.9286273033927 z" fill="#16a34a" stroke="none"/><rect x="0" y="877" width="16" height="11" fill="#eef2ff" stroke="#555"/><text x="21" y="886.5" font-size="11" fill="#1d1d1b">A state IS a TCP connection state (RFC 9293 §3.3.2); the two CLOSED states are one state drawn twice — only this label says so</text><rect x="0" y="897" width="16" height="11" fill="#fff" stroke="#2563eb"/><text x="21" y="906.5" font-size="11" fill="#1d1d1b">Connection-setup transition — opening the connection (OPEN, SYN exchange, first ACK)</text><rect x="0" y="917" width="16" height="11" fill="#fff" stroke="#dc2626"/><text x="21" y="926.5" font-size="11" fill="#1d1d1b">Active-close path (typically the client) — calls CLOSE first: FIN-WAIT-1 → FIN-WAIT-2 / CLOSING → TIME-WAIT → CLOSED</text><rect x="0" y="937" width="16" height="11" fill="#fff" stroke="#16a34a"/><text x="21" y="946.5" font-size="11" fill="#1d1d1b">Passive-close path (typically the server) — receives the peer's FIN first: CLOSE-WAIT → LAST-ACK → CLOSED</text><rect x="0" y="957" width="16" height="11" fill="#fff" stroke="#9333ea" stroke-dasharray="6 4"/><text x="21" y="966.5" font-size="11" fill="#1d1d1b">Rare / simultaneous transition — simultaneous open or close, or a reset/abort (RST, close from a half-open state)</text></g><metadata id="figdown-source" data-sha256="57b42da67a86c0efb89d82ca6abe85c3fb27e364420cf2dbc7fbeb5682bc4522" data-engine-version="0.5.0"><![CDATA[
2
2
  # FigDown — figures as text. Spec: https://github.com/FigDown/figdown
3
3
 
4
4
  figdown 0.2 statechart
@@ -74,18 +74,18 @@ class server "Passive-close path (typically the server) — receives the peer's
74
74
  class rare "Rare / simultaneous transition — simultaneous open or close, or a reset/abort (RST, close from a half-open state)" stroke=#9333ea style=dashed
75
75
 
76
76
  # ── The 11 states (CLOSED drawn twice; see `IDENTITY-ASSERTION` note above) ────────
77
- state closed "CLOSED" shape=rounded class=states
78
- state listen "LISTEN" shape=rounded class=states
79
- state synsent "SYN-SENT" shape=rounded class=states
80
- state synrcvd "SYN-RECEIVED" shape=rounded class=states
81
- state estab "ESTABLISHED" shape=rounded class=states
82
- state fw1 "FIN-WAIT-1" shape=rounded class=states
83
- state fw2 "FIN-WAIT-2" shape=rounded class=states
84
- state closing "CLOSING" shape=rounded class=states
85
- state closewait "CLOSE-WAIT" shape=rounded class=states
86
- state lastack "LAST-ACK" shape=rounded class=states
87
- state timewait "TIME-WAIT" shape=rounded class=states
88
- state closed2 "CLOSED" shape=rounded class=states
77
+ state closed "CLOSED" class=states
78
+ state listen "LISTEN" class=states
79
+ state synsent "SYN-SENT" class=states
80
+ state synrcvd "SYN-RECEIVED" class=states
81
+ state estab "ESTABLISHED" class=states
82
+ state fw1 "FIN-WAIT-1" class=states
83
+ state fw2 "FIN-WAIT-2" class=states
84
+ state closing "CLOSING" class=states
85
+ state closewait "CLOSE-WAIT" class=states
86
+ state lastack "LAST-ACK" class=states
87
+ state timewait "TIME-WAIT" class=states
88
+ state closed2 "CLOSED" class=states
89
89
 
90
90
  flow down
91
91
 
@@ -128,17 +128,46 @@ layout
128
128
  # list than it used to be.
129
129
  #
130
130
  # The grid reproduces the CANONICAL Figure 5 composition, mirrored about
131
- # one central vertical axis (x≈430, the shared centre of CLOSED, LISTEN
132
- # and ESTABLISHED):
131
+ # one central vertical axis (x≈430, the shared centre of LISTEN and
132
+ # ESTABLISHED):
133
133
  # row0 CLOSED · row1 LISTEN · row2 SYN-RECEIVED | SYN-SENT (a mirrored
134
134
  # pair) · row3 ESTABLISHED · row4 FIN-WAIT-1 | CLOSE-WAIT (same mirror)
135
- # · row5 FIN-WAIT-2, CLOSING, LAST-ACK · row6 TIME-WAIT, CLOSED
135
+ # · row4½ CLOSING · row5 FIN-WAIT-2, LAST-ACK · row6 TIME-WAIT, CLOSED
136
136
  # (terminus, under LAST-ACK as the RFC draws it).
137
- # Column discipline: CLOSED→LISTEN, SYN-RECEIVED→FIN-WAIT-1→FIN-WAIT-2,
138
- # CLOSING→TIME-WAIT, CLOSE-WAIT→LAST-ACK→CLOSED run dead vertical; the
139
- # simultaneous-open transition SYN-SENT→SYN-RECEIVED runs dead horizontal
140
- # between the mirrored pair. All meaning is in the transitions; this is
141
- # layout, not knowledge.
137
+ #
138
+ # COLUMN DISCIPLINE, AS IT ACTUALLY IS. This block used to
139
+ # claim four dead-vertical chains and only two of them were: measuring
140
+ # the centres found SYN-RECEIVED→FIN-WAIT-1 off by 32 px and
141
+ # CLOSE-WAIT→LAST-ACK off by 7 px, both of which had been read as
142
+ # vertical since the pins were written. A comment that names a chain the
143
+ # pins below it do not honour is worse than no comment, so this is the
144
+ # measured list and nothing else is claimed:
145
+ # DEAD VERTICAL FIN-WAIT-1→FIN-WAIT-2 (centre x 209 both)
146
+ # LAST-ACK→CLOSED (732.8 / 732.6)
147
+ # DEAD HORIZONTAL SYN-SENT→SYN-RECEIVED (centre y 328 both) — the
148
+ # simultaneous-open transition, across the mirrored pair
149
+ # NEAR, NOT DEAD CLOSE-WAIT→LAST-ACK (7.2 px), the passive-close column
150
+ # DELIBERATELY OFF-AXIS CLOSED (535.6) sits right of the LISTEN column
151
+ # (429.6), and CLOSING (439.2) right of TIME-WAIT (350.4)
152
+ # The last row is the 0.4 pin change and both offsets are load-
153
+ # bearing, not taste. CLOSED sat dead above LISTEN, which ran the
154
+ # CLOSED↔SYN-SENT diagonal pair straight through the `passive OPEN /
155
+ # create TCB` label on the CLOSED→LISTEN line; moving CLOSED right (and
156
+ # LISTEN 50 px down) gives each of `CLOSE / delete TCB`, `passive OPEN /
157
+ # create TCB` and `active OPEN / create TCB, snd SYN` its own line to
158
+ # belong to. CLOSING sat dead above TIME-WAIT, and that vertical ran
159
+ # through the `rcv FIN,ACK / snd ACK` label of the FIN-WAIT-1→TIME-WAIT
160
+ # diagonal (RFC Note 2's transition); lifting CLOSING up and right opens
161
+ # the channel that label needs. Measured: `layout-lint` lblcol 2→0 and
162
+ # F5 1→0, figure score 4→0.
163
+ # DROPPING FIN-WAIT-2 DEEPER WAS TRIED AND REJECTED. It is the obvious
164
+ # way to widen the same channel, and the measurement says it never is:
165
+ # across 150 combinations of CLOSING and TIME-WAIT positions taken at
166
+ # y=690 and y=700, FIN-WAIT-2 deeper is worse in 30 and better in NONE.
167
+ # What goes wrong is the neighbour: the FIN-WAIT-2→TIME-WAIT diagonal
168
+ # shortens onto its own `rcv FIN / snd ACK` label. At the pins used here
169
+ # it costs exactly one lblcol strike. FIN-WAIT-2 stays on row 5 at y=670.
170
+ # All meaning is in the transitions; this is layout, not knowledge.
142
171
  #
143
172
  # WHAT CHANGED, AND WHY THE TOP ROWS ARE WIDER.
144
173
  # Hand-routed corridors used to sit below these pins: the active
@@ -159,15 +188,15 @@ layout
159
188
  # anti-parallel fan-out is the language's answer to a same-pair transition, and
160
189
  # the class colours (blue = setup, purple = rare) say which stroke is
161
190
  # which.
162
- pin closed at=(394,20)
163
- pin listen at=(394,170)
191
+ pin closed at=(500,20)
192
+ pin listen at=(394,220)
164
193
  pin synrcvd at=(120,290)
165
194
  pin synsent at=(700,290)
166
195
  pin estab at=(376,410)
167
196
  pin fw1 at=(159,540)
168
197
  pin closewait at=(690,540)
169
198
  pin fw2 at=(159,670)
170
- pin closing at=(311,670)
199
+ pin closing at=(400,620)
171
200
  pin lastack at=(690,670)
172
201
  pin timewait at=(304,800)
173
202
  pin closed2 at=(697,800)
@@ -131,6 +131,31 @@ is one place to look and nothing to reconcile.
131
131
  > are the same protocol answered both ways, which is the cheapest way to see
132
132
  > the difference.
133
133
 
134
+ > **Three symptoms that ask you to CONFIRM the genre — and none of them
135
+ > decides it.** Each one means: go back to [authoring.md Step 2](authoring.md#step-2--pick-the-genre-main-standard-first)'s
136
+ > gate, ask the three questions again, and then either KEEP the figure where it
137
+ > is with the reason written down, or MOVE it. Confirming and keeping is a
138
+ > result; there is deliberately no lint on any of the three, because a symptom
139
+ > that fires on correct figures is not a rule.
140
+ >
141
+ > - **A hand-written `shape=diamond` with `yes`/`no` edges under a scene
142
+ > genre.** The full row, with the measured reason there is no lint and the
143
+ > two live instances that are right for opposite reasons, is in the
144
+ > [authoring.md pitfall table](authoring.md#field-tested-pitfalls-quick-reference)
145
+ > — read it there rather than twice.
146
+ > - **Ordinal mid-labels — `-[1: SYN]->`, `-[2: SYN-ACK]->`.** These are the
147
+ > sanctioned interim for time order under a scene genre and they are *naming,
148
+ > not semantics* (`MEANING-RECOVERY-SOURCE`; the rows above and the Known-limits entry below say
149
+ > so). Keeping them is the **deliberate-portability** pattern and it is only
150
+ > correct when the document SAYS so: a comment naming `sequence` as the genre
151
+ > not taken (it needs `figdown 0.4`) and stating that the numbers are a
152
+ > convention no parser reads. `examples/showcase/tcp-handshake.fd` is that
153
+ > pattern written out. Unstated ordinals are the failure case.
154
+ > - **Many parallel edges crowding one pair of boxes.** Settled by the boxed
155
+ > note immediately above — *messages between one pair over time* versus
156
+ > *distinct transitions between states* — and in the second case the answer
157
+ > is [layout.md](layout.md), not a new header line.
158
+
134
159
  ---
135
160
 
136
161
  ## Data & format
package/guide/showcase.md CHANGED
@@ -334,18 +334,18 @@ class client "Active-close path (typically the client) — calls CLOSE first: F
334
334
  class server "Passive-close path (typically the server) — receives the peer's FIN first: CLOSE-WAIT → LAST-ACK → CLOSED" stroke=#16a34a
335
335
  class rare "Rare / simultaneous transition — simultaneous open or close, or a reset/abort (RST, close from a half-open state)" stroke=#9333ea style=dashed
336
336
 
337
- state closed "CLOSED" shape=rounded class=states
338
- state listen "LISTEN" shape=rounded class=states
339
- state synsent "SYN-SENT" shape=rounded class=states
340
- state synrcvd "SYN-RECEIVED" shape=rounded class=states
341
- state estab "ESTABLISHED" shape=rounded class=states
342
- state fw1 "FIN-WAIT-1" shape=rounded class=states
343
- state fw2 "FIN-WAIT-2" shape=rounded class=states
344
- state closing "CLOSING" shape=rounded class=states
345
- state closewait "CLOSE-WAIT" shape=rounded class=states
346
- state lastack "LAST-ACK" shape=rounded class=states
347
- state timewait "TIME-WAIT" shape=rounded class=states
348
- state closed2 "CLOSED" shape=rounded class=states
337
+ state closed "CLOSED" class=states
338
+ state listen "LISTEN" class=states
339
+ state synsent "SYN-SENT" class=states
340
+ state synrcvd "SYN-RECEIVED" class=states
341
+ state estab "ESTABLISHED" class=states
342
+ state fw1 "FIN-WAIT-1" class=states
343
+ state fw2 "FIN-WAIT-2" class=states
344
+ state closing "CLOSING" class=states
345
+ state closewait "CLOSE-WAIT" class=states
346
+ state lastack "LAST-ACK" class=states
347
+ state timewait "TIME-WAIT" class=states
348
+ state closed2 "CLOSED" class=states
349
349
 
350
350
  flow down
351
351
 
@@ -149,6 +149,36 @@ function parseFailed(label, errors) {
149
149
  );
150
150
  }
151
151
 
152
+ // GEOMETRY-TIME REFUSAL (spec core §8, §8.4). A document whose source is
153
+ // impeccable line by line can still describe a drawing that states something
154
+ // it does not — a `group` band enclosing a non-member, a `pin` covering a node
155
+ // completely. Those come from `render`, not from `parse`, and core §8 makes
156
+ // them cost exactly what a parse error costs: nothing is drawn and no artifact
157
+ // is written. Reporting them under the PARSE FAILED banner would send the
158
+ // author to look for a typo on a line that has none, so they get their own
159
+ // heading and their own remedy sentence.
160
+ function figureRefused(label, errors) {
161
+ return text(
162
+ 'FIGURE REFUSED — ' + errors.length + ' geometry-time diagnostic(s)' + (label ? ' in ' + label : '') + '.\n' +
163
+ 'The source parses clean; the DRAWING would state something the source does not,\n' +
164
+ 'so nothing was rendered and no artifact was written (spec core §8). Each line names\n' +
165
+ 'the line you can act on — usually the `pin` that fixed the position.',
166
+ errors.join('\n')
167
+ );
168
+ }
169
+
170
+ // WHICH CHANNEL REFUSED. `render()` reports parse errors when there are any and
171
+ // geometry-time diagnostics ONLY when there are none — the two are mutually
172
+ // exclusive by construction, because nothing is rendered for a document that
173
+ // does not parse. So asking the parser is enough to name the channel, and it
174
+ // is asked directly rather than inferred from the message text: a banner keyed
175
+ // on wording would go wrong the first time a message is reworded.
176
+ function refused(label, src, errors) {
177
+ return figdown.parse(src).errors.length
178
+ ? parseFailed(label, errors)
179
+ : figureRefused(label, errors);
180
+ }
181
+
152
182
  // `source` or `path`, never both, never neither. Returns {src, label, file} or
153
183
  // throws a message string.
154
184
  function takeSource(args, exts) {
@@ -172,8 +202,12 @@ function toolBuild(args) {
172
202
  try { s = takeSource(args, ['.fd']); } catch (m) { return toolError(m); }
173
203
 
174
204
  const opts = args.with_title === true ? { title: true } : undefined;
205
+ // `artifact` refuses on EITHER channel (dist 0.4): a parse error, or
206
+ // a geometry-time diagnostic that only `render` can see. Both give svg=null
207
+ // and a non-empty `errors`, so nothing below can write a picture the engine
208
+ // has already said is wrong.
175
209
  const { svg, errors } = figdown.artifact(s.src, opts);
176
- if (errors.length) return parseFailed(s.label, errors);
210
+ if (errors.length) return refused(s.label, s.src, errors);
177
211
 
178
212
  const wrote = args.write === true && s.file
179
213
  ? s.file.replace(/\.fd$/, '') + '.svg'
@@ -247,9 +281,13 @@ function toolCheck(args) {
247
281
 
248
282
  if (!units.length) return text('ok: true 0 .fd file(s) found under ' + args.path + ' — nothing to check.');
249
283
 
284
+ // BOTH CHANNELS, because a checker that clears a document `figdown_build`
285
+ // then refuses is a checker that lies. `render` reports parse errors when
286
+ // there are any and geometry-time diagnostics (core §8) otherwise, which is
287
+ // exactly the set that decides whether an artifact can be written.
250
288
  const bad = [];
251
289
  for (const u of units) {
252
- const errs = figdown.parse(u.src).errors;
290
+ const errs = figdown.render(u.src).errors;
253
291
  if (errs.length) bad.push(u.label + ':\n' + errs.map(e => ' ' + e).join('\n'));
254
292
  }
255
293
 
@@ -291,9 +329,17 @@ function toolRead(args) {
291
329
  }
292
330
  const sidecar = s.file ? s.file.replace(/\.svg$/, '.fd') : null;
293
331
  if (sidecar && fs.existsSync(sidecar) && recordedSha) {
294
- const live = figdown.artifact(fs.readFileSync(sidecar, 'utf8')).svg;
332
+ const rebuilt = figdown.artifact(fs.readFileSync(sidecar, 'utf8'));
333
+ const live = rebuilt.svg;
295
334
  const liveSha = live && (live.match(/data-sha256="([0-9a-f]{64})"/) || [])[1];
296
- notes.push(liveSha === recordedSha
335
+ // A sidecar the engine now REFUSES cannot be compared, and calling that
336
+ // "stale" would name the wrong defect: the .fd did not drift, it stopped
337
+ // being drawable. Say which it is.
338
+ notes.push(rebuilt.errors.length
339
+ ? 'SIDECAR REFUSED: ' + path.basename(sidecar) + ' no longer builds — '
340
+ + rebuilt.errors.length + ' diagnostic(s), so the recorded hash could not be checked:\n '
341
+ + rebuilt.errors.join('\n ')
342
+ : liveSha === recordedSha
297
343
  ? 'sidecar ' + path.basename(sidecar) + ' matches the artifact\'s recorded hash'
298
344
  : 'STALE ARTIFACT: ' + path.basename(sidecar) + ' has changed since this .svg was built. '
299
345
  + 'The .fd is truth — rebuild.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figdown",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A missing edge still looks fine. Text doesn't. A closed, deterministic figure language for Markdown whose source states the meaning, so the next reader can check the figure instead of only looking at it.",
5
5
  "license": "MIT",
6
6
  "author": "FigDown <hello@figdown.org>",
@@ -32,7 +32,7 @@
32
32
  "gate:page": "node tools/page-check.js",
33
33
  "gate:shape": "node tools/shape-check.js --strict",
34
34
  "gate:strip": "node tools/strip-check.js --strict",
35
- "gate:layout": "node tools/layout-lint.js --strict",
35
+ "gate:layout": "node tools/layout-lint.js --strict examples figures tools/layout-lint-fixtures/b66-typed-title",
36
36
  "gate:namespace": "node tools/namespace-check.js --strict",
37
37
  "gate:artifact": "node tools/artifact-check.js --strict",
38
38
  "gate:dist": "node tools/dist-check.js --strict",
@@ -48,6 +48,8 @@
48
48
  "gate:standards": "node tools/standards-check.js --strict",
49
49
  "gate:plugin": "node tools/plugin-check.js --strict",
50
50
  "gate:mcp": "node integrations/mcp-server/test.js",
51
+ "gate:proof": "node tools/make-proof.js --check",
52
+ "gate:safesvg": "node tools/safe-svg-check.js --strict",
51
53
  "gates:list": "node -e \"const s=require(process.cwd()+'/package.json').scripts||{};const g=Object.keys(s).filter(k=>k.startsWith('gate:'));if(!g.length){console.error('gates:list — no gate:* scripts found');process.exit(2);}if(process.env.GATES_JSON)console.log(JSON.stringify(g));else g.forEach(k=>console.log(k+' -> '+s[k]));\"",
52
54
  "test": "node -e \"const s=require(process.cwd()+'/package.json').scripts||{};const sp=require('child_process').spawnSync;const g=Object.keys(s).filter(k=>k.startsWith('gate:'));if(!g.length){console.error('npm test — no gate:* scripts found; refusing to report success');process.exit(2);}const bad=[];g.forEach(k=>{const a=s[k].split(' ');const bin=a[0]==='node'?process.execPath:a[0];console.log('');console.log('=== GATE '+k+' ['+s[k]+'] ===');const r=sp(bin,a.slice(1),{stdio:'inherit'});if(r.error)console.error(' spawn error: '+r.error.message);const c=(r.status===null||r.status===undefined)?1:r.status;console.log('--- '+k+': '+(c===0?'PASS':'FAIL (exit '+c+')')+' ---');if(c!==0)bad.push(k+' ['+s[k]+'] exit '+c);});console.log('');console.log('=== GATE SUMMARY: '+(g.length-bad.length)+'/'+g.length+' passed ===');if(bad.length){console.log('FAILED GATES:');bad.forEach(b=>console.log(' '+b));process.exit(1);}console.log('ALL GATES GREEN');\"",
53
55
  "gate:reference": "node tools/reference-gate.js"
@@ -0,0 +1,130 @@
1
+ # FigDown agent skill
2
+
3
+ An installable skill that teaches an AI coding agent (Claude Code and
4
+ compatible agent frameworks) to maintain documentation figures with
5
+ FigDown: edit `.fd` sources, build deterministic `.svg` artifacts, and
6
+ embed them in Markdown with the `source:` footer convention.
7
+
8
+ The bundle is self-contained: `SKILL.md`, a `reference/` directory,
9
+ `build-svg.js` (validator/renderer CLI) and `figdown.html` (the engine
10
+ it extracts at runtime — also the editor: open it in any browser).
11
+
12
+ **It is built for progressive disclosure.** `SKILL.md` is the only file
13
+ always loaded, and it holds the workflow, the genre-independent
14
+ language, and a **router**. Everything else is pulled on demand, and
15
+ the routing rule is mechanical: line 1 of a `.fd` names its genre, so
16
+ the agent knows which file it needs before it needs it.
17
+
18
+ ```
19
+ SKILL.md always loaded
20
+ reference/scene.md genre `block` — its OWN vocabulary
21
+ reference/bitfield.md
22
+ reference/table.md
23
+ reference/layout.md arranging a scene
24
+ reference/reading.md reading a .fd for meaning
25
+ reference/transcribe.md transcribing an existing figure
26
+ reference/experimental/block.md block's EXPERIMENTAL markers and zones
27
+ reference/experimental/chart.md a chart from a table (any genre
28
+ that can host one)
29
+ reference/experimental/topology.md the five EXPERIMENTAL genres, each
30
+ reference/experimental/flowchart.md declaring its OWN vocabulary in
31
+ reference/experimental/statechart.md its own file, so an agent can
32
+ reference/experimental/timing.md ignore the ones it is not using
33
+ reference/experimental/sequence.md — the ladder genre, and the one
34
+ whose load set has no layout file
35
+ ```
36
+
37
+ `sequence` is the exception to the "genre file + layout file" pairing above:
38
+ both of its axes are declaration order, so nothing in the layout namespace
39
+ moves a mark there and its own file is the whole of what an author needs.
40
+
41
+ **Every scene genre's file is self-sufficient (`SUBJECT-VOCABULARY-SCOPE`).** There is
42
+ no shared scene-vocabulary file and there was never meant to be one: only
43
+ `figdown`, `title` and `layout` are cross-genre by definition, and a word two
44
+ genres spell the same is two independent declarations that happen to agree
45
+ today. So an agent authoring a `topology` figure loads `topology.md` and does
46
+ not have to follow a link into `block`'s file to find out what `bundle` means
47
+ — which is what it used to have to do, and what it found there was written for
48
+ a different domain. The file this list used to call
49
+ `reference/experimental/constructs.md` was that shared file; it was removed
50
+ with the ruling, and its contents went to the genres that own them.
51
+
52
+ The `reference/` files are a generated copy of [`read/0.5/`](../read/0.5/reading.md),
53
+ which is the source of truth; see "Keeping the bundle fresh" below. If you only
54
+ want to READ a `.fd` from this repository, go to `read/0.5/` and install
55
+ nothing.
56
+
57
+ An agent *reading* a figure loads `SKILL.md` + `reference/reading.md`
58
+ and nothing else; an agent authoring a `bitfield` never pays for any
59
+ scene genre's vocabulary. `node tools/skill-coverage.js --strict` is the gate:
60
+ it checks that every registered keyword, option key and enum value is
61
+ taught in its genre's load set, that no retired spelling is taught, and
62
+ that the always-loaded file stays genre-independent.
63
+
64
+ ## Install
65
+
66
+ Two paths, one bundle. The plugin is a **wrapper**, not a second copy:
67
+ `.claude-plugin/plugin.json` at the repository root declares
68
+ `"skills": "./skill/"`, so the plugin ships exactly the directory documented
69
+ here. Nothing below is duplicated anywhere.
70
+
71
+ **Claude Code plugin (recommended — installs and updates itself):**
72
+
73
+ ```sh
74
+ /plugin marketplace add FigDown/figdown
75
+ /plugin install figdown@figdown
76
+ ```
77
+
78
+ `/plugin marketplace update figdown` refreshes the catalogue; the plugin is
79
+ pinned to the `version` in its manifest, so a new release arrives when that
80
+ number changes. No submission and no review are involved: the marketplace is
81
+ [`.claude-plugin/marketplace.json`](../.claude-plugin/marketplace.json) in this
82
+ repository.
83
+
84
+ **Copy the directory (no plugin mechanism, every project):**
85
+
86
+ ```sh
87
+ cp -r skill/figdown ~/.claude/skills/figdown
88
+ ```
89
+
90
+ **Copy the directory (no plugin mechanism, one project only):**
91
+
92
+ ```sh
93
+ cp -r skill/figdown <your-repo>/.claude/skills/figdown
94
+ ```
95
+
96
+ The copy paths remain fully supported: the bundle is self-contained, so a
97
+ `cp` of `skill/figdown` into any skills directory works with no network, no
98
+ repository and no manifest.
99
+
100
+ Then ask the agent for a figure ("draw the ingress datapath as a
101
+ figure in docs/arch.md") — the skill triggers on diagram/figure work
102
+ and follows the FigDown workflow automatically. Requires Node.js for
103
+ the build step; no other dependencies, no network.
104
+
105
+ **Other agent frameworks:** nothing here is Claude-specific except the
106
+ directory convention. `SKILL.md` and `reference/` are ordinary Markdown —
107
+ point the agent at `skill/figdown/SKILL.md` and it follows the same router.
108
+ [guide/agents.md](../guide/agents.md) is the orientation page that leads here;
109
+ it teaches no syntax, because this bundle owns that.
110
+
111
+ ## Keeping the bundle fresh
112
+
113
+ `figdown/figdown.html`, `figdown/build-svg.js` and everything under
114
+ `figdown/reference/` are **build artifacts** regenerated from their single
115
+ sources (`editor/figdown.html`, `tools/build-svg.js`, and — since `GENRE-REFERENCE-ADDRESS` —
116
+ [`read/0.5/`](../read/0.5/reading.md)) — never edited by hand:
117
+
118
+ ```sh
119
+ node tools/make-skill.js
120
+ ```
121
+
122
+ `SKILL.md` is the one hand-maintained source left in the bundle;
123
+ `make-skill.js` does not own it and does not delete it.
124
+
125
+ **Why `reference/` is vendored rather than linked.** `read/0.5/` is where a
126
+ reader of this repository is sent — nothing to install. This directory is
127
+ copied out of the repository into `~/.claude/skills/`, so a path pointing back
128
+ at `read/0.5/` would dangle the moment it is installed. The copy is therefore
129
+ generated, and `node tools/skill-coverage.js --strict` fails on any byte of
130
+ difference (check 0, VENDOR). Edit `read/0.5/`, then re-run `make-skill.js`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figdown
3
- description: Draw a figure a human can read and an agent can read too — FigDown .fd text that states the meaning, with a deterministic SVG embedded in Markdown. Use when asked to create, edit, fix, or read diagrams/figures in docs — block and architecture diagrams, topologies, flowcharts, bit-level layouts (packet headers, hardware registers), tables, timing waveforms — or when a .md contains an SVG with a "source: *.fd" footer.
3
+ description: Draw a figure a human can read and an agent can read too — FigDown .fd text that states the meaning, with a deterministic SVG embedded in Markdown. Use when asked to create, edit, fix, or read diagrams/figures in docs — block and architecture diagrams, topologies, flowcharts, state machines, message-sequence exchanges between parties, bit-level layouts (packet headers, hardware registers), tables, timing waveforms — or when a .md contains an SVG with a "source: *.fd" footer.
4
4
  ---
5
5
 
6
6
  # FigDown — figures as text, one source, two readers
@@ -52,6 +52,26 @@ Recover from there. On hash mismatch the `.fd` is truth: rebuild. If the hash
52
52
  matches but your drawing differs, compare `data-engine-version` — that is the
53
53
  other half of "same source → same SVG".
54
54
 
55
+ ## Repair loop, truthfulness and delivery order
56
+
57
+ **Bounded repair loop.** After step 2 fails, keep fixing and rerunning only
58
+ while each attempt's error count is a new minimum — strictly lower than
59
+ every attempt before it. Two consecutive rounds with no improvement mean
60
+ another identical round will not help either: stop, and report the
61
+ remaining errors truthfully instead of trying a third time.
62
+
63
+ **Truthfulness.** A run that still reports errors is never described as a
64
+ success, in any wording. Never delete or weaken meaningful declared content
65
+ — a node, an edge, a field, a row, a signal, whatever the genre calls it —
66
+ merely to make the error count reach zero; removing what the figure states
67
+ is not a repair, it is a smaller, different figure wearing the same
68
+ filename. Once a run passes cleanly, the document is frozen: no further
69
+ edits chasing "one more improvement."
70
+
71
+ **Artifact first.** Write the file, or the fenced block, before explaining
72
+ it. Commentary, reasoning and a description of what changed come after the
73
+ artifact exists — never before it, and never interleaved with it.
74
+
55
75
  ## When the figure comes from a source
56
76
 
57
77
  Drawing is step 5 of 6. The steps before it decide what is true; the step after
@@ -67,7 +87,11 @@ everything it produces is a hypothesis until checked.
67
87
  is not a graph: it is a `table`. Conditions with one outcome per combination is
68
88
  a `table`, not a `flowchart` — drawn as a chart it buys crossing edges and
69
89
  nothing else. Two mirrored mechanisms are two figures. A run in which most of a
70
- page's figures become tables is a correct run.
90
+ page's figures become tables is a correct run. **The SHAPES in the source
91
+ drawing are the tempting signal and they carry nothing** — the original author
92
+ picked them by habit, by stencil and by tool default, and no two drawings agree;
93
+ what decides the genre is the ROLES and the QUESTIONS in it, which is what the
94
+ extraction gives you and the picture does not.
71
95
 
72
96
  **3 — Separate what the source states from what you concluded.** Write the
73
97
  stated thing; for the rest, leave a `#` comment at the point of doubt and list
@@ -143,8 +167,21 @@ Two more files answer a **task** rather than a genre:
143
167
  format → `reference/transcribe.md`.
144
168
 
145
169
  Pick the genre by what the figure IS, not by its subject: the left-hand
146
- column above says what each is for. Prefer the first three — they are the
147
- portable ones.
170
+ column above says what each is for.
171
+
172
+ **Where portability and that answer disagree, the ANSWER decides.** `block`,
173
+ `bitfield` and `table` are the portable three — inside the v0.1 conformance
174
+ surface and its compatibility promise — and taking one of them *against* the
175
+ answer is a trade you STATE, never a default. State it in a `#` comment that
176
+ names the genre you did not write and what the reader must not conclude from
177
+ the one you did. Two different prices hide under the word "portable" and they
178
+ are charged by different rows: `topology` and `flowchart` are dispatchable at
179
+ `figdown 0.1`, so choosing them moves no version and only carries the EXPERIMENTAL
180
+ withdrawal risk; `statechart` and `sequence` move the declared version as well.
181
+ "It must render on any released version" is therefore not an argument for
182
+ leaving the first two. An approximation you do not state is indistinguishable
183
+ from a judgement about what the figure is, so the reader inherits the wrong
184
+ one.
148
185
 
149
186
  **Load the genre file BEFORE you write line 2, not only when something
150
187
  fails.** A scene genre may spell the thing and the line with **its own
@@ -186,11 +223,14 @@ figdown 0.1 block # REQUIRED first significant line; comments and
186
223
  # A later `figdown 0.1 <genre>` starts a new
187
224
  # section with its own genre; one file still
188
225
  # renders to one SVG.
189
- # The VERSION is `0.1` or `0.2`. Write the LOWEST
190
- # one that carries what the figure needs — `0.2`
191
- # only for `statechart`, which does not exist at
192
- # `0.1`. Sections may differ; each declares its
193
- # own.
226
+ # The VERSION is `0.1`, `0.2` or `0.4`. Write the
227
+ # LOWEST one that carries what the figure needs.
228
+ # `0.1` carries every other genre. `0.2` is the
229
+ # floor for `statechart` and `0.4` the floor for
230
+ # `sequence`: neither exists at `0.1`, and a
231
+ # version below a genre's floor is a line error
232
+ # (`figdown 0.3 sequence` does not parse).
233
+ # Sections may differ; each declares its own.
194
234
  title "Some Title" # optional; the quotes are REQUIRED
195
235
  # comments start with '#'; inside quotes the only escapes are \n \" \\
196
236
  class hot "Congested path" stroke=#dc2626 # meaning + style, declared once