skilldrop-cli 0.16.21 → 0.16.22
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 +132 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/eval_queries.json +12 -0
- package/packs/solution-architect/skills/interactive-diagram/evals/evals.json +74 -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/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/examples/checkout-map.html +186 -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/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/leave-approval.html +186 -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 +151 -0
- package/packs/solution-architect/skills/interactive-diagram/scripts/build_diagram.py +1682 -0
|
@@ -0,0 +1,186 @@
|
|
|
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-x8uMUP6tcCdtva9qPkw2RN4FPyZ3BQ4hbm/v2UzDBzA='; 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 1807 239" width="1807" height="239" role="group" aria-label="How annual leave gets approved"><defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="arrow-start" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker></defs><rect class="canvas" x="0" y="0" width="1807" height="239"/><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="M246.0,122.0 L275.0,122.0 Q282.0,122.0 289.0,122.0 L318.0,122.0"/><path class="line" d="M246.0,122.0 L275.0,122.0 Q282.0,122.0 289.0,122.0 L318.0,122.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="M497.0,108.0 L526.0,108.0 Q533.0,108.0 533.0,101.0 L533.0,94.0 Q533.0,87.0 540.0,87.0 L574.0,87.0"/><path class="line" d="M497.0,108.0 L526.0,108.0 Q533.0,108.0 533.0,101.0 L533.0,94.0 Q533.0,87.0 540.0,87.0 L574.0,87.0" marker-end="url(#arrow)"/><rect class="lbg" x="509.9" y="78.0" width="58.4" height="18.0" rx="3"/><text class="elabel" x="539.1" y="91.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="M497.0,136.0 L526.0,136.0 Q533.0,136.0 533.0,143.0 L533.0,170.0 Q533.0,177.0 540.0,177.0 L569.0,177.0"/><path class="line" d="M497.0,136.0 L526.0,136.0 Q533.0,136.0 533.0,143.0 L533.0,170.0 Q533.0,177.0 540.0,177.0 L569.0,177.0" marker-end="url(#arrow)"/><rect class="lbg" x="506.6" y="147.5" width="52.9" height="18.0" rx="3"/><text class="elabel" x="533.0" y="160.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="M756.3,75.2 L824.5,75.2 Q831.5,75.2 831.5,82.2 L831.5,115.0 Q831.5,122.0 838.5,122.0 L863.0,122.0"/><path class="line" d="M756.3,75.2 L824.5,75.2 Q831.5,75.2 831.5,82.2 L831.5,115.0 Q831.5,122.0 838.5,122.0 L863.0,122.0" marker-end="url(#arrow)"/><rect class="lbg" x="817.4" y="106.0" width="28.2" height="18.0" rx="3"/><text class="elabel" x="831.5" y="119.0" 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="M756.3,98.8 L815.5,98.8 Q822.5,98.8 822.5,91.8 L822.5,37.0 Q822.5,30.0 829.5,30.0 L1012.0,30.0 Q1019.0,30.0 1019.0,37.0 L1019.0,103.2 Q1019.0,110.2 1026.0,110.2 L1084.1,110.2"/><path class="line" d="M756.3,98.8 L815.5,98.8 Q822.5,98.8 822.5,91.8 L822.5,37.0 Q822.5,30.0 829.5,30.0 L1012.0,30.0 Q1019.0,30.0 1019.0,37.0 L1019.0,103.2 Q1019.0,110.2 1026.0,110.2 L1084.1,110.2" marker-end="url(#arrow)"/><rect class="lbg" x="909.7" y="21.0" width="22.1" height="18.0" rx="3"/><text class="elabel" x="920.8" y="34.0" 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="M983.0,122.0 L1013.1,122.0 Q1019.0,122.0 1019.0,127.9 L1019.0,127.9 Q1019.0,133.8 1024.9,133.8 L1084.1,133.8"/><path class="line" d="M983.0,122.0 L1013.1,122.0 Q1019.0,122.0 1019.0,127.9 L1019.0,127.9 Q1019.0,133.8 1024.9,133.8 L1084.1,133.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="M1233.9,110.2 L1297.6,110.2 Q1303.5,110.2 1303.5,116.1 L1303.5,116.1 Q1303.5,122.0 1309.4,122.0 L1335.0,122.0"/><path class="line" d="M1233.9,110.2 L1297.6,110.2 Q1303.5,110.2 1303.5,116.1 L1303.5,116.1 Q1303.5,122.0 1309.4,122.0 L1335.0,122.0" marker-end="url(#arrow)"/><rect class="lbg" x="1305.2" y="113.0" width="28.2" height="18.0" rx="3"/><text class="elabel" x="1319.2" y="126.0" 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="M1233.9,133.8 L1287.5,133.8 Q1294.5,133.8 1294.5,126.8 L1294.5,37.0 Q1294.5,30.0 1301.5,30.0 L1517.0,30.0 Q1524.0,30.0 1524.0,37.0 L1524.0,102.7 Q1524.0,109.7 1531.0,109.7 L1563.8,109.7"/><path class="line" d="M1233.9,133.8 L1287.5,133.8 Q1294.5,133.8 1294.5,126.8 L1294.5,37.0 Q1294.5,30.0 1301.5,30.0 L1517.0,30.0 Q1524.0,30.0 1524.0,37.0 L1524.0,102.7 Q1524.0,109.7 1531.0,109.7 L1563.8,109.7" marker-end="url(#arrow)"/><rect class="lbg" x="1398.2" y="21.0" width="22.1" height="18.0" rx="3"/><text class="elabel" x="1409.2" y="34.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="M1488.0,122.0 L1517.8,122.0 Q1524.0,122.0 1524.0,128.2 L1524.0,128.2 Q1524.0,134.3 1530.2,134.3 L1563.8,134.3"/><path class="line" d="M1488.0,122.0 L1517.8,122.0 Q1524.0,122.0 1524.0,128.2 L1524.0,128.2 Q1524.0,134.3 1530.2,134.3 L1563.8,134.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="30.0" y="94.0" width="216.0" height="56.0" rx="28.0"/><text class="label" x="138.0" y="118.0" text-anchor="middle">Submit request in the HR</text><text class="label" x="138.0" y="135.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="318.0" y="97.0" width="179.0" height="50.0" rx="6"/><text class="label" x="407.5" y="126.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="M680.0,45.0 L786.0,87.0 L680.0,129.0 L574.0,87.0 Z"/><text class="label" x="680.0" y="83.0" text-anchor="middle">Over 10 working</text><text class="label" x="680.0" y="100.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="863.0" y="97.0" width="120.0" height="50.0" rx="6"/><text class="label" x="923.0" y="126.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="M1159.0,80.0 L1263.0,122.0 L1159.0,164.0 L1055.0,122.0 Z"/><text class="label" x="1159.0" y="118.0" text-anchor="middle">In the year-end</text><text class="label" x="1159.0" y="135.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="1335.0" y="97.0" width="153.0" height="50.0" rx="6"/><text class="label" x="1411.5" y="126.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="1560.0" y="100.0" width="207.0" height="44.0" rx="22.0"/><text class="label" x="1663.5" y="126.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="569.0" y="155.0" width="222.0" height="44.0" rx="22.0"/><text class="label" x="680.0" y="181.5" text-anchor="middle">Employee told it's declined</text><text class="badge warn" x="786.0" y="151.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
|
+
.issues{margin:8px 24px 0;padding:8px 12px;border:1px solid var(--warn);border-radius:6px;color:var(--warn);font-size:13px}
|
|
53
|
+
/* diagram */
|
|
54
|
+
.canvas{fill:var(--canvas)}
|
|
55
|
+
.group rect{fill:var(--group);stroke:var(--line);stroke-width:1}.group.d2 rect{fill:var(--group2)}.group.d3 rect{fill:var(--group)}
|
|
56
|
+
.glabel{font-size:12px;font-weight:600;fill:var(--muted);letter-spacing:.02em}
|
|
57
|
+
.group.st-removed rect{stroke:var(--bad);stroke-dasharray:6 4;fill:none}
|
|
58
|
+
.node{cursor:pointer;outline:none}.node .shape{stroke-width:1.5;fill:var(--panel)}.node .shape.solid{fill:var(--ink);stroke:var(--ink)}
|
|
59
|
+
.node .shape-line{fill:none;stroke-width:1.2}.node .glyph{fill:var(--muted)}
|
|
60
|
+
.node .label{font-size:13px;font-weight:600;fill:var(--ink)}.node .note{font-size:11px;fill:var(--muted)}
|
|
61
|
+
.node .badge{font-size:10px;fill:var(--ok)}.node .badge.warn{fill:var(--warn)}
|
|
62
|
+
.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)}
|
|
63
|
+
.c-blue .shape,.c-blue .shape-line{stroke:#3b82f6}.c-violet .shape{stroke:#8b5cf6}.c-green .shape,.c-green .shape-line{stroke:#16a34a}
|
|
64
|
+
.c-teal .shape,.c-teal .shape-line{stroke:#0d9488}.c-amber .shape,.c-amber .shape-line{stroke:#d97706}.c-slate .shape{stroke:#64748b}
|
|
65
|
+
.c-rose .shape{stroke:#e11d48}.c-indigo .shape{stroke:#4f46e5}.k-external .shape{stroke-dasharray:5 3}
|
|
66
|
+
.node.assumed .shape{stroke:var(--warn)!important;stroke-dasharray:6 4}
|
|
67
|
+
.node.st-added .shape{stroke:var(--ok)!important;stroke-width:2.5}.node.st-changed .shape{stroke:var(--accent)!important;stroke-width:2.5}
|
|
68
|
+
.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}
|
|
69
|
+
.edge .line{fill:none;stroke:var(--edge);stroke-width:1.5}.edge .hit{fill:none;stroke:transparent;stroke-width:12;cursor:pointer}
|
|
70
|
+
.edge.s-dashed .line{stroke-dasharray:7 5}.edge.s-dotted .line{stroke-dasharray:2 4}.edge.emph .line{stroke-width:2.6}
|
|
71
|
+
.edge.assumed .line{stroke:var(--warn);stroke-dasharray:6 4}.edge.st-added .line{stroke:var(--ok);stroke-width:2.2}
|
|
72
|
+
.edge.st-removed .line{stroke:var(--bad);stroke-dasharray:6 4;opacity:.7}
|
|
73
|
+
.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)}
|
|
74
|
+
.lifeline{stroke:var(--line);stroke-width:1.2;stroke-dasharray:4 4}.seqno{font-size:10px;fill:var(--muted)}
|
|
75
|
+
.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}
|
|
76
|
+
svg.focus .node,svg.focus .edge,svg.focus .group{opacity:.18;transition:opacity .15s}
|
|
77
|
+
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}
|
|
78
|
+
svg .node.sel .shape{stroke:var(--hl)!important;stroke-width:3}svg .node:focus-visible .shape{stroke:var(--accent)!important;stroke-width:3}
|
|
79
|
+
@media (prefers-reduced-motion:no-preference){svg .edge.on.flow .line{stroke-dasharray:8 6;animation:flow 1s linear infinite}}
|
|
80
|
+
@keyframes flow{to{stroke-dashoffset:-28}}
|
|
81
|
+
@media print{.bar,aside,.caption{display:none!important}.wrap{height:auto}#stage{border:0}#stage svg{position:static;height:auto}}
|
|
82
|
+
</style>
|
|
83
|
+
<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, "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, "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, "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, "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, "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, "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, "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, "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"}</script>
|
|
84
|
+
<script>
|
|
85
|
+
(function(){
|
|
86
|
+
var DATA=JSON.parse(document.getElementById('diagram-data').textContent);
|
|
87
|
+
var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
|
|
88
|
+
var root=document.documentElement;
|
|
89
|
+
var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
|
|
90
|
+
if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
|
|
91
|
+
root.setAttribute('data-theme',pref);
|
|
92
|
+
var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
|
|
93
|
+
var vb={x:0,y:0,w:W,h:H};
|
|
94
|
+
function apply(){svg.setAttribute('viewBox',vb.x+' '+vb.y+' '+vb.w+' '+vb.h)}
|
|
95
|
+
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()}
|
|
96
|
+
function zoom(f,cx,cy){var r=stage.getBoundingClientRect();if(cx==null){cx=r.width/2;cy=r.height/2}
|
|
97
|
+
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;
|
|
98
|
+
vb.x=px-(px-vb.x)*k;vb.y=py-(py-vb.y)*k;vb.w=nw;vb.h=vb.h*k;apply()}
|
|
99
|
+
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});
|
|
100
|
+
var drag=null;
|
|
101
|
+
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)});
|
|
102
|
+
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()});
|
|
103
|
+
stage.addEventListener('pointerup',function(){drag=null;stage.classList.remove('drag')});
|
|
104
|
+
// Lookups are Maps, never plain objects: ids come from the spec and the URL, and a key such as
|
|
105
|
+
// __proto__ must stay a key.
|
|
106
|
+
var nodes=new Map(),edges=new Map(),outE=new Map(),inE=new Map();
|
|
107
|
+
DATA.nodes.forEach(function(n){nodes.set(n.id,n);outE.set(n.id,[]);inE.set(n.id,[])});
|
|
108
|
+
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)});
|
|
109
|
+
function el(kind,id){return document.getElementById(kind+'-'+id)}
|
|
110
|
+
function clear(){svg.classList.remove('focus');svg.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')})}
|
|
111
|
+
function light(ns,es,flow){clear();svg.classList.add('focus');ns.forEach(function(i){var x=el('n',i);x&&x.classList.add('on')});
|
|
112
|
+
es.forEach(function(i){var x=el('e',i);if(x){x.classList.add('on');flow&&x.classList.add('flow')}})}
|
|
113
|
+
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)}})}
|
|
114
|
+
return {n:Array.from(seen),e:es}}
|
|
115
|
+
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;
|
|
116
|
+
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]}));
|
|
117
|
+
nb.forEach(function(p){if(!prev.has(p[0])){prev.set(p[0],[u,p[1]]);q.push(p[0])}})}
|
|
118
|
+
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()}}
|
|
119
|
+
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
|
120
|
+
function evHtml(list){if(!list||!list.length)return '<p class="muted">No evidence cited.</p>';
|
|
121
|
+
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:''):''));
|
|
122
|
+
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>'}
|
|
123
|
+
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>':'');
|
|
124
|
+
return '<li>'+t+v+(e.snippet?'<pre>'+esc(e.snippet)+'</pre>':'')+'</li>'}).join('')+'</ul>'}
|
|
125
|
+
var selected=null;
|
|
126
|
+
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>';
|
|
127
|
+
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>';
|
|
128
|
+
if(x.note)h+='<p>'+esc(x.note)+'</p>';if(x.detail)h+='<p>'+esc(x.detail)+'</p>';
|
|
129
|
+
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>';
|
|
130
|
+
h+='<div class="acts"><button data-act="up">What it depends on</button><button data-act="down">What depends on it</button><button data-act="near">Neighbours</button></div>';
|
|
131
|
+
h+='<p class="muted" style="font-size:12px">Shift-click another part to see the path between them.</p>';
|
|
132
|
+
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('');
|
|
133
|
+
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('');
|
|
134
|
+
if(o)h+='<b>Goes to</b><ul>'+o+'</ul>';if(n)h+='<b>Comes from</b><ul>'+n+'</ul>';
|
|
135
|
+
h+='<b>Evidence</b>'+evHtml(x.evidence);
|
|
136
|
+
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');
|
|
137
|
+
}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>';
|
|
138
|
+
if(x.detail)h+='<p>'+esc(x.detail)+'</p>';h+='<b>Evidence</b>'+evHtml(x.evidence);light([x.from,x.to],[id],true)}
|
|
139
|
+
panel.innerHTML=h;panel.classList.add('open');selected=kind==='n'?id:null;
|
|
140
|
+
panel.querySelector('.close').onclick=function(){panel.classList.remove('open');clear();selected=null};
|
|
141
|
+
panel.querySelectorAll('.jump').forEach(function(a){a.onclick=function(){show('n',a.getAttribute('data-n'))}});
|
|
142
|
+
panel.querySelectorAll('[data-act]').forEach(function(b){b.onclick=function(){var a=b.getAttribute('data-act');
|
|
143
|
+
if(a==='near'){show('n',id);return}var r=reach(id,a==='down'?1:-1);light(r.n,r.e,true);el('n',id).classList.add('sel')}})}
|
|
144
|
+
svg.addEventListener('click',function(e){var g=e.target.closest('.node');if(g){var id=g.getAttribute('data-id');
|
|
145
|
+
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}
|
|
146
|
+
var ed=e.target.closest('.edge');if(ed){show('e',ed.getAttribute('data-id'));return}});
|
|
147
|
+
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'))}});
|
|
148
|
+
document.addEventListener('keydown',function(e){if(e.key==='Escape'){panel.classList.remove('open');clear();selected=null}
|
|
149
|
+
if(e.target.tagName==='INPUT')return;if(e.key==='ArrowRight'&&tour)step(1);if(e.key==='ArrowLeft'&&tour)step(-1)});
|
|
150
|
+
var q=document.getElementById('find');q.addEventListener('input',function(){var v=q.value.trim().toLowerCase();if(!v){clear();return}
|
|
151
|
+
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,[])});
|
|
152
|
+
document.getElementById('zin').onclick=function(){zoom(1/1.25)};document.getElementById('zout').onclick=function(){zoom(1.25)};document.getElementById('zfit').onclick=fit;
|
|
153
|
+
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){}};
|
|
154
|
+
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)}
|
|
155
|
+
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');
|
|
156
|
+
c.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')});
|
|
157
|
+
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(';');
|
|
158
|
+
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];
|
|
159
|
+
c.insertBefore(st,c.firstChild);return new XMLSerializer().serializeToString(c)}
|
|
160
|
+
var base=(DATA.slug||'diagram');window.interactiveDiagram={svg:standalone,mermaid:function(){return DATA.mermaid}};
|
|
161
|
+
document.getElementById('xsvg').onclick=function(){download(base+'.svg','image/svg+xml',standalone())};
|
|
162
|
+
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)})};
|
|
163
|
+
img.src='data:image/svg+xml;charset=utf-8,'+encodeURIComponent(standalone())};
|
|
164
|
+
document.getElementById('xmmd').onclick=function(){var t=DATA.mermaid;if(navigator.clipboard)navigator.clipboard.writeText(t).catch(function(){});download(base+'.mmd','text/plain',t)};
|
|
165
|
+
var tour=null,cap=document.getElementById('caption');
|
|
166
|
+
var steps=DATA.story&&DATA.story.length?DATA.story:(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]}}):[]);
|
|
167
|
+
var tb=document.getElementById('tour');if(!steps.length){tb.style.display='none'}
|
|
168
|
+
function step(d){tour.i=Math.max(0,Math.min(steps.length-1,tour.i+d));var s=steps[tour.i];
|
|
169
|
+
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);
|
|
170
|
+
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')}
|
|
171
|
+
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)};
|
|
172
|
+
document.getElementById('prev').onclick=function(){if(tour)step(-1)};document.getElementById('next').onclick=function(){if(!tour){tb.onclick()}else step(1)};
|
|
173
|
+
if(!steps.length){document.getElementById('prev').style.display='none';document.getElementById('next').style.display='none'}
|
|
174
|
+
function size(){var wr=document.querySelector('.wrap');var avail=Math.max(window.innerHeight-150,360);var r=stage.getBoundingClientRect();
|
|
175
|
+
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'}
|
|
176
|
+
size();fit();window.addEventListener('resize',function(){size();fit()});
|
|
177
|
+
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))});
|
|
178
|
+
if(kv.get('theme'))root.setAttribute('data-theme',kv.get('theme'));
|
|
179
|
+
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'));
|
|
180
|
+
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)}
|
|
181
|
+
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)}
|
|
182
|
+
if(kv.get('step')&&steps.length){tour={i:-1};tb.textContent='Stop';step(+kv.get('step'))}}
|
|
183
|
+
hash();window.addEventListener('hashchange',hash);
|
|
184
|
+
})();
|
|
185
|
+
</script>
|
|
186
|
+
</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.1.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 or sequence) 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 or a set of tables, 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,151 @@
|
|
|
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` or `sequence` |
|
|
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). ERD and sequence are always LR |
|
|
15
|
+
| `groups` | no | Boxes around parts: `{"id", "label", "parent"}`. `parent` nests a group inside another |
|
|
16
|
+
| `nodes` | yes | The parts (for `sequence`, call them `participants`) |
|
|
17
|
+
| `edges` | no | The connections (for `sequence`, call them `messages`; their order is the order on the page) |
|
|
18
|
+
| `story` | no | A walkthrough: `[{"text", "nodes": [...], "edges": [...]}]`, three to seven steps |
|
|
19
|
+
| `frames` | no | Sequence only: `{"kind": "alt\|loop\|opt\|par", "label", "from": <message id>, "to": <message id>}` |
|
|
20
|
+
| `notes` | no | Lines shown under the text version, such as what was left out |
|
|
21
|
+
| `source_url` | no | Turns file citations into links, e.g. `https://github.com/org/repo/blob/<commit>/` |
|
|
22
|
+
|
|
23
|
+
Ids are unique across groups, nodes and edges. An edge without an `id` is numbered `e1`,
|
|
24
|
+
`e2`, … in the order written (`m1`, `m2`, … for messages); give an id to any edge a story
|
|
25
|
+
step or a frame names.
|
|
26
|
+
|
|
27
|
+
## Nodes
|
|
28
|
+
|
|
29
|
+
| Field | Meaning |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `id` | Short, unique, never shown |
|
|
32
|
+
| `label` | A few words; cut on the map past about 40 characters |
|
|
33
|
+
| `kind` | Sets the shape and colour; see below. Defaults to the type's usual kind |
|
|
34
|
+
| `group` | A group id |
|
|
35
|
+
| `note` | One short line shown on the box (a port, a framework, a role) |
|
|
36
|
+
| `detail` | Longer text shown when the box is clicked |
|
|
37
|
+
| `fields` | ERD columns (or any list of members): `{"name", "type", "key": "pk"\|"fk"\|"uk"\|[...], "references": "table.column", "note"}` |
|
|
38
|
+
| `evidence` | List of citations; see below |
|
|
39
|
+
| `assumed` | `true` when nothing shows it; drawn dashed and listed |
|
|
40
|
+
| `status` | `added`, `removed` or `changed`, when marking a proposal by hand (`--compare` sets it for you) |
|
|
41
|
+
|
|
42
|
+
Kinds:
|
|
43
|
+
|
|
44
|
+
| Shape | Kinds |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Box | `service`, `app`, `function`, `ui`, `client`, `job`, `worker`, `step`, `task`, `manual`, `security`, `network`, `external` (dashed border) |
|
|
47
|
+
| Cylinder | `datastore`, `database`, `cache`, `storage` |
|
|
48
|
+
| Pipe | `queue`, `topic`, `stream` |
|
|
49
|
+
| Person | `person`, `actor` |
|
|
50
|
+
| Diamond | `decision` |
|
|
51
|
+
| Pill | `start`, `end` |
|
|
52
|
+
| Rounded | `state` |
|
|
53
|
+
| Dots | `initial`, `final` (lifecycle start and end; the label is hidden unless it differs from the id) |
|
|
54
|
+
| Document | `document`, `file` |
|
|
55
|
+
| Table | `table`, `entity`, `view` |
|
|
56
|
+
|
|
57
|
+
## Edges and messages
|
|
58
|
+
|
|
59
|
+
| Field | Meaning |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `from`, `to` | Node ids |
|
|
62
|
+
| `label` | What passes or happens, in a few words |
|
|
63
|
+
| `style` | `solid` (default), `dashed` (async, optional), `dotted` (weak reference) |
|
|
64
|
+
| `kind` | Sequence: `call` (default), `return` (dashed back), `async` |
|
|
65
|
+
| `emphasis` | `true` thickens the main path |
|
|
66
|
+
| `both` | `true` draws arrowheads at both ends |
|
|
67
|
+
| `from_field`, `to_field` | ERD: attach to these columns' rows |
|
|
68
|
+
| `from_card`, `to_card` | ERD: `1`, `0..1`, `*`, `1..*` shown at each end |
|
|
69
|
+
| `detail`, `evidence`, `assumed`, `status` | As for nodes |
|
|
70
|
+
|
|
71
|
+
## Evidence
|
|
72
|
+
|
|
73
|
+
A list of strings (or objects with the same fields):
|
|
74
|
+
|
|
75
|
+
| Form | Means | Checked with `--repo-root` |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `"src/api.py:40-52"` | Lines 40 to 52 of that file | File exists inside the root, lines exist |
|
|
78
|
+
| `"src/api.py:12#PaymentIntent"` | Line 12, which contains that text | Also: the text is on those lines (case and spacing ignored) |
|
|
79
|
+
| `"docker-compose.yml"` | The whole file | File exists |
|
|
80
|
+
| `"user: anything over ten days goes to HR"` | The person's own words | Shown as a quote |
|
|
81
|
+
| `"doc: the runbook says failover is manual"` | A document they gave you | Shown as a quote |
|
|
82
|
+
|
|
83
|
+
Prefer line ranges with a short quote: the quote is what catches a line number that drifted.
|
|
84
|
+
With `--repo-root` the page shows up to 14 cited lines when a part is clicked, except from
|
|
85
|
+
files whose names say they hold secrets, and with secret-looking lines replaced.
|
|
86
|
+
|
|
87
|
+
## What the checks catch
|
|
88
|
+
|
|
89
|
+
Errors stop the build: unknown type, duplicate ids, an edge to a missing node, a group or
|
|
90
|
+
story step naming a missing id, an item with no evidence that isn't marked assumed, a cited
|
|
91
|
+
file that doesn't exist or is outside the root, a line range past the end of the file, a
|
|
92
|
+
quote that isn't on its lines. Warnings are shown on the page and in the output: unknown
|
|
93
|
+
kinds, long labels, parts with no connections, more than 36 parts, file citations that
|
|
94
|
+
weren't checked because `--repo-root` was missing.
|
|
95
|
+
|
|
96
|
+
`--check` runs the checks and writes nothing. `--json` prints the result as JSON:
|
|
97
|
+
`{"ok", "nodes", "edges", "assumed", "file_citations", "verified", "errors", "warnings", "output"}`.
|
|
98
|
+
|
|
99
|
+
## What the page does
|
|
100
|
+
|
|
101
|
+
- Pan (drag), zoom (scroll, or + and −), Fit.
|
|
102
|
+
- Click a part: its note, detail, fields, connections in and out, and its evidence with the
|
|
103
|
+
cited lines. From there: what it depends on, what depends on it, its neighbours.
|
|
104
|
+
- Shift-click a second part: the path between them.
|
|
105
|
+
- Find a part by name, note or column.
|
|
106
|
+
- Walk through: the story, step by step (← and →); a sequence walks its messages in order.
|
|
107
|
+
- Light and dark, following the system setting until toggled.
|
|
108
|
+
- Export the diagram as SVG or PNG, and the Mermaid source (copied and downloaded).
|
|
109
|
+
- Deep links: `#n=<id>` opens a part, `#e=<id>` a connection, `#path=a,b`,
|
|
110
|
+
`#reach=<id>,up|down`, `#step=3`, `#theme=dark`.
|
|
111
|
+
- The whole diagram as text in the page, read first by screen readers and by anything that
|
|
112
|
+
doesn't run JavaScript; keyboard focus on every part (Tab, Enter, Esc).
|
|
113
|
+
|
|
114
|
+
## Comparing two versions
|
|
115
|
+
|
|
116
|
+
Write a spec for each version and build the newer one with `--compare older.json`. Parts and
|
|
117
|
+
connections only in the newer are marked added, those only in the older are drawn struck
|
|
118
|
+
through as removed, and a part whose label, kind, group or note changed is marked changed.
|
|
119
|
+
Connections match on their ends and label.
|
|
120
|
+
|
|
121
|
+
## Sequence example
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{"type": "sequence", "title": "Sign-in with SSO",
|
|
125
|
+
"participants": [
|
|
126
|
+
{"id": "u", "label": "User", "kind": "person", "evidence": ["user: people sign in from the web app"]},
|
|
127
|
+
{"id": "app", "label": "Web app", "evidence": ["web/src/auth.ts:10-30#signIn"]},
|
|
128
|
+
{"id": "idp", "label": "Identity provider", "kind": "external", "evidence": ["web/src/auth.ts:14#authorizeUrl"]}],
|
|
129
|
+
"messages": [
|
|
130
|
+
{"from": "u", "to": "app", "label": "click Sign in", "evidence": ["web/src/auth.ts:10"]},
|
|
131
|
+
{"from": "app", "to": "idp", "label": "redirect with PKCE challenge", "evidence": ["web/src/auth.ts:14-18"]},
|
|
132
|
+
{"id": "back", "from": "idp", "to": "app", "label": "authorization code", "kind": "return", "evidence": ["web/src/auth.ts:22"]}],
|
|
133
|
+
"frames": [{"kind": "alt", "label": "code missing", "from": "back", "to": "back"}]}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## ERD example
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{"type": "erd", "title": "Orders schema",
|
|
140
|
+
"nodes": [
|
|
141
|
+
{"id": "customers", "label": "customers", "evidence": ["db/schema.sql:1-5"],
|
|
142
|
+
"fields": [{"name": "id", "type": "uuid", "key": "pk"}, {"name": "email", "type": "text", "key": "uk"}]},
|
|
143
|
+
{"id": "orders", "label": "orders", "evidence": ["db/schema.sql:7-12"],
|
|
144
|
+
"fields": [{"name": "id", "type": "uuid", "key": "pk"},
|
|
145
|
+
{"name": "customer_id", "type": "uuid", "key": "fk", "references": "customers.id"}]}],
|
|
146
|
+
"edges": [{"from": "orders", "to": "customers", "from_field": "customer_id", "to_field": "id",
|
|
147
|
+
"from_card": "*", "to_card": "1", "evidence": ["db/schema.sql:9#references customers"]}]}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A column whose name suggests a reference (`user_id`) but has no foreign key in the schema is
|
|
151
|
+
listed as a column, and any relationship drawn for it is `"assumed": true`.
|