Central principle, borrowed from archify and kept as-is because it's correct: model does NOT draw.
The model only fills logic into a schema'd declaration (which blocks exist, how block A connects to block B).
The rendering is the job of deterministic code. Once drawn, the machine re-inspects the layout itself; if broken, keep the previous
good version and state clearly what's wrong, absolutely never emit a garbage shape.
That's also why this skill exists instead of letting the model draw SVG itself: a wrong shape still looks "sort of okay", so layout errors are the type of error that slips through the farthest.
WHAT
Purpose and context
- Purpose: draw DIAGRAMS (relations/flows) and CHARTS (data) with deterministic code — the model only fills a schema'd declaration, the machine renders and self-inspects the layout; if broken, keep the previous good version and state clearly what's wrong.
- Trigger (when to use): user says "draw a diagram", "draw a chart", "draw a flow", "architecture diagram", "processing flow", "flow", "graph", "chart", "visualization", "compare two flows", "draw a diagram", "sequence diagram", "state machine", "dependency graph", "plot", "dashboard", "convert Mermaid", "diff two flows" (full list at
description). - Non-goals: NOT used for UI/landing page screens — that's
hallmark. Don't hand-draw SVG coordinates for a new shape. Don't replace the generator for diagrams in auto-generated documents.
Mental model
câu hỏi người xem → có TRỤC SỐ? → Sơ đồ (ngữ pháp → archify) hoặc Biểu đồ (dataviz + 5 luật lieflat) → file HTML → cổng nhà (frontend-antipattern, tuỳ chọn visual-receipt) → giao nguyên tử (xanh mới thay, đỏ giữ bản cũ). Model fills logic; code renders; deterministic gate decides pass/fail.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | description of the flow/relation or data | yes | the question the viewer needs answered |
| In | diagram type | no | architecture, workflow, sequence, dataflow, lifecycle; if left blank the engine guesses |
| In | existing Mermaid | no | paste it in, archify redraws it |
| In | anchor to real code | no | sources[] + meta.repository + --repo-root (full 40-char SHA) |
| Out | self-contained HTML file | yes | diagram or chart, through the home gate |
| Out | gate verdict | yes | rc of frontend-antipattern.py (0 clean, 1 FAIL, 2 WARN only); optional visual-receipt.py (0 pass, 1 fail, 2 SKIPPED) + 4 images + JSON receipt + contact sheet |
Rules and capabilities
- RULE-01 (MUST): The model does not draw. The model fills in the declaration; code builds the figure. If you find yourself typing SVG coordinates by hand for a new figure, you have already gone down the wrong branch.
- RULE-02 (MUST): Do not release junk figures. If the new version does not pass the gate, keep the old good version, and clearly report where it is wrong. This repo's generator already does exactly that (
_deliverinbuild-overstack-docs.py: render to a temp file, run the gate on the temp file, only if green thenos.replace). - RULE-03 (MUST): SKIP ≠ CLEAN. Missing Chrome, missing network, missing engine → say "skip", never report "pass". A gate that lies while still showing green is worse than no gate at all.
- RULE-04 (MUST): Do not import an external engine's style constraints as house rules. Measure first: archify demands arrows be straight horizontal/vertical, but this repo's diagrams intentionally fan out (6·4·10 valid diagonals across three diagrams) — so that rule was rejected during absorption, while the other three geometry rules were accepted.
- RULE-05 (MUST): For diagrams in auto-generated documents, use the generator, do not call an agent. Each diagram drawn via chat costs one agent turn, forever; SVG-generating code costs 0. Only use the chat branch for new figures or one-off figures.
- Capabilities: call the deterministic diagram-drawing engine and the chart engine; run a 0-token static gate on HTML files; optionally run a real browser to capture and measure requests; write the destination file atomically.
Failure boundaries
- Unclear whether there is a NUMERICAL AXIS → clarify (ask "does what I'm drawing have a NUMERICAL AXIS?").
- The request is a product screen → blocked, hand off to
hallmark. - Chart violates discipline (truncated axis, glow/3-D on data marks, data too thin) → refuse/downgrade with reasons, offer an honest alternative.
deliverred or gate FAIL → failed, the destination file remains the old good version; report where it is wrong.- Missing Chrome/network/engine → SKIP, never report "pass" (RULE-03).
- Wrong code anchor (SHA not exactly 40 characters,
origindoes not match) → blocked (fail-closed).
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | request | Choose branch: has NUMERICAL AXIS → Chart; not → Diagram; screen → hallmark |
branch | UI → blocked; ambiguous → clarify |
| W02 | judgment | viewer question | Diagram: choose GRAMMAR (topology, sequence, process, state, hierarchy, matrix); one question one figure | archify type | two questions → two figures |
| W03 | effect | declaration | Diagram: call archify in natural language or node ~/.agents/skills/archify/bin/archify.mjs (B01) |
HTML file | rc 1 → failed, old version stays unchanged |
| W04 | effect | data | Chart: load dataviz before the first line of code + 5 lieflat rules (B02) |
HTML file | discipline violation → reject/downgrade |
| W05 | deterministic | HTML file | Gate python3 fdk/tools/frontend-antipattern.py <file.html> |
rc 0 | 1 → fix declaration, repeat W03/W04 |
| W06 | deterministic | HTML file | Optional browser evidence python3 fdk/tools/visual-receipt.py <file.html> (B03) |
images + receipt | 2 → report SKIPPED, do not report passed |
| W07 | effect | file that passed the gate | Atomic delivery: green replaces the target file; red keeps the old good version + reports where it is wrong | target file | — |
Details of each step (source of truth for W01–W07):
Choose the branch before doing anything
| What you are representing | Branch | Engine |
|---|---|---|
| Relationships / flows / sequences between components (boxes + arrows) | Diagram | archify |
| Data — comparison, distribution, trend, proportion | Chart | dataviz + the lieflat discipline below |
| A product screen | not the job here | hallmark |
Not sure? Ask: "does what I am drawing have a NUMBER AXIS?" Yes → chart. No → diagram.
Within the Diagram branch — choose the correct GRAMMAR before choosing an engine
Ask first: what question must the viewer be able to answer? — the question decides the grammar, the grammar
decides what the drawing looks like (DISSOLVED from plannotator/effective-html, see [[design-foundation]]):
| Viewer's question | Grammar | archify type example |
|---|---|---|
| What is there, connected to what? | Topology / system diagram | architecture |
| What happens over time? | Sequence / timeline | sequence |
| How does the decision/transformation happen? | Process flow | workflow |
| How does this change state? | State diagram | lifecycle |
| What contains/owns what? | Hierarchy / containment | architecture (nested branch) |
| How do the options compare with each other? | Matrix / row-aligned comparison | table, no archify needed |
| How much / how often / how fast? | Quantitative chart | → branch Chart below |
Don't cram two questions into one figure. Need both → two figures (or toggle layers), not one overloaded figure. Once the grammar is chosen, then comes the engine selection step (HTML/CSS, SVG, Canvas, WebGL) per the section below.
DIAGRAM branch — via archify
External engine, PULLED EXTERNALLY per [[adapt-modes]]: we do not rewrite it, only pin + call.
Install: npx skills add Rheinmir/archify -g -s archify (the real target is ~/.agents/skills/archify).
This is a fork of tt-a1i/archify: adds a 5th preset macos (liquid glass + Roboto) and sets it
as default — leaving it empty meta.visual_preset gives exactly the house theme, without applying any extra
step. The patched branch macos-roboto is the DEFAULT branch of the fork, so no need to pin a ref.
Two traps we've already paid for, don't repeat them:
- There is no flag
--ref. Andowner/repo@nhánhCANNOT pin the branch at install time: the CLI printsSource: …git @macos-robotobut still installs the default branch, and also understands the part after@as a SKILL NAME so it reportsNo matching skills found. That's why we pin by default-branch, not by syntax. -lcannot replace a real install.add … -lreportsFound 1 skillgreen while the real install command still fails. Checking the install path must actually install and then draw a diagram, see whatdata-presetoutputs.
When upstream releases a new version, rebase the branch, don't overwrite-install with tt-a1i/archify.
Call in natural language in chat — no command prefix:
Use Archify to draw: Browser -> API -> Redis cache -> PostgreSQL fallback.
State the TYPE clearly and there's no guessing — architecture · workflow · sequence · dataflow ·
lifecycle. Pasting in existing Mermaid works too, it redraws it nicely.
Three things you must know before calling, verified by actually running it:
- The package named
"private": true— is not on npm.npx archifydoes not run. Every shell command isnode ~/.agents/skills/archify/bin/archify.mjs …. - Exit codes don't follow the 0/1 convention:
2is both a usage error (unknown command, unknown flag, unknown diagram type) and "visual-check skipped because there's no Chrome".1is the real failure of validate/render/deliver. Calling it in CI must branch deliberately. - After a
deliverRED, don't go inspecting the target file — it's still the good OLD version, not the one that just failed. This is a direct consequence of atomicity, not a bug.
Separately lifecycle, three rules live in the code, not in the schema — measured 2026-09-10,
patched at the fork so only a fresh install has them:
- The lane name is the contract.
main→ upper band,terminal→ lower band (Outcomes), every other id falls into the middle band. Naming it anything else (outcome,recover, …) drops into the middle band and collides — this is where 4 layout rounds got burned. - Two floors of different heights. No terminal band used →
viewBox[1] ≥ 510; used →≥ 630. Before the patch both reported 566, so declaringterminalat 566 drops into the loop "State exceeds the vertical lifecycle area" with no way out. - The main rail does NOT become a relationship on its own. The order
steponly draws lines; for Route Probe / passport / trace to see it you must declare a real transition (drop the label if the slot is narrow). Click a node and read the Semantic Passport: seeingNo connected relationshipsmeans the diagram is hollow even though validate/visual-check are both green.
For an architecture diagram to stick to the real code, declare sources[] (path + line range) + meta.repository
--repo-root. That gate fail-closed: the revision must be a full 40-character SHA,--repo-rootmust be a top-level checkout with a matchingorigin. Wrong anchor and it refuses to draw.
CHART branch — dataviz + lieflat-charts discipline
Load the skill dataviz before writing the first line of chart code, in every environment (HTML,
SVG, matplotlib/plotly/d3/Recharts, or a PNG that will render). Plus five rules absorbed from
lieflat-charts, all stand above aesthetics:
- Refusing is CORRECT behavior, not stubbornness. Cutting an axis (broken axis) → refuse, offer three honest alternatives. Glow / glassmorphism / 3-D on data marks → refuse. More than six categories and still demanding color → fall back to a mono scale. Data too thin for the chosen form (three nodes and demanding a force graph) → downgrade and say why.
- Number of charts = number of INDEPENDENT conclusions, not number of data columns. One question → one chart; a whole article → four to six; cap six charts per page then split pages.
- The title is the CONCLUSION, not the chart-type name. "Churn doubles after the April release", not "Churn bar chart".
- Show the evidence for the chart choice. Name at least three candidates and the reason each was rejected BEFORE charting. "I used a bar chart" with no candidate rejected means you never chose.
- Deterministic render. Ban
Math.random()in charts — two refreshes must produce exactly one image. One color system per file; mixing two means redo.
Once you have the HTML file — run the HOUSE gate
The engine guards its layout; the gate below guards what it doesn't know: this repo's conventions.
python3 fdk/tools/frontend-antipattern.py <file.html> # 0 sạch · 1 FAIL · 2 chỉ WARN
It's deterministic and 0 tokens:
- Geometry — two boxes partially overlap, a box outside
viewBox, text overflows its container box. - Accessibility + self-contained — missing
role="img", missing<title>or<title>is not the first child,<title>/<desc>empty, and remote referencehttp(a self-contained page that opened offline is missing images). - Evidence anchors — a node declares
data-src="path"/data-src="path:line"but the path doesn't resolve, or the line number exceeds the file length. Fail-closed: if not declared it isn't asked, if declared it must be correct.
It needs real browser evidence (not just static analysis):
python3 fdk/tools/visual-receipt.py <file.html> # 0 pass · 1 fail · 2 BỎ QUA
Outputs four images (1440×900 and 2048×1320, each size light + dark), a JSON receipt, and a contact sheet for humans to grade. It checks in a real browser three things regex can't do: horizontal overflow, console errors, and every request that escapes outside — that is, self-containment measured by behavior rather than by strings in the file.
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | no numeric axis (relations, flow, sequence) | Diagram branch via archify (PULLS OUTSIDE, fork Rheinmir/archify) |
archify not installed → install npx skills add Rheinmir/archify -g -s archify; exit 2 due to missing Chrome → SKIP, not pass |
W05 |
| B02 | conditional_required | has numeric axis | Chart branch via dataviz + 5 lieflat rules |
request violates rules → refuse, give three honest options | W05 |
| B03 | capability_optional | needs real browser evidence and has Chrome | visual-receipt.py: 4 images, JSON receipt, contact sheet for human grading |
no Chrome → rc 2 SKIP | W07 |
| B04 | user_optional | architecture diagram must stick to real code | declares sources[] + meta.repository + --repo-root |
anchor wrong → engine refuses to draw (fail-closed) | W03 |
| B05 | conditional_required | diagram lives in an auto-generated document | use the repo's generator (e.g. build-overstack-docs.py), don't call an agent |
new image or one-off use → chat branch | W05 |
Validation and stopping
Deterministic: rc of frontend-antipattern.py (geometry, accessibility + self-contained, anchored data-src) and visual-receipt.py (horizontal overflow, console errors, outbound requests); with archify, rc 1 is a real failure, rc 2 is both a usage error and a skipped visual-check, so branching must be deliberate. Need someone: contact sheet of visual-receipt; with lifecycle click a node to read the Semantic Passport, see that No connected relationships is hollow even though the gate is green. Stop when the gate is green and the target file has changed; if red, keep the old version and report.
Examples
- Positive: "draw the flow Browser -> API -> Redis cache -> PostgreSQL fallback" → no numeric axis → Diagram, topology grammar →
Use Archify to draw: Browser -> API -> Redis cache -> PostgreSQL fallback.→frontend-antipattern.pyrc 0 → deliver the preset HTML filemacos. - Boundary/failure: "draw a revenue chart, cut the axis to show a sharp increase" → Chart branch, broken axis rejected (lieflat rule 1), offer three honest ways; the title is the conclusion, not "Revenue Bar Chart".
- Boundary: lifecycle places lane
outcome→ falls into the middle band overlapping each other → change toterminalandviewBox[1] ≥ 630; ifdeliveris still red then the target file is the old good version, do not inspect it as a new one.
Origin
- Diagram branch:
Rheinmir/archify@macos-roboto(PULLED OUT — fork oftt-a1i/archify; the engine lives outside, we pin + call it). Seesources/draft/080926-archify-fork-pin.md. - Chart discipline:
larashero3-dotcom/lieflat-charts(DISSOLVED — take only the rules, not the drawing suite). - Geometry gate + atomic delivery: absorbed from archify into
fdk/tools/frontend-antipattern.pyandfdk/tools/build-overstack-docs.py, seewiki/sources/draft/040926-absorb-round.md.