ADR-0031: Ferris Sweep keyboard adoption (34-key split, supersedes the custom 31-key path)
Context
Section titled “Context”ADR-0018 committed the KN-86 keyboard to a custom mech-keeb build: hot-swap Choc v1 sockets, MBK keycaps, 1N4148 diodes, QMK-compatible controller, internal USB hub IC. It named the preferred PCB path as a custom-fabricated unified 31-key board (Option B) and the fallback as a modified split layout (Option C — Corne, Ferris Sweep, or similar reconnected under a single keyplate).
ADR-0024 narrowed the controller to a single Adafruit KB2040 (RP2040). ADR-0022 finalized the 31-key keycap legend manifest (function block + phone-style numpad + TERM) with right-column Lisp punctuation, bottom-row * 0 # ENT, and shift secondaries (` " : + ( . )) on specific primaries. ADR-0023 pinned the LIS3DH accelerometer to the keyboard MCU’s I²C bus.
Six batches of inspiration / prototype / research / effect references captured 2026-06-06 (docs/plans/2026-06-06-inspiration-synthesis.md §1) culminated in Batch 5’s prototype/keyboard-decision.md, which records the shape decision: the KN-86 will use a Ferris Sweep rather than a fully custom keyboard. Batch 6 (b0ad6f9, PR #44) reinforced the choice with two concrete hardware artifacts — a Ferris Sweep v2 case STL and the sculpted Choc keycap option.
This ADR ratifies the Batch 5 decision into the canonical corpus and works out the consequences across ADR-0018, ADR-0022, ADR-0023, ADR-0024, and ADR-0016. The synthesis doc carries the design narrative + worked-out 34-key layout; this ADR carries the immutable spec delta.
Forcing functions
Section titled “Forcing functions”- Batch 5 decided; the canonical spec is stale.
CLAUDE.mdCanonical Hardware Specification still reads “31 physical keys (14 function + 16 numpad + 1 TERM, phone-layout)” and “PCB path is custom-fab unified 31-key (preferred) or modified split layout (Corne / Ferris Sweep) reconnected under a single keyplate (fallback).” Both are false post-Batch-5 — the canonical surface is a contradiction with the recorded decision until this ADR lands. - Pre-order window. The synthesis doc §6 task #2 is “order Ferris Sweep Soldered kit from holykeebs.” The order BOM depends on this ADR’s §3 pin assignment + §F1 PCB-path confirmation. Ordering ahead of the ADR risks an incompatible BOM.
- QMK keymap retarget is blocking firmware bring-up. The existing
firmware/kn86-keyboard/keymap.c(KBD-04 deliverable fromADR-0018) targets a custom 31-key handwired keyboard. The Sweep retarget needs a fresh keymap.c on the QMK communitykeyboards/ferris/sweeptree; the bring-up sequence depends on this ADR’s §3.4 layer manifest. - Two ADRs reference values this ADR changes.
ADR-0023§2 references “the same MCU that scans the 31-key matrix” — true underADR-0018, false under split topology.ADR-0024§3 pin map is for a single MCU. Both need explicit amendment.
Constraints
Section titled “Constraints”- The Sweep is a shipping product. PCB topology, key positions, thumb-cluster geometry, and TRRS inter-half topology are fixed. Our job is to map the KN-86’s logical functions onto the Sweep’s physical layout, not to redesign the Sweep.
- Spec Hygiene Rule 1 (
CLAUDE.md). This ADR does not restate canonical values; it amends them. The Keys row update is the one canonical statement. - Spec Hygiene Rule 3 (
CLAUDE.md). This ADR carries the doc-sweep checklist as a hard requirement, not aspirational. - The LSHIFT-as-thumb resolution closes a deferred decision.
ADR-0022§Known Unknowns #1 left the shift gesture (long-press / hold-and-tap / chord) open. This ADR closes it: LSHIFT is a physical thumb key (LT1), QMKMO(SHIFT)momentary layer activation. No timing tax. - Nokia multi-tap letter-to-digit mapping is preserved.
ADR-0016§6 andADR-0022§2 lock 2=ABC … 9=WXYZ. The Sweep adoption does not touch this binding; only the physical positions of the digits move. - CIPHER-LINE invariant unchanged. The
ADR-0015256×64 SSD1322 is the authoritative auxiliary display. The Sweep PCB has an optional 128×32 SSD1306 mount slot; v0.1 skips it (would create a confused two-aux-display architecture). - Pelican 1170 chassis unchanged. The Sweep mounts inside the Pelican on a 3D-printed bezel insert per
prototype/qrp-pi.mdlineage. The Pelican shell is not modified.
Decision
Section titled “Decision”The KN-86 keyboard subsystem is a stock Ferris Sweep (David Barr design) sourced from holykeebs as a Soldered kit with 34 keys arranged as two 17-key halves connected by TRRS. The 14 Lisp primitives + TERM + numpad/Nokia multi-tap canonical functions from ADR-0022 are realized via the worked-out 34-key layout in docs/plans/2026-06-06-inspiration-synthesis.md §4, ratified below.
Concrete commitments:
1. Source — holykeebs Ferris Sweep Soldered kit
Section titled “1. Source — holykeebs Ferris Sweep Soldered kit”- PCB: holykeebs Ferris Sweep, current revision (hotswap sockets + 1N4148 SMD diodes pre-populated).
- Build tier: Soldered kit — SMD components pre-populated; switch socket population + plate / case assembly remain ours.
- Order configuration: per
prototype/holykeebs-buyers-guide.md§“KN-86 BOM checklist.” Decided line items:- Controllers: 2 × Adafruit KB2040 (RP2040, USB-C, Pro Micro form factor) on custom low-profile 5 mm headers
- Switches: Kailh Choc v1 family (specific switch TBD via hotswap iteration; bring-up suggestion: Choc v1 brown)
- Key spacing: Choc 18 × 17 mm
- Keycaps: MBK low-profile (canonical) — sculpted Choc per
prototype/choc-sculpted-keycaps.mddocumented as an operator-swap alternative, not v0.1 canonical - OLED: skip for v0.1 — CIPHER-LINE 256×64 (
ADR-0015) remains the authoritative auxiliary display - Pointing device: deferred — no commitment in v0.1
- Top / middle / bottom plates: slim low-profile direction; specific stack confirmed at order time
- Case: 3D-printed (color-matched to Pelican-1170 cyberdeck aesthetic —
prototype/ferris-sweep-v2-case.mdis the current candidate STL)
2. Topology — two halves, one master / one slave, TRRS bridge
Section titled “2. Topology — two halves, one master / one slave, TRRS bridge”- Halves: LEFT (master) and RIGHT (slave). Each carries one KB2040 + 17 hotswap switch positions (15 main + 2 thumb).
- Inter-half link: TRRS cable per Sweep convention. QMK split-keyboard transport carries matrix events from slave to master.
- USB to Pi: Master half’s KB2040 USB-C → internal USB hub IC (
ADR-0018§5) → Pi Zero 2 W OTG. Single USB HID interface from Linux’s perspective (ADR-0011early-boot key-scan gate unaffected). - Master = LEFT by convention. Two reasons: (a) the LIS3DH (§5) lives on master; LEFT is the conventional master-half choice in QMK split keyboards (Corne, Sweep, Lily58, etc.) and matches the Adafruit ecosystem’s default. (b) The LEFT half also carries the EVAL thumb (LT0), which is the keymap’s commit verb — concentrating “the keys nOSh dispatch cares about most” on the master half avoids inter-half-RTT latency on EVAL.
3. Pin assignment — per half
Section titled “3. Pin assignment — per half”Update — pin map amended by ADR-0032 §3 (2026-06-07): With 2× trackpoint added (one per half), the I²C0 pins (D4/D5 = GP4/GP5) on both halves are now consumed by the trackpoint PS/2 adapter. The LIS3DH on the master half moves from I²C0 (D4/D5) to I²C1 (D6/D7 = GP6/GP7); the originally-reserved INT1 line on D6 is released (v1 LIS3DH is polling-only). The §3.1 layer manifest below is unchanged; only the master-half I²C peripheral assignment for the LIS3DH moves. See ADR-0032 §3 for the full updated pin table.
Each KB2040 scans its half’s 3×5 + 2 thumb matrix (17 positions). Matrix wiring is fixed by the Sweep PCB — we do not author the matrix layout; we author the QMK keymap on top of the PCB’s matrix.
| Half | Function | KB2040 silkscreen | RP2040 pin | Notes |
|---|---|---|---|---|
| Both | Matrix rows (4 rows incl. thumb row) | per Sweep PCB | per Sweep PCB | Fixed by PCB — not a KN-86 decision. |
| Both | Matrix cols (5 cols on main + thumb cluster) | per Sweep PCB | per Sweep PCB | Fixed by PCB. |
| Both | TRRS data | per Sweep PCB (typically GPIO1) | per Sweep PCB | QMK split-transport — serial driver on RP2040. |
| LEFT (master) only | LIS3DH SDA | D4 (silk: SDA) | GP4 | KB2040 silkscreen-default I²C0 SDA. Preserves the ADR-0024 as-built I²C convention. |
| LEFT (master) only | LIS3DH SCL | D5 (silk: SCL) | GP5 | KB2040 silkscreen-default I²C0 SCL. |
| LEFT (master) only | LIS3DH INT1 | D6 | GP6 | Reserved for future interrupt-driven motion detection — unchanged from ADR-0024 §3. |
| LEFT (master) only | USB to Pi | USB-C | — | Master half is the USB host endpoint. |
| RIGHT (slave) | USB-C | USB-C | — | Used only for flashing the slave KB2040; not connected to Pi during operation. Operator uses the master’s USB-C for normal use. |
| Both | SWD | SWCLK / SWDIO | — | Test pads on PCB underside (KB2040). |
Why the LIS3DH is master-half only: the sensor reports motion events to nOSh via a vendor-defined HID report on the master’s USB HID interface (ADR-0023 §6). Putting it on the slave would require either (a) routing motion events from slave to master over TRRS, consuming inter-half link bandwidth and adding 8 ms typical latency for an event that gates on ≤300 ms double-tap windows, or (b) breaking the single-USB-HID-interface invariant by enumerating the slave on the hub. Neither is worth it. The sensor mounts on the master PCB (LEFT half) — vibration coupling per ADR-0023 §3 still works because the Pelican 1170 shell rigidly couples both halves’ inset-panel mounts to the case.
3.1 Layer manifest — QMK keymap (rendered for both halves)
Section titled “3.1 Layer manifest — QMK keymap (rendered for both halves)”Update — L1 SHIFT manifest reorganized + L2 bound by ADR-0044 (2026-06-21): The as-written L1 SHIFT right-half manifest below (
! @ $ % ^ & # …) predates the KEC Lisp language standard (ADR-0037) and leaves five KEC-required characters ('<>=?) unreachable —<and?are hard blockers. ADR-0044 §2 reorganizes L1 SHIFT (drops! $ % ^ & #; promotes< > = ? '; keys4-5-6shift to the< = >home-row triplet) so every KEC-required glyph is printed and ≤1 hold, and binds L2 (below, “reserved”) as an unprinted long-tail symbol layer reached by a LSHIFT+TERM tri-layer hold. The BASE layer, the T9 dialpad, the function block, and the four locked thumbs are unchanged. See ADR-0044 §1–§4 for the as-adopted manifest; the L1 SHIFT grid and the “L2 — reserved” note below are superseded by it.
The Sweep is one logical keyboard from QMK’s perspective despite being two physical halves. The keymap is a single C source file (firmware/kn86-keyboard/keymap.c) with LAYOUT_split_3x5_2(...) macro entries covering all 34 keys.
Amendment — 2026-06-21 (left-half home-row ergonomic revision). The left-half home row is reordered so the two highest-frequency list-walk primitives CAR and CDR land on the two strongest, adjacent fingers (index home + middle) as a rest-and-rock pair, and the three navigation verbs each get a dedicated finger. The home row changes from
APPLY · QUOTE · BACK · CAR · CDRtoAPPLY · BACK · CDR · CAR · QUOTE(pinky → index inner reach):
Finger (left half) Position Was Now Pinky (outer reach) L10 APPLY APPLY (unchanged) Ring L11 QUOTE BACK Middle L12 BACK CDR Index (home) L13 CAR CAR (unchanged) Index (inner reach) L14 CDR QUOTE Rationale. CAR (drill-in) is the single most-used navigation key → strongest finger (index home). CDR (next sibling) sits adjacent on the middle, so the dominant scan-and-drill loop (
CDR CDR … CAR) becomes a two-finger rock instead of one index finger rolling its home + inner-reach columns. The nav cluster now reads outer→inner as BACK (out) · CDR (next) · CAR (in) — every navigation verb on its own finger; QUOTE (defer/bookmark, lower-frequency) takes the index inner reach. The weak outer pinky reach keeps the lowest-frequency key (APPLY).Scope of the change. Keycap legends move with their keys; the QMK keycode travels with its KN-86 function, so the
KC_*→KN86_KEY_*resolution table is unchanged — only the physical position order in theLAYOUT_split_3x5_2(...)macro changes (L11/L12/L14 swap which key they carry). No change to the logical key set, key semantics, color coding (ADR-0022§1 — color follows the key, not the position), the NoshAPI/FFI surface, or scancodes. The diagrams and per-key table below already reflect the revised order. The QMK keymap retarget (F3) is still pending real hardware; it inherits this order directly.
L0 — BASE
Section titled “L0 — BASE”LEFT HALF (function block) RIGHT HALF (numpad)
Pinky Ring Middle Index InnerIdx InnerIdx Index Middle Ring PinkyT [ NIL ][INFO ][LAMBDA][CONS ][LINK ] [ 1 ][ 2 ][ 3 ][ / ][ , ]H [APPLY][BACK ][ CDR ][CAR ][QUOTE ] [ 4 ][ 5 ][ 6 ][ ; ][ . ]B [<v2> ][<v2> ][ SYS ][EQ ][ATOM ] [ 7 ][ 8 ][ 9 ][ - ][ ENT ]Thumbs: [EVAL ][LSHFT] [ 0 ][TERM ]<v2> = KC_NO in v0.1 keymap; reserved slots for v2 cart-defined verbs.
Per-key keycode → KN-86 keycode mapping (carried through Linux evdev → SDL → nOSh):
| Sweep position | QMK keycode | KN-86 keycode | Notes |
|---|---|---|---|
| L00 | KC_F1 (or custom) | KN86_KEY_NIL | Function-block keycode mapping via custom keymap. |
| L01 | KC_F2 | KN86_KEY_INFO | |
| L02 | KC_F3 | KN86_KEY_LAMBDA | Printed legend FN per ADR-0022 §1. |
| L03 | KC_F4 | KN86_KEY_CONS | |
| L04 | KC_F5 | KN86_KEY_LINK | |
| L10 | KC_F6 | KN86_KEY_APPLY | Pinky (outer reach). Unchanged. |
| L11 | KC_F8 | KN86_KEY_BACK | Ring. Moved from L12 → L11 (2026-06-21 home-row revision). Also delete in :nemacs-literal / :prompt-text per §3.3 below (replaces ADR-0022 §7 * binding). |
| L12 | KC_F10 | KN86_KEY_CDR | Middle. Moved from L14 → L12 (2026-06-21 home-row revision). CDR/CAR are now the index+middle rest-and-rock pair. |
| L13 | KC_F9 | KN86_KEY_CAR | Index home. Unchanged — highest-frequency nav key on the strongest finger. |
| L14 | KC_F7 | KN86_KEY_QUOTE | Index inner reach. Moved from L11 → L14 (2026-06-21 home-row revision). |
| L20 | KC_NO | (reserved) | v2 spare. |
| L21 | KC_NO | (reserved) | v2 spare. |
| L22 | KC_F11 | KN86_KEY_SYS | SYS+INFO×4 easter egg (ADR-0021) still works — INFO is L01, both on left half within two-finger reach. |
| L23 | KC_F12 | KN86_KEY_EQ | |
| L24 | custom 1 | KN86_KEY_ATOM | QMK custom keycode beyond F12. |
| LT0 | custom 2 | KN86_KEY_EVAL | |
| LT1 | MO(L1) | (modifier — no KN-86 keycode) | Momentary SHIFT layer activation. Not a KN-86 keycode — handled in firmware. |
| R00 | KC_COMMA | KN86_KEY_COMMA | |
| R01 | KC_SLASH | KN86_KEY_SLASH | Also case-toggle in :nemacs-literal / :prompt-text (unchanged from ADR-0022 §7). |
| R02 | KC_3 | KN86_KEY_PAD_3 | Phone 3rd-position digit. Nokia multi-tap: D E F. |
| R03 | KC_2 | KN86_KEY_PAD_2 | Nokia: A B C. |
| R04 | KC_1 | KN86_KEY_PAD_1 | Nokia: punctuation cycle . , ' " : ; !. |
| R10 | KC_DOT | KN86_KEY_DOT | |
| R11 | KC_SCLN | KN86_KEY_SEMICOLON | |
| R12 | KC_6 | KN86_KEY_PAD_6 | Nokia: M N O. |
| R13 | KC_5 | KN86_KEY_PAD_5 | Nokia: J K L. |
| R14 | KC_4 | KN86_KEY_PAD_4 | Nokia: G H I. |
| R20 | KC_ENT | KN86_KEY_PAD_ENTER | |
| R21 | KC_MINS | KN86_KEY_MINUS | |
| R22 | KC_9 | KN86_KEY_PAD_9 | Nokia: W X Y Z (per ADR-0022 §6 / ADR-0016 §6). |
| R23 | KC_8 | KN86_KEY_PAD_8 | Nokia: T U V. |
| R24 | KC_7 | KN86_KEY_PAD_7 | Nokia: P Q R S. |
| RT0 | KC_0 | KN86_KEY_PAD_0 | Doubles as space in Nokia multi-tap per ADR-0016 §6. |
| RT1 | custom 3 | KN86_KEY_TERM | Context-sensitive dispatch (CLAUDE.md Keys row, ADR-0021 easter-egg participant). |
L1 — SHIFT (LT1 held)
Section titled “L1 — SHIFT (LT1 held)”LEFT HALF (passthrough — keys retain base-layer KN-86 keycodes)
Pinky Ring Middle Index InnerIdxT [ NIL ][INFO ][LAMBDA][CONS ][LINK ]H [APPLY][BACK ][ CDR ][CAR ][QUOTE ]B [<v2> ][<v2> ][ SYS ][EQ ][ATOM ]Thumbs: [EVAL ][LSHFT-held]
RIGHT HALF (shift secondaries)
InnerIdx Index Middle Ring PinkyT [ ` ][ ! ][ @ ][ " ][ ) ]H [ $ ][ % ][ ^ ][ : ][ ( ]B [ * ][ & ][ # ][ + ][ \ ]Thumbs: [ . ][TERM ]Shift mapping captures every secondary glyph from ADR-0022 §2 plus the * # glyphs that ADR-0022 carried as bottom-row primaries (recovered onto shift+7 and shift+9 respectively). The ! @ $ % ^ & \ set rounds out the ASCII printables a cart author + REPL surface need.
Per ADR-0016 §9, in :nemacs-literal / :prompt-text scopes, KEY_DOUBLE_TAP is suppressed on numpad keys (Nokia multi-tap takes over). Shift-layer activation while in those scopes is also suppressed — shift-held + digit-tap falls through to the multi-tap cycle, not the shift secondary. The operator drops LSHIFT to access symbols; the scope label on Row 24 + CIPHER-LINE Row 1 advertises the active behavior.
L2 — reserved unbound → bound as the unprinted SYM layer by ADR-0044
Section titled “L2 — reserved unbound → bound as the unprinted SYM layer by ADR-0044”Reserved for v2 cart-defined verbs or an optional ALPHA layer. v0.1 leaves L2 unbound. Update (2026-06-21): ADR-0044 §3–§4 binds L2 in v0.1 as the unprinted long-tail symbol layer (! # & $ % ^ ~ | _ [ ] { }), activated by holding LSHIFT + TERM together (tri-layer; no thumb tap sacrificed per ADR-0032 §4). The L2 left half remains passthrough/reserved for v2 cart-defined verbs.
3.2 Shift gesture — closed
Section titled “3.2 Shift gesture — closed”ADR-0022 §Known Unknowns #1 left the shift gesture (long-press, hold-and-tap, or chord) deferred. This ADR closes it: shift is a physical thumb key (LT1, left outer thumb), held as a momentary modifier (MO(L1)). No timing tax. The operator’s left thumb rests in the outer-thumb position; pressing down activates the shift layer for the duration of the hold. Standard QMK convention.
3.3 case-toggle / delete — rebound
Section titled “3.3 case-toggle / delete — rebound”ADR-0022 §7 bound delete to * (R4C1 base primary). Under Sweep, * is a shift secondary (L1 SHIFT layer, shift+7), not a base primary. Rebinding:
case-toggle=/(R01 base primary). Unchanged./survives as a base-layer primary on the Sweep right half.delete= BACK (L11 base primary — ring; moved from L12 by the 2026-06-21 §3.1 home-row revision). Rebound from*to BACK. Semantic match: BACK already means “go back” in default scopes (nav-pop, undo). In:nemacs-literal/:prompt-textit cleanly extends to “back up one character.” Left-hand BACK + right-hand multi-tap is an ergonomically clean two-hand pattern, matching the way mobile keyboards typically place backspace opposite the active letter-tap zone.
The context-polymorphism contract from ADR-0016 §3 + ADR-0022 §7 is preserved: BACK types its normal semantic in default scopes; in literal-entry scopes it deletes. The active scope label on Row 24 + CIPHER-LINE Row 1 advertises which behavior is active — no hidden state.
4. QMK firmware
Section titled “4. QMK firmware”- Target:
keyboards/ferris/sweepin QMK upstream (existing community keyboard tree). Master/slave handoff handled by QMK split-keyboard subsystem viaserialdriver on RP2040. - MCU:
rp2040(both halves). - Bootloader:
rp2040(drag-drop UF2 to BOOTSEL volume — same asADR-0024§4). - USB: native RP2040 hardware Full Speed on master only. Slave half’s USB-C is for flashing only; not connected to Pi during operation.
- Keymap source:
firmware/kn86-keyboard/keymap.crewritten as a 34-keyLAYOUT_split_3x5_2(...)layout per §3.1. The existing 31-keykeymap.cis moved to_archive/firmware/kn86-keyboard-handwired-31key/for design-history reference. - LIS3DH driver: unchanged from
ADR-0023§5; runs on master half only. - HID vendor report: unchanged from
ADR-0023§6; emitted on the master’s USB HID interface.
5. LIS3DH host — master half only
Section titled “5. LIS3DH host — master half only”Update — I²C peripheral amended by ADR-0032 §3 (2026-06-07): I²C0 (D4/D5) is consumed by the master-half trackpoint. LIS3DH moves to I²C1 (D6/D7 = GP6/GP7) on the same KB2040. LIS3DH-on-master invariant preserved; only the I²C peripheral changes. INT1 reservation on D6 released — v1 polling-only.
The LIS3DH is reflowed (or breakout-mounted, depending on Sweep PCB stack — see §F2) on the LEFT (master) half’s PCB, adjacent to the master KB2040. I²C bus shared with the keyboard MCU’s existing I²C0 (D4/D5 silkscreen, GP4/GP5 per RP2040).
Vibration coupling per ADR-0023 §3 still works because the Pelican 1170 shell rigidly couples both halves’ inset-panel mounts to the case. A case-jostle event propagates equally to both halves’ PCBs through the inset-panel structure. One sensor on master is sufficient for the 4-bucket motion classification.
6. CIPHER-LINE auxiliary display — unchanged
Section titled “6. CIPHER-LINE auxiliary display — unchanged”Update — Sweep OLED skip made permanent for v0.x by ADR-0032 §2 (2026-06-07): the “preserves the option to add a pointing device later” rationale at the end of this section is consumed — the pointing device option is now exercised on both halves (2× trackpoint per ADR-0032 §1). The Sweep OLED is permanently skipped for v0.x. Re-introduction in any future revision requires a new ADR per ADR-0032 §2.
ADR-0015 256×64 SSD1322 OLED remains the authoritative auxiliary display. The Sweep PCB has an optional 128×32 SSD1306 mount slot; v0.1 ships without it. Reasons:
- Adding a third display creates a confused two-aux-display architecture for no operator gain (CIPHER-LINE already serves status / modeline / palette / echo per
ADR-0015andADR-0016§4). - The Sweep OLED is much smaller (128×32 vs 256×64) and uses a different driver IC (SSD1306 vs SSD1322) — no glyph-rendering code sharing.
- The OLED mount location on the Sweep is mutually exclusive with the pointing-device mount per
prototype/holykeebs-buyers-guide.md. Skipping the OLED preserves the option to add a pointing device later without re-spinning the build.
7. Pointing device — deferred → decided 2026-06-07: 2× trackpoint
Section titled “7. Pointing device — deferred → decided 2026-06-07: 2× trackpoint”Update — superseded by ADR-0032 §1 (2026-06-07): the v0.1 build commits to 2× holykeebs trackpoint modules (Sprintek SK8707-01 sensor + adapter PCB + red rubber cap, one per half — under each operator index finger). The pre-amendment text below is preserved as design history; the trackpoint commitment is canonical going forward. See
prototype/trackpoint-module.mdfor the operator-facing capability description and ADR-0032 §1 for the BOM line items.
No pointing device in v0.1 per prototype/keyboard-decision.md “Implications” / “What this leaves open.” Hotswap PCB defers the commitment; can be added in v0.2 without re-ordering the PCB. The mount location on either half remains open.
Options Considered
Section titled “Options Considered”Option A: Adopt Ferris Sweep per Batch 5, with the §3.1 layout (ACCEPTED)
Section titled “Option A: Adopt Ferris Sweep per Batch 5, with the §3.1 layout (ACCEPTED)”Described above. Stock Sweep PCB pair from holykeebs, dual KB2040, TRRS link, 34-key layout with LSHIFT-as-thumb.
Option B: Keep ADR-0018 Option B (custom-fab unified 31-key PCB) (REJECTED)
Section titled “Option B: Keep ADR-0018 Option B (custom-fab unified 31-key PCB) (REJECTED)”The pre-Batch-5 preferred path. Custom KiCad 31-key PCB, single KB2040, fab via JLCPCB / OshPark / PCBWay.
Rejected because: the Batch 5 prototype/keyboard-decision.md argument stands. Custom-fab carries fab-iteration risk, plate-tolerance bring-up risk, matrix-debouncing firmware risk (mitigated by stock QMK but not eliminated), and 2-4 months of design time. The Sweep eliminates all of it. The cost paid by the Sweep adoption — three additional keys, a split topology, an extra MCU, a TRRS cable — is materially less than the cost of one custom PCB design + fab cycle.
Option C: ADR-0018 Option C (split-PCB reconnected under a unified keyplate) (REJECTED — distinct from Sweep adoption)
Section titled “Option C: ADR-0018 Option C (split-PCB reconnected under a unified keyplate) (REJECTED — distinct from Sweep adoption)”The pre-Batch-5 fallback. Buy two Corne or Sweep halves, wire them to a single controller, mount under a single keyplate.
Rejected because: Option C was a fallback compromise — “use a split PCB but pretend it’s not split.” The Sweep adoption commits to the split topology honestly: two halves, two MCUs, TRRS, QMK split keyboard subsystem. This is mechanically simpler (no custom plate cutout for two PCBs under one plate), electrically simpler (no inter-PCB wiring we author), and ergonomically more honest (the Sweep is designed as a split; using it as one is the highest-leverage path). The honest-split adoption supersedes the dishonest-split fallback.
Option D: Keep ADR-0018 PCB path but adopt a different community PCB (Corne, Lily58, etc.) (REJECTED)
Section titled “Option D: Keep ADR-0018 PCB path but adopt a different community PCB (Corne, Lily58, etc.) (REJECTED)”A different community split keyboard could substitute for the Sweep. Corne has 42 keys (more), Lily58 has 58 keys (much more), Kyria has 50, etc.
Rejected because: the Sweep’s specific size (34 keys) is the closest match to the KN-86’s existing 31-key target. Adding a Corne’s 11 extra keys means 11 more legends to print and 11 more semantic homes to author; the marginal value over the Sweep’s 34 is low. The Sweep also has the strongest single-source path (holykeebs Soldered kit is one-stop) and the cleanest open-hardware lineage (David Barr’s original is widely-respected in the community). The Batch 5 decision named the Sweep specifically for these reasons; this ADR ratifies that choice without revisiting it.
Trade-off Analysis
Section titled “Trade-off Analysis”Against Option B (custom 31-key PCB):
- Risk — Sweep wins decisively. No fab risk, no plate-tolerance risk, no debouncing-firmware risk. Sweep is a shipping product.
- Time — Sweep wins decisively. Order arrives in 1-2 weeks; custom fab is 4-6 weeks for first fab + likely a second fab cycle to fix bring-up issues.
- Cost — Sweep wins on PCB ($27 base kit vs $30-60 custom fab × 5-board minimum). Loses slightly on MCU count (2 KB2040 = $17.90 vs 1 = $8.95). Net: roughly equal.
- Aesthetic cohesion — custom-PCB wins on “one plate, one controller, unified silhouette.” Sweep is honestly a split. Mitigated by the Pelican 1170 chassis hiding the internals — the operator never sees the keyboard PCBs.
- Manufacturability at scale — Sweep wins. holykeebs handles the manufacturing; we author keymap + bezel insert only.
- Community knowledge — Sweep wins. Active community, working QMK firmware, dozens of published builds, well-understood quirks.
- Open-hardware ethos — tied. Custom PCB would be our open hardware; Sweep is David Barr’s open hardware. Both honor the lineage.
Against Option C (split-reconnected under unified keyplate):
- Mechanical simplicity — Sweep adoption wins. Honest split means no custom plate cutout, no inter-PCB authoring on our part.
- Operator perception — tied. The operator sees the Pelican-1170 closed case in both options.
- Bring-up time — Sweep wins. Stock product, stock firmware.
Against Option D (different community split keyboard):
- Size match — Sweep wins. 34 vs 31 is the closest fit. Corne 42, Lily58 58, Kyria 50 all add 10+ keys we’d have to give homes to.
- Sourcing — Sweep wins. holykeebs Soldered kit is one-stop. Corne / Lily58 / Kyria sourcing fragments across multiple vendors.
Cost the chosen option pays:
- Three extra keys to legend and bind — net positive: gives us spares for v2 cart-defined verbs.
- Split topology means two MCUs — adds $8.95 BOM line + one TRRS cable + one extra USB-C-for-flashing. Trivial against the rest of the build.
- Phone-shape numpad framing relaxes — covered in §3.3 above. Spatial relationship preserved; literal topology can’t be.
deletebinding moves from*to BACK — a single ADR-0022 §7 paragraph update; clean semantic improvement.- Sweep OLED option skipped — small affordance loss; CIPHER-LINE covers the same use cases.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Eliminates custom-PCB risk + time. No fab cycle. No plate tolerance. No matrix wiring authoring. Order from holykeebs; build the bezel; flash QMK.
- Closes the deferred shift gesture. LSHIFT-as-thumb resolves
ADR-0022§Known Unknowns #1 without timing tax or chord complexity. - Cleans up the
deletesemantics. BACK = delete in literal scopes is more ergonomic than*= delete and more semantically honest. The previous binding existed because*was a base primary on the prior phone layout; with that primary gone, the new binding is strictly better. - Recovers
* #as proper symbol secondaries. UnderADR-0022,* #were bottom-row base primaries — operators would type*(multiplication) and#(reader macro prefix) with single keystrokes. Under Sweep they move to shift secondaries. Marginal regression (one more keypress) for two characters that are nonetheless common in Lisp. Mitigated by the strong + uniform shift gesture (LT1 hold). - 2 spare keys for v2 expansion at L20 and L21. Cart-defined verbs, runtime-internal toggles, or whatever v2 needs.
- LIS3DH topology is cleaner. Single sensor on master is structurally simpler than the prior “the keyboard MCU” framing (which was ambiguous about which MCU under split topology).
- TRRS link is well-understood. QMK split-keyboard subsystem handles inter-half communication; no firmware authoring on our part.
- Open-hardware lineage preserved. David Barr’s Sweep is open hardware; holykeebs revisions also open. KN-86 inherits the x0xb0x / Cyberdeck Cafe open-hardware ethos without authoring our own PCB.
- Marketing line “you can build one yourself” gets stronger. Sweep is a known build; the KN-86 BOM becomes “Sweep + Pelican + Pi + cart sled + bezel inserts” — every piece off-the-shelf or 3D-printable from public STLs.
Negative / Accepted costs
Section titled “Negative / Accepted costs”- Phone-shape numpad framing relaxes (
ADR-0016§5). Spatial relationship preserved; literal 16-key phone topology not. Marketing copy that says “phone-style numpad” needs to rephrase. Acceptable cost; the muscle memory that mattered (digit ↔ letter binding) is preserved. - Two-MCU BOM adds $8.95 + TRRS cable. Trivial.
* #move from base to shift secondaries — one more keystroke for common Lisp glyphs. Acceptable.deletekeycap legend moves from*to BACK — BACK already exists as a legend; no new printed-keycap art needed, but documentation that says “press * to delete in literal mode” becomes wrong and needs updating.- Sweep OLED option skipped. Loses a small affordance (layer indicator on the keyboard itself). CIPHER-LINE covers the same use cases at higher fidelity. Acceptable.
- Two USB-C ports inside the case (one per half — master for normal use, slave for flashing only). One extra port to plan internal cable routing around. Acceptable.
- Pelican 1170 bezel-insert design grows. The unified-keyplate bezel insert (
ADR-0018§K-mounting via QRPπ template) becomes a two-keyplate-aperture insert with a TRRS-cable pass-through. Tracked as a bezel-design follow-up inprototype/keyboard-decision.md. - Doc surface to sweep. Eight ADRs,
CLAUDE.md, the hand-wired prototype build guide, and the firmware tree all need updates. Tracked in Documentation Updates below.
Follow-on work this ADR creates
Section titled “Follow-on work this ADR creates”- F1 — Order Ferris Sweep Soldered kit from holykeebs per
prototype/holykeebs-buyers-guide.md. Owner: Josh. Blocks F3 (firmware bring-up against real hardware). - F2 — LIS3DH integration into master half PCB. Two paths: (a) reflow LIS3DH bare IC onto master PCB during Soldered-kit assembly (preferred if holykeebs can do it on the soldered-kit pass); (b) mount Adafruit ADA-2809 breakout on master PCB underside via flying-lead I²C wires (fallback). Owner: Hardware agent. Decision at order time.
- F3 — QMK keymap retarget.
firmware/kn86-keyboard/keymap.crewritten asLAYOUT_split_3x5_2(...)for the Sweep per §3.1. Existing 31-key keymap moved to_archive/firmware/kn86-keyboard-handwired-31key/. Owner: Platform Engineering. Blocked by F1. - F4 — Bezel insert re-design. Pelican 1170 inset panel design grows from single-keyplate aperture to two-keyplate-aperture with TRRS pass-through. Owner: Hardware agent. Blocked by F1 (need PCB dimensions in hand).
- F5 — Recast hand-wired prototype build guide.
docs/plans/2026-04-27-keyboard-prototype-build-guide.mdcurrently describes a 31-key hand-wired path. Recast as “use only if Sweep PCB order is delayed; for parallel bring-up of firmware against breadboarded KB2040 + LIS3DH.” Owner: Platform Engineering. Can be done independently of F1. - F6 — Marketing copy sweep for “phone-style numpad.” Rephrase to “phone-style digit-to-letter mapping” or “Nokia-style literal entry.” Owner: Marketing / Product agent. Independent of F1-F5.
- F7 — Bring-up validation. Once F1 + F3 land, validate: (a) both halves enumerate over TRRS as one logical keyboard; (b) all 34 key positions report through
xxd /dev/input/event*; (c) LIS3DH driver finds the chip on master I²C; (d) vendor HID report emits on/dev/hidraw*. Owner: Hardware + Platform Engineering. Blocked by F1 + F3.
Documentation Updates (REQUIRED — part of the decision, not aspirational)
Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”This ADR lands in a single PR (this one) including the following updates per CLAUDE.md Spec Hygiene Rule 3:
-
docs/adr/ADR-0031-ferris-sweep-keyboard-adoption.md— this file (Proposed; flips to Accepted on Josh’s approval). -
docs/adr/README.md— append entry for ADR-0031 at the top of the chronological index. -
CLAUDE.md— Canonical Hardware Specification:- Keys row: 31 → 34. Update the build pattern from “custom mechanical keyboard: hot-swap Kailh Choc v1 switches, MBK keycaps, QMK-compatible RP2040-class controller (Adafruit KB2040 for v0.1; Sea-Picro fallback) per ADR-0024, USB-C HID to the Pi via an internal USB hub IC. PCB path is custom-fab unified 31-key (preferred) or modified split layout (Corne / Ferris Sweep) reconnected under a single keyplate (fallback)” to “Ferris Sweep (David Barr design) sourced from holykeebs as a Soldered kit, two halves (3×5 main + 2 thumb per side) connected by TRRS, hot-swap Kailh Choc v1 switches, MBK keycaps, 2× Adafruit KB2040 (one per half, master/slave per QMK split-keyboard topology), USB-C HID from the master half via internal USB hub IC. See ADR-0031.”
- Keys row: update the keymap description — replace “Numpad uses phone-style layout (1-2-3 top row, 0 bottom-center) per ADR-0016” with “Right half carries the digit 3×3 (1-2-3 top, 4-5-6 home, 7-8-9 bottom) preserving Nokia digit-to-letter binding from ADR-0016 §6, plus
,./;-punctuation primaries and ENT. Left half carries the 14 Lisp primitives function block (per ADR-0022 §1 legends). Thumbs: LEFT [EVAL][LSHIFT], RIGHT [0][TERM]. SHIFT layer (LSHIFT held) delivers`!@$%^*&#:+()\"shift secondaries. See ADR-0031 §3.1.” - Keys row: replace “Finalized keycap legend manifest … is locked in ADR-0022” with “Keycap legend manifest in ADR-0022 §1 (left-half Lisp primitives) is preserved logically; right-half manifest superseded by ADR-0031 §3.1.”
- Keys row: replace “RP2040-class controller (Adafruit KB2040 for v0.1; Sea-Picro fallback) per ADR-0024” with “2× Adafruit KB2040 (one per half) per ADR-0024 + ADR-0031; Sea-Picro fallback.”
- Accelerometer row: clarify “hosted on the keyboard MCU” → “hosted on the master (LEFT) half’s keyboard MCU per ADR-0031 §5.”
-
docs/adr/ADR-0018-custom-mechanical-keyboard-build.md— add a “Update — superseded by ADR-0031 (2026-06-06)” footnote at the top of §3 PCB path. Body remains as design history; §1 commodity-parts framing, §5 internal USB hub, §6 firmware-stays-dumb doctrine remain canonical. -
docs/adr/ADR-0022-keyboard-layout-finalization.md— add “Update — partially superseded by ADR-0031 (2026-06-06)” footnote at the top of §2 (numpad physical arrangement) and §4 (bottom-row rationale). §1 (function block legends) preserved. §7 (deletebinding) — add the BACK rebind. §Known Unknowns #1 (shift gesture) marked closed by ADR-0031 §3.2. -
docs/adr/ADR-0024-keyboard-mcu-rp2040-selection.md— add “Update — pin map amended by ADR-0031 (2026-06-06)” footnote at the top of §3 (canonical as-built pin map). The single-MCU pin map remains historical reference; the split-topology pin map is in ADR-0031 §3. -
docs/adr/ADR-0023-accelerometer-ambient-glitch.md— add “Update — LIS3DH host pinned to master (LEFT) half by ADR-0031 (2026-06-06)” footnote at the top of §2. -
docs/adr/ADR-0016-nemacs-repl-input-model.md— add footnote at §5 (phone-style keypad layout): “Update — phone-shape topology relaxed by ADR-0031 (2026-06-06); digit-to-letter binding in §6 preserved.” Add footnote at §7 closure-block (lines 100-104): “Update —deleterebound from*to BACK by ADR-0031 §3.3.” -
docs/plans/2026-04-27-keyboard-prototype-build-guide.md— add a banner at the top: “Update 2026-06-06: ADR-0031 adopts the Ferris Sweep instead of the custom 31-key PCB. This guide is no longer the v0.1 build path. Use it only as a parallel firmware-bring-up path while waiting for the Sweep PCB order, or as a teaching artifact.” Full recast deferred to F5. -
docs/device/hardware/keyboard.md— full rewrite around Sweep specifics. Replace the 31-key product entry with the Sweep + holykeebs Soldered kit configuration + master/slave topology + LIS3DH-on-master. Tracked as F3 follow-up but a banner at the top noting the rewrite-pending state lands in this PR. -
docs/device/hardware/build-specification.md— keyboard subsystem table + topology diagram + §“Why a USB HID keyboard over direct GPIO matrix” updated to reference ADR-0031 and the Sweep split topology. -
docs/device/hardware/keyboard-electrical-spec.txt— superseded notice at the top: “Superseded by ADR-0031 (2026-06-06). The Sweep PCB is fabricated by holykeebs; matrix wiring is per the Sweep PCB design, not authored here. This file retained as design history.” -
firmware/kn86-keyboard/README.md— header note that the keymap.c is being retargeted fromkeyboards/handwired/kn86tokeyboards/ferris/sweepper ADR-0031 §4 / F3. Full keymap rewrite tracked as F3. - Project-wide grep sweep for stale claims:
- “31 physical keys” / “31-key” / “thirty-one keys” — must update or annotate every instance. Permitted to remain: ADR-0018 (design history), ADR-0022 (design history), this ADR’s options-considered section,
_archive/. - “phone-style numpad” / “phone layout” — must rephrase per §3.3 + L4 of
docs/plans/2026-06-06-inspiration-synthesis.md§2. Permitted to remain: ADR-0016 design history, ADR-0022 design history, this ADR’s options-considered section. - “unified keyplate” / “single keyplate” — must annotate as superseded. Permitted to remain: ADR-0018 design history.
- “custom 31-key PCB” / “custom-fab PCB” — must annotate as superseded.
- “31 physical keys” / “31-key” / “thirty-one keys” — must update or annotate every instance. Permitted to remain: ADR-0018 (design history), ADR-0022 (design history), this ADR’s options-considered section,
A PR that lands this ADR without ticking all of these fails review per CLAUDE.md Spec Hygiene Rule 3. The grep sweep is enforced by the audit agent on the next scheduled run.
Narrative (for the design history)
Section titled “Narrative (for the design history)”For most of the project, the KN-86 keyboard was a custom mechanical keyboard with 14 function keys carrying Lisp primitives, a 16-key phone-style numpad carrying digits + Nokia multi-tap letters, and a single TERM context key — 31 keys under a unified keyplate, hand-wired to a Pro Micro (later a KB2040), with the long-term plan of fabbing a custom PCB. That plan was workable but expensive in time and risk; every iteration spent on plate cutout tolerance, matrix debouncing firmware, and PCB-fab lead time was iteration not spent on the carts, the runtime, the bezel design, the marketing. The 2026-06-06 inspiration corpus argued, with overwhelming evidence, that the mech-keeb hobbyist community has already solved every problem in the prior spec as commodity parts. The Ferris Sweep — David Barr’s 34-key split, in holykeebs’ hotswap revision — is what that argument ultimately produced. Two halves, two RP2040 controllers, a TRRS cable between them, a single USB-C interface from the master half to the Pi via the internal hub IC. Three extra keys gives us spares; the LSHIFT-as-thumb resolves the deferred shift-gesture question without a timing tax; the 14 Lisp primitives keep their left-half home; the 1-9 digit cluster preserves Nokia letter muscle memory within a 3×3; the * # glyphs that were phone-bottom-row primaries become honest shift secondaries; delete semantics move from * (no longer base) to BACK (a strictly better semantic match in literal-entry scopes). The phone-layout fiction relaxes — copy needs to say “phone-style digit-to-letter mapping” instead of “phone-style numpad” — but the muscle memory the operator actually owns (digit ↔ letter binding) survives intact. A future reader should care because this ADR sets the project’s hardware-procurement posture: when an existing community product satisfies 90% of a spec and removes 100% of a fab risk, take the community product. The Sweep adoption is the largest single risk-reduction the KN-86 program has booked since the Pi pivot.