drawio-figure-reviewer
Skill11 starsChanged 3 months agoMITView on GitHub
Review or author a draw.io / diagrams.net .drawio file. Catches the silent failure modes the GUI doesn't tell you about - reserved cell IDs, double-escape requirements for `<` and `>`, scroll-offset traps that make the file look empty, edges that cross through intermediate nodes. Use when authoring a new diagram, debugging "my drawio file is empty / won't export", or reviewing one for clarity.
In this repository18 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- android-app-reverserSkill
- android-emulator-app-live-inspectSkill
- aula-pii-scanSkill
- de-ai-proseSkill
- de-ai-repoSkill
- explainer-videoSkill
- justfile-hygieneSkill
- model-raceSkill
- nix-scaffoldSkill
- phase1-prd-grillSkill
- phase2-backlog-snowballSkill
- phase3-backlog-ralphSkill
- readme-improverSkill
- refactor-smellerSkill
- scrub-for-publicSkill
- xterm-tmux-controlSkill
- zellij-controlSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 5 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 136
Run the symptom-to-rule lookup:
- Distinctive: line 158
When the bisect points at one or two cells, inspect their IDs against the reserved-name list.
- Distinctive: line 233
Use separate files when:
- Very specific: line 238
If splitting into separate files: keep filename prefix consistent (dataflow-scan.drawio, dataflow-locate.drawio, ...) so they sort together …
- Distinctive: line 242
When reviewing, be specific and concrete: point at the cell ID, the line number, the exact rule.
- Distinctive: line 244
When pushing back on a request ("split this monolith"), explain *why* the current is crowded (count nodes/edges, identify the dense region) …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
drawio Figure Reviewer
drawio files are XML, easy to author programmatically — and full of silent failure modes the GUI does not flag. CLI export and the GUI app disagree on what's a fatal problem; some bugs only manifest in one of the two. This skill captures the known traps.
The five hard-won rules
dx="0" dy="0"onmxGraphModelwhen authoring programmatically.- Never use reserved SVG/HTML element names as cell IDs (
filter,mask,defs,style,script,g,path,clipPath,linearGradient,radialGradient, ...). - Double-escape
<and>in cell values and edge labels:&lt;/&gt;(not</>). - Don't park a node on the straight line between two other nodes that have a direct edge — the edge will pass through it visually.
- Verify via CLI export AND by opening in the GUI app. They mask different bugs.
When to use
- User asks to "make a drawio diagram of X" or to author one programmatically.
- User says "the drawio file is empty / won't open / won't export" — these symptoms map to specific rules above.
- Reviewing an existing
.drawiofor clarity and correctness. - Splitting a crowded monolithic diagram into per-aspect tabs/files.
The pitfalls, in detail
Rule 1 — dx/dy are scroll offsets, not metadata
<mxGraphModel dx="0" dy="0" ...>dx/dy are the saved scroll position of the viewport. When drawio opens the file, it scrolls to that position. If you wrote a programmatically-large value (e.g. dx="1700" dy="1100") and your content sits at small coordinates, the GUI shows a blank canvas — the content is fine, the viewport is parked off it.
Symptoms: file opens, canvas appears empty, but drawio -x -f png -o out.png file.drawio still produces a non-empty PNG.
Fix: set dx="0" dy="0" for hand-authored / scripted files. When the user saves from the GUI, drawio writes whatever scroll position they last had — that's fine because they were viewing real content.
Rule 2 — reserved IDs silently break CLI export
Symptoms: drawio -x -f svg -o out.svg file.drawio produces no output and prints Error: Export failed: file.drawio. GUI may still render the file fine.
Cause: drawio's export pipeline emits SVG, and cell IDs become element IDs. Collisions with SVG/HTML reserved element names break the export. Confirmed-bad IDs include:
filter mask defs style script g path clipPath
linearGradient radialGradient text image use symbolProbably others (full list is the SVG element registry; safest assumption is "avoid any name that's also an HTML or SVG tag").
Fix: rename to a non-reserved synonym (e.g. filter → filt, mask → mask1, path → path_box). The cell's value (what's displayed) can stay as the human name — only the id="..." attribute matters.
To audit a file:
grep -oP 'id="[a-zA-Z][a-zA-Z0-9_-]*"' file.drawio | sort -uCross-reference against the SVG element registry: https://developer.mozilla.org/en-US/docs/Web/SVG/Element.
Rule 3 — <T> in cell text gets eaten
Symptoms: a cell or edge label written as Vec<ScannedAp> renders as just Vec. The angle-bracketed part is gone.
Cause: drawio cell styles include html=1 by default (rounded boxes, all standard shapes). The value= attribute is rendered as HTML. <ScannedAp> is parsed as an unknown HTML tag and silently dropped.
Fix: in the XML, write the angle brackets as &lt; and &gt;. After XML parse → < >. After HTML render → literal < >. Example:
<mxCell id="scanner" value="scanner.rs
parse → Vec&lt;ScannedAp&gt;" ...>Same trap applies to edge labels (mxCell with edge="1"'s value).
Rule 4 — edges respect geometry, not topology
If node A is at (60, 200), node C is at (800, 200), and you have an edge A→C, the edge is drawn as a straight line through y=200 between x=60 and x=800. If you also place node B at (400, 200), the edge passes through B. drawio does not auto-route around obstacles unless you tell it to.
Fix options:
- Move B off the straight line (e.g. to y=120, above the edge).
- Give the edge explicit waypoints (
<Array as="points"><mxPoint .../></Array>inside the edge'smxGeometry).
Rule 5 — verify both ways
| Bug class | Caught by CLI export? | Caught by GUI app? |
| Reserved ID | YES (fails) | NO (renders) |
Bad <> escape | NO (silently strips) | NO (silently strips) — visible in render |
Bad dx/dy | NO (renders fine) | YES (canvas blank) |
| Edge through a node | NO (renders) | NO (renders) — visible to humans |
| Reserved schema attribute | YES (fails) | sometimes |
So the verification protocol is:
# 1. CLI export to PNG — proves the file is structurally exportable.
rm -f ~/.config/drawio/Singleton*
drawio -x -f png -o /tmp/probe.png file.drawio
[ -s /tmp/probe.png ] || echo "FAIL: CLI export produced nothing"
# 2. Open in the GUI — proves content is on-screen at viewport open.
drawio file.drawio &
# ask the human: "do you see content, or a blank canvas?"Procedure
When authoring a new drawio file
- Start from a known-good template — copy an existing working file (e.g. user's
architecture.drawioif available) and edit, rather than building from scratch. Avoids forgetting a wrapper element. - Use these in the header:
<mxfile host="Electron" agent="<short-identifier>" version="29.0.3"> <diagram name="<Human Name>" id="<safe_id>"> <mxGraphModel dx="0" dy="0" grid="1" gridSize="10" ...> <root> <mxCell id="0" /> <mxCell id="1" parent="0" /> ... - Pick cell IDs from a safe namespace: prefix all IDs (
n_for nodes,e_for edges,el_for edge labels — e.g.n_filter,e_parse_emit,el_parse_emit) so collisions with SVG element names are impossible. - Escape angle brackets the moment you write them:
&lt;&gt;. - Layout sanity-check before adding edges: list every (id, x, y, w, h). For every pair of nodes A, B that have an edge A→B, check whether any third node C has its bounding box on the straight-line segment from A's edge-exit point to B's edge-entry point. If so, move C or add waypoints.
- Validate: CLI export → non-empty PNG; open in GUI → content visible.
When debugging "the drawio file is empty / broken"
Run the symptom-to-rule lookup:
| Symptom | Likely rule |
Error: Export failed: file.drawio | Rule 2 (reserved ID) |
| CLI export OK but GUI canvas blank | Rule 1 (bad dx/dy) |
Vec / <X> rendered without its angle-bracketed part | Rule 3 (double-escape) |
| Edge visibly crosses through a box | Rule 4 (geometry vs. topology) |
| GUI renders fine, CLI fails | Rule 2 (almost always) |
Then bisect:
# If a file fails to export, bisect by stripping cells:
python3 - <<'PYEOF'
import xml.etree.ElementTree as ET
t = ET.parse('file.drawio')
gm = t.getroot().find('.//mxGraphModel/root')
# Remove half the data cells, test export, narrow down.
PYEOFWhen the bisect points at one or two cells, inspect their IDs against the reserved-name list.
When reviewing a finished drawio for clarity
Run through:
| Check | How to spot |
| dx/dy non-zero in a hand-authored file | Header dx="N" dy="M" with N,M > 0 — but skip this check if drawio app last saved the file |
| Reserved ID | grep -oP 'id="[a-z]+"' and cross-reference SVG element names |
Unescaped <T> in value= | grep '<[A-Z]' file.drawio looks for un-escaped pointy generics |
| Edge crosses a node | Open in GUI, visually inspect; or compute geometrically (intermediate-node bounding-box vs. edge straight-line segment) |
| Crowded diagram (too many edges in one view) | >25 nodes or >40 edges → suggest splitting into per-aspect files/tabs |
| Inconsistent edge styling | Same logical edge type (data / trigger / read) using different style="..." patterns across cells |
| Page bounds | All nodes within pageWidth × pageHeight? grep -oP 'x="[0-9]+" y="[0-9]+" width="[0-9]+" height="[0-9]+"' and check |
Programmatic structural check (Python, runs against any .drawio):
import xml.etree.ElementTree as ET, sys
t = ET.parse(sys.argv[1])
gm = t.getroot().find('.//mxGraphModel')
page_w = int(gm.get('pageWidth')); page_h = int(gm.get('pageHeight'))
nodes, edges = {}, []
for c in gm.findall('.//mxCell'):
cid = c.get('id')
if c.get('vertex') == '1':
g = c.find('mxGeometry')
if g is not None and g.get('width'):
nodes[cid] = tuple(float(g.get(k, 0)) for k in ('x','y','width','height'))
elif c.get('edge') == '1':
edges.append((cid, c.get('source'), c.get('target')))
# Reserved IDs
RESERVED = {'filter','mask','defs','style','script','g','path','clipPath',
'linearGradient','radialGradient','text','image','use','symbol'}
bad_ids = [n for n in nodes if n in RESERVED]
if bad_ids: print(f"BAD: reserved IDs: {bad_ids}")
# Edge endpoints resolve
for eid, s, t_ in edges:
if s and s not in nodes and s not in ('0','1'):
print(f"BAD: edge {eid} source={s} not a node")
if t_ and t_ not in nodes and t_ not in ('0','1'):
print(f"BAD: edge {eid} target={t_} not a node")
# Overlap
keys = list(nodes); over = []
for i in range(len(keys)):
for j in range(i+1, len(keys)):
ax,ay,aw,ah = nodes[keys[i]]; bx,by,bw,bh = nodes[keys[j]]
if ax < bx+bw and ax+aw > bx and ay < by+bh and ay+ah > by:
over.append((keys[i], keys[j]))
if over: print(f"OVERLAP: {over[:5]}")
# Page bounds
oob = [n for n,v in nodes.items() if v[0]<0 or v[1]<0 or v[0]+v[2]>page_w or v[1]+v[3]>page_h]
if oob: print(f"OUT OF BOUNDS: {oob}")Anti-patterns (push back on these)
- Monolithic diagrams with 30+ nodes — splits into per-aspect files (or per-aspect tabs in a multi-
<diagram>file) are almost always more readable. Propose the split before refining a crowded layout. - Hand-tuning every edge waypoint to avoid overlaps — usually means the layout itself is wrong. Reposition nodes first.
- Reproducing the entire SQLite schema / object model in the diagram — drawio is for flow / topology, not ER. If the user wants ER, propose dbdiagram.io or PlantUML ER.
- Lane backgrounds via huge
fillColor=#fafafarectangles — these read fine in CLI export but can become "click-traps" in the GUI when the user wants to click an underlying node. Use them sparingly; considerpointerEvents=0on the background to make it click-through. - Custom agent strings — pretty (
agent="my-cool-diagram") but drawio prefers to overwrite with the real Mozilla UA on save. Don't fight it; let drawio write what it wants.
Multi-diagram files (tabs vs. separate files)
drawio supports multiple <diagram> elements inside one <mxfile> — they show as tabs at the bottom of the canvas. Use this when:
- The diagrams are aspects of one system (good:
Scan,Locate,History,Persistfor a daemon's dataflow). - A reader benefits from quickly switching between views.
Use separate files when:
- The diagrams are independent (a network topology and a deployment topology don't belong as tabs of one file).
- You want to reference them individually from a README (per-file SVG export is one-to-one).
If splitting into separate files: keep filename prefix consistent (dataflow-scan.drawio, dataflow-locate.drawio, ...) so they sort together in ls.
Tone
When reviewing, be specific and concrete: point at the cell ID, the line number, the exact rule. Don't hand-wave "diagram could be cleaner" — say "node n_parse overlaps node n_emit at (590, 1090)" or "edge e_parse_emit passes through node n_buffer between x=300 and x=600".
When pushing back on a request ("split this monolith"), explain why the current is crowded (count nodes/edges, identify the dense region) and propose the specific split.
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
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.

