Skip to content

Architecture

This section is for contributors hacking on the config layer and compilers. For the whole-repo map (shells, adapters, stores, where-to-change), start with Project structure; this page zooms into the config subsystem.

The whole subsystem lives under src/firmware/config/ and is exported through one barrel, @firmware/config (src/firmware/config/index.ts). It depends only on the keycode catalog and shared types — no UI, no transport.

The data-flow

┌── device (MockKeyboardService.getConfigSource) ──┐
│                                                  │   source JSON (surface form)
└── builder canvas / Edit JSON ────────────────────┘


            parseSurface()         Zod validation + cross-reference checks
                     │             (schema.ts)

            normalizeKeymap()      expand shorthand → explicit nodes
                     │             (normalize.ts)

            ConfigKeymap           the canonical in-memory document
            (Zustand: configStore) (types.ts)
                 │        │
   live editor ─┘        └─ export

            serializeKeymap() ─┴─► surface JSON (download / save)
                              │    (serialize.ts; preferredSourceJson keeps
                              │     your original spelling)

            getCompiler(target).compile(config) ─► ExportedFile[] + diagnostics
                              │                     (compiler.ts + compilers/*)

            buildProjectBundle(config, target) ─► full repo skeleton (.zip)
                                                   (bundle.ts)

The key invariant: one canonical form. normalize lowers every surface shorthand (bare-string keys, mod_tap/layer_tap presets, "Ctrl+C" combo strings) into explicit CanonAction nodes, so neither the live-edit path nor any compiler ever branches on surface sugar. The config is never round-tripped through the runtime KeyAction model — that projection is display-only and lossy.

Module map

ConcernFile(s)
Canonical types (source of truth)types.ts
Surface Zod schema + validationschema.ts
Surface → canonicalnormalize.ts
Canonical → surface (round-trip)serialize.ts
Public barrelindex.ts
Keycode catalog / resolutionkeycodes.ts, names.ts
Compiler Strategy + registrycompiler.ts
Per-firmware compilerscompilers/zmk/, compilers/qmk.ts, compilers/viaJson.ts, compilers/vialJson.ts, compilers/qmkKeyboardJson.ts
Capability gatingcapabilities.ts
Project bundle (repo skeleton + CI)bundle.ts
.conf / config.h / rules.mk derivationfirmwareConf.ts
Readiness checkscompleteness.ts
Matrix derivationmatrix.ts
Controller / pins resolutioncontroller.ts, pinmaps.ts
Diagnosticsdiagnostics.ts
JSON Schema generationjsonSchema.ts
Builder firmware target descriptorsfirmwareTargets.ts
Defaultsdefaults.ts
Vial UID/unlock helpersvial.ts
Editor palette metadataeditorMeta.ts
Tests__tests__/

Public surface

index.ts is a pure barrel — re-exports types, parsers (parseSurface, parseKeymap), serializers, normalization, validation (KeymapSchema, buildConfigJsonSchema), capabilities, matrix helpers, compilers (getCompiler/hasCompiler/registerCompiler) and buildProjectBundle. The two side-effect imports at the bottom register the concrete compilers:

ts
import './compilers/zmk'
import './compilers/qmk'

Next

Apache-2.0. Originally forked from ZMK Studio; application layer fully rewritten.