agentleFS
atleronningenatleronningen/skills/system-map/SKILL.md

system-map

Skill0 starsChanged 3 months agoMITView on GitHub

Use when the user asks for a system map, an architecture or ecosystem diagram to show on screen, or says things like "build a widget", "system map", "show how X connects", "node diagram", "hub and spoke", "integration diagram", "interactive diagram for the deck", "sources in and deliverables out". Also use when updating, restyling, or changing the content of a system map built with this skill. Produces one self-contained dark .html file with a glowing core, curved connections, hover tooltips, and no build step.

5 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions5 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 5

    1. mmaripanguec/dev-sdd-kit★ 2same name
    2. rivendale/hsi-operator★ 2same name
    3. brian-bell/agent-skills★ 1same name
    4. tslateman/skills★ 1same name
    5. lifemixture/ai-orchestration-repo★ 0same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repositoryOne other file it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. 0 are common, 8 are distinctive, and 8 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 8

    Distinctive

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Never write the widget from scratch: Always start from whichever of the four files in references/examples/ is closest to your content — …

    line 27
  2. Distinctive:

    Ask the user which of the four examples is the closest match for their content (or pick for them if it's obvious from context), and read …

    line 45
  3. Very specific:

    Ask which color direction they want — see references/palette.md for the four options.

    line 48
  4. Distinctive:

    Ask whether they want a second language.

    line 51
  5. Very specific:

    Run scripts/layout.py <left-count> <right-count> if the number of nodes changes.

    line 55
  6. Distinctive:

    Every tooltip opens with a bold sentence that's the whole point, then one to three sentences of detail.

    line 83
  7. Very specific:

    Keep the core's visible label short (one or two words).

    line 88
  8. Distinctive:

    Default is one language (English) and zero language machinery.

    line 155
  9. Distinctive:

    Only wrap strings that actually differ.

    line 167
  10. Very specific:

    Add the display-toggle CSS (CODE is the language code from step 2):

    line 170
  11. Very specific:

    Add the language buttons and the L keyboard shortcut:

    line 181
  12. Very specific:

    Update the hint text and <title> to mention the language switch, and translate every wrapped string.

    line 227
  13. Distinctive:

    Ask the user which direction fits before you build, or default to Neutral.

    line 245
  14. Distinctive:

    All of these are real bugs from building the reference examples, found in a browser.

    line 249
  15. Distinctive:

    Don't cherry-pick individual checks from here and run them by hand — the whole reason this file exists is that manual partial checks let …

    line 272
  16. Very specific:

    Check C and D require group buttons to set b.__mdGuard = true where the mousedown guard is attached.

    line 287

“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

HTML Widget

One system map on one screen: sources on the left, a glowing core in the middle, deliverables on the right, and a machinery box underneath. Hovering a node lights up its connection, dims the rest, and explains it in a tooltip card.

Built for a projector. One fixed 16:9 coordinate space that scales with pure CSS, so it looks the same at 1280×800 and 1920×1080. No build, no server, no external calls — a double-clickable file.

⛔ Only permitted workflow

Never write the widget from scratch. Always start from whichever of the four files in references/examples/ is closest to your content — they are finished, debugged starting points: the curve geometry, tooltip placement, idle cycle, and scaling are all fixed through actual testing in a browser. From scratch, you lose all of that, and every entry in Pitfalls comes back.

Steps 1–3 are questions to actually ask — batch them into one message — before touching any file, even when the request is a single terse instruction like "run the system-map skill on X" with no preferences stated. A prompt that doesn't mention a palette or a language is not the same as the user having no preference; it means you haven't asked yet.

RationalizationReality
"The request was just 'run the skill', so speed matters more than asking"One message with the open questions costs less than a wrong palette costs to redo
"No palette was mentioned, so they have no preference"Unmentioned ≠ no preference. You haven't asked.
"It's obvious which palette fits the content"Only the example match (step 1) can be inferred from content — palette and language are aesthetic choices, always ask
  1. Ask the user which of the four examples is the closest match for their content (or pick for them if it's obvious from context), and read that file into context.
  2. Ask which color direction they want — see references/palette.md for the four options. Default to Neutral only if, once asked, the user says they don't care.
  3. Ask whether they want a second language. Default is English only, with no language-switching machinery at all. If they want a second language, see "Adding a second language" below.
  4. Clarify the content with the user (see Content model).
  5. Run scripts/layout.py <left-count> <right-count> if the number of nodes changes. It prints finished coordinates, <g> blocks, and CSS rules. Use every block it prints, not just the curves. Change the curves without the nodes' top: values and the curves end up in one place, the nodes in another — the bug looks like "the curves are floating loose" and is easy to miss in a screenshot.
  6. Swap in text, icons, and nodes. Keep all the CSS machinery.
  7. Run scripts/selfcheck.js in the browser at both 1920×1080 and 1280×800. Everything must be ALL PASS before you ship it. See Verification.

Content model

ZoneRoleCount that works
LeftWhat feeds the system — sources, inputs, integrations3–5
CoreThe one thing everything passes through1
RightWhat comes out — deliverables, artifacts, results3–5
Bottom boxThe machinery that makes it possible, in two rows3–4 and 4–6

Columns don't need equal node counts. The band is the same, so different density still reads as two equally tall blocks.

The bottom box's two rows carry a point the reference examples make on purpose: row 1 is the units, row 2 is the pattern that repeats in each of them. Use that structure when it fits, and collapse to one row when it doesn't.

Every tooltip opens with a bold sentence that's the whole point, then one to three sentences of detail. Fact-check every name and number against the source before shipping — a system map with a wrong detail is worse than no system map.

Keep the core's visible label short (one or two words). A long label widens the core's bounding box enough to collide with the nearest tooltips — see self-check H. Put the fuller description in the aria-label and the .ctip text underneath instead.

Geometry

Everything is measured in a 1600×900 space. The scaling is pure CSS, no JS:

--u: calc(min(100vw, 177.78vh) / 100);   /* 1u = 1% of the stage width */
.stage{width:min(100vw,177.78vh);aspect-ratio:16/9}

The SVG layer uses viewBox="0 0 1600 900" and scales itself. Nodes are positioned in %. All typography is set as calc(var(--u) * n).

Fixed anchor points: the core at (800, 395) with radius 58, the node band at y 200–590, the left column at x 56–368, the right column at x 1232–1544, the bottom box at y 645–872.

The column width of 312 (19.5%) is set by the longest label you actually use, not by aesthetics. Check your longest label against it — self-check F requires at least 8px of clearance, in every language you support. Need longer labels, increase COL_W in layout.py and regenerate the curves — don't shrink the font size, it's set for projector readability.

The curves are hand-drawn beziers between fixed points. That's why the layout is fixed rather than responsive: a fluid layout would need JS that measures the nodes and recomputes the curves on every resize. Let layout.py compute them.

Tooltip placement

Tooltips are the part most likely to end up off-stage. The rules:

NodeClassesWhy
Left columninRPoints inward, toward the core
Right columninLPoints inward, toward the core
Topmost in a column+ tTopCentered would poke into the tagline
Bottommost in a column+ tBotCentered would poke into the bottom box
Everyone elsenoneCentered on the node
Bottom box row 1.r1Above the box, bottom: 100% + 4.2u
Bottom box row 2.r2Above the box, bottom: 100% + 10.2u
Outermost chip in a row:first-child / :last-childAnchored to its own edge instead of centered

The bottom box's tooltips are centered on the chip, but the tooltip is 30u wide against a chip under 10u. At the edge of the row it then runs out past the node columns. The rule that fixes it, regardless of chip count:

.chip:first-child .tip{left:0;right:auto;transform:none}
.chip:last-child .tip{left:auto;right:0;transform:none}

The bottom box's tooltip has to clear both the core's underside and the box's top edge. If it gets too tall, make it wider rather than moving it — a wider card gets shorter.

The tooltip covers the middle part of the curve it explains. That's intentional and matches the reference examples' behavior; the alternatives collide with something worse.

Adding a second language

Default is one language (English) and zero language machinery. If the user wants a second language, add this on top of an otherwise-finished widget:

  1. Set data-lang="en" on .stage: <main class="stage" data-lang="en">.
  2. Wrap every piece of text that actually differs between the two languages in a pair of spans, using the second language's two-letter code (no, fr, de, whatever the user asked for) instead of en:

    <span class="tr tr-en">Customer request</span><span class="tr tr-CODE">…</span>

    Only wrap strings that actually differ. Folder names, filenames, and proper nouns are usually identical in both languages — leave them as plain text.

  3. Add the display-toggle CSS (CODE is the language code from step 2):

    .stage .tr{display:none}
    .stage[data-lang="en"] .tr-en{display:contents}
    .stage[data-lang="CODE"] .tr-CODE{display:contents}

    display:contents is deliberate — the wrapper disappears from layout, so flex-gap on nodes and display:block on <b> inside tooltips behave the same in both languages. display:inline would add an extra flex item.

  4. Add the language buttons and the L keyboard shortcut:

    <div class="lang"><button data-set="en">EN</button><button data-set="CODE">CODE-UPPER</button></div>
    .lang{position:absolute;left:2.4%;bottom:2.6%;display:flex;gap:calc(var(--u)*.35);z-index:30}
    .lang button{font-family:inherit;font-size:calc(var(--u)*.82);letter-spacing:.07em;
      padding:calc(var(--u)*.3) calc(var(--u)*.8);border-radius:calc(var(--u)*.38);
      border:1px solid rgba(255,255,255,.12);background:transparent;color:var(--txt-3);
      cursor:pointer;transition:.22s}
    .lang button:hover{color:var(--txt-2);border-color:rgba(255,255,255,.28)}
    .stage[data-lang="en"] .lang button[data-set="en"],
    .stage[data-lang="CODE"] .lang button[data-set="CODE"]{
      color:var(--accent);border-color:rgba(var(--accent-rgb),.5);background:rgba(var(--accent-rgb),.10)}
    .lang button:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(255,255,255,.4)}
    <script>
    var stage = document.querySelector('.stage');
    function setLang(l) {
      stage.dataset.lang = l;
      document.documentElement.lang = l;
    }
    document.querySelectorAll('.lang button').forEach(function (b) {
      b.addEventListener('mousedown', function (e) { e.preventDefault(); });
      b.__mdGuard = true;
      b.addEventListener('click', function () { setLang(b.dataset.set); });
    });
    addEventListener('keydown', function (e) {
      if (e.key === 'l' || e.key === 'L') {
        setLang(stage.dataset.lang === 'en' ? 'CODE' : 'en');
      }
    });
    </script>

    The mousedown → preventDefault() guard matters: without it, a mouse click gives the clicked button focus, and the very next keypress makes the browser treat the session as keyboard-driven, so the focused button matches :focus-visible and inherits the selected button's accent color — both languages end up looking selected. b.__mdGuard = true is what lets self-check D confirm the guard is actually attached (event listeners are otherwise invisible from JS).

  5. Update the hint text and <title> to mention the language switch, and translate every wrapped string.
  6. Re-run scripts/selfcheck.js — it detects the .tr-CODE classes automatically and runs checks E (language pairs) and F–H once per language. If you want automatic detection of untranslated leftover text, write your own probe for your specific two languages — the check isn't generic across arbitrary language pairs, so it isn't built in.

Palette

See references/palette.md for the four color directions (Neutral, Ember, Signal, Volt) and their exact variable values. Every glow, shadow, and gradient color in the stylesheet is written as var(--accent) or rgba(var(--accent-rgb), alpha) (and the -2/-3 equivalents) — swap the six variables in :root and the whole widget recolors, including the gradient (wired through the .stopA/.stopB/.stopC classes on the <stop> elements).

Ask the user which direction fits before you build, or default to Neutral.

Pitfalls

All of these are real bugs from building the reference examples, found in a browser. The right column shows which self-check catches them again.

SymptomCauseFixCaught by
Tooltip text runs off the card, doesn't wrapwhite-space:nowrap on .node/.chip inherited into .tip.tip{white-space:normal;word-break:break-word}G
The lit curve turns the wrong colorOne gradient with gradientUnits="userSpaceOnUse" spanning the full width — left and right halves hit the wrong stopsTwo gradients, #gL (x 300→800) and #gR (x 800→1300), both toward --accent at the core— (visual, check the screenshot)
The pulse keeps running on dimmed curves during hoverStatic opacity:0 doesn't stop a running animation.stage:has(.node:hover) .cmt{animation:none;opacity:0}— (visual, check the screenshot)
Node text wraps to two lines on a narrow screenRounding makes the text juuust not fitwhite-space:nowrap on .node and a slightly smaller font sizeF
Curves don't reach the nodes after the count changedOnly the <g> blocks from layout.py were pasted in, not the top: valuesUse both blocks. Check that the number of .wire equals the number of .nodeA + B
Both languages show at once in the taglineA descendant selector like .hd span{display:block} also hits the .tr wrappers and overrides display:noneUse a child selector: .hd > span. Applies anywhere you style span inside a text container— (fix manually; see "Adding a second language")
The core's label collides with a nearby tooltipA long core label widens the .core-lbl bounding box far enough to overlap the closest node tooltipsKeep the visible core label to one or two words. Put the full name in aria-label and the .ctip text insteadH
The bottom box's tooltip lands on top of a node in the columnThe card is centered on the chip and much wider than it. At the edge of the row it reaches over the columnAnchor the outermost chip to its own edge with :first-child / :last-child, see the table aboveH
A label overflows the node, most visible after adding a second languagewhite-space:nowrap prevents wrapping, so text that's too long for one language runs out of the box without showing up in a tooltip sweepIncrease COL_W in layout.py and regenerate the curves. Measure the label in every language you support — check FF
Both language buttons light up after a mouse click followed by LThe click gave the button focus. The first keypress makes the browser treat the session as keyboard-driven, so the focused button matches :focus-visible and inherits the accent color from the focus ruleGive focus its own visual form — a box-shadow ring, not border-color. Also add mousedown → preventDefault() on the buttons, so a mouse click never moves focusC + D
The widget ships in the default Neutral palette without the user ever being askedA terse invocation ("run the skill on X") was read as license to skip the step-2 question instead of asking and defaulting only if told "no preference"Ask steps 1–3 in one message before reading or writing any file, regardless of how terse the request was— (process; verify the question was asked before any file was touched)

Verification

Paste scripts/selfcheck.js into the browser console on the widget. Run it at both 1920×1080 and 1280×800. ALL PASS is required before you ship it.

Don't cherry-pick individual checks from here and run them by hand — the whole reason this file exists is that manual partial checks let three bugs through in a row. Run the whole file, at both resolutions.

CheckCatches
A wires/nodesNumber of .wire ≠ number of .node
B wire attachmentA node moved without the curve following — half of layout.py's output was used
C focus/selectedA :focus-visible rule on a group button sets something other than outline/box-shadow/border-width. If it shares an expression with the selected state, two buttons light up alike the moment the user touches the keyboard
D focus trapA group button without mousedown → preventDefault(), so a mouse click leaves focus behind
E language pairsOnly runs if the widget has a second language (auto-detected from .tr-CODE classes). Imbalance between the two codes, or a missing data-lang in the markup
F label widthUnder 8px of clearance on a node label, in every language present
G tooltipText that doesn't wrap, or a card that ends up off-stage
H collisionA tooltip that covers a node, the core, the tagline, or the language switch

Check C and D require group buttons to set b.__mdGuard = true where the mousedown guard is attached. Without the flag, the test can't see it — event listeners are otherwise invisible from JS.

Beyond the self-check, which is still manual: zero console errors, and fact-check every name and number in the tooltips against the source.

About the preview panel

Two things that look like bugs in the widget but are the panel:

  • A mouse click lands in the wrong place. The tool reports one coordinate and the page receives another. Use read_page + ref, or test by keyboard.
  • Style changes on a single element don't re-layout. el.style.top = … changes the style attribute without getBoundingClientRect() moving. An injected <style> does work. If you want to sabotage layout to test it, do it through a stylesheet or by changing a curve's d.

Files

FileRole
references/examples/agent-workflow.htmlAI agent workflow, Signal palette, 4 sources / 5 outputs
references/examples/business-integration.htmlBusiness systems integration, Neutral palette, 3 / 4
references/examples/data-pipeline.htmlData/observability pipeline, Ember palette, 5 / 3
references/examples/hr-platform.htmlHR/people-data platform, Volt palette, 4 / 4
references/palette.mdThe four color directions and their variables
scripts/layout.pyComputes node positions, bezier curves, and CSS rules. python3 layout.py 4 5
scripts/selfcheck.jsEight invariants, auto-detecting single- vs two-language builds. Must give ALL PASS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.