How phone-typed one-line wire jots become real automotive wiring diagrams: a ~40-line adapter feeding WireViz. This note covers the why behind the design — the code itself is small and self-evident.
Successor project — cluzter (2026-07-30)
cluzter is a GUI wiring-diagram editor designed for the same vehicle, and it imports this project’s 5-field jot grammar rather than orphaning it — one grammar, three consumers: phone capture (here), file import, and cluzter’s in-app omnibox. Four decisions below carry forward; see Inherited from the predecessor.
Two things this pipeline got bitten by are now structural rules there: the
BL-renders-black silent failure and connector-name-as-identity (ecuvsECU) — cluzter-wiring-gotchas. The two documented gaps at the bottom of this note (wire gauge, splices) are first-class requirements in cluzter.
The Problem
Wiring gets figured out in the garage, on a phone, one wire at a time. Wiring diagrams get read at a desk, all wires at once. Those are two different formats, and the gap between them is the whole project.
Two hard requirements fell out of that:
- Jot on the phone. Whatever the input format is, it has to be typeable one-handed in Obsidian mobile with a soft keyboard.
- Jot separately, combine later. Harnesses get worked on one at a time (dash today, engine next weekend), but the payoff is one full-car diagram that stitches them together.
The Tool Doing the Real Work — WireViz
WireViz v0.4.1 is the engine. It does the layout, the wire bundling, and the bill of materials — everything that is actually hard.
Zero-install
uvx wireviz file.yml— no venv, no pip, nothing to maintain. Only real dependency is graphviz (dot), already present via homebrew. The converter side runsuv run --with pyyaml python, so pyyaml is likewise ephemeral.
Input is YAML describing connectors, cables, and connections. Output per run: PNG + SVG + HTML + a .tsv bill of materials.
The reason WireViz won over everything else is that it is electrically aware, not just a box-and-line drawer:
- Connectors have pins, and pins are addressable endpoints
- Cables have colors and (optionally) gauge
- The BOM is auto-generated from the model — wire lengths, connector counts, the lot
Alternatives considered
| Tool | Why rejected |
|---|---|
| D2 / Mermaid | Renders natively in Obsidian and previews on the phone — genuinely tempting. But no pins, no BOM, not electrically aware. It draws a picture of wiring rather than modelling it. |
| KiCad | Real schematic capture + PCB layout. Wrong job entirely, and far too heavy for a car harness. |
| Fritzing | Breadboard pictures, GUI-only, not text-driven — fails the “type it on a phone” requirement outright. |
The Gap — Why Custom Code Exists At All
Nothing off the shelf accepts a flat, one-line-per-wire jot format. WireViz needs structured, nested YAML, and structured YAML is not phone-typeable — indentation, colons, and nesting on a soft keyboard is a non-starter.
So the only thing written by hand is the adapter: jot text → WireViz YAML. Everything downstream of that is WireViz.
Jot Format
<harness> <color> <from.pin> <to.pin> <purpose...>
Example:
dash RD batt.1 ign.1 constant 12v feed
Space separated; purpose runs to end of line. The design goal was five words and zero punctuation to hunt for on a phone keyboard — no quotes, no braces, no colons, no indentation. The single . in batt.1 is the only special character, and it sits on the primary keyboard layer.
Design Decisions and Why
1. Jots live in ```jot fenced blocks inside normal .md notes
The deciding constraint
Obsidian mobile cannot create or edit non-markdown files. A
.jotfile extension would have been cleaner conceptually, and it would have made the whole thing unusable from a phone. That single fact settled the format.
So jots are fenced blocks inside ordinary notes. The converter opens .md files, scans for fences tagged exactly jot, and parses only what is inside them. Everything else in the note — prose, tables, plain ``` blocks — is ignored.
Useful side effect: index can document the line format inside a plain code fence and the parser walks straight past it. The docs and the data live in the same folder without colliding.
2. One note per harness; combining = passing more files
dash and engine are separate notes, each holding its own jot block. Combining them is not a merge step — it is just handing the converter more file paths.
Connectors with the same name across notes automatically become one shared node. This is the mechanism that makes “jot separately, combine later” work at all; without it the two notes would render as two disconnected islands.
Verified on the real notes:
ecuappears in both files and merged into a single connector carrying pins 3, 5 (from dash) and 7, 8, 9 (from engine)- Three ground wires — one in dash, two in engine — all landed on one shared
gndnode
There is no registry, no ID scheme, no declaration step. The connector name is the identity. Which also means a typo (ecu vs ECU) silently creates a second connector — see limitations.
3. Wires group by the harness column into ONE cable per harness
The first implementation emitted one WireViz cable per wire. Technically correct, visually wrong: it drew five loose individual wires floating between connectors instead of a bundled harness. That is not how a car is wired and not how anyone reads an automotive diagram.
For Agents
The
harnesscolumn exists specifically to drive this grouping — it is not a label or a tag. All rows sharing a harness value collapse into a single WireVizcables:entry, with per-wirecolorsandwirelabelsarrays. If you change the converter, preserve this: one harness value → one cable.
4. Pin numbers are arbitrary integers, remapped internally
WireViz wants sequential 1-based pin indices. Real connectors do not oblige — you jot pin 7, 8, 9 because that is what is stamped on the connector.
The converter collects every pin number seen for a connector, sorts them, and maps each to its sequential WireViz index — while setting pinlabels to the true pin number for display. So ecu.7 becomes internal index 1 but still reads “7” on the diagram.
The user gets to jot real pin numbers that need not start at 1 nor be contiguous. Nothing has to be renumbered when a new pin is discovered mid-project.
Files
| Path | Role |
|---|---|
~/coding/wiring_diagram/jot2wv.py | jot → WireViz YAML converter (~40 lines of logic) |
~/coding/wiring_diagram/render.sh | Driver: resolves notes, runs converter, runs wireviz |
~/coding/wiring_diagram/out/ | All outputs land here (.yml, .png, .svg, .html, .tsv) |
~/levandor_obsidian/projects/wiring-diagram/ | The jot notes — this folder |
Rendering
~/coding/wiring_diagram/render.sh # all vault notes -> out/car.png
~/coding/wiring_diagram/render.sh dash # -> out/dash.png
~/coding/wiring_diagram/render.sh dash engine # -> out/dash-engine.pngNo args globs every .md in the project folder (index/log/topics notes included — they have no jot fences, so they contribute nothing). With args, the output basename is the note names joined by -.
Pipeline flow
graph LR Phone["Obsidian mobile<br/><i>types a jot line</i>"] --> Notes["<b>dash.md</b> / <b>engine.md</b><br/>```jot fences"] Notes --> Render["render.sh<br/><i>picks notes</i>"] Render --> Conv["jot2wv.py<br/><i>group by harness<br/>remap pins</i>"] Conv --> Yml["out/<base>.yml<br/><i>WireViz YAML</i>"] Yml --> WV["wireviz<br/><i>uvx, graphviz</i>"] WV --> Out["PNG + SVG + HTML<br/>+ BOM .tsv"] style Phone fill:#264653,stroke:#2a9d8f,color:#fff style Conv fill:#3d2020,stroke:#a55,color:#fff style WV fill:#2d2d2d,stroke:#888,color:#fff style Out fill:#264653,stroke:#2a9d8f,color:#fff
Limitations / Not Yet Modelled
Known gaps
- No wire gauge. Would be a 6th field. WireViz supports it; the jot format does not yet carry it. Adding it means deciding whether it is optional (parser currently demands exactly 5 fields and hard-exits otherwise).
- No splices. A wire tapping another mid-run has no representation. Workaround: model the tap point as a named connector and route both wires through it.
- Connector identity is the literal name. No normalisation —
ecuandECUare two different connectors.render.sh <missing-note>gives a rawFileNotFoundErrortraceback. No friendly error.- A failed conversion leaves a 0-byte
.ymlinout/. The shell redirect creates the file before python runs, so a parse error leaves the empty stub behind. Harmless —set -estops the script and wireviz never runs on it — but it can look alarming when poking aroundout/.
Related
- index — line format, color codes, render commands
- dash — dash harness jots
- engine — engine harness jots
- cluzter — the GUI successor; imports this grammar, inherits four of these decisions, fixes the identity bug and fills the gauge/splice gaps
- cluzter-wiring-gotchas — the
BL-renders-black andecu-vs-ECUfailures generalised into structural rules