skilldrop-cli 0.16.21 → 0.16.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skilldrop.js +5 -3
- package/catalogue.json +1 -0
- package/model-routing.json +4 -0
- package/package.json +1 -1
- package/packs/solution-architect/skills/interactive-diagram/SKILL.md +160 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/eval_queries.json +12 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/evals.json +164 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/1/README.md +11 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/1/api/app.py +43 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/1/docker-compose.yml +20 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/1/web/checkout.js +9 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/1/worker/worker.py +23 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/10/build-4127-timing.json +179 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AbstractSensor.java +36 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AlertService.java +15 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Greenhouse.java +36 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Sensor.java +10 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/SoilMoistureSensor.java +25 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/TemperatureSensor.java +19 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Zone.java +26 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/3/schema.sql +35 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/README.md +26 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/config/default.json +29 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/clients/transcription.js +25 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/db.js +16 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/handlers/storageWebhook.js +43 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/jobs/transcribeEpisode.js +28 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/mailer.js +18 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/server.js +32 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/worker.js +32 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/6/notes.txt +7 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/6/trace-quote-slow.jsonl +13 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/7/oncall-notes.md +16 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/7/system-log.txt +14 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/8/run-4471.json +151 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/9/chat-export.txt +16 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/9/lesson-sync.log +24 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/files/9/ticket-8812.csv +6 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.html +203 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.json +48 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.md +31 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.html +203 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.json +110 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.html +203 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.json +21 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/api/checkout.py +41 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/docker-compose.yml +20 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/worker/fulfil.py +23 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/inputs/incident/oncall-notes.md +14 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/inputs/trace/checkout-trace.jsonl +9 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.html +203 -0
- package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.json +28 -0
- package/packs/solution-architect/skills/interactive-diagram/manifest.json +39 -0
- package/packs/solution-architect/skills/interactive-diagram/reference.md +231 -0
- package/packs/solution-architect/skills/interactive-diagram/scripts/build_diagram.py +2605 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
|
|
3
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-KdTstAM29gVIP104X7Tju5/PTeQKbRDMMG1TjJdsOyI='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
|
|
6
|
+
<title>How annual leave gets approved</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
|
|
7
|
+
<body>
|
|
8
|
+
<header class="top"><h1>How annual leave gets approved</h1><p class="q">What happens to a leave request from the moment it is submitted?</p><p class="sum">Every request goes to the line manager; requests over ten working days also need HR, and anything that clashes with the year-end freeze needs the director. Approved leave is booked in the team calendar automatically.</p><p class="muted" style="font-size:12px;margin:6px 0 0">2 items drawn dashed as assumptions</p></header>
|
|
9
|
+
<section class="text" id="outline" aria-label="The diagram as text">
|
|
10
|
+
<details open><summary>The diagram as text</summary><h2>Parts</h2><ul><li><b>Submit request in the HR portal</b> <span class="ev">(<code>user: “you request leave in the HR portal”</code>)</span></li><li><b>Line manager reviews</b> <span class="ev">(<code>user: “it goes to your line manager first”</code>)</span></li><li><b>Over 10 working days?</b> <span class="ev">(<code>user: “anything over ten working days also needs HR”</code>)</span></li><li><b>HR approves</b> <span class="ev">(<code>user: “also needs HR”</code>)</span></li><li><b>In the year-end freeze?</b> <span class="ev">(<code>user: “nothing in the last two weeks of December without the director”</code>)</span></li><li><b>Director approves</b> <span class="ev">(<code>user: “without the director”</code>)</span></li><li><b>Booked in team calendar</b> <span class="ev">(<code>user: “once it's approved it shows up in the team calendar by itself”</code>)</span></li><li><b>Employee told it's declined</b> <span class="tag warn">assumption</span></li></ul><h2>Connections</h2><ul><li>Submit request in the HR portal → Line manager reviews <span class="ev">(<code>user: “it goes to your line manager first”</code>)</span></li><li>Line manager reviews → Over 10 working days?: approves <span class="ev">(<code>user: “it goes to your line manager first”</code>)</span></li><li>Line manager reviews → Employee told it's declined: declines <span class="tag warn">assumption</span></li><li>Over 10 working days? → HR approves: yes <span class="ev">(<code>user: “anything over ten working days also needs HR”</code>)</span></li><li>Over 10 working days? → In the year-end freeze?: no <span class="ev">(<code>user: “anything over ten working days also needs HR”</code>)</span></li><li>HR approves → In the year-end freeze? <span class="ev">(<code>user: “also needs HR”</code>)</span></li><li>In the year-end freeze? → Director approves: yes <span class="ev">(<code>user: “without the director”</code>)</span></li><li>In the year-end freeze? → Booked in team calendar: no <span class="ev">(<code>user: “once it's approved it shows up in the team calendar by itself”</code>)</span></li><li>Director approves → Booked in team calendar <span class="ev">(<code>user: “once it's approved it shows up in the team calendar by itself”</code>)</span></li></ul><h2>Notes</h2><ul><li>Not in the description, so drawn as an assumption: what happens when HR or the director says no.</li></ul></details></section>
|
|
11
|
+
<nav class="bar" aria-label="Diagram controls">
|
|
12
|
+
<input id="find" type="search" placeholder="Find a part…" aria-label="Find a part">
|
|
13
|
+
<button id="zin" title="Zoom in">+</button><button id="zout" title="Zoom out">−</button><button id="zfit" title="Fit to screen">Fit</button>
|
|
14
|
+
<span class="sep"></span><button id="tour">Walk through</button><button id="prev" title="Previous step">←</button><button id="next" title="Next step">→</button>
|
|
15
|
+
<span class="sep"></span><button id="xsvg">SVG</button><button id="xpng">PNG</button><button id="xmmd" title="Copy and download Mermaid">Mermaid</button>
|
|
16
|
+
<button id="theme" title="Light or dark">◐</button>
|
|
17
|
+
<span class="hint">Click a part for detail and evidence · shift-click a second part for the path · drag to pan · scroll to zoom</span></nav>
|
|
18
|
+
<div class="legend"><span><i></i>connection</span><span><i class="d"></i>async / optional</span><span><i class="a"></i>assumption (not shown by the evidence)</span></div>
|
|
19
|
+
<div class="caption" id="caption" aria-live="polite"></div>
|
|
20
|
+
<div class="wrap"><div id="stage"><svg id="map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1071 405" width="1071" height="405" role="group" aria-label="How annual leave gets approved"><defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="arrow-start" viewBox="0 0 10 10" refX="1" refY="5" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="tri" viewBox="0 0 12 12" refX="11" refY="6" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto-start-reverse"><path d="M1,1 L11,6 L1,11 z" class="mk-open"/></marker><marker id="open" viewBox="0 0 12 12" refX="11" refY="6" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto-start-reverse"><path d="M1,1 L11,6 L1,11" class="mk-line"/></marker><marker id="dia-solid" viewBox="0 0 16 10" refX="15" refY="5" markerUnits="userSpaceOnUse" markerWidth="18" markerHeight="11" orient="auto-start-reverse"><path d="M1,5 L8,1 L15,5 L8,9 z" class="arrowhead"/></marker><marker id="dia-open" viewBox="0 0 16 10" refX="15" refY="5" markerUnits="userSpaceOnUse" markerWidth="18" markerHeight="11" orient="auto-start-reverse"><path d="M1,5 L8,1 L15,5 L8,9 z" class="mk-open"/></marker></defs><rect class="canvas" x="0" y="0" width="1071" height="405"/><g class="edge s-solid" id="e-e1" data-id="e1" data-from="submit" data-to="manager"><title>submit → manager</title><path class="hit" d="M274.5,107.0 L303.5,107.0 Q310.5,107.0 317.5,107.0 L346.5,107.0"/><path class="line" d="M274.5,107.0 L303.5,107.0 Q310.5,107.0 317.5,107.0 L346.5,107.0" marker-end="url(#arrow)"/></g><g class="edge s-solid" id="e-e2" data-id="e2" data-from="manager" data-to="long"><title>approves</title><path class="hit" d="M525.5,93.0 L550.0,93.0 Q557.0,93.0 557.0,86.0 L557.0,79.0 Q557.0,72.0 564.0,72.0 L602.5,72.0"/><path class="line" d="M525.5,93.0 L550.0,93.0 Q557.0,93.0 557.0,86.0 L557.0,79.0 Q557.0,72.0 564.0,72.0 L602.5,72.0" marker-end="url(#arrow)"/><rect class="lbg" x="541.4" y="63.0" width="58.4" height="18.0" rx="3"/><text class="elabel" x="570.6" y="76.0" text-anchor="middle">approves</text></g><g class="edge s-solid assumed" id="e-e3" data-id="e3" data-from="manager" data-to="declined"><title>declines</title><path class="hit" d="M525.5,121.0 L550.0,121.0 Q557.0,121.0 557.0,128.0 L557.0,155.0 Q557.0,162.0 564.0,162.0 L597.5,162.0"/><path class="line" d="M525.5,121.0 L550.0,121.0 Q557.0,121.0 557.0,128.0 L557.0,155.0 Q557.0,162.0 564.0,162.0 L597.5,162.0" marker-end="url(#arrow)"/><rect class="lbg" x="530.5" y="132.5" width="52.9" height="18.0" rx="3"/><text class="elabel" x="557.0" y="145.5" text-anchor="middle">declines</text></g><g class="edge s-solid" id="e-e4" data-id="e4" data-from="long" data-to="hr"><title>yes</title><path class="hit" d="M784.8,60.2 L844.0,60.2 Q851.0,60.2 851.0,67.2 L851.0,100.0 Q851.0,107.0 858.0,107.0 L891.5,107.0"/><path class="line" d="M784.8,60.2 L844.0,60.2 Q851.0,60.2 851.0,67.2 L851.0,100.0 Q851.0,107.0 858.0,107.0 L891.5,107.0" marker-end="url(#arrow)"/><rect class="lbg" x="817.1" y="51.2" width="28.2" height="18.0" rx="3"/><text class="elabel" x="831.1" y="64.2" text-anchor="middle">yes</text></g><g class="edge s-solid" id="e-e5" data-id="e5" data-from="long" data-to="freeze"><title>no</title><path class="hit" d="M784.8,83.8 L853.0,83.8 Q860.0,83.8 860.0,90.8 L860.0,191.5 Q860.0,198.5 853.0,198.5 L37.0,198.5 Q30.0,198.5 30.0,205.5 L30.0,289.2 Q30.0,296.2 37.0,296.2 L91.6,296.2"/><path class="line" d="M784.8,83.8 L853.0,83.8 Q860.0,83.8 860.0,90.8 L860.0,191.5 Q860.0,198.5 853.0,198.5 L37.0,198.5 Q30.0,198.5 30.0,205.5 L30.0,289.2 Q30.0,296.2 37.0,296.2 L91.6,296.2" marker-end="url(#arrow)"/><rect class="lbg" x="849.0" y="132.1" width="22.1" height="18.0" rx="3"/><text class="elabel" x="860.0" y="145.1" text-anchor="middle">no</text></g><g class="edge s-solid" id="e-e6" data-id="e6" data-from="hr" data-to="freeze"><title>hr → freeze</title><path class="hit" d="M1011.5,107.0 L1024.0,107.0 Q1031.0,107.0 1031.0,114.0 L1031.0,200.5 Q1031.0,207.5 1024.0,207.5 L46.0,207.5 Q39.0,207.5 39.0,214.5 L39.0,312.8 Q39.0,319.8 46.0,319.8 L91.6,319.8"/><path class="line" d="M1011.5,107.0 L1024.0,107.0 Q1031.0,107.0 1031.0,114.0 L1031.0,200.5 Q1031.0,207.5 1024.0,207.5 L46.0,207.5 Q39.0,207.5 39.0,214.5 L39.0,312.8 Q39.0,319.8 46.0,319.8 L91.6,319.8" marker-end="url(#arrow)"/></g><g class="edge s-solid" id="e-e7" data-id="e7" data-from="freeze" data-to="director"><title>yes</title><path class="hit" d="M241.4,296.2 L304.6,296.2 Q310.5,296.2 310.5,302.1 L310.5,302.1 Q310.5,308.0 316.4,308.0 L359.5,308.0"/><path class="line" d="M241.4,296.2 L304.6,296.2 Q310.5,296.2 310.5,302.1 L310.5,302.1 Q310.5,308.0 316.4,308.0 L359.5,308.0" marker-end="url(#arrow)"/><rect class="lbg" x="275.7" y="287.2" width="28.1" height="18.0" rx="3"/><text class="elabel" x="289.8" y="300.2" text-anchor="middle">yes</text></g><g class="edge s-solid" id="e-e8" data-id="e8" data-from="freeze" data-to="booked"><title>no</title><path class="hit" d="M241.4,319.8 L303.5,319.8 Q310.5,319.8 310.5,326.8 L310.5,358.0 Q310.5,365.0 317.5,365.0 L550.0,365.0 Q557.0,365.0 557.0,358.0 L557.0,302.7 Q557.0,295.7 564.0,295.7 L608.8,295.7"/><path class="line" d="M241.4,319.8 L303.5,319.8 Q310.5,319.8 310.5,326.8 L310.5,358.0 Q310.5,365.0 317.5,365.0 L550.0,365.0 Q557.0,365.0 557.0,358.0 L557.0,302.7 Q557.0,295.7 564.0,295.7 L608.8,295.7" marker-end="url(#arrow)"/><rect class="lbg" x="422.7" y="356.0" width="22.1" height="18.0" rx="3"/><text class="elabel" x="433.8" y="369.0" text-anchor="middle">no</text></g><g class="edge s-solid" id="e-e9" data-id="e9" data-from="director" data-to="booked"><title>director → booked</title><path class="hit" d="M512.5,308.0 L559.8,308.0 Q566.0,308.0 566.0,314.2 L566.0,314.2 Q566.0,320.3 572.2,320.3 L608.8,320.3"/><path class="line" d="M512.5,308.0 L559.8,308.0 Q566.0,308.0 566.0,314.2 L566.0,314.2 Q566.0,320.3 572.2,320.3 L608.8,320.3" marker-end="url(#arrow)"/></g><g class="node c-green k-start" id="n-submit" data-id="submit" tabindex="0" role="button" aria-label="Submit request in the HR portal"><title>Submit request in the HR portal</title><rect class="shape" x="58.5" y="79.0" width="216.0" height="56.0" rx="28.0"/><text class="label" x="166.5" y="103.0" text-anchor="middle">Submit request in the HR</text><text class="label" x="166.5" y="120.0" text-anchor="middle">portal</text></g><g class="node c-rose k-manual" id="n-manager" data-id="manager" tabindex="0" role="button" aria-label="Line manager reviews"><title>Line manager reviews</title><rect class="shape" x="346.5" y="82.0" width="179.0" height="50.0" rx="6"/><text class="label" x="436.0" y="111.5" text-anchor="middle">Line manager reviews</text></g><g class="node c-amber k-decision" id="n-long" data-id="long" tabindex="0" role="button" aria-label="Over 10 working days?"><title>Over 10 working days?</title><path class="shape" d="M708.5,30.0 L814.5,72.0 L708.5,114.0 L602.5,72.0 Z"/><text class="label" x="708.5" y="68.0" text-anchor="middle">Over 10 working</text><text class="label" x="708.5" y="85.0" text-anchor="middle">days?</text></g><g class="node c-rose k-manual" id="n-hr" data-id="hr" tabindex="0" role="button" aria-label="HR approves"><title>HR approves</title><rect class="shape" x="891.5" y="82.0" width="120.0" height="50.0" rx="6"/><text class="label" x="951.5" y="111.5" text-anchor="middle">HR approves</text></g><g class="node c-amber k-decision" id="n-freeze" data-id="freeze" tabindex="0" role="button" aria-label="In the year-end freeze?"><title>In the year-end freeze?</title><path class="shape" d="M166.5,266.0 L270.5,308.0 L166.5,350.0 L62.5,308.0 Z"/><text class="label" x="166.5" y="304.0" text-anchor="middle">In the year-end</text><text class="label" x="166.5" y="321.0" text-anchor="middle">freeze?</text></g><g class="node c-rose k-manual" id="n-director" data-id="director" tabindex="0" role="button" aria-label="Director approves"><title>Director approves</title><rect class="shape" x="359.5" y="283.0" width="153.0" height="50.0" rx="6"/><text class="label" x="436.0" y="312.5" text-anchor="middle">Director approves</text></g><g class="node c-slate k-end" id="n-booked" data-id="booked" tabindex="0" role="button" aria-label="Booked in team calendar"><title>Booked in team calendar</title><rect class="shape" x="605.0" y="286.0" width="207.0" height="44.0" rx="22.0"/><text class="label" x="708.5" y="312.5" text-anchor="middle">Booked in team calendar</text></g><g class="node c-slate k-end assumed" id="n-declined" data-id="declined" tabindex="0" role="button" aria-label="Employee told it's declined (assumption)"><title>Employee told it's declined</title><rect class="shape" x="597.5" y="140.0" width="222.0" height="44.0" rx="22.0"/><text class="label" x="708.5" y="166.5" text-anchor="middle">Employee told it's declined</text><text class="badge warn" x="814.5" y="136.0" text-anchor="end">assumed</text></g></svg></div><aside id="panel" aria-live="polite"></aside></div>
|
|
21
|
+
<style id="diagram-css">
|
|
22
|
+
:root{--bg:#f7f8fa;--panel:#ffffff;--ink:#16181d;--muted:#5b6170;--line:#d5d9e0;--edge:#6b7385;--accent:#2563eb;
|
|
23
|
+
--warn:#b45309;--ok:#15803d;--bad:#b91c1c;--hl:#f59e0b;--canvas:#fbfcfd;--group:#eef1f6;--group2:#e6eaf2;--shadow:0 1px 2px rgba(0,0,0,.06)}
|
|
24
|
+
:root[data-theme=dark]{--bg:#0f1115;--panel:#171a21;--ink:#e7e9ee;--muted:#9aa1b2;--line:#2b303b;--edge:#8b93a7;--accent:#60a5fa;
|
|
25
|
+
--warn:#f59e0b;--ok:#4ade80;--bad:#f87171;--hl:#fbbf24;--canvas:#12151b;--group:#1a1e27;--group2:#20252f;--shadow:none}
|
|
26
|
+
*{box-sizing:border-box}html,body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
27
|
+
body{display:flex;flex-direction:column}body>*{flex:none}section.text{order:10}
|
|
28
|
+
header.top{padding:18px 24px 10px;max-width:1400px}header.top h1{margin:0 0 4px;font-size:21px}
|
|
29
|
+
.q{color:var(--muted);margin:0 0 6px}.sum{margin:0;max-width:900px}
|
|
30
|
+
.bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:8px 24px;position:sticky;top:0;background:var(--bg);z-index:5;border-bottom:1px solid var(--line)}
|
|
31
|
+
.bar button,.bar input{font:inherit;font-size:13px;border:1px solid var(--line);background:var(--panel);color:var(--ink);border-radius:6px;padding:4px 9px;cursor:pointer}
|
|
32
|
+
.bar input{cursor:text;width:180px}.bar button:hover{border-color:var(--accent)}.bar .sep{width:1px;height:20px;background:var(--line);margin:0 4px}
|
|
33
|
+
.bar .hint{color:var(--muted);font-size:12px;margin-left:auto}
|
|
34
|
+
.wrap{display:flex;gap:0;height:calc(100vh - 150px);min-height:420px;padding:10px 24px 0}
|
|
35
|
+
#stage{flex:1;position:relative;overflow:hidden;border:1px solid var(--line);border-radius:10px;background:var(--canvas);touch-action:none;cursor:grab}
|
|
36
|
+
#stage.drag{cursor:grabbing}#stage svg{position:absolute;left:0;top:0;width:100%;height:100%}
|
|
37
|
+
aside#panel{width:340px;margin-left:12px;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:auto;padding:14px 16px;display:none}
|
|
38
|
+
aside#panel.open{display:block}aside h3{margin:0 0 2px;font-size:16px}aside .kind{color:var(--muted);font-size:12px;margin-bottom:8px}
|
|
39
|
+
aside .close{float:right;border:0;background:none;color:var(--muted);font-size:18px;cursor:pointer}
|
|
40
|
+
aside pre{background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:8px;overflow:auto;font-size:12px;max-height:260px}
|
|
41
|
+
aside ul{padding-left:18px;margin:4px 0}aside a.jump{color:var(--accent);cursor:pointer;text-decoration:none}
|
|
42
|
+
aside .acts button{font:inherit;font-size:12px;margin:2px 4px 2px 0;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:5px;padding:3px 7px;cursor:pointer}
|
|
43
|
+
.caption{display:none;margin:8px 24px 0;padding:8px 12px;border-left:3px solid var(--hl);background:var(--panel);border-radius:4px}
|
|
44
|
+
.caption.on{display:block}
|
|
45
|
+
section.text{max-width:1100px;padding:10px 24px 40px}section.text h2{font-size:15px;margin:18px 0 4px}
|
|
46
|
+
.tag{font-size:11px;padding:0 5px;border-radius:4px;border:1px solid var(--line);margin-left:4px}.tag.warn{color:var(--warn);border-color:var(--warn)}
|
|
47
|
+
.tag.st-added{color:var(--ok);border-color:var(--ok)}.tag.st-removed{color:var(--bad);border-color:var(--bad)}.tag.st-changed{color:var(--accent);border-color:var(--accent)}
|
|
48
|
+
.muted{color:var(--muted)}.ev{color:var(--muted);font-size:12px}code{font-size:12px}
|
|
49
|
+
.legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--muted);padding:6px 24px 0}
|
|
50
|
+
.legend i{display:inline-block;width:22px;height:0;border-top:2px solid var(--edge);vertical-align:middle;margin-right:5px}
|
|
51
|
+
.legend i.d{border-top-style:dashed}.legend i.a{border-top:2px dashed var(--warn)}
|
|
52
|
+
.findings{margin:10px 0 0;padding:8px 14px;border-left:3px solid var(--accent);background:var(--panel);border-radius:4px;max-width:1000px}
|
|
53
|
+
.findings ul{margin:4px 0 0;padding-left:18px}.findings li{margin:2px 0}
|
|
54
|
+
table.answers{border-collapse:collapse;margin:10px 0 0;max-width:1100px;font-size:13px}
|
|
55
|
+
table.answers th,table.answers td{border:1px solid var(--line);padding:5px 9px;text-align:left;vertical-align:top}
|
|
56
|
+
table.answers th{background:var(--group2);font-weight:600}table.answers td.ev{font-size:12px;color:var(--muted)}
|
|
57
|
+
.issues{margin:8px 24px 0;padding:8px 12px;border:1px solid var(--warn);border-radius:6px;color:var(--warn);font-size:13px}
|
|
58
|
+
/* diagram */
|
|
59
|
+
.canvas{fill:var(--canvas)}
|
|
60
|
+
.group rect{fill:var(--group);stroke:var(--line);stroke-width:1}.group.d2 rect{fill:var(--group2)}.group.d3 rect{fill:var(--group)}
|
|
61
|
+
.glabel{font-size:12px;font-weight:600;fill:var(--muted);letter-spacing:.02em}
|
|
62
|
+
.group.st-removed rect{stroke:var(--bad);stroke-dasharray:6 4;fill:none}
|
|
63
|
+
.node{cursor:pointer;outline:none}.node .shape{stroke-width:1.5;fill:var(--panel)}.node .shape.solid{fill:var(--ink);stroke:var(--ink)}
|
|
64
|
+
.node .shape-line{fill:none;stroke-width:1.2}.node .glyph{fill:var(--muted)}
|
|
65
|
+
.node .label{font-size:13px;font-weight:600;fill:var(--ink)}.node .note{font-size:11px;fill:var(--muted)}
|
|
66
|
+
.node .badge{font-size:10px;fill:var(--ok)}.node .badge.warn{fill:var(--warn)}
|
|
67
|
+
.table-head{fill:var(--group2)}.fname{font-size:12px;fill:var(--ink)}.ftype{font-size:11px;fill:var(--muted)}.fkey{font-size:9px;font-weight:700;fill:var(--warn)}
|
|
68
|
+
.c-blue .shape,.c-blue .shape-line{stroke:#3b82f6}.c-violet .shape{stroke:#8b5cf6}.c-green .shape,.c-green .shape-line{stroke:#16a34a}
|
|
69
|
+
.c-teal .shape,.c-teal .shape-line{stroke:#0d9488}.c-amber .shape,.c-amber .shape-line{stroke:#d97706}.c-slate .shape{stroke:#64748b}
|
|
70
|
+
.c-rose .shape{stroke:#e11d48}.c-indigo .shape{stroke:#4f46e5}.k-external .shape{stroke-dasharray:5 3}
|
|
71
|
+
.node.assumed .shape{stroke:var(--warn)!important;stroke-dasharray:6 4}
|
|
72
|
+
.node.st-added .shape{stroke:var(--ok)!important;stroke-width:2.5}.node.st-changed .shape{stroke:var(--accent)!important;stroke-width:2.5}
|
|
73
|
+
.node.st-removed{opacity:.55}.node.st-removed .shape{stroke:var(--bad)!important;stroke-dasharray:6 4}.node.st-removed .label{text-decoration:line-through}
|
|
74
|
+
.edge .line{fill:none;stroke:var(--edge);stroke-width:1.5}.edge .hit{fill:none;stroke:transparent;stroke-width:12;cursor:pointer}
|
|
75
|
+
.edge.s-dashed .line{stroke-dasharray:7 5}.edge.s-dotted .line{stroke-dasharray:2 4}.edge.emph .line{stroke-width:2.6}
|
|
76
|
+
.edge.assumed .line{stroke:var(--warn);stroke-dasharray:6 4}.edge.st-added .line{stroke:var(--ok);stroke-width:2.2}
|
|
77
|
+
.edge.st-removed .line{stroke:var(--bad);stroke-dasharray:6 4;opacity:.7}
|
|
78
|
+
.arrowhead{fill:var(--edge)}.lbg{fill:var(--canvas);opacity:.92}.elabel{font-size:11px;fill:var(--muted)}.card{font-size:11px;font-weight:700;fill:var(--muted)}
|
|
79
|
+
.lifeline{stroke:var(--line);stroke-width:1.2;stroke-dasharray:4 4}.seqno{font-size:10px;fill:var(--muted)}
|
|
80
|
+
.frame rect{fill:none;stroke:var(--muted);stroke-width:1}.frame .ftab{fill:var(--group2);stroke:var(--muted)}.frame text{font-size:11px;fill:var(--ink);font-weight:600}
|
|
81
|
+
svg.focus .node,svg.focus .edge,svg.focus .group{opacity:.18;transition:opacity .15s}
|
|
82
|
+
svg.focus .on,svg.focus .group.on{opacity:1}svg .node.on .shape{stroke-width:2.6}svg .edge.on .line{stroke:var(--hl);stroke-width:2.6}
|
|
83
|
+
svg .node.sel .shape{stroke:var(--hl)!important;stroke-width:3}svg .node:focus-visible .shape{stroke:var(--accent)!important;stroke-width:3}
|
|
84
|
+
@media (prefers-reduced-motion:no-preference){svg .edge.on.flow .line{stroke-dasharray:8 6;animation:flow 1s linear infinite}}
|
|
85
|
+
@keyframes flow{to{stroke-dashoffset:-28}}
|
|
86
|
+
.lane rect{fill:var(--group);stroke:var(--line)}.lane-line{stroke:var(--line);stroke-width:2}
|
|
87
|
+
.axis line{stroke:var(--muted);stroke-width:1}.axis text{font-size:11px;fill:var(--muted)}.axis .grid{stroke:var(--line);stroke-dasharray:2 4}
|
|
88
|
+
.brk{stroke:var(--muted);stroke-width:1.5;fill:none}.brk-label{font-size:10px;fill:var(--muted)}
|
|
89
|
+
.node.event .shape{stroke-width:2;fill:var(--panel)}.node.event .bar{fill:var(--group2);stroke-width:1.5}
|
|
90
|
+
.node.event .leader{stroke:var(--line);stroke-width:1}.node.event.approx .shape{stroke-dasharray:3 2}.node .label.tl{font-size:12px}.node .note.tl{font-size:10px}
|
|
91
|
+
.node.span .rowbg{fill:transparent}.node.span:hover .rowbg,.node.span.on .rowbg{fill:var(--group)}
|
|
92
|
+
.node.span .bar{stroke:none;opacity:.88}.node.span.crit .bar{stroke:var(--hl);stroke-width:2.5;opacity:1}
|
|
93
|
+
.node.span.err .bar{stroke:var(--bad);stroke-width:2.5}.node .label.wf{font-size:12px;font-weight:500}.node .note.wf{font-size:11px}
|
|
94
|
+
.node .label.italic{font-style:italic}.mk-open{fill:var(--canvas);stroke:var(--edge);stroke-width:1.2}.mk-line{fill:none;stroke:var(--edge);stroke-width:1.4}
|
|
95
|
+
.edge.tree .line{stroke:var(--line);stroke-width:1.5}
|
|
96
|
+
aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside table.facts th{text-align:left;color:var(--muted);font-weight:400;padding:2px 10px 2px 0}
|
|
97
|
+
@media print{.bar,aside,.caption{display:none!important}.wrap{height:auto}#stage{border:0}#stage svg{position:static;height:auto}}
|
|
98
|
+
</style>
|
|
99
|
+
<script type="application/json" id="diagram-data">{"type": "workflow", "slug": "how-annual-leave-gets-approved", "source_url": null, "story": [], "nodes": [{"id": "submit", "label": "Submit request in the HR portal", "kind": "start", "note": null, "detail": null, "fields": [], "evidence": [{"said": "you request leave in the HR portal", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "manager", "label": "Line manager reviews", "kind": "manual", "note": null, "detail": null, "fields": [], "evidence": [{"said": "it goes to your line manager first", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "long", "label": "Over 10 working days?", "kind": "decision", "note": null, "detail": null, "fields": [], "evidence": [{"said": "anything over ten working days also needs HR", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "hr", "label": "HR approves", "kind": "manual", "note": null, "detail": null, "fields": [], "evidence": [{"said": "also needs HR", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "freeze", "label": "In the year-end freeze?", "kind": "decision", "note": null, "detail": null, "fields": [], "evidence": [{"said": "nothing in the last two weeks of December without the director", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "director", "label": "Director approves", "kind": "manual", "note": null, "detail": null, "fields": [], "evidence": [{"said": "without the director", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "booked", "label": "Booked in team calendar", "kind": "end", "note": null, "detail": null, "fields": [], "evidence": [{"said": "once it's approved it shows up in the team calendar by itself", "source": "user"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "declined", "label": "Employee told it's declined", "kind": "end", "note": null, "detail": "The description doesn't say what happens when someone says no.", "fields": [], "evidence": [], "assumed": true, "status": null, "facts": null, "group_label": null}], "edges": [{"id": "e1", "from": "submit", "to": "manager", "label": "", "detail": null, "evidence": [{"said": "it goes to your line manager first", "source": "user"}], "assumed": false, "status": null}, {"id": "e2", "from": "manager", "to": "long", "label": "approves", "detail": null, "evidence": [{"said": "it goes to your line manager first", "source": "user"}], "assumed": false, "status": null}, {"id": "e3", "from": "manager", "to": "declined", "label": "declines", "detail": null, "evidence": [], "assumed": true, "status": null}, {"id": "e4", "from": "long", "to": "hr", "label": "yes", "detail": null, "evidence": [{"said": "anything over ten working days also needs HR", "source": "user"}], "assumed": false, "status": null}, {"id": "e5", "from": "long", "to": "freeze", "label": "no", "detail": null, "evidence": [{"said": "anything over ten working days also needs HR", "source": "user"}], "assumed": false, "status": null}, {"id": "e6", "from": "hr", "to": "freeze", "label": "", "detail": null, "evidence": [{"said": "also needs HR", "source": "user"}], "assumed": false, "status": null}, {"id": "e7", "from": "freeze", "to": "director", "label": "yes", "detail": null, "evidence": [{"said": "without the director", "source": "user"}], "assumed": false, "status": null}, {"id": "e8", "from": "freeze", "to": "booked", "label": "no", "detail": null, "evidence": [{"said": "once it's approved it shows up in the team calendar by itself", "source": "user"}], "assumed": false, "status": null}, {"id": "e9", "from": "director", "to": "booked", "label": "", "detail": null, "evidence": [{"said": "once it's approved it shows up in the team calendar by itself", "source": "user"}], "assumed": false, "status": null}], "mermaid": "flowchart LR\n submit([\"Submit request in the HR portal\"])\n manager[\"Line manager reviews\"]\n long{\"Over 10 working days?\"}\n hr[\"HR approves\"]\n freeze{\"In the year-end freeze?\"}\n director[\"Director approves\"]\n booked([\"Booked in team calendar\"])\n declined([\"Employee told it's declined\"])\n submit --\u003e manager\n manager --\u003e|\"approves\"| long\n manager -.-\u003e|\"declines\"| declined\n long --\u003e|\"yes\"| hr\n long --\u003e|\"no\"| freeze\n hr --\u003e freeze\n freeze --\u003e|\"yes\"| director\n freeze --\u003e|\"no\"| booked\n director --\u003e booked\n classDef assumed stroke-dasharray: 5 4\n class declined assumed\n", "words": {"out": "Goes to", "in": "Comes from", "reach_out": "What can follow", "reach_in": "What leads here"}, "auto_steps": []}</script>
|
|
100
|
+
<script>
|
|
101
|
+
(function(){
|
|
102
|
+
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
|
|
103
|
+
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
104
|
+
var root=document.documentElement;
|
|
105
|
+
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
106
|
+
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
107
|
+
root.setAttribute('data-theme',pref);
|
|
108
|
+
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
109
|
+
var vb={x:0,y:0,w:W,h:H};
|
|
110
|
+
function apply(){svg.setAttribute('viewBox',vb.x+' '+vb.y+' '+vb.w+' '+vb.h)}
|
|
111
|
+
function fit(){var r=stage.getBoundingClientRect();var s=Math.max(W/r.width,H/r.height,0.6);vb.w=r.width*s;vb.h=r.height*s;vb.x=(W-vb.w)/2;vb.y=(H-vb.h)/2;apply()}
|
|
112
|
+
function zoom(f,cx,cy){var r=stage.getBoundingClientRect();if(cx==null){cx=r.width/2;cy=r.height/2}
|
|
113
|
+
var px=vb.x+cx/r.width*vb.w,py=vb.y+cy/r.height*vb.h;var nw=Math.min(Math.max(vb.w*f,120),W*6);var k=nw/vb.w;
|
|
114
|
+
vb.x=px-(px-vb.x)*k;vb.y=py-(py-vb.y)*k;vb.w=nw;vb.h=vb.h*k;apply()}
|
|
115
|
+
stage.addEventListener('wheel',function(e){e.preventDefault();var r=stage.getBoundingClientRect();zoom(e.deltaY>0?1.12:1/1.12,e.clientX-r.left,e.clientY-r.top)},{passive:false});
|
|
116
|
+
var drag=null;
|
|
117
|
+
stage.addEventListener('pointerdown',function(e){if(e.target.closest('.node')||e.target.closest('.edge'))return;drag={x:e.clientX,y:e.clientY,vx:vb.x,vy:vb.y};stage.classList.add('drag');stage.setPointerCapture(e.pointerId)});
|
|
118
|
+
stage.addEventListener('pointermove',function(e){if(!drag)return;var r=stage.getBoundingClientRect();vb.x=drag.vx-(e.clientX-drag.x)*vb.w/r.width;vb.y=drag.vy-(e.clientY-drag.y)*vb.h/r.height;apply()});
|
|
119
|
+
stage.addEventListener('pointerup',function(){drag=null;stage.classList.remove('drag')});
|
|
120
|
+
// Lookups are Maps, never plain objects: ids come from the spec and the URL, and a key such as
|
|
121
|
+
// __proto__ must stay a key.
|
|
122
|
+
var nodes=new Map(),edges=new Map(),outE=new Map(),inE=new Map();
|
|
123
|
+
DATA.nodes.forEach(function(n){nodes.set(n.id,n);outE.set(n.id,[]);inE.set(n.id,[])});
|
|
124
|
+
DATA.edges.forEach(function(e){edges.set(e.id,e);if(outE.has(e.from))outE.get(e.from).push(e.id);if(inE.has(e.to))inE.get(e.to).push(e.id)});
|
|
125
|
+
function el(kind,id){return document.getElementById(kind+'-'+id)}
|
|
126
|
+
function clear(){svg.classList.remove('focus');svg.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')})}
|
|
127
|
+
function light(ns,es,flow){clear();svg.classList.add('focus');ns.forEach(function(i){var x=el('n',i);x&&x.classList.add('on')});
|
|
128
|
+
es.forEach(function(i){var x=el('e',i);if(x){x.classList.add('on');flow&&x.classList.add('flow')}})}
|
|
129
|
+
function reach(id,dir){if(!nodes.has(id))return {n:[],e:[]};var seen=new Set([id]),es=[],q=[id];while(q.length){var u=q.shift();(dir>0?outE.get(u):inE.get(u)).forEach(function(eid){var e=edges.get(eid);es.push(eid);var v=dir>0?e.to:e.from;if(!seen.has(v)){seen.add(v);q.push(v)}})}
|
|
130
|
+
return {n:Array.from(seen),e:es}}
|
|
131
|
+
function path(a,b,directed){if(!nodes.has(a)||!nodes.has(b))return null;var prev=new Map([[a,null]]),q=[a];while(q.length){var u=q.shift();if(u===b)break;
|
|
132
|
+
var nb=outE.get(u).map(function(i){return [edges.get(i).to,i]});if(!directed)nb=nb.concat(inE.get(u).map(function(i){return [edges.get(i).from,i]}));
|
|
133
|
+
nb.forEach(function(p){if(!prev.has(p[0])){prev.set(p[0],[u,p[1]]);q.push(p[0])}})}
|
|
134
|
+
if(!prev.has(b))return null;var ns=[b],es=[],c=b;while(prev.get(c)){es.push(prev.get(c)[1]);c=prev.get(c)[0];ns.push(c)}return {n:ns.reverse(),e:es.reverse()}}
|
|
135
|
+
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
|
136
|
+
function evHtml(list){if(!list||!list.length)return '<p class="muted">No evidence cited.</p>';
|
|
137
|
+
return '<ul>'+list.map(function(e){var t=e.said!=null?(esc(e.source||'user')+': “'+esc(e.said)+'”'):esc(e.file+(e.start?':'+e.start+(e.end!==e.start?'-'+e.end:''):''));
|
|
138
|
+
if(e.file&&DATA.source_url){t='<a href="'+esc(DATA.source_url.replace(/\/?$/,'/')+e.file+(e.start?'#L'+e.start+(e.end!==e.start?'-L'+e.end:''):''))+'" target="_blank" rel="noopener">'+t+'</a>'}
|
|
139
|
+
var v=e.verified===true?' <span class="tag" style="color:var(--ok)">checked</span>':(e.verified===null&&e.file?' <span class="tag">not checked</span>':'');
|
|
140
|
+
return '<li>'+t+v+(e.snippet?'<pre>'+esc(e.snippet)+'</pre>':'')+'</li>'}).join('')+'</ul>'}
|
|
141
|
+
var selected=null;
|
|
142
|
+
function show(kind,id){var x=kind==='n'?nodes.get(id):edges.get(id);if(!x)return;var h='<button class="close" aria-label="Close">×</button>';
|
|
143
|
+
if(kind==='n'){h+='<h3>'+esc(x.label)+'</h3><div class="kind">'+esc(x.kind)+(x.group_label?' · '+esc(x.group_label):'')+(x.assumed?' · <span style="color:var(--warn)">assumption</span>':'')+(x.status?' · '+esc(x.status):'')+'</div>';
|
|
144
|
+
if(x.note)h+='<p>'+esc(x.note)+'</p>';if(x.detail)h+='<p>'+esc(x.detail)+'</p>';
|
|
145
|
+
if(x.fields&&x.fields.length)h+='<ul>'+x.fields.map(function(f){return '<li><code>'+esc(f.name)+'</code> '+esc(f.type||'')+(f.key?' <b>'+esc([].concat(f.key).join('/').toUpperCase())+'</b>':'')+(f.references?' → '+esc(f.references):'')+(f.note?' — '+esc(f.note):'')+'</li>'}).join('')+'</ul>';
|
|
146
|
+
if(x.facts&&x.facts.length)h+='<table class="facts">'+x.facts.map(function(f){return '<tr><th>'+esc(f[0])+'</th><td>'+esc(f[1])+'</td></tr>'}).join('')+'</table>';
|
|
147
|
+
h+='<div class="acts"><button data-act="out">'+esc(WD.reach_out)+'</button><button data-act="in">'+esc(WD.reach_in)+'</button><button data-act="near">Neighbours</button></div>';
|
|
148
|
+
h+='<p class="muted" style="font-size:12px">Shift-click another part to see the path between them.</p>';
|
|
149
|
+
var o=outE.get(id).map(function(i){var e=edges.get(i);return '<li><a class="jump" data-n="'+esc(e.to)+'">'+esc(nodes.get(e.to).label)+'</a>'+(e.label?' — '+esc(e.label):'')+'</li>'}).join('');
|
|
150
|
+
var n=inE.get(id).map(function(i){var e=edges.get(i);return '<li><a class="jump" data-n="'+esc(e.from)+'">'+esc(nodes.get(e.from).label)+'</a>'+(e.label?' — '+esc(e.label):'')+'</li>'}).join('');
|
|
151
|
+
if(o)h+='<b>'+esc(WD.out)+'</b><ul>'+o+'</ul>';if(n)h+='<b>'+esc(WD.in)+'</b><ul>'+n+'</ul>';
|
|
152
|
+
h+='<b>Evidence</b>'+evHtml(x.evidence);
|
|
153
|
+
var nb=new Set([id]);outE.get(id).concat(inE.get(id)).forEach(function(i){nb.add(edges.get(i).from);nb.add(edges.get(i).to)});light(Array.from(nb),outE.get(id).concat(inE.get(id)));el('n',id).classList.add('sel');
|
|
154
|
+
}else{h+='<h3>'+esc(nodes.get(x.from).label)+' → '+esc(nodes.get(x.to).label)+'</h3><div class="kind">'+(x.label?esc(x.label):'connection')+(x.assumed?' · <span style="color:var(--warn)">assumption</span>':'')+(x.status?' · '+esc(x.status):'')+'</div>';
|
|
155
|
+
if(x.detail)h+='<p>'+esc(x.detail)+'</p>';h+='<b>Evidence</b>'+evHtml(x.evidence);light([x.from,x.to],[id],true)}
|
|
156
|
+
panel.innerHTML=h;panel.classList.add('open');selected=kind==='n'?id:null;
|
|
157
|
+
panel.querySelector('.close').onclick=function(){panel.classList.remove('open');clear();selected=null};
|
|
158
|
+
panel.querySelectorAll('.jump').forEach(function(a){a.onclick=function(){show('n',a.getAttribute('data-n'))}});
|
|
159
|
+
panel.querySelectorAll('[data-act]').forEach(function(b){b.onclick=function(){var a=b.getAttribute('data-act');
|
|
160
|
+
if(a==='near'){show('n',id);return}var r=reach(id,a==='out'?1:-1);light(r.n,r.e,true);el('n',id).classList.add('sel')}})}
|
|
161
|
+
svg.addEventListener('click',function(e){var g=e.target.closest('.node');if(g){var id=g.getAttribute('data-id');
|
|
162
|
+
if(e.shiftKey&&selected&&selected!==id){var p=path(selected,id,true)||path(selected,id,false);if(p){light(p.n,p.e,true)}return}show('n',id);return}
|
|
163
|
+
var ed=e.target.closest('.edge');if(ed){show('e',ed.getAttribute('data-id'));return}});
|
|
164
|
+
svg.addEventListener('keydown',function(e){var g=e.target.closest&&e.target.closest('.node');if(g&&(e.key==='Enter'||e.key===' ')){e.preventDefault();show('n',g.getAttribute('data-id'))}});
|
|
165
|
+
document.addEventListener('keydown',function(e){if(e.key==='Escape'){panel.classList.remove('open');clear();selected=null}
|
|
166
|
+
if(e.target.tagName==='INPUT')return;if(e.key==='ArrowRight'&&tour)step(1);if(e.key==='ArrowLeft'&&tour)step(-1)});
|
|
167
|
+
var q=document.getElementById('find');q.addEventListener('input',function(){var v=q.value.trim().toLowerCase();if(!v){clear();return}
|
|
168
|
+
var ns=DATA.nodes.filter(function(n){return (n.label+' '+(n.note||'')+' '+(n.fields||[]).map(function(f){return f.name}).join(' ')).toLowerCase().indexOf(v)>=0}).map(function(n){return n.id});light(ns,[])});
|
|
169
|
+
document.getElementById('zin').onclick=function(){zoom(1/1.25)};document.getElementById('zout').onclick=function(){zoom(1.25)};document.getElementById('zfit').onclick=fit;
|
|
170
|
+
document.getElementById('theme').onclick=function(){var t=root.getAttribute('data-theme')==='dark'?'light':'dark';root.setAttribute('data-theme',t);try{localStorage.setItem('idg-theme',t)}catch(e){}};
|
|
171
|
+
function download(name,type,data){var a=document.createElement('a');a.href=URL.createObjectURL(new Blob([data],{type:type}));a.download=name;document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(a.href);a.remove()},500)}
|
|
172
|
+
function standalone(){var c=svg.cloneNode(true);c.setAttribute('viewBox','0 0 '+W+' '+H);c.setAttribute('width',W);c.setAttribute('height',H);c.classList.remove('focus');
|
|
173
|
+
c.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')});
|
|
174
|
+
var cs=getComputedStyle(root),vars=['--bg','--panel','--ink','--muted','--line','--edge','--accent','--warn','--ok','--bad','--hl','--canvas','--group','--group2'].map(function(v){return v+':'+cs.getPropertyValue(v)}).join(';');
|
|
175
|
+
var st=document.createElementNS('http://www.w3.org/2000/svg','style');st.textContent='svg{'+vars+';font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}'+document.getElementById('diagram-css').textContent.split('/* diagram */')[1];
|
|
176
|
+
c.insertBefore(st,c.firstChild);return new XMLSerializer().serializeToString(c)}
|
|
177
|
+
var base=(DATA.slug||'diagram');window.interactiveDiagram={svg:standalone,mermaid:function(){return DATA.mermaid}};
|
|
178
|
+
document.getElementById('xsvg').onclick=function(){download(base+'.svg','image/svg+xml',standalone())};
|
|
179
|
+
document.getElementById('xpng').onclick=function(){var img=new Image();img.onload=function(){var k=2,cv=document.createElement('canvas');cv.width=W*k;cv.height=H*k;var ctx=cv.getContext('2d');ctx.scale(k,k);ctx.drawImage(img,0,0);cv.toBlob(function(b){download(base+'.png','image/png',b)})};
|
|
180
|
+
img.src='data:image/svg+xml;charset=utf-8,'+encodeURIComponent(standalone())};
|
|
181
|
+
document.getElementById('xmmd').onclick=function(){var t=DATA.mermaid;if(navigator.clipboard)navigator.clipboard.writeText(t).catch(function(){});download(base+'.mmd','text/plain',t)};
|
|
182
|
+
var tour=null,cap=document.getElementById('caption');
|
|
183
|
+
var steps=DATA.story&&DATA.story.length?DATA.story:(DATA.auto_steps&&DATA.auto_steps.length)?DATA.auto_steps:(DATA.type==='sequence'?DATA.edges.map(function(m,i){return {text:(i+1)+'. '+nodes.get(m.from).label+' → '+nodes.get(m.to).label+(m.label?': '+m.label:''),edges:[m.id],nodes:[m.from,m.to]}}):[]);
|
|
184
|
+
var tb=document.getElementById('tour');if(!steps.length){tb.style.display='none'}
|
|
185
|
+
function step(d){tour.i=Math.max(0,Math.min(steps.length-1,tour.i+d));var s=steps[tour.i];
|
|
186
|
+
light((s.nodes||[]).concat((s.edges||[]).map(function(i){return edges.get(i)?edges.get(i).from:null}).filter(Boolean)).concat((s.edges||[]).map(function(i){return edges.get(i)?edges.get(i).to:null}).filter(Boolean)),s.edges||[],true);
|
|
187
|
+
cap.innerHTML='<b>Step '+(tour.i+1)+' of '+steps.length+'</b> — '+esc(s.text||'')+' <span class="muted">(← → to move, Esc to stop)</span>';cap.classList.add('on')}
|
|
188
|
+
tb.onclick=function(){if(tour){tour=null;cap.classList.remove('on');clear();tb.textContent='Walk through';return}tour={i:-1};tb.textContent='Stop';step(1)};
|
|
189
|
+
document.getElementById('prev').onclick=function(){if(tour)step(-1)};document.getElementById('next').onclick=function(){if(!tour){tb.onclick()}else step(1)};
|
|
190
|
+
if(!steps.length){document.getElementById('prev').style.display='none';document.getElementById('next').style.display='none'}
|
|
191
|
+
function size(){var wr=document.querySelector('.wrap');var avail=Math.max(window.innerHeight-150,360);var r=stage.getBoundingClientRect();
|
|
192
|
+
var s=Math.max(W/Math.max(r.width,1),0.6);wr.style.height=Math.min(avail,Math.max(H/s+24,300))+'px'}
|
|
193
|
+
size();fit();window.addEventListener('resize',function(){size();fit()});
|
|
194
|
+
function hash(){var h=decodeURIComponent(location.hash.slice(1));if(!h)return;var kv=new Map();h.split('&').forEach(function(p){var i=p.indexOf('=');if(i>0)kv.set(p.slice(0,i),p.slice(i+1))});
|
|
195
|
+
if(kv.get('theme'))root.setAttribute('data-theme',kv.get('theme'));
|
|
196
|
+
if(kv.get('n')&&nodes.get(kv.get('n')))show('n',kv.get('n'));if(kv.get('e')&&edges.get(kv.get('e')))show('e',kv.get('e'));
|
|
197
|
+
if(kv.get('path')){var ab=kv.get('path').split(',');var p=path(ab[0],ab[1],true)||path(ab[0],ab[1],false);if(p)light(p.n,p.e,true)}
|
|
198
|
+
if(kv.get('reach')){var ar=kv.get('reach').split(',');var r=reach(ar[0],ar[1]==='up'?-1:1);light(r.n,r.e,true)}
|
|
199
|
+
if(kv.get('step')&&steps.length){tour={i:-1};tb.textContent='Stop';step(+kv.get('step'))}}
|
|
200
|
+
hash();window.addEventListener('hashchange',hash);
|
|
201
|
+
})();
|
|
202
|
+
</script>
|
|
203
|
+
</body></html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "workflow",
|
|
3
|
+
"title": "How annual leave gets approved",
|
|
4
|
+
"question": "What happens to a leave request from the moment it is submitted?",
|
|
5
|
+
"summary": "Every request goes to the line manager; requests over ten working days also need HR, and anything that clashes with the year-end freeze needs the director. Approved leave is booked in the team calendar automatically.",
|
|
6
|
+
"nodes": [
|
|
7
|
+
{"id": "submit", "label": "Submit request in the HR portal", "kind": "start", "evidence": ["user: you request leave in the HR portal"]},
|
|
8
|
+
{"id": "manager", "label": "Line manager reviews", "kind": "manual", "evidence": ["user: it goes to your line manager first"]},
|
|
9
|
+
{"id": "long", "label": "Over 10 working days?", "kind": "decision", "evidence": ["user: anything over ten working days also needs HR"]},
|
|
10
|
+
{"id": "hr", "label": "HR approves", "kind": "manual", "evidence": ["user: also needs HR"]},
|
|
11
|
+
{"id": "freeze", "label": "In the year-end freeze?", "kind": "decision", "evidence": ["user: nothing in the last two weeks of December without the director"]},
|
|
12
|
+
{"id": "director", "label": "Director approves", "kind": "manual", "evidence": ["user: without the director"]},
|
|
13
|
+
{"id": "booked", "label": "Booked in team calendar", "kind": "end", "evidence": ["user: once it's approved it shows up in the team calendar by itself"]},
|
|
14
|
+
{"id": "declined", "label": "Employee told it's declined", "kind": "end", "assumed": true, "detail": "The description doesn't say what happens when someone says no."}
|
|
15
|
+
],
|
|
16
|
+
"edges": [
|
|
17
|
+
{"from": "submit", "to": "manager", "evidence": ["user: it goes to your line manager first"]},
|
|
18
|
+
{"from": "manager", "to": "long", "label": "approves", "evidence": ["user: it goes to your line manager first"]},
|
|
19
|
+
{"from": "manager", "to": "declined", "label": "declines", "assumed": true},
|
|
20
|
+
{"from": "long", "to": "hr", "label": "yes", "evidence": ["user: anything over ten working days also needs HR"]},
|
|
21
|
+
{"from": "long", "to": "freeze", "label": "no", "evidence": ["user: anything over ten working days also needs HR"]},
|
|
22
|
+
{"from": "hr", "to": "freeze", "evidence": ["user: also needs HR"]},
|
|
23
|
+
{"from": "freeze", "to": "director", "label": "yes", "evidence": ["user: without the director"]},
|
|
24
|
+
{"from": "freeze", "to": "booked", "label": "no", "evidence": ["user: once it's approved it shows up in the team calendar by itself"]},
|
|
25
|
+
{"from": "director", "to": "booked", "evidence": ["user: once it's approved it shows up in the team calendar by itself"]}
|
|
26
|
+
],
|
|
27
|
+
"notes": ["Not in the description, so drawn as an assumption: what happens when HR or the director says no."]
|
|
28
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "interactive-diagram",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Turn a codebase, a system description, a process or a database schema into one self-contained interactive HTML diagram (architecture, workflow, data flow, lifecycle, ERD, sequence, class, tree, timeline or waterfall) where every box and arrow cites the file and line or the words it came from, anything inferred is drawn dashed as an assumption, and each cited line is checked against the repository before the page is written. Laid out automatically, opens offline with no CDN, and exports SVG, PNG and Mermaid. Use when the user asks to \"visualize\", \"map\" or \"make an interactive diagram of\" a repo, a request path, a pipeline, an approval process, a set of tables, an incident's events or a slow trace, or wants a diagram they can click through and share as one file.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"architecture-diagrams",
|
|
16
|
+
"mermaid-render",
|
|
17
|
+
"reverse-architecture"
|
|
18
|
+
],
|
|
19
|
+
"tags": [
|
|
20
|
+
"diagram",
|
|
21
|
+
"architecture",
|
|
22
|
+
"interactive",
|
|
23
|
+
"html",
|
|
24
|
+
"erd",
|
|
25
|
+
"sequence-diagram",
|
|
26
|
+
"workflow",
|
|
27
|
+
"evidence"
|
|
28
|
+
],
|
|
29
|
+
"permissions": {
|
|
30
|
+
"network": [],
|
|
31
|
+
"commands": [],
|
|
32
|
+
"files": "project",
|
|
33
|
+
"notes": "Reads the spec, the base spec for --compare, and the files the spec cites under --repo-root; writes only the HTML and Mermaid paths given. It matches secret-file names (.env, keys, credentials) only to refuse to show them. The page it writes makes no network requests."
|
|
34
|
+
},
|
|
35
|
+
"model": {
|
|
36
|
+
"tier": "standard",
|
|
37
|
+
"rationale": "Tracing a path through code and citing each part is careful reading, not adversarial judgement; the layout and the checks are done by the script."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# interactive-diagram — spec reference
|
|
2
|
+
|
|
3
|
+
The spec is one JSON file. The script reads it, checks it, lays it out and writes one HTML
|
|
4
|
+
file. Nothing in the spec is a coordinate or a colour.
|
|
5
|
+
|
|
6
|
+
## Top level
|
|
7
|
+
|
|
8
|
+
| Field | Required | Meaning |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `type` | yes | `architecture`, `dataflow`, `workflow`, `lifecycle`, `erd`, `sequence`, `class`, `tree`, `timeline` or `waterfall` |
|
|
11
|
+
| `title` | yes | What the map shows, in a few words |
|
|
12
|
+
| `question` | no | The one question the map answers; shown under the title |
|
|
13
|
+
| `summary` | no | The takeaway in one or two sentences; shown under the question |
|
|
14
|
+
| `direction` | no | `LR` (default, flow left to right) or `TB` (top to bottom; the default for `class`). ERD, sequence, tree, timeline and waterfall have their own layouts |
|
|
15
|
+
| `wrap` | no | A long left-to-right flow wraps onto rows, like text, choosing the number that keeps its text largest on a laptop screen. `false` keeps one row; a number sets how many steps go on each row |
|
|
16
|
+
| `groups` | no | Boxes around parts: `{"id", "label", "parent"}`. `parent` nests a group inside another |
|
|
17
|
+
| `nodes` | yes | The parts. `sequence` may call them `participants`, `timeline` `events`, `waterfall` `spans`, `class` `classes` |
|
|
18
|
+
| `edges` | no | The connections (for `sequence`, call them `messages`; their order is the order on the page) |
|
|
19
|
+
| `story` | no | A walkthrough: `[{"text", "nodes": [...], "edges": [...]}]`, three to seven steps |
|
|
20
|
+
| `frames` | no | Sequence only: `{"kind": "alt\|loop\|opt\|par", "label", "from": <message id>, "to": <message id>}` |
|
|
21
|
+
| `notes` | no | Lines shown under the text version, such as what was left out |
|
|
22
|
+
| `answers` | no | The person's questions or guesses, answered: `[{"question", "answer", "evidence"}]` (or `"assumed": true`). Shown as a table at the top of the page; evidence is checked like any other |
|
|
23
|
+
| `timezone` | no | Timelines: the zone times are shown in (`UTC`, `-05:00`, `+05:30`). Default: the first event's |
|
|
24
|
+
| `source_url` | no | Turns file citations into links, e.g. `https://github.com/org/repo/blob/<commit>/` |
|
|
25
|
+
|
|
26
|
+
Ids are unique across groups, nodes and edges. An edge without an `id` is numbered `e1`,
|
|
27
|
+
`e2`, … in the order written (`m1`, `m2`, … for messages); give an id to any edge a story
|
|
28
|
+
step or a frame names.
|
|
29
|
+
|
|
30
|
+
## Nodes
|
|
31
|
+
|
|
32
|
+
| Field | Meaning |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `id` | Short, unique, never shown |
|
|
35
|
+
| `label` | A few words; cut on the map past about 40 characters |
|
|
36
|
+
| `kind` | Sets the shape and colour; see below. Defaults to the type's usual kind |
|
|
37
|
+
| `group` | A group id |
|
|
38
|
+
| `note` | One short line shown on the box (a port, a framework, a role) |
|
|
39
|
+
| `detail` | Longer text shown when the box is clicked |
|
|
40
|
+
| `fields` | ERD columns (or any list of members): `{"name", "type", "key": "pk"\|"fk"\|"uk"\|[...], "references": "table.column", "note"}` |
|
|
41
|
+
| `evidence` | List of citations; see below |
|
|
42
|
+
| `assumed` | `true` when nothing shows it; drawn dashed and listed |
|
|
43
|
+
| `status` | `added`, `removed` or `changed`, when marking a proposal by hand (`--compare` sets it for you) |
|
|
44
|
+
|
|
45
|
+
Kinds:
|
|
46
|
+
|
|
47
|
+
| Shape | Kinds |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Box | `service`, `app`, `function`, `ui`, `client`, `job`, `worker`, `step`, `task`, `manual`, `security`, `network`, `external` (dashed border) |
|
|
50
|
+
| Cylinder | `datastore`, `database`, `cache`, `storage` |
|
|
51
|
+
| Pipe | `queue`, `topic`, `stream` |
|
|
52
|
+
| Person | `person`, `actor` |
|
|
53
|
+
| Diamond | `decision` |
|
|
54
|
+
| Pill | `start`, `end` |
|
|
55
|
+
| Rounded | `state` |
|
|
56
|
+
| Dots | `initial`, `final` (lifecycle start and end; the label is hidden unless it differs from the id) |
|
|
57
|
+
| Document | `document`, `file` |
|
|
58
|
+
| Table | `table`, `entity`, `view` |
|
|
59
|
+
| Class box | `class`, `interface`, `abstract`, `enum`, `record` (the kind is shown as a stereotype) |
|
|
60
|
+
| Tree items | any box kind, plus `folder`, `module`, `package`, `capability`, `feature`, `team`, `role` |
|
|
61
|
+
| Timeline marks | `event`, `alert`, `change`, `action` (dots), `milestone` (diamond) |
|
|
62
|
+
|
|
63
|
+
## Edges and messages
|
|
64
|
+
|
|
65
|
+
| Field | Meaning |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `from`, `to` | Node ids |
|
|
68
|
+
| `label` | What passes or happens, in a few words |
|
|
69
|
+
| `style` | `solid` (default), `dashed` (async, optional), `dotted` (weak reference) |
|
|
70
|
+
| `kind` | Sequence: `call` (default), `return` (dashed back), `async` |
|
|
71
|
+
| `emphasis` | `true` thickens the main path |
|
|
72
|
+
| `both` | `true` draws arrowheads at both ends |
|
|
73
|
+
| `from_field`, `to_field` | ERD: attach to these columns' rows |
|
|
74
|
+
| `from_card`, `to_card` | ERD: `1`, `0..1`, `*`, `1..*` shown at each end |
|
|
75
|
+
| `detail`, `evidence`, `assumed`, `status` | As for nodes |
|
|
76
|
+
|
|
77
|
+
## Evidence
|
|
78
|
+
|
|
79
|
+
A list of strings (or objects with the same fields):
|
|
80
|
+
|
|
81
|
+
| Form | Means | Checked with `--repo-root` |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `"src/api.py:40-52"` | Lines 40 to 52 of that file | File exists inside the root, lines exist |
|
|
84
|
+
| `"src/api.py:12#PaymentIntent"` | Line 12, which contains that text | Also: the text is on those lines (case and spacing ignored) |
|
|
85
|
+
| `"docker-compose.yml"` | The whole file | File exists |
|
|
86
|
+
| `"user: anything over ten days goes to HR"` | The person's own words | Shown as a quote |
|
|
87
|
+
| `"doc: the runbook says failover is manual"` | A document they gave you | Shown as a quote |
|
|
88
|
+
|
|
89
|
+
Prefer line ranges with a short quote: the quote is what catches a line number that drifted.
|
|
90
|
+
With `--repo-root` the page shows up to 14 cited lines when a part is clicked, except from
|
|
91
|
+
files whose names say they hold secrets, and with secret-looking lines replaced.
|
|
92
|
+
|
|
93
|
+
## What the checks catch
|
|
94
|
+
|
|
95
|
+
Errors stop the build: unknown type, duplicate ids, an edge to a missing node, a group or
|
|
96
|
+
story step naming a missing id, an item with no evidence that isn't marked assumed, a cited
|
|
97
|
+
file that doesn't exist or is outside the root, a line range past the end of the file, a
|
|
98
|
+
quote that isn't on its lines. Warnings are shown on the page and in the output: unknown
|
|
99
|
+
kinds, long labels, parts with no connections, more than 36 parts, file citations that
|
|
100
|
+
weren't checked because `--repo-root` was missing.
|
|
101
|
+
|
|
102
|
+
For a waterfall or a timeline the script computes what the data shows and puts it at the top
|
|
103
|
+
of the page under **What it shows**: for a waterfall, the total, what the whole waited on most
|
|
104
|
+
(own work of spans on the critical path), spans that mostly wait on their children, siblings
|
|
105
|
+
that overlap (with the wall-clock time and the misleading sum), and spans that end after their
|
|
106
|
+
parent; for a timeline, the span, the longest quiet stretch, approximate times and the time
|
|
107
|
+
zones the sources used. Warnings about the spec are printed for whoever builds the page, never
|
|
108
|
+
published on it.
|
|
109
|
+
|
|
110
|
+
`--check` runs the checks and writes nothing. `--json` prints the result as JSON:
|
|
111
|
+
`{"ok", "nodes", "edges", "assumed", "file_citations", "verified", "errors", "warnings", "output", "reply_first_line"}`.
|
|
112
|
+
A successful build also prints `reply_first_line`: the titled link the reply opens with.
|
|
113
|
+
|
|
114
|
+
## What the page does
|
|
115
|
+
|
|
116
|
+
- Pan (drag), zoom (scroll, or + and −), Fit.
|
|
117
|
+
- Click a part: its note, detail, fields or members, facts (a span's timings, an event's time,
|
|
118
|
+
how much a tree item contains), connections in and out, and its evidence with the cited
|
|
119
|
+
lines. From there: everything it reaches, everything that reaches it, its neighbours (named
|
|
120
|
+
for the diagram: "Everything inside it" on a tree, "What led to it" on a timeline).
|
|
121
|
+
- Shift-click a second part: the path between them.
|
|
122
|
+
- Find a part by name, note or column.
|
|
123
|
+
- Walk through: the story, step by step (← and →); a sequence walks its messages in order.
|
|
124
|
+
- Light and dark, following the system setting until toggled.
|
|
125
|
+
- Export the diagram as SVG or PNG, and the Mermaid source (copied and downloaded).
|
|
126
|
+
- Deep links: `#n=<id>` opens a part, `#e=<id>` a connection, `#path=a,b`,
|
|
127
|
+
`#reach=<id>,up|down`, `#step=3`, `#theme=dark`.
|
|
128
|
+
- The whole diagram as text in the page, read first by screen readers and by anything that
|
|
129
|
+
doesn't run JavaScript; keyboard focus on every part (Tab, Enter, Esc).
|
|
130
|
+
|
|
131
|
+
## Comparing two versions
|
|
132
|
+
|
|
133
|
+
Write a spec for each version and build the newer one with `--compare older.json`. Parts and
|
|
134
|
+
connections only in the newer are marked added, those only in the older are drawn struck
|
|
135
|
+
through as removed, and a part whose label, kind, group or note changed is marked changed.
|
|
136
|
+
Connections match on their ends and label.
|
|
137
|
+
|
|
138
|
+
## Class diagrams
|
|
139
|
+
|
|
140
|
+
`classes` (or `nodes`) take `members`: `{"name": "charge(amount)", "type": "Receipt", "visibility": "+"}`
|
|
141
|
+
(`+`, `-`, `#`, `~`, or `public`, `private`, `protected`, `package`). Each relation in `edges`
|
|
142
|
+
(or `relations`) has a `kind`:
|
|
143
|
+
|
|
144
|
+
| `kind` | `from` → `to` | Drawn as |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `inherits` | child → parent | solid line, hollow triangle at the parent |
|
|
147
|
+
| `implements` | class → interface | dashed line, hollow triangle at the interface |
|
|
148
|
+
| `composition` | whole → part | filled diamond at the whole |
|
|
149
|
+
| `aggregation` | whole → part | hollow diamond at the whole |
|
|
150
|
+
| `association` | one → the other it holds | solid line, open arrow |
|
|
151
|
+
| `dependency` | user → used | dashed line, open arrow |
|
|
152
|
+
|
|
153
|
+
Parents and interfaces are placed above what extends them.
|
|
154
|
+
|
|
155
|
+
## Trees
|
|
156
|
+
|
|
157
|
+
Each item names its `parent` (the top item has none; several top items make a forest). Edges
|
|
158
|
+
are not used: the lines come from the parents. The page shows how many items each one
|
|
159
|
+
contains, and "Everything inside it" highlights a subtree.
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{"type": "tree", "title": "What the payments service is made of",
|
|
163
|
+
"nodes": [{"id": "root", "label": "payments/", "kind": "folder", "evidence": ["payments/README.md"]},
|
|
164
|
+
{"id": "api", "label": "api/", "kind": "folder", "parent": "root", "evidence": ["payments/api/__init__.py"]}]}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Timelines
|
|
168
|
+
|
|
169
|
+
Each event has `at`, and optionally `end` (drawn as a bar) and `lane` (a group id, or any
|
|
170
|
+
name). Times are dates or date-times (`2026-10-03`, `2026-10-03T09:12`, with or without an
|
|
171
|
+
offset), times of day (`09:12`), or plain numbers, but all the same way in one spec. The axis
|
|
172
|
+
is to scale, except that a quiet stretch longer than about a third of the whole is shortened
|
|
173
|
+
and marked with how long it was, so a burst of events stays readable. `edges` between events
|
|
174
|
+
(`caused`, `detected`) are drawn as straight links; mark them assumed unless something shows
|
|
175
|
+
the link. The walkthrough steps through the events in time order.
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{"type": "timeline", "title": "Payment outage, 3 October",
|
|
179
|
+
"groups": [{"id": "alerts", "label": "Monitoring"}],
|
|
180
|
+
"events": [{"id": "a1", "label": "Card error rate above 5%", "at": "2026-10-03T09:19", "lane": "alerts",
|
|
181
|
+
"kind": "alert", "evidence": ["incident.md:12"]}]}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Waterfalls
|
|
185
|
+
|
|
186
|
+
Each span has `start` and either `end` or `duration`, and optionally `parent`, `service` (sets
|
|
187
|
+
its colour) and `status` (`error` outlines it red). Numbers are in `unit` (default `ms`);
|
|
188
|
+
date-times work too. The page marks the critical path (the span that finished last, the one
|
|
189
|
+
that finished last before that one started, and so on, inside each span), and each span's
|
|
190
|
+
panel gives its start, duration, share of the whole and the time not covered by a child span.
|
|
191
|
+
A span that runs outside its parent is a warning, because clocks on two machines disagree or
|
|
192
|
+
the parent is wrong. The walkthrough follows the critical path.
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{"type": "waterfall", "title": "Where a slow checkout spent its time", "unit": "ms",
|
|
196
|
+
"spans": [{"id": "root", "label": "POST /checkout", "start": 0, "end": 1840, "service": "api", "evidence": ["trace-7f3a.json"]},
|
|
197
|
+
{"id": "pay", "label": "stripe.PaymentIntent.create", "start": 430, "duration": 1190, "parent": "root",
|
|
198
|
+
"service": "stripe", "evidence": ["trace-7f3a.json"]}]}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Sequence example
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{"type": "sequence", "title": "Sign-in with SSO",
|
|
205
|
+
"participants": [
|
|
206
|
+
{"id": "u", "label": "User", "kind": "person", "evidence": ["user: people sign in from the web app"]},
|
|
207
|
+
{"id": "app", "label": "Web app", "evidence": ["web/src/auth.ts:10-30#signIn"]},
|
|
208
|
+
{"id": "idp", "label": "Identity provider", "kind": "external", "evidence": ["web/src/auth.ts:14#authorizeUrl"]}],
|
|
209
|
+
"messages": [
|
|
210
|
+
{"from": "u", "to": "app", "label": "click Sign in", "evidence": ["web/src/auth.ts:10"]},
|
|
211
|
+
{"from": "app", "to": "idp", "label": "redirect with PKCE challenge", "evidence": ["web/src/auth.ts:14-18"]},
|
|
212
|
+
{"id": "back", "from": "idp", "to": "app", "label": "authorization code", "kind": "return", "evidence": ["web/src/auth.ts:22"]}],
|
|
213
|
+
"frames": [{"kind": "alt", "label": "code missing", "from": "back", "to": "back"}]}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## ERD example
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{"type": "erd", "title": "Orders schema",
|
|
220
|
+
"nodes": [
|
|
221
|
+
{"id": "customers", "label": "customers", "evidence": ["db/schema.sql:1-5"],
|
|
222
|
+
"fields": [{"name": "id", "type": "uuid", "key": "pk"}, {"name": "email", "type": "text", "key": "uk"}]},
|
|
223
|
+
{"id": "orders", "label": "orders", "evidence": ["db/schema.sql:7-12"],
|
|
224
|
+
"fields": [{"name": "id", "type": "uuid", "key": "pk"},
|
|
225
|
+
{"name": "customer_id", "type": "uuid", "key": "fk", "references": "customers.id"}]}],
|
|
226
|
+
"edges": [{"from": "orders", "to": "customers", "from_field": "customer_id", "to_field": "id",
|
|
227
|
+
"from_card": "*", "to_card": "1", "evidence": ["db/schema.sql:9#references customers"]}]}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
A column whose name suggests a reference (`user_id`) but has no foreign key in the schema is
|
|
231
|
+
listed as a column, and any relationship drawn for it is `"assumed": true`.
|