Skip to content

ADR-0044: KEC-complete keyboard layout — SHIFT reorganization, unprinted L2 symbol layer, EQ authoring binding

Notion task: GWP-554 — Issue ADR-0044: KEC-complete keyboard layout


ADR-0031 §3.1 finalized the Ferris Sweep 34-key layout with two bound layers — L0 BASE and L1 SHIFT (MO(L1) on the left outer thumb) — and left L2 reserved and unbound (“Reserved for v2 cart-defined verbs or an optional ALPHA layer”). ADR-0022 had earlier reassigned the numpad’s fourth column away from calculator arithmetic (÷ × − +) toward Lisp punctuation, and locked the shift secondaries. That work predated the KEC Lisp language standard (ADR-0037): the shift layer was populated with the conventional ASCII symbol set, not with the specific punctuation KEC Lisp actually requires.

Josh derived the unique punctuation set KEC Lisp requires19 characters in two roles:

  • Reader / structural (special-cased by the Fe kernel reader): ( ) " . ; ' ` , @ (only as ,@) \ (only inside strings).
  • Identifier / operator (baked into kernel symbol names, no word alias): < (<, <= — kernel primitives), > (>, >=, and -> in number->string etc.), = (=, ==, /=, <=, >=), + - * / (arithmetic; - is also kebab-case in nearly every multi-word name), ? (the entire predicate family — nil? pair? number? …), : (keyword symbols / type-of results like :number).

