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 (ecu vs ECU) — 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:

  1. Jot on the phone. Whatever the input format is, it has to be typeable one-handed in Obsidian mobile with a soft keyboard.
  2. 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 runs uv 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

ToolWhy rejected
D2 / MermaidRenders 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.
KiCadReal schematic capture + PCB layout. Wrong job entirely, and far too heavy for a car harness.
FritzingBreadboard 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 .jot file 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:

  • ecu appears 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 gnd node

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 harness column exists specifically to drive this grouping — it is not a label or a tag. All rows sharing a harness value collapse into a single WireViz cables: entry, with per-wire colors and wirelabels arrays. 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

PathRole
~/coding/wiring_diagram/jot2wv.pyjot → WireViz YAML converter (~40 lines of logic)
~/coding/wiring_diagram/render.shDriver: 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.png

No 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 -.

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 — ecu and ECU are two different connectors.
  • render.sh <missing-note> gives a raw FileNotFoundError traceback. No friendly error.
  • A failed conversion leaves a 0-byte .yml in out/. The shell redirect creates the file before python runs, so a parse error leaves the empty stub behind. Harmless — set -e stops the script and wireviz never runs on it — but it can look alarming when poking around out/.
  • 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 and ecu-vs-ECU failures generalised into structural rules