From 8e6c553dd7aa5d53c348be582b7e2a303891ada1 Mon Sep 17 00:00:00 2001 From: felixwidjaja Date: Thu, 12 Nov 2026 17:25:33 +0700 Subject: [PATCH] docs(skills): add article diagram examples and Mermaid layout rules Add the Datum Desktop and five-article designed diagrams with their Mermaid versions as examples, document the Mermaid width and subgraph-direction rules, the cache-after-publish check, and fix a broken code fence. --- .claude/skills/datum-diagram/SKILL.md | 25 +- .../examples/desktop-article.mjs | 114 ++++ .../datum-diagram/examples/diagrams.mjs | 523 +++++++++++++++--- .../datum-diagram/examples/five-articles.mjs | 302 ++++++++++ .../examples/mermaid/agentThreePieces.mmd | 11 + .../examples/mermaid/chickenAndEgg.mmd | 7 + .../examples/mermaid/fourFlavors.mmd | 6 + .../examples/mermaid/landAnything.mmd | 24 + .../examples/mermaid/pathsDirectRelay.mmd | 10 + .../examples/mermaid/privateByDefault.mmd | 11 + .../examples/mermaid/requestFlow.mmd | 10 + 11 files changed, 968 insertions(+), 75 deletions(-) create mode 100644 .claude/skills/datum-diagram/examples/desktop-article.mjs create mode 100644 .claude/skills/datum-diagram/examples/five-articles.mjs create mode 100644 .claude/skills/datum-diagram/examples/mermaid/agentThreePieces.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/chickenAndEgg.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/fourFlavors.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/landAnything.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/pathsDirectRelay.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/privateByDefault.mmd create mode 100644 .claude/skills/datum-diagram/examples/mermaid/requestFlow.mmd diff --git a/.claude/skills/datum-diagram/SKILL.md b/.claude/skills/datum-diagram/SKILL.md index 6028f7f7..e680af34 100644 --- a/.claude/skills/datum-diagram/SKILL.md +++ b/.claude/skills/datum-diagram/SKILL.md @@ -49,16 +49,30 @@ Exported from the Figma "Components - Diagram" file (PNG inside SVG). Complex se In the Strapi body, write the designed image, then the Mermaid fence directly after it: ```` -![Caption shared by both versions](https://…/diagram_designed.png) +![Caption shared by both versions](https://.../diagram_designed.png) ```mermaid flowchart TB - … + a["first"]:::datum -.-> b["second"]:::ext +``` ```` -``` +`src/plugins/rehypeMermaid.ts` turns that pair into an "Illustrated | Mermaid" tab set (`src/components/DiagramTabs.astro`, `components-diagram-tabs.css`). Illustrated shows on landing; the Mermaid panel renders off-screen and appears when its tab is chosen, with a "Copy Mermaid" button. The caption sits under both. A mermaid fence with no image right before it renders alone, as before. + +## Mermaid layout rules -`src/plugins/rehypeMermaid.ts` turns that pair into an "Illustrated | Mermaid" tab set (`src/components/DiagramTabs.astro`, `components-diagram-tabs.css`). Illustrated shows on landing; the Mermaid panel renders off-screen and appears when its tab is chosen, with a "Copy Mermaid" button. The caption sits under both. A mermaid fence with no image right before it renders alone, as before. Stack panels vertically in Mermaid (root `flowchart TB` plus an invisible `a ~~~ b` link); side by side makes everything small. +- Aim for a natural width under about 650px (it scales to the article column; narrower reads better on a phone). Stack panels and steps top to bottom. +- Stack panels with a root `flowchart TB` plus invisible `a ~~~ b` links. +- A subgraph's own `direction` is ignored as soon as an edge crosses its boundary. To get a tall column of nodes that all link to one hub, use a root `flowchart LR` and link each node individually (see `examples/mermaid/landAnything.mmd`). Do not link the subgraph itself. +- Request flows read best as a real `sequenceDiagram` (see `examples/mermaid/requestFlow.mmd`); the theme styles them. +- Do not put a diagram's grey outer panel in Mermaid as a coloured node: panels are subgraphs (dashed grey), moss is for Datum nodes only. + +## Examples + +- `examples/diagrams.mjs`: the seven IPv6 article diagrams (designed). +- `examples/desktop-article.mjs`: the two Datum Desktop article diagrams (direct vs relay, request flow). +- `examples/five-articles.mjs`: four-flavors, Galactic VPC (land anything, private by default), every-agent-needs-an-edge, network-vs-compute. +- `examples/mermaid/`: the matching Mermaid versions. ## Gotchas learned @@ -67,8 +81,9 @@ flowchart TB - Mermaid `look: handDrawn` does not apply to sequence diagrams and was dropped. - Zero-length path segments (identical consecutive points) draw nothing: `line()` de-duplicates them. - Strapi bodies: publishing is outward-facing. Draft first, ask before publishing. +- After publishing, check the live page on the plain URL several times. Right after a deploy the site can serve the build-time article cache; re-saving the published article in Strapi (or re-publishing in the admin) re-fires the revalidation webhook. +- The Mermaid theme is read when the Astro server starts; restart the dev server to see theme changes. ## Facts to check before drawing See `reference/datum-glossary.md`. It is a starting point, not a source of truth: confirm against https://www.datum.net/docs before stating a technical fact in a diagram. -``` diff --git a/.claude/skills/datum-diagram/examples/desktop-article.mjs b/.claude/skills/datum-diagram/examples/desktop-article.mjs new file mode 100644 index 00000000..99c1c3db --- /dev/null +++ b/.claude/skills/datum-diagram/examples/desktop-article.mjs @@ -0,0 +1,114 @@ +import { + C, + SW, + text, + tag, + label, + badge, + ext, + datum, + tile, + extFrame, + line, + deco, + svg, +} from '../kit.mjs'; + +const mono = (x, y, s, o = {}) => text(x, y, s, { mono: true, size: 12.5, fill: C.muted, ...o }); + +// direct UDP vs relay: both paths tried in parallel, fastest wins +export const pathsDirectRelay = () => { + const W = 1140, + H = 600; + let b = ''; + b += deco(6, 1000, 470, 150); + b += extFrame(30, 30, 1080, 520, 'Two paths between endpoints, tried in parallel'); + // endpoints + b += + tile(190, 150, 230, 250) + + datum(190, 255, 'hub', { r: 46 }) + + text(190, 345, 'Gateway', { size: 17, weight: 500 }) + + text(190, 368, 'endpoint', { size: 15, fill: C.muted }); + b += + tile(950, 150, 230, 250) + + datum(950, 255, 'laptop', { r: 46 }) + + text(950, 345, 'Desktop', { size: 17, weight: 500 }) + + text(950, 368, 'node', { size: 15, fill: C.muted }); + // direct path (top) + b += line( + [ + [310, 215], + [830, 215], + ], + 'ok', + { both: true } + ); + b += tag(570, 215, 'Direct UDP · hole-punched', { fill: C.pine }); + // relay path (bottom) through the home relay + b += + tile(570, 340, 210, 130) + + datum(570, 392, 'cloud', { r: 30 }) + + text(570, 450, 'Home relay', { size: 15, weight: 500 }); + b += line( + [ + [310, 385], + [458, 385], + ], + 'neutral', + { both: true } + ); + b += line( + [ + [682, 385], + [830, 385], + ], + 'neutral', + { both: true } + ); + b += tag(384, 363, 'encrypted', { fill: C.ink }) + tag(756, 363, 'encrypted', { fill: C.ink }); + // outcome + b += `${text(570, 508, 'both paths tried in parallel · fastest wins, automatic failover', { size: 15, weight: 500, fill: C.navy })}`; + return svg(W, H, b); +}; + +// request flow: Envoy -> iroh-gateway -> Desktop node +export const requestFlow = () => { + const W = 1140, + H = 900; + let b = ''; + b += deco(4, 1050, -90, 150); + const cols = [ + [210, 'Envoy', 'ext'], + [570, 'iroh-gateway', 'datum'], + [930, 'Desktop node', 'datum'], + ]; + for (const [x, name, kind] of cols) { + b += ``; + if (kind === 'datum') b += tile(x, 40, 210, 100) + text(x, 98, name, { size: 17, weight: 500 }); + else + b += `${text(x, 98, name, { size: 17, weight: 500, fill: C.slate })}`; + } + const step = (n, x1, x2, y, t, note, kind = 'neutral', fill) => { + const cx = (x1 + x2) / 2; + const tagHalf = (t.length * 11 * 0.62 + 18) / 2; + let s = + line( + [ + [x1, y], + [x2, y], + ], + kind + ) + + tag(cx, y - 24, t, { fill: fill ?? C.ink }) + + badge(cx - tagHalf - 20, y - 24, n); + if (note) s += text(cx, y + 26, note, { size: 14, fill: C.muted }); + return s; + }; + b += step(1, 215, 565, 230, 'HTTP request', '(via Unix socket)'); + b += step(2, 575, 925, 340, 'resolve endpoint ID', 'from request metadata'); + b += step(3, 575, 925, 470, 'open QUIC stream', '(reuse connection or establish new one)'); + b += step(4, 575, 925, 600, 'forward request', 'over QUIC stream', 'ok', C.pine); + b += step(5, 925, 575, 730, 'HTTP response', null, 'neutral'); + b += step(6, 565, 215, 810, 'HTTP response', null, 'neutral'); + return svg(W, H, b); +}; diff --git a/.claude/skills/datum-diagram/examples/diagrams.mjs b/.claude/skills/datum-diagram/examples/diagrams.mjs index a43ac1eb..3bb2febe 100644 --- a/.claude/skills/datum-diagram/examples/diagrams.mjs +++ b/.claude/skills/datum-diagram/examples/diagrams.mjs @@ -1,128 +1,410 @@ -import { logo, deco, tile, C, SW, text, tag, label, badge, ext, datum, rack, icon, datumFrame, extFrame, line, hatch, swoosh, squares, svg } from '../kit.mjs'; +import { + logo, + deco, + tile, + C, + SW, + text, + tag, + label, + badge, + ext, + datum, + rack, + icon, + datumFrame, + extFrame, + line, + hatch, + swoosh, + squares, + svg, +} from '../kit.mjs'; const mono = (x, y, s, o = {}) => text(x, y, s, { mono: true, size: 12.5, fill: C.muted, ...o }); const outcome = (x, y, w, h, lines, o = {}) => - `${text(x + w / 2, y + h / 2 - ((lines.length - 1) * 11) + 5, lines, { size: o.size ?? 16, lh: 22, fill: o.color ?? C.navy, weight: 500 })}`; + `${text(x + w / 2, y + h / 2 - (lines.length - 1) * 11 + 5, lines, { size: o.size ?? 16, lh: 22, fill: o.color ?? C.navy, weight: 500 })}`; // 1 ─ dual stack vs IPv6-native export const image1 = () => { - const W = 1240, H = 960; + const W = 1240, + H = 960; let b = ''; b += deco(4, 1090, -70, 200) + deco(6, -50, 850, 140); // panel A (everywhere else = external, grayscale) b += extFrame(50, 40, 1140, 400, 'Everywhere else: dual stack'); b += ext(170, 270, 'laptop') + text(170, 345, 'client', { size: 16 }); - b += logo(640, 270, 'nginx') + text(640, 345, 'nginx', { size: 16 }) + mono(640, 366, 'listen 8080;', { anchor: 'middle' }); - b += line([[236, 232], [574, 232]], 'neutral') + badge(262, 232, 1) + tag(405, 232, 'try IPv6'); - b += line([[574, 270], [236, 270]], 'bad') + tag(405, 270, 'refused', { fill: C.clay }); - b += line([[236, 308], [574, 308]], 'ok') + badge(262, 308, 2) + tag(405, 308, 'quietly retry IPv4', { fill: C.pine }); - b += line([[708, 270], [880, 270]], 'ok'); + b += + logo(640, 270, 'nginx') + + text(640, 345, 'nginx', { size: 16 }) + + mono(640, 366, 'listen 8080;', { anchor: 'middle' }); + b += + line( + [ + [236, 232], + [574, 232], + ], + 'neutral' + ) + + badge(262, 232, 1) + + tag(405, 232, 'try IPv6'); + b += + line( + [ + [574, 270], + [236, 270], + ], + 'bad' + ) + tag(405, 270, 'refused', { fill: C.clay }); + b += + line( + [ + [236, 308], + [574, 308], + ], + 'ok' + ) + + badge(262, 308, 2) + + tag(405, 308, 'quietly retry IPv4', { fill: C.pine }); + b += line( + [ + [708, 270], + [880, 270], + ], + 'ok' + ); b += outcome(890, 224, 250, 92, ['200 OK', 'nobody notices']); // panel B (Datum Compute) b += extFrame(50, 490, 1140, 420, 'Datum Compute: IPv6-native'); b += tile(190, 665, 160, 185); b += datum(190, 735, 'hub') + text(190, 820, 'Datum ALB', { size: 16 }); - b += logo(640, 735, 'nginx') + text(640, 810, 'nginx', { size: 16 }) + mono(640, 831, 'listen 8080;'); - b += line([[280, 715], [574, 715]], 'neutral') + tag(415, 715, 'IPv6 only'); - b += line([[574, 758], [280, 758]], 'bad') + tag(415, 758, 'refused', { fill: C.clay }); - b += line([[190, 665], [190, 600], [880, 600]], 'bad', { r: 14 }); - b += outcome(890, 548, 250, 104, ['503', "the config's IPv4", 'assumption, surfaced'], { fill: C.clayPale, stroke: C.clay, color: C.clayDeep, size: 15 }); + b += + logo(640, 735, 'nginx') + + text(640, 810, 'nginx', { size: 16 }) + + mono(640, 831, 'listen 8080;'); + b += + line( + [ + [280, 715], + [574, 715], + ], + 'neutral' + ) + tag(415, 715, 'IPv6 only'); + b += + line( + [ + [574, 758], + [280, 758], + ], + 'bad' + ) + tag(415, 758, 'refused', { fill: C.clay }); + b += line( + [ + [190, 665], + [190, 600], + [880, 600], + ], + 'bad', + { r: 14 } + ); + b += outcome(890, 548, 250, 104, ['503', "the config's IPv4", 'assumption, surfaced'], { + fill: C.clayPale, + stroke: C.clay, + color: C.clayDeep, + size: 15, + }); b += deco(12, 1120, 836, 52); return svg(W, H, b); }; // 2 ─ shared link vs one link per instance export const image2 = () => { - const W = 1300, H = 1000; + const W = 1300, + H = 1000; let b = ''; b += extFrame(40, 60, 600, 860, 'Home LAN or Podman: a shared link'); b += ext(340, 230, 'hub') + text(340, 304, 'one shared link', { size: 16 }); - b += line([[340, 322], [340, 385]], 'link', { dot: true }); - b += line([[340, 385], [340, 410]], 'link', { solid: true, arrow: false }); - b += line([[140, 410], [540, 410]], 'link', { arrow: false }); - b += line([[140, 410], [140, 470]], 'link', { dotEnd: true }); - b += line([[340, 410], [340, 470]], 'link', { dotEnd: true }); - b += line([[540, 410], [540, 470]], 'link', { dotEnd: true }); + b += line( + [ + [340, 322], + [340, 385], + ], + 'link', + { dot: true } + ); + b += line( + [ + [340, 385], + [340, 410], + ], + 'link', + { solid: true, arrow: false } + ); + b += line( + [ + [140, 410], + [540, 410], + ], + 'link', + { arrow: false } + ); + b += line( + [ + [140, 410], + [140, 470], + ], + 'link', + { dotEnd: true } + ); + b += line( + [ + [340, 410], + [340, 470], + ], + 'link', + { dotEnd: true } + ); + b += line( + [ + [540, 410], + [540, 470], + ], + 'link', + { dotEnd: true } + ); b += ext(140, 540, 'router') + text(140, 614, 'router', { size: 16 }); b += ext(340, 540, 'laptop') + text(340, 614, 'a', { size: 16 }); b += ext(540, 540, 'laptop') + text(540, 614, 'b', { size: 16 }); - b += line([[540, 640], [540, 710], [340, 710], [340, 640]], 'ok', { r: 14, both: true }); + b += line( + [ + [540, 640], + [540, 710], + [340, 710], + [340, 640], + ], + 'ok', + { r: 14, both: true } + ); b += tag(440, 710, 'direct', { fill: C.pine }); b += text(440, 765, 'NDP finds b’s MAC', { size: 15, fill: C.slate }); // right: Datum, one link per instance b += extFrame(700, 60, 560, 860, 'Datum: one link per instance'); b += tile(835, 140, 176, 200) + tile(1125, 140, 176, 200) + tile(980, 650, 200, 200); - b += rack(835, 215) + text(835, 292, 'instance', { size: 16 }) + mono(835, 313, 'fd20:0:53::1:0:0'); - b += rack(1125, 215) + text(1125, 292, 'instance', { size: 16 }) + mono(1125, 313, 'fd20:0:53::2:0:0'); - b += line([[835, 345], [835, 560], [980, 560], [980, 650]], 'link', { dot: true, r: 16 }); - b += line([[1125, 345], [1125, 560], [980, 560]], 'link', { dot: true, arrow: false, r: 16 }); - b += line([[945, 215], [1015, 215]], 'bad', { both: true }); - b += tag(980, 184, 'link-local ×', { fill: C.clay }) + tag(980, 246, 'ff02::1 ×', { fill: C.clay }); + b += + rack(835, 215) + text(835, 292, 'instance', { size: 16 }) + mono(835, 313, 'fd20:0:53::1:0:0'); + b += + rack(1125, 215) + + text(1125, 292, 'instance', { size: 16 }) + + mono(1125, 313, 'fd20:0:53::2:0:0'); + b += line( + [ + [835, 345], + [835, 560], + [980, 560], + [980, 650], + ], + 'link', + { dot: true, r: 16 } + ); + b += line( + [ + [1125, 345], + [1125, 560], + [980, 560], + ], + 'link', + { dot: true, arrow: false, r: 16 } + ); + b += line( + [ + [945, 215], + [1015, 215], + ], + 'bad', + { both: true } + ); + b += + tag(980, 184, 'link-local ×', { fill: C.clay }) + tag(980, 246, 'ff02::1 ×', { fill: C.clay }); b += label(980, 470, ['same /64, still', 'via the router'], { size: 15, fill: C.slate }); - b += datum(980, 720, 'router') + text(980, 800, 'router', { size: 16 }) + mono(980, 821, 'fd20:0:53::1'); + b += + datum(980, 720, 'router') + + text(980, 800, 'router', { size: 16 }) + + mono(980, 821, 'fd20:0:53::1'); return svg(W, H, b); }; // 3 ─ network → locations → instances export const image3 = () => { - const W = 1180, H = 1120; + const W = 1180, + H = 1120; let b = ''; b += deco(3, 0, 10, 120) + deco(6, 1040, 960, 130) + deco(12, 1090, 30, 56); b += tile(150, 480, 240, 220); b += datum(150, 560, 'hub', { r: 56 }); - b += text(150, 650, 'network blog-net', { size: 17, weight: 500 }) + mono(150, 672, 'fd20:0:53::/48', { size: 13.5 }); + b += + text(150, 650, 'network blog-net', { size: 17, weight: 500 }) + + mono(150, 672, 'fd20:0:53::/48', { size: 13.5 }); const locs = [ - { y: 215, n: 'us-west-1', c: 'fd20:0:53:0::/64', kids: [[120, 'fd20:0:53::1:0:0/96'], [310, 'fd20:0:53::2:0:0/96']] }, + { + y: 215, + n: 'us-west-1', + c: 'fd20:0:53:0::/64', + kids: [ + [120, 'fd20:0:53::1:0:0/96'], + [310, 'fd20:0:53::2:0:0/96'], + ], + }, { y: 560, n: 'us-central-1', c: 'fd20:0:53:2::/64', kids: [[560, 'fd20:0:53:2:0:1::/96']] }, { y: 905, n: 'us-east-1', c: 'fd20:0:53:1::/64', kids: [[905, 'fd20:0:53:1:0:1::/96']] }, ]; for (const L of locs) { - b += line([[282, 560], [330, 560], [330, L.y], [420, L.y]], 'ok', { r: 18 }); - b += ext(480, L.y, 'pin') + text(480, L.y + 70, L.n, { size: 17, weight: 500 }) + mono(480, L.y + 92, L.c, { size: 13 }); + b += line( + [ + [282, 560], + [330, 560], + [330, L.y], + [420, L.y], + ], + 'ok', + { r: 18 } + ); + b += + ext(480, L.y, 'pin') + + text(480, L.y + 70, L.n, { size: 17, weight: 500 }) + + mono(480, L.y + 92, L.c, { size: 13 }); } - const rows = [[130, 'fd20:0:53::1:0:0/96'], [300, 'fd20:0:53::2:0:0/96'], [560, 'fd20:0:53:2:0:1::/96'], [905, 'fd20:0:53:1:0:1::/96']]; - const src = [[215, 130], [215, 300], [560, 560], [905, 905]]; + const rows = [ + [130, 'fd20:0:53::1:0:0/96'], + [300, 'fd20:0:53::2:0:0/96'], + [560, 'fd20:0:53:2:0:1::/96'], + [905, 'fd20:0:53:1:0:1::/96'], + ]; + const src = [ + [215, 130], + [215, 300], + [560, 560], + [905, 905], + ]; src.forEach(([sy, ty], i) => { - b += line([[545, sy], [680, sy], [680, ty], [746, ty]], 'ok', { r: 18 }); - b += tile(880, ty - 62, 250, 150) + rack(880, ty - 12) + mono(880, ty + 58, rows[i][1], { size: 12.5 }); + b += line( + [ + [545, sy], + [680, sy], + [680, ty], + [746, ty], + ], + 'ok', + { r: 18 } + ); + b += + tile(880, ty - 62, 250, 150) + + rack(880, ty - 12) + + mono(880, ty + 58, rows[i][1], { size: 12.5 }); }); return svg(W, H, b); }; // 4 ─ IPv4-only client through the ALB to an IPv6-only app export const image4 = () => { - const W = 820, H = 1580; + const W = 820, + H = 1580; let b = ''; b += deco(4, 600, 0, 170) + deco(6, 10, 1380, 120); b += ext(410, 80, 'laptop') + text(410, 154, 'IPv4-only client', { size: 17, weight: 500 }); - b += line([[410, 174], [410, 322]], 'neutral') + tag(410, 250, 'HTTPS · anycast · A + AAAA'); + b += + line( + [ + [410, 174], + [410, 322], + ], + 'neutral' + ) + tag(410, 250, 'HTTPS · anycast · A + AAAA'); b += tile(410, 330, 280, 215); - b += datum(410, 400, 'hub', { r: 52 }) + text(410, 488, 'Datum ALB', { size: 17, weight: 500 }) + text(410, 510, 'Envoy at the nearest edge', { size: 14, fill: C.muted }); - b += line([[410, 553], [410, 740]], 'ok'); - b += label(410, 630, ['new connection from', 'fd42:…'], { size: 14 }) + tag(410, 690, 'X-Forwarded-For: client IPv4'); + b += + datum(410, 400, 'hub', { r: 52 }) + + text(410, 488, 'Datum ALB', { size: 17, weight: 500 }) + + text(410, 510, 'Envoy at the nearest edge', { size: 14, fill: C.muted }); + b += line( + [ + [410, 553], + [410, 740], + ], + 'ok' + ); + b += + label(410, 630, ['new connection from', 'fd42:…'], { size: 14 }) + + tag(410, 690, 'X-Forwarded-For: client IPv4'); b += datumFrame(190, 750, 440, 230, 'IPv6 inside'); - b += rack(410, 855) + text(410, 925, 'your app', { size: 16, weight: 500 }) + mono(410, 946, 'fd20:0:53::1:0:0 · IPv6 only', { size: 12.5 }); + b += + rack(410, 855) + + text(410, 925, 'your app', { size: 16, weight: 500 }) + + mono(410, 946, 'fd20:0:53::1:0:0 · IPv6 only', { size: 12.5 }); // left: DNS64 + NAT64 - b += line([[300, 980], [300, 1010], [240, 1010], [240, 1132]], 'neutral', { r: 14 }); + b += line( + [ + [300, 980], + [300, 1010], + [240, 1010], + [240, 1132], + ], + 'neutral', + { r: 14 } + ); b += label(240, 1075, ['IPv4-only name', '→ 64:ff9b::… address'], { size: 13.5 }); b += tile(240, 1140, 280, 195); - b += datum(240, 1210, 'swap', { r: 46 }) + text(240, 1290, 'DNS64 + NAT64', { size: 16, weight: 500 }) + text(240, 1312, 'per-location egress', { size: 14, fill: C.muted }); - b += line([[240, 1343], [240, 1400], [350, 1400], [350, 1440]], 'neutral', { r: 14 }); + b += + datum(240, 1210, 'swap', { r: 46 }) + + text(240, 1290, 'DNS64 + NAT64', { size: 16, weight: 500 }) + + text(240, 1312, 'per-location egress', { size: 14, fill: C.muted }); + b += line( + [ + [240, 1343], + [240, 1400], + [350, 1400], + [350, 1440], + ], + 'neutral', + { r: 14 } + ); b += label(300, 1368, ['leaves from a shared', 'IPv4 per location'], { size: 13.5 }); // right: literal address fails - b += line([[520, 980], [520, 1010], [610, 1010], [610, 1200]], 'bad', { r: 14 }); - b += line([[610, 1262], [610, 1400], [470, 1400], [470, 1440]], 'bad', { r: 14 }); + b += line( + [ + [520, 980], + [520, 1010], + [610, 1010], + [610, 1200], + ], + 'bad', + { r: 14 } + ); + b += line( + [ + [610, 1262], + [610, 1400], + [470, 1400], + [470, 1440], + ], + 'bad', + { r: 14 } + ); b += tag(610, 1235, 'a literal like 1.1.1.1 ×', { fill: C.clay }); b += label(610, 1320, ['nothing to', 'translate'], { size: 13.5, fill: C.clayDeep }); - b += ext(410, 1500, 'globe', { r: 40 }) + text(410, 1556, 'IPv4-only internet', { size: 16, weight: 500 }); + b += + ext(410, 1500, 'globe', { r: 40 }) + + text(410, 1556, 'IPv4-only internet', { size: 16, weight: 500 }); return svg(W, H, b); }; // 5 ─ configs from other platforms export const image5 = () => { - const W = 1560, H = 720; + const W = 1560, + H = 720; let b = ''; - b += ext(110, 360, 'case', { r: 54 }) + text(110, 442, 'configs from', { size: 16 }) + text(110, 464, 'other platforms', { size: 16 }); + b += + ext(110, 360, 'case', { r: 54 }) + + text(110, 442, 'configs from', { size: 16 }) + + text(110, 464, 'other platforms', { size: 16 }); const rows = [ ['listen 8080; · 0.0.0.0', 'healthy-looking, 503'], ['8.8.8.8 · 1.1.1.1', 'no route to a literal'], @@ -133,21 +415,48 @@ export const image5 = () => { const ys = [90, 225, 360, 495, 630]; rows.forEach(([a, r], i) => { const y = ys[i]; - b += line([[170, 360], [210, 360], [210, y], [270, y]], 'neutral', { r: 16, dot: false }); + b += line( + [ + [170, 360], + [210, 360], + [210, y], + [270, y], + ], + 'neutral', + { r: 16, dot: false } + ); b += `${text(445, y + 5.5, a, { size: 14, mono: true, fill: C.ink })}`; - b += line([[610, y], [706, y]], 'bad'); + b += line( + [ + [610, y], + [706, y], + ], + 'bad' + ); b += `${text(866, y + 5.5, r, { size: 16, fill: C.clayDeep })}`; - b += line([[1016, y], [1096, y]], 'ok'); + b += line( + [ + [1016, y], + [1096, y], + ], + 'ok' + ); }); b += datumFrame(1106, 40, 420, 640, ''); - b += text(1316, 330, ['grep · ss -tln', 'dig AAAA ipv4only.arpa'], { size: 17, mono: true, lh: 26, weight: 500 }); + b += text(1316, 330, ['grep · ss -tln', 'dig AAAA ipv4only.arpa'], { + size: 17, + mono: true, + lh: 26, + weight: 500, + }); b += text(1316, 410, 'before you move', { size: 17, weight: 500, fill: C.pine }); return svg(W, H, b); }; // 7 ─ four ways to connect regions export const image7 = () => { - const W = 1500, H = 1700; + const W = 1500, + H = 1700; let b = ''; // Datum b += extFrame(220, 30, 1060, 420, 'Datum: one network, every metro, no setup'); @@ -155,9 +464,38 @@ export const image7 = () => { b += rack(750, 190) + text(750, 262, 'us-central-1', { size: 16, weight: 500 }); b += rack(430, 305) + text(430, 380, 'us-west-1', { size: 16, weight: 500 }); b += rack(1070, 305) + text(1070, 380, 'us-east-1', { size: 16, weight: 500 }); - b += line([[648, 190], [570, 190], [570, 300], [533, 300]], 'ok', { both: true, r: 14 }); - b += line([[852, 190], [930, 190], [930, 300], [967, 300]], 'ok', { both: true, r: 14 }); - b += line([[533, 372], [570, 372], [570, 424], [930, 424], [930, 372], [967, 372]], 'ok', { both: true, r: 14 }); + b += line( + [ + [648, 190], + [570, 190], + [570, 300], + [533, 300], + ], + 'ok', + { both: true, r: 14 } + ); + b += line( + [ + [852, 190], + [930, 190], + [930, 300], + [967, 300], + ], + 'ok', + { both: true, r: 14 } + ); + b += line( + [ + [533, 372], + [570, 372], + [570, 424], + [930, 424], + [930, 372], + [967, 372], + ], + 'ok', + { both: true, r: 14 } + ); b += tag(750, 424, '1 hop · MTU 1440', { fill: C.pine }); // Fly + Railway b += extFrame(30, 500, 1440, 300, 'Fly.io and Railway: WireGuard mesh'); @@ -165,48 +503,93 @@ export const image7 = () => { b += logo(1350, 665, 'railway') + text(1350, 740, 'Railway', { size: 16 }); b += `${text(420, 674, 'lax', { size: 17, fill: C.clayDeep, weight: 500 })}`; b += `${text(1080, 674, 'iad', { size: 17, fill: C.clayDeep, weight: 500 })}`; - b += line([[540, 667], [960, 667]], 'bad', { both: true }); + b += line( + [ + [540, 667], + [960, 667], + ], + 'bad', + { both: true } + ); b += tag(750, 667, '3 hops · MTU 1370 / 1316', { fill: C.clay }); // Google b += extFrame(30, 850, 1440, 300, 'Google Cloud: global VPC, you plan it'); b += logo(150, 1015, 'google') + text(150, 1090, 'Google Cloud', { size: 16 }); b += `${text(440, 1008, 'us-west1', { size: 16, weight: 500 })}${mono(440, 1034, '10.10.1.0/24 ✍', { anchor: 'middle' })}`; b += `${text(1060, 1008, 'us-east4', { size: 16, weight: 500 })}${mono(1060, 1034, '10.10.2.0/24 ✍', { anchor: 'middle' })}`; - b += line([[580, 1015], [920, 1015]], 'neutral', { both: true }); + b += line( + [ + [580, 1015], + [920, 1015], + ], + 'neutral', + { both: true } + ); b += tag(750, 1015, 'firewall rules first'); // Vultr b += extFrame(30, 1200, 1440, 300, 'Vultr: a VPC per region, IPv4-only'); b += logo(150, 1365, 'vultr') + text(150, 1440, 'Vultr', { size: 16 }); b += `${text(420, 1374, 'ewr VPC', { size: 16, fill: '#fff', weight: 500 })}`; b += `${text(1080, 1374, 'lax VPC', { size: 16, fill: '#fff', weight: 500 })}`; - b += line([[540, 1367], [960, 1367]], 'bad', { both: true }); + b += line( + [ + [540, 1367], + [960, 1367], + ], + 'bad', + { both: true } + ); b += tag(750, 1367, 'public internet · 14 hops', { fill: C.clay }); return svg(W, H - 140, b); }; // 11 ─ SRv6 sequence export const image11 = () => { - const W = 1620, H = 1180; + const W = 1620, + H = 1180; let b = ''; const cols = [ - ['west', 'instance', 150, 'dc'], ['west node', '(kernel + eBPF)', 440, 'dc'], ['next-door', 'instance', 730, 'dc'], - ['backbone', '', 1020, 'ext'], ['east', 'node', 1240, 'dc'], ['east', 'instance', 1450, 'dc'], + ['west', 'instance', 150, 'dc'], + ['west node', '(kernel + eBPF)', 440, 'dc'], + ['next-door', 'instance', 730, 'dc'], + ['backbone', '', 1020, 'ext'], + ['east', 'node', 1240, 'dc'], + ['east', 'instance', 1450, 'dc'], ]; // bands b += ``; b += ``; - for (const [, , x] of cols) b += ``; + for (const [, , x] of cols) + b += ``; for (const [t1, t2, x, k] of cols) { const dc = k === 'dc'; b += `${text(x, t2 ? 55 : 63, t1, { size: 15, weight: 500 })}${t2 ? text(x, 77, t2, { size: 14, fill: C.muted }) : ''}`; } - const note = (x, y, w, lines) => `${text(x, y + 28, lines, { size: 14.5, lh: 22, fill: C.ink })}`; - const msg = (x1, x2, y, s, kind = 'neutral', t) => line([[x1, y], [x2, y]], kind) + tag((x1 + x2) / 2, y - 22, s, { fill: t ?? C.ink }); - b += label(190, 178, 'next door: same node', { size: 16, bg: C.clayPale, fill: C.clayDeep, weight: 500 }); + const note = (x, y, w, lines) => + `${text(x, y + 28, lines, { size: 14.5, lh: 22, fill: C.ink })}`; + const msg = (x1, x2, y, s, kind = 'neutral', t) => + line( + [ + [x1, y], + [x2, y], + ], + kind + ) + tag((x1 + x2) / 2, y - 22, s, { fill: t ?? C.ink }); + b += label(190, 178, 'next door: same node', { + size: 16, + bg: C.clayPale, + fill: C.clayDeep, + weight: 500, + }); b += msg(150, 440, 230, 'packet · hop limit 64'); b += note(440, 262, 230, ['kernel routes it', 'inside the VRF']); b += msg(440, 730, 390, 'packet · hop limit 63'); - b += label(250, 528, 'across the country: wrapped in SRv6', { size: 16, bg: C.mossPale, fill: C.pine, weight: 500 }); + b += label(250, 528, 'across the country: wrapped in SRv6', { + size: 16, + bg: C.mossPale, + fill: C.pine, + weight: 500, + }); b += msg(150, 440, 590, 'packet · hop limit 64'); b += note(440, 622, 300, ['eBPF wraps it:', 'outer IPv6 header to', '“east node, blog-net”']); b += msg(440, 1020, 780, 'outer | packet', 'ok', C.pine); diff --git a/.claude/skills/datum-diagram/examples/five-articles.mjs b/.claude/skills/datum-diagram/examples/five-articles.mjs new file mode 100644 index 00000000..9c3abc63 --- /dev/null +++ b/.claude/skills/datum-diagram/examples/five-articles.mjs @@ -0,0 +1,302 @@ +import { + C, + SW, + text, + tag, + label, + badge, + ext, + datum, + tile, + extFrame, + line, + deco, + svg, +} from '../kit.mjs'; + +const chip = (x, y, w, h, lines, kind = 'ext') => { + const L = Array.isArray(lines) ? lines : [lines]; + const fill = kind === 'ok' ? C.mossPale : C.panel, + stroke = kind === 'ok' ? C.pine : C.line; + return `${text(x + w / 2, y + h / 2 + 5 - (L.length - 1) * 10, L, { size: 15, lh: 20, fill: kind === 'ok' ? C.navy : C.slate })}`; +}; +const note = (x, y, s, o = {}) => text(x, y, s, { size: 14, fill: C.muted, ...o }); + +// 1 ─ four flavors: who runs what +export const fourFlavors = () => { + const W = 1240, + H = 640; + let b = deco(6, 1130, 520, 130); + b += extFrame(30, 30, 1180, 560, 'Four flavors: who runs what'); + const colX = [200, 450, 700, 950], + cw = 235; + const cols = [ + { + name: 'Public Cloud', + sub: 'for sale · multi-tenant', + cp: ['Datum', 1], + inf: ['Datum', 1, 'picks hardware,|locations and network paths'], + best: 'just works, on demand', + }, + { + name: 'Dedicated Cloud', + sub: 'for sale · fully managed', + cp: ['Datum', 1], + inf: ['Datum-managed', 1, 'dedicated to you'], + best: 'control and isolation', + }, + { + name: 'Bring Your Own Cloud', + sub: 'for sale', + cp: ['Datum', 1], + inf: ['You', 0, 'your data plane,|your infrastructure'], + best: 'sovereign or regulated', + }, + { + name: 'Open Source', + sub: 'not for sale · AGPLv3', + cp: ['You', 0, 'run it yourself'], + inf: ['You', 0, 'run it yourself'], + best: 'use it, study it, fix it', + }, + ]; + b += text(60, 232, 'Control plane', { size: 15, weight: 500, anchor: 'start' }); + b += text(60, 372, 'Infrastructure', { size: 15, weight: 500, anchor: 'start' }); + b += text(60, 372 + 14, 'and data plane', { size: 15, weight: 500, anchor: 'start' }); + b += text(60, 500, 'Good for', { size: 15, weight: 500, anchor: 'start' }); + cols.forEach((c, i) => { + const x = colX[i]; + b += text(x + cw / 2, 108, c.name, { size: 18, weight: 500 }) + note(x + cw / 2, 132, c.sub); + const cell = (y, h, [who, datumOwned, sub]) => { + let s = datumOwned ? tile(x + cw / 2, y, cw, h) : chip(x, y, cw, h, '', 'ext'); + s += text(x + cw / 2, y + h / 2 - (sub ? 4 : -6), who, { + size: 18, + weight: 500, + fill: datumOwned ? C.navy : C.slate, + }); + if (sub) + sub.split('|').forEach((l, k) => { + s += note(x + cw / 2, y + h / 2 + 18 + k * 17, l); + }); + return s; + }; + b += cell(185, 100, c.cp.length === 2 ? [...c.cp, null] : c.cp); + b += cell(325, 120, c.inf.length === 2 ? [...c.inf, null] : c.inf); + b += text(x + cw / 2, 504, c.best, { size: 15, fill: C.slate }); + }); + return svg(W, H, b); +}; + +// 2 ─ Galactic VPC: land anything here +export const landAnything = () => { + const W = 1240, + H = 660; + let b = deco(4, 1090, -80, 170); + b += extFrame(30, 30, 1180, 600, 'Galactic VPC: land anything, on one virtual backbone'); + const left = [ + 'MASQUE tunnel', + 'WireGuard peer', + 'Plain old VPN', + 'Layer 2 VLAN', + 'Layer 3 BGP session', + 'NNI to a carrier', + ]; + b += note(180, 100, 'however the connection arrives', { size: 14 }); + left.forEach((t, i) => { + const y = 130 + i * 78; + b += + chip(60, y, 240, 52, t) + + line( + [ + [300, y + 26], + [468, y + 26], + ], + 'neutral' + ); + }); + b += + tile(620, 118, 300, 460) + + datum(620, 280, 'hub', { r: 56 }) + + text(620, 385, 'Galactic VPC', { size: 20, weight: 500 }) + + note(620, 412, 'a virtual meet-me room') + + note(620, 432, 'and a virtual backbone') + + note(620, 466, 'built on SRv6', { size: 13 }); + b += note(1060, 100, 'interchangeable providers underneath'); + ['Bare metal, one provider', 'VMs, another provider', 'GPUs, a third provider'].forEach( + (t, i) => { + const y = 175 + i * 120; + b += + line( + [ + [776, y + 26], + [940, y + 26], + ], + 'ok' + ) + chip(948, y, 230, 52, t); + } + ); + return svg(W, H, b); +}; + +// 3 ─ private by default +export const privateByDefault = () => { + const W = 1240, + H = 460; + let b = deco(6, 1110, 330, 120); + b += extFrame( + 30, + 30, + 1180, + 400, + 'Private by default: nothing is addressable on the public internet' + ); + const xs = [150, 450, 790, 1080]; + b += + ext(xs[0], 220, 'laptop') + + text(xs[0], 292, 'Docker container', { size: 16, weight: 500 }) + + note(xs[0], 314, 'or desktop application'); + b += + tile(xs[1], 130, 220, 200) + + datum(xs[1], 215, 'lock', { r: 42 }) + + text(xs[1], 292, 'Datum Tunnel', { size: 17, weight: 500 }) + + note(xs[1], 313, 'QUIC · iroh · routes by key'); + b += + tile(xs[2], 130, 220, 200) + + datum(xs[2], 215, 'hub', { r: 42 }) + + text(xs[2], 292, 'Galactic VPC', { size: 17, weight: 500 }) + + note(xs[2], 313, 'private backbone'); + b += ext(xs[3], 220, 'cloud') + text(xs[3], 292, 'AWS', { size: 16, weight: 500 }); + b += + line( + [ + [210, 220], + [336, 220], + ], + 'neutral' + ) + + line( + [ + [564, 220], + [676, 220], + ], + 'ok' + ) + + line( + [ + [904, 220], + [1022, 220], + ], + 'ok' + ); + b += + tag(273, 196, 'lands', { fill: C.ink }) + + tag(620, 196, 'lands', { fill: C.pine }) + + tag(963, 196, 'routes out', { fill: C.pine }); + b += `${text(620, 388, 'no exposed ports · no overlay to babysit', { size: 15, weight: 500 })}`; + return svg(W, H, b); +}; + +// 4 ─ every agent needs an edge, tunnels and a backbone +export const agentThreePieces = () => { + const W = 1240, + H = 700; + let b = deco(3, 20, 560, 100); + b += extFrame(30, 30, 1180, 640, 'Every agent gets its own private network'); + b += + tile(160, 250, 230, 220) + + datum(160, 330, 'server', { r: 44 }) + + text(160, 415, 'Your agent', { size: 18, weight: 500 }) + + note(160, 438, 'and its workloads'); + const rows = [ + { + y: 105, + n: 1, + t: 'Edge', + d: ['Envoy proxy meets the internet at scale', 'protection from bots, threats and DDoS'], + to: 'the public internet', + }, + { + y: 285, + n: 2, + t: 'Tunnels', + d: [ + 'peer-to-peer QUIC connections (Iroh)', + 'land application traffic exactly where you want it', + ], + to: 'your apps and endpoints', + }, + { + y: 465, + n: 3, + t: 'Backbone', + d: [ + 'deterministic, auditable “fast lanes”', + 'SRv6 overlay with VPC-style control and observability', + ], + to: 'clouds, partners, providers', + }, + ]; + rows.forEach((r) => { + b += + tile(620, r.y, 470, 140) + + badge(410, r.y + 36, r.n) + + text(440, r.y + 42, r.t, { size: 20, weight: 500, anchor: 'start' }) + + note(440, r.y + 80, r.d[0], { anchor: 'start' }) + + note(440, r.y + 102, r.d[1], { anchor: 'start' }); + b += line( + [ + [276, 360], + [340, 360], + [340, r.y + 70], + [380, r.y + 70], + ], + 'ok', + { r: 14 } + ); + b += line( + [ + [860, r.y + 70], + [940, r.y + 70], + ], + 'neutral' + ); + b += chip(948, r.y + 40, 220, 60, r.to); + }); + return svg(W, H, b); +}; + +// 5 ─ chicken and egg +export const chickenAndEgg = () => { + const W = 1140, + H = 560; + let b = deco(12, 1060, 40, 46); + b += extFrame(30, 30, 1080, 500, 'Compute and Galactic VPC need each other'); + b += + tile(230, 140, 300, 190) + + datum(230, 210, 'server', { r: 40 }) + + text(230, 282, 'Datum Compute', { size: 19, weight: 500 }) + + note(230, 304, 'microVMs that start in milliseconds'); + b += + tile(910, 140, 300, 190) + + datum(910, 210, 'hub', { r: 40 }) + + text(910, 282, 'Galactic VPC', { size: 19, weight: 500 }) + + note(910, 304, 'private backbone built on SRv6'); + b += + line( + [ + [392, 205], + [748, 205], + ], + 'ok' + ) + tag(570, 205, 'every workload comes up attached', { fill: C.pine }); + b += + line( + [ + [748, 268], + [392, 268], + ], + 'neutral' + ) + tag(570, 268, 'real workloads show its value', { fill: C.ink }); + b += `${text(570, 410, 'The payoff is the one the big clouds get', { size: 15, weight: 500 })}${text(570, 438, 'lower egress costs · reduced latency · a smaller security blast radius', { size: 15, fill: C.slate })}`; + return svg(W, H, b); +}; diff --git a/.claude/skills/datum-diagram/examples/mermaid/agentThreePieces.mmd b/.claude/skills/datum-diagram/examples/mermaid/agentThreePieces.mmd new file mode 100644 index 00000000..94530853 --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/agentThreePieces.mmd @@ -0,0 +1,11 @@ +flowchart TB + agent["Your agent
and its workloads"]:::datum + edge["1 · Edge
Envoy proxy
bot, threat and
DDoS protection"]:::datum + tun["2 · Tunnels
peer-to-peer QUIC
(Iroh)"]:::datum + bb["3 · Backbone
SRv6 fast lanes
VPC-style control"]:::datum + net["public internet"]:::ext + apps["your apps
and endpoints"]:::ext + prov["clouds, partners,
providers"]:::ext + agent -.-> edge -.-> net + agent -.-> tun -.-> apps + agent -.-> bb -.-> prov diff --git a/.claude/skills/datum-diagram/examples/mermaid/chickenAndEgg.mmd b/.claude/skills/datum-diagram/examples/mermaid/chickenAndEgg.mmd new file mode 100644 index 00000000..566900b0 --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/chickenAndEgg.mmd @@ -0,0 +1,7 @@ +flowchart TB + compute["Datum Compute
microVMs that start in milliseconds"]:::datum + gvpc["Galactic VPC
private backbone built on SRv6"]:::datum + pay["lower egress costs · reduced latency
a smaller security blast radius"]:::ok + compute -.->|"every workload comes up attached"| gvpc + gvpc -.->|"real workloads show its value"| compute + gvpc -.-> pay diff --git a/.claude/skills/datum-diagram/examples/mermaid/fourFlavors.mmd b/.claude/skills/datum-diagram/examples/mermaid/fourFlavors.mmd new file mode 100644 index 00000000..2400cd7c --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/fourFlavors.mmd @@ -0,0 +1,6 @@ +flowchart TB + pub["Public Cloud · for sale
Datum runs it all: infrastructure,
hardware, locations, network paths"]:::datum + ded["Dedicated Cloud · for sale
fully managed by Datum,
dedicated to you"]:::datum + byoc["Bring Your Own Cloud · for sale
Datum runs the control plane,
you bring the infrastructure"]:::datum + oss["Open Source · AGPLv3 · not for sale
you run it yourself"]:::ext + pub ~~~ ded ~~~ byoc ~~~ oss diff --git a/.claude/skills/datum-diagram/examples/mermaid/landAnything.mmd b/.claude/skills/datum-diagram/examples/mermaid/landAnything.mmd new file mode 100644 index 00000000..c67f1215 --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/landAnything.mmd @@ -0,0 +1,24 @@ +flowchart LR + subgraph arrive["however the connection arrives"] + a1["MASQUE tunnel"]:::ext + a2["WireGuard peer"]:::ext + a3["plain old VPN"]:::ext + a4["Layer 2 VLAN"]:::ext + a5["Layer 3 BGP session"]:::ext + a6["NNI to a carrier"]:::ext + end + gvpc["Galactic VPC
a virtual meet-me room
and a virtual backbone
built on SRv6"]:::datum + subgraph prov["interchangeable providers"] + p1["bare metal,
one provider"]:::ext + p2["VMs,
another provider"]:::ext + p3["GPUs,
a third provider"]:::ext + end + a1 -.-> gvpc + a2 -.-> gvpc + a3 -.-> gvpc + a4 -.-> gvpc + a5 -.-> gvpc + a6 -.-> gvpc + gvpc -.-> p1 + gvpc -.-> p2 + gvpc -.-> p3 diff --git a/.claude/skills/datum-diagram/examples/mermaid/pathsDirectRelay.mmd b/.claude/skills/datum-diagram/examples/mermaid/pathsDirectRelay.mmd new file mode 100644 index 00000000..1b787d1b --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/pathsDirectRelay.mmd @@ -0,0 +1,10 @@ +flowchart LR + subgraph both["both paths tried in parallel · fastest wins, automatic failover"] + direction LR + gw["Gateway endpoint"]:::datum + relay["Home relay"]:::datum + dn["Desktop node"]:::datum + gw <-.->|"encrypted"| relay + relay <-.->|"encrypted"| dn + gw <-.->|"direct UDP
hole-punched"| dn + end diff --git a/.claude/skills/datum-diagram/examples/mermaid/privateByDefault.mmd b/.claude/skills/datum-diagram/examples/mermaid/privateByDefault.mmd new file mode 100644 index 00000000..452446e4 --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/privateByDefault.mmd @@ -0,0 +1,11 @@ +flowchart TB + c["Docker container
or desktop application"]:::ext + subgraph priv["no exposed ports · no overlay to babysit"] + direction TB + t["Datum Tunnel
QUIC · iroh · routes by key"]:::datum + g["Galactic VPC
private backbone"]:::datum + end + a["AWS"]:::ext + c -.->|"lands"| t + t -.->|"lands"| g + g -.->|"routes out"| a diff --git a/.claude/skills/datum-diagram/examples/mermaid/requestFlow.mmd b/.claude/skills/datum-diagram/examples/mermaid/requestFlow.mmd new file mode 100644 index 00000000..4d4b8f3f --- /dev/null +++ b/.claude/skills/datum-diagram/examples/mermaid/requestFlow.mmd @@ -0,0 +1,10 @@ +sequenceDiagram + participant E as Envoy + participant G as iroh-gateway + participant D as Desktop node + E->>G: 1 · HTTP request
(via Unix socket) + G->>D: 2 · resolve endpoint ID
from request metadata + G->>D: 3 · open QUIC stream
(reuse connection or establish new one) + G->>D: 4 · forward request
over QUIC stream + D-->>G: 5 · HTTP response + G-->>E: 6 · HTTP response