Characters not required and therefore not load-bearing on the deck’s surface: ! # & % ^ $ ~ | — none are reader-special and none appear in any kernel or KEC Core name. (! in particular: KEC has no bang-mutation conventionset / setcar are words.)

  1. The keyboard cannot type idiomatic KEC Lisp as it stands. Intersecting the 19 required characters with the as-shipped layout (BASE: , / . ; -; SHIFT: ` ! @ " ) $ % ^ : ( * & # + \) leaves five required characters unreachable: ' < > = ?. Two of them are hard blockers with no in-language workaround:
    • << and <= are Fe kernel primitives, punctuation-only. You cannot write any ordering comparison without it.
    • ? — the entire stdlib predicate family (nil?, pair?, number?, bound?, even?, …) carries ? in the name. You cannot call a standard predicate without it. = (equality / <= / >=), > (ordering / -> converters), and ' (quote shorthand) round out the gap.
  2. The shift budget is being spent on the wrong characters. The L1 SHIFT layer currently dedicates six slots to KEC-irrelevant ASCII! $ % ^ & #. Reclaiming those for the five missing characters is a net −1 (one slot to spare). The fix lives inside the existing layer count; the problem was allocation, not capacity.
  3. GWP-407 (QMK keymap retarget) is about to encode the layout. The keymap implementation task targets ADR-0031 §3.1 + ADR-0032 §3 as written. Implementing the §3.1 SHIFT manifest verbatim would bake the KEC gap into firmware. This ADR must land before the keymap is authored.
  4. The keycap legends predate KEC Lisp. The 14 function-block legends (CAR / CDR / CONS / NIL / QUOTE / EVAL / APPLY / FN / ATOM / EQ + the runtime verbs INFO / LINK / BACK / SYS) were chosen as Lisp primitives + gameplay verbs before the KEC vocabulary was named. They need an audit against KEC’s actual kernel symbols.
  • The T9 dialpad is preserved. The Nokia digit-to-letter binding (2=ABC … 9=WXYZ, ADR-0016 §6) and the physical 3×3 digit cluster + 0-on-thumb are not touched. This ADR moves only shift secondaries and an unprinted layer, never a base digit.
  • The four thumb keys are locked. ADR-0032 §4: none of LT0 EVAL, LT1 LSHIFT, RT0 0, RT1 TERM may have its tap binding sacrificed. The L2 activation gesture must respect this.
  • The function block is preserved. All 14 left-half functions keep their position, Lisp semantics, and gameplay verbs (load-bearing across the 17 module specs in lisp-paradigm.md). No keycap-art changes.
  • Not every layer is printed. Per Josh’s direction, L2 is muscle-memory only — the deck leans on the L0/L1 printed surface plus Nokia T9 multi-tap and aggressive token prediction (TAGS, ADR-0009 / ADR-0016 §7) to make authoring natural, rather than printing a third legend tier on the caps.
  • Spec Hygiene Rules 1–3 (CLAUDE.md): one canonical statement (the Keys row), no forked specs, full doc cascade in this PR.

A three-layer keyboard model in which every KEC-required character is reachable in ≤1 hold and printed on a keycap, the rare ASCII long tail lives on an unprinted L2, and the function-block legends are confirmed (with one authoring-binding clarification).

LayerPrinted?ActivationCarries
L0 BASEyes (primary legend)defaultT9 dialpad, 5 punctuation primaries, function block, thumbs — unchanged from ADR-0031 §3.1
L1 SHIFTyes (secondary legend)hold LSHIFT (LT1, MO(L1)) — unchangedall 19 KEC-required characters reachable in ≤1 hold (reorganized — §2)
L2 SYMno (muscle memory)hold LSHIFT + TERM together (tri-layer — §3)the rare ASCII long tail: `! # & $ % ^ ~

BASE right half is unchanged (reproduced for reference; digits + 5 punctuation primaries):

InnerIdx Index Middle Ring Pinky
Top 1 2 3 / ,
Home 4 5 6 ; .
Bottom 7 8 9 - ENT
Thumb 0 (inner) TERM (outer)

NEW L1 SHIFT right half — drop the six KEC-irrelevant glyphs (! $ % ^ & #), promote < > = ? ':

InnerIdx Index Middle Ring Pinky
Top ` ? @ " )
Home < = > : (
Bottom * ' (—) + \
Thumb . (inner) TERM (outer) (—) = 1 spare slot (9-key shift)

Per-key change ledger (the inner three columns are the digit keys; their shift secondary changes — the base digit never moves):

Position (base key)Old shiftNew shiftNote
InnerIdx-Top (1)``kept — backtick/quasiquote, consistent with ADR-0022 §5
Index-Top (2)!?predicate suffix — sits directly above =
Middle-Top (3)@@kept — only as ,@ splice
InnerIdx-Home (4)$<
Index-Home (5)%=
Middle-Home (6)^>
InnerIdx-Bottom (7)**kept
Index-Bottom (8)&'quote shorthand
Middle-Bottom (9)#spare (reserved)
Ring/Pinky columns" ) : ( + \" ) : ( + \unchanged — the ADR-0022 §3 semantic pairs (/", ;:, .(, ,), -+, ENT→\) all survive

The 4-5-6 → < = > home-row triplet is the centerpiece: the comparison operators read left-to-right on the strongest fingers, and the compounds roll naturally — <= (4→5), >= (6→5), == (5→5), /= (/base→5), -> (-base→6), ,@ (,base→3).

Result: all 19 KEC-required characters are now on a printed cap — BASE , / . ; - + SHIFT ` ? @ " ) < = > : ( * ' + \. The two hard blockers < and ? are first-class. The L1 SHIFT left half remains passthrough (function keys retain their base KN-86 keycodes while LSHIFT is held), unchanged from ADR-0031 §3.1.

3. L2 activation — LSHIFT + TERM tri-layer

Section titled “3. L2 activation — LSHIFT + TERM tri-layer”

L2 is reached by holding LSHIFT (LT1) and TERM (RT1) simultaneously — the standard QMK “both-thumb adjust layer” idiom (update_tri_layer / layer-state function of which keys are physically down). It threads the ADR-0032 §4 constraint cleanly:

  • No thumb tap is sacrificed. Tapping TERM alone is still TERM dispatch; tapping LSHIFT-position alone is still the momentary L1 modifier. L2 lights only while both thumbs are held. There is no tap-hold timing tax on any hot key — layer state is a pure function of which keys are currently down.
  • No mouse-click reassignment (the specific §4 prohibition) — L2 is a layer, not a pointer binding.
  • Ergonomics: left thumb + right thumb held → all eight fingers free to type the symbol. This is the same gesture family operators know from Miryoku/Corne-class boards.

Bring-up validation item: confirm no collision between a held TERM-in-L1 and any TERM long-press context binding (ADR-0016 §9). There should be none — L2 requires LSHIFT to be co-held, and a TERM long-press alone (no LSHIFT) is untouched.

4. L2 contents — the unprinted long tail

Section titled “4. L2 contents — the unprinted long tail”

L2 carries the ASCII not on BASE/SHIFT that cart authors and display/string text occasionally want, but which KEC Lisp never requires:

L2 right half (unprinted; LSHIFT + TERM held):
InnerIdx Index Middle Ring Pinky
Top ! # & [ ]
Home $ % ^ { }
Bottom ~ | _ (—) (—)

The six demoted ASCII (! # & $ % ^) plus brackets/braces (paired open/close on the Ring/Pinky columns, mirroring the parens) plus ~ | _. Because L2 is unprinted, this arrangement is a firmware-bring-up tuning detail, not a keycap-art commitment — it may be refined during GWP-407 without an ADR amendment, provided the set is covered. The L2 left half is passthrough/reserved in v0.1. The ADR-0016 §6 1-key T9 punctuation cycle (. , ' " : ; !) is unchanged and remains the in-literal-entry path; L2 is the direct path usable in any scope.

5. Keycap legend audit + EQ authoring binding (closes Josh’s Q2)

Section titled “5. Keycap legend audit + EQ authoring binding (closes Josh’s Q2)”

The 14 function-block legends were audited against the actual Fe kernel + KEC Core vocabulary (ADR-0037). Thirteen of fourteen are correct and unchanged:

  • CAR · CDR · CONS · QUOTE · ATOM · NIL · FN map to real Fe kernel symbols (car cdr cons quote atom nil fn) and carry established gameplay verbs across the 17 module specs. Note FN is now more KEC-accurate than the retired “LAMBDA” label — Fe’s lambda primitive is literally fn. ADR-0022 §1’s brevity choice turns out to also be the correct keyword.
  • EVAL · APPLY are canonical gameplay verbs (execute / replay) and authoring concepts; their KEC Core surface symbols are bound in the keymap per ADR-0037.
  • INFO · LINK · BACK · SYS are correctly non-Lisp runtime / navigation verbs — they are not KEC keywords and should not be.

The one correction — EQ. KEC has no eq: identity/equality is the word is, and numeric equality is = / == / /= (now directly typeable on the SHIFT home row, §2). The EQ cap therefore names a concept whose KEC symbol differs from the legend. Decision: keep the EQ keycap — it is a clear “identity check” gameplay verb, canonical across every module spec, and relabeling it would ripple through 17 specs for zero gameplay gain — but bind its authoring-insert to KEC’s is (exact binding confirmed against the KEC Core vocabulary during GWP-407). This is a keymap-binding + documentation clarification with no keycap-art change.

The two bottom-left v2 spares (L20 / L21) remain reserved — authoring primitives are not promoted onto them; the deck covers the rest of the KEC vocabulary through T9 + token prediction per the §Constraints direction.


Option A: Reorganize the printed SHIFT only (2 layers) (rejected — too little)

Section titled “Option A: Reorganize the printed SHIFT only (2 layers) (rejected — too little)”

Drop ! $ % ^ & #, promote < > = ? ' into the freed SHIFT slots; no L2. Closes the gap with every KEC char printed, no new gesture.

Rejected because: it strands the six demoted ASCII glyphs on the T9 punctuation cycle only (reachable solely in literal-entry scope). Cart authors writing display strings / comments in other scopes lose direct access. The marginal cost of adding L2 (one tri-layer gesture, zero printed legends) is low enough that keeping direct access is worth it.

Option B: Reorganize SHIFT + add an unprinted L2 long-tail (3 layers) (ACCEPTED)

Section titled “Option B: Reorganize SHIFT + add an unprinted L2 long-tail (3 layers) (ACCEPTED)”

Option A’s SHIFT reorg for all 19 KEC chars, plus an unprinted L2 (LSHIFT+TERM) for the rare ASCII. Frequency-correct: the frequent comparison/predicate operators sit on the printed shift; the rare ASCII lives in muscle memory. Matches Josh’s “don’t print every layer; lean on T9 + TAGS” direction.

Option C: Comparison cluster on the SHIFT-left half (2 layers) (rejected)

Section titled “Option C: Comparison cluster on the SHIFT-left half (2 layers) (rejected)”

Put < > = ? on the currently-passthrough SHIFT left half (semantic affinity with the predicate-heavy Lisp primitives); ' fills the spare right slot; keep most current ASCII on SHIFT-right.

Rejected because: it puts the highest-frequency operators (comparisons) into a same-hand-as-LSHIFT chord (LSHIFT is the left thumb). Frequency argues for the opposite hand. It also keeps the KEC-irrelevant ASCII on prime printed real estate. Option B’s 4-5-6 → < = > on the right hand is both more comfortable and more mnemonic.

Option D: Add L2 but reach the missing chars only on L2 (keep SHIFT as-is) (rejected)

Section titled “Option D: Add L2 but reach the missing chars only on L2 (keep SHIFT as-is) (rejected)”

Leave the printed SHIFT untouched; put < > = ? ' on the unprinted L2.

Rejected because: it buries the most common operators (<, =, ? are everywhere in real Lisp) on the unprinted, two-thumb layer while leaving KEC-irrelevant symbols (! $ % ^ & #) on the easy printed shift. Exactly inverted frequency. The unprinted layer is for the rare tail, not the staples.


  • Discoverability vs. surface area. Every KEC-required glyph is printed (L0/L1). The long tail is unprinted (L2) — acceptable because those glyphs are rare in KEC authoring and the gesture is a familiar idiom. Net: the printed surface now matches the language; the hidden surface holds only what’s hidden-worthy.
  • One new gesture. LSHIFT+TERM tri-layer is the only new motor pattern. It costs nothing on the hot path (pure layer-state function, no timing) and sacrifices no thumb tap.
  • Keycap art. L1 secondary legends change for six keys (2 4 5 6 8 shifts + removing # from 9). v0.1 ships blank caps (ADR-0018), so this only affects the eventual UV-print manifest — zero impact on the v0.1 build. The function block (L0) art is unchanged.
  • Software dispatch. The QMK keymap (GWP-407) gains an L2 layer + tri-layer + the EQ→is authoring binding. The Linux HID → nOSh keycode path is unchanged (same KN86_KEY_* codes; only secondary glyph dispatch changes). No FFI or runtime change.
  • Cost accepted. Six ASCII glyphs move from a one-hold printed shift to a two-hold unprinted layer. They are not KEC-load-bearing and remain reachable (L2 in any scope; the T9 cycle in literal entry). Acceptable.

  • The deck can finally type idiomatic KEC Lisp. The two hard blockers (<, ?) and the three soft gaps (=, >, ') are all printed and ≤1 hold.
  • Frequency-correct layout. Comparison/predicate operators on the strong home-row of the printed shift; rare ASCII in muscle memory.
  • The < = > triplet is a teaching mnemonic — “shift the 4-5-6 row.”
  • L2 puts the reserved layer to work without printing a third legend tier, matching the T9 + TAGS authoring philosophy.
  • The legend audit confirms the function block and upgrades the justification for FN (now keyword-accurate, not just shorter).
  • One spare SHIFT slot (9-key shift) for a future KEC need.
  • Six ASCII glyphs demote from printed shift to unprinted L2 (still reachable). Accepted — not KEC-required.
  • One new gesture (LSHIFT+TERM) operators must learn for the long tail. Mitigated by it being a community-standard idiom and only needed for rare symbols.
  • EQ legend ≠ EQ authoring symbol (is). Mitigated: the cap is a gameplay verb; the authoring binding is documented; = is independently typeable.
  • Doc + keymap cascade. ADR-0031/0022/0016 footnotes, CLAUDE.md Keys row, input-dispatch.md, the firmware keymap README, and GWP-407 all update. Tracked below.
  • F1 — GWP-407 keymap update. The QMK retarget implements the §2 SHIFT reorg, the §3 tri-layer L2, the §4 L2 set, and the §5 EQ→is authoring binding — not the ADR-0031 §3.1 SHIFT manifest as originally written. Owner: Platform Engineering. (GWP-407 acceptance criteria updated to cite this ADR.)
  • F2 — Per-key authoring-insert table. The exact KEC symbol each function key contributes in :repl-prompt / :nemacs-* scopes (especially EQ→is, ATOM, EVAL, APPLY) is defined against the KEC Core vocabulary during the input-model implementation (ADR-0016 Implementation Queue). Owner: C Engineer.
  • F3 — Keycap UV-print manifest. The eventual print job uses the §2 L1 manifest. Owner: Hardware, post-v0.1 polish cadence.

Documentation Updates (REQUIRED — part of the decision, not aspirational)

Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”

Per CLAUDE.md Spec Hygiene Rule 3, this ADR lands in a single PR including:

  • docs/adr/ADR-0044-kec-complete-keyboard-layout.md — this file.
  • docs/adr/README.md — ADR-0044 row at the top of the index; status notes on 0031/0022/0016 updated to cite amendment by ADR-0044.
  • docs/adr/ADR-0031-ferris-sweep-keyboard-adoption.md — amendment footnote at §3.1 (L1 SHIFT reorganized; L2 bound; tri-layer activation) pointing here.
  • docs/adr/ADR-0022-keyboard-layout-finalization.md — amendment footnote at §2/§3 (shift-secondary manifest is KEC-completed by ADR-0044 §2).
  • docs/adr/ADR-0016-nemacs-repl-input-model.md — footnote at §6 (demoted ASCII gain a direct L2 path; cycle unchanged).
  • CLAUDE.md Keys row — replace the SHIFT-secondary clause with the KEC-complete manifest + the unprinted L2 (LSHIFT+TERM) note; cite ADR-0044. (Canonical Hardware Specification lives in the umbrella ~/src/kinoshita/CLAUDE.md — see PR description; companion edit there.)
  • docs/software/runtime/input-dispatch.md — existing stale-layout banner threaded with ADR-0044 (current SHIFT manifest, L2, EQ→is). The §1/§3D/§8D shift-secondary tables rewrite is part of the broader keyboard-sweep rewrite tracked under GWP-407 / F1 (the doc was already flagged stale for ADR-0031).
  • firmware/kn86-keyboard/README.mddoes not exist yet (the firmware tree is a design-only skeleton per hosts/device/kn86-firmware/README.md, gated behind GWP-152). The keymap README + keymap.c land under GWP-407 and must reflect ADR-0044 §2–§4 then. Tracked as F1.
  • Project-wide grep sweep for stale claims:
    • “L2 … reserved” / “L2 leaves … unbound” — annotate as bound by ADR-0044 (permitted to remain: ADR-0031 design history with footnote).
    • Shift-secondary lists containing ! $ % ^ & # as KN-86 layout — annotate/repoint to ADR-0044 §2 (permitted: ADR-0031/0022 design history with footnotes).
    • “EQ” described as a KEC/Lisp symbol the key inserts — clarify to is per §5.

A PR that lands this ADR without ticking the in-scope ([x]) items fails review per CLAUDE.md Spec Hygiene Rule 3. The [ ] items are F1/F2 follow-ons (keymap + input-dispatch implementation) tracked under GWP-407.


For most of the project the deck’s shift layer was a conventional ASCII symbol row — ! @ # $ % ^ & and friends — chosen before the authoring language had a name. When ADR-0037 named KEC Lisp and froze the Fe kernel, the question finally had a precise answer: which punctuation does this deck actually need to type? Josh worked it out — nineteen characters — and discovered that five of them (', <, >, =, ?) weren’t reachable at all, and two of those (<, ?) had no in-language escape: you simply could not write an ordering comparison or call a standard predicate. Meanwhile the shift layer was spending six slots on symbols KEC never uses. The fix was not “find more keys” — it was “stop printing the wrong ones.” Drop ! $ % ^ & #, and the comparison operators fall onto the 4-5-6 home row reading left-to-right, < = >, which is the kind of mnemonic you only get when the layout and the language finally agree. The rare ASCII the cart authors still occasionally want went to an unprinted third layer reached by holding both thumbs — the deck leaning, as it was always meant to, on Nokia multi-tap and token prediction rather than on printing every glyph it can produce. And the keycap audit had a small gift in it: FN, chosen years earlier purely because it was shorter than LAMBDA, turned out to be the actual Fe keyword. The one real correction was EQ — KEC says is, not eq — but that one stays on the cap, because by now the keys are gameplay verbs as much as they are Lisp, and EQ means “are these the same?” to every operator who has ever drilled a wreck or compared two bands. A future reader should care because this is the moment the keyboard stopped being a generic terminal and became a KEC Lisp instrument.