A small CLI that reads a Vial keymap export (.vil)
and emits a single self-contained HTML page documenting every layer of a split
keyboard — keys, modifiers, tap dances, and macros all decoded into readable
glyphs.
The project lives at https://github.com/one7two99/vial-doc. Issues, pull requests, and tagged releases all happen there.
The goal is personal layout documentation that lives next to the layout
source in a docs/ folder — not a generic VIA/QMK viewer and not a flasher.
v0.1 supports the Ferris Sweep
(34-key 3×5 + 2-thumb Choc split). Adding new physical layouts is a matter of
dropping another module under src/vial_doc/layouts/.
For each non-empty layer in the .vil:
- A horizontal table-of-contents anchor at the top of the page, linking to every rendered layer.
- An inline SVG of the keyboard with all 34 keys positioned per the upstream
QMK PCB definition (
keyboards/ferris/sweep/keyboard.json). - Readable cap-face labels:
- Plain keycodes use short names or glyphs (
A,⏎,␣,⇥,⌫, arrows↑ ↓ ← →, keypad*/+/-, media⏯ ⏹ ⏭ ⏮, etc.). LSFT(KC_3)renders as#,LSFT(KC_GRAVE)as~, etc. (US-ANSI shifted glyphs).- Other modifier-wraps get a modifier-glyph prefix:
LGUI(KC_LEFT) → ❖←,LCTL(KC_X) → ⌃X,SGUI(KC_Q) → ⇧❖Q,MEH(KC_A) → ⌃⌥⇧A. - Tap-dance keys show the on-tap glyph as the main label with a small
green
TD{n}badge in the corner. - Layer-tap keys (
LT(n, kc)and Vial'sLT{n}(kc)compact form) show the tap glyph with an amberL{n}badge. - Long labels auto-shrink to fit the 54×54 px cap; nothing overflows.
- Hover any cap to see the raw mnemonic in the
<title>tooltip.
- Plain keycodes use short names or glyphs (
- Access-key highlighting: keys on other layers that activate the
current layer (via
MO,LT,TG,TO,OSL,TT,DF, or via a tap-dance slot containing one of those) are highlighted in amber. A one-line "Access:" summary under the layer heading names each access key and how it triggers, including tap-dance attribution (e.g. "hold via TD(10) on_hold on L0 (row 3, col 1)"). - Tap Dances on this layer table listing every
TD(n)referenced on the layer with itson_tap/on_hold/on_double_tap/on_tap_holdvalues, tapping term in ms, and a heuristic "Used as" classification:HRM—on_holdis a plain modifier (home-row mod)Layer access— at least one slot activates a layerMixed— bothCustom— neither
- Macros on this layer table listing every referenced
Mnwith a pretty-printed action sequence — e.g.Text "$?",Tap GRAVE, SPACE,Down RSHIFT, RALT · Tap Q · Up RALT, RSHIFT. - A footer lists skipped empty layers and any keycode mnemonics the parser didn't recognise (rendered in red on their cap as a visual cue).
CSS is inlined. No external assets, no JS, no fonts to fetch — the page works offline and copies cleanly between machines.
- Python 3.11+
- uv (
curl -LsSf https://astral.sh/uv/install.sh | sh, or your package manager's equivalent) - A
.vilkeymap exported from the Vial GUI (File → Save current layout)
Install the latest main straight from GitHub, into uv's tool environment:
uv tool install git+https://github.com/one7two99/vial-doc.gitThis puts a vial-doc executable on your PATH. Updates with:
uv tool upgrade vial-docAfter installation you can run the CLI directly:
vial-doc INPUT.vil --os-layout us-intlTo pin a specific commit or tag, append @<ref>:
uv tool install git+https://github.com/one7two99/vial-doc.git@v0.1.0To remove:
uv tool uninstall vial-docgit clone https://github.com/one7two99/vial-doc.git
cd vial-doc
uv syncuv sync resolves dependencies, creates a local .venv, and installs
vial-doc in editable mode. From there, every command uses uv run to pick
up the local environment (uv run vial-doc INPUT.vil).
If installed as a tool (uv tool install …), invoke directly:
vial-doc INPUT_FILE [OPTIONS]From a development clone, prefix with uv run:
uv run vial-doc INPUT_FILE [OPTIONS]Real-world example:
vial-doc Cadence-FerrisSweep.vil --os-layout us-intl
# wrote Cadence-FerrisSweep.vil.html (≈130 kB)Or against the bundled fixture (clone mode):
uv run vial-doc tests/fixtures/ferris_sweep_minimal.vil -o /tmp/out.html
# wrote /tmp/out.html (≈20 kB)Open the resulting .html in any browser — Firefox, Chromium, Safari all work
identically since the page has no JS dependencies.
| Flag | Default | Meaning |
|---|---|---|
-o, --output PATH |
<input>.html next to the input |
Output HTML file |
-l, --layout NAME |
ferris_sweep |
Physical layout (only ferris_sweep in v0.1) |
--title TEXT |
input stem | Title shown in the page header |
--no-empty / --keep-empty |
--no-empty |
Skip empty layers (default). --keep-empty warns and currently has no effect — empty layers are always skipped in v0.1. |
--theme {light,dark,auto} |
auto |
Color theme. Only light is implemented in v0.1; dark emits a warning and renders light. |
--os-layout {us,us-intl} |
us |
OS keyboard layout used to decode AltGr (RALT) combos. With us-intl, RALT(KC_Q) → ä, RALT(KC_5) → €, RALT(KC_S) → ß, etc. |
-v, --verbose |
off | Debug logging to stderr |
--version |
Show version and exit | |
--help |
Full Typer-generated help |
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Parse error (unreadable file, bad JSON, schema validation failure) |
| 2 | Unknown --layout value |
| 3 | I/O error writing the output file |
uv run vial-doc tests/fixtures/cadence_reduced.vil -o /tmp/cadence.html
firefox /tmp/cadence.html # or open it from your file managerYou should see 4 rendered layers (L0–L3), an access-key amber highlight on L1 / L2 / L3, per-layer Tap-Dance tables on L0–L2, and a per-layer Macro table on L1.
All four quality gates run locally:
uv run ruff check
uv run ruff format --check
uv run mypy --strict src/
uv run pytestThe test suite uses two fixtures under tests/fixtures/:
ferris_sweep_minimal.vil— small synthetic 2-layer fixture for unit testscadence_reduced.vil— a real-world 16-layer Ferris Sweep export used for end-to-end smoke tests
There is also a small scripts/reduce_layers.py helper that produces the
reduced fixture from a full Cadence export by emptying layers above a chosen
index.
See docs/architecture.md for the explicit list of
features out of scope for v0.1: combos, encoders, key overrides, dark-mode
CSS, other keyboard layouts (Corne, Lily58, …), binary .vial support, and
a .vil writer.