Explore: dongle display (Prospector/OLED), Zephyr 4 line, DE/EN/programming keymap, training plan #2
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Current state:
totem_dongleshield onxiao_ble, no display.config/west.ymltrackszmkfirmware/zmk@main, which moved to Zephyr 4.1 in Dec 2025. #1 already fixed the resultingxiao_ble//zmkboard-variant break. The oldtotem-prospectorbranch is stale (only touched dtsi/kscan, predates the current dongle setup, safe to ignore/delete later).1. Dongle display options
mainlinemainbranch = ZMK v0.3/Zephyr 3.5 only — need thefeat/new-status-screensbranch for our lineprospector_adaptershield to match its pinoutmainalready updated for Zephyr 4.1upgrade-4.1branchDecide hardware appetite first: englmaxi's is the cheap way to get a display on the current dongle as-is; Prospector is the nicer end state but means sourcing/soldering its board.
2. Zephyr 4 line
Already on it via unpinned
zmk@main(Zephyr 4.1 since zmk.dev/blog/2025/12/09/zephyr-4-1, see #1). Two open questions:main— no v0.4 release exists yet, so staying onmainmeans staying in "tester" territory a while longer.3. CI
.github/workflows/build-user-config.ymlis a vendored/patched copy of upstream's workflow (artifact-upload v3 pin for Forgejo, seema3s7r0/nix-config#82) — adding a shield doesn't touch it,build.yaml's matrix already drives it. What will need changes once a display module is picked:config/west.yml: add the module's remote + project entry.build.yaml: wire the display shield into thetotem_donglebuild entry (check each module's docs for exact mechanism — Prospector uses a separateprospector_adaptershield, the OLED/LVGL ones may instead be Kconfig/snippet-driven).4. DE/EN/programming keymap
Current
config/totem.keymaphas 5 layers: BASE (QWERTY + home-row mods), NAV, SYM (already covers most programming symbols), ADJ, GER. GER is a thin AltGr passthrough (&kp RA(Q)= Ä, etc.) that only works if the host OS keyboard layout maps those AltGr combos — not documented anywhere which OS layout that assumes.Open questions to explore:
5. Training schedule
Deferred until the keymap decisions in #4 land — a schedule for a keymap that's still being designed gets thrown out. Once base layout is decided:
Research pass — findings, corrections, and a revised plan
Worked through all five sections. Several premises in the original body turned out to be wrong; those are corrected below rather than edited out. Everything here is a proposal for review, not a decision — and claims that could not be verified are marked as such.
Corrections to the premises
The Totem has never been used — and neither has the committed keymap.
The daily driver is the Sofle (
ma3s7r0/zmk-sofle-eyelash-config). From its keymap and README: plain QWERTY, 58 keys with a number row, no home-row mods, no hold-taps, no combos, no macros, momentary layers (&mo). Mods sit on the bottom row and outer columns.This reframes §4 and §5 entirely. This is not a migration — there is no Totem muscle memory to preserve. But there is also zero experience with the two things
config/totem.keymapleans on hardest: home-row mods and sticky layers (&sl). The git history (try sticky layer only,try combining layer tap with sticky layers,stuff) confirms it is a series of untested experiments, not a trained setup.§4: the GER layer's host-layout assumption is now documented — and it is proven in production.
Verified against xkeyboard-config 2.47 and 2.48 (
symbols/us):us(intl)maps AltGr+q/s/y/p/5 → ä ß ü ö €, andus(altgr-intl)includes it pluslevel3(ralt_switch). That matches all five GER bindings exactly. The assumed host layout isus+ variantaltgr-intl+lv3:ralt_switch.Not a theoretical scheme: the Sofle's
layer_1uses the identicalRA(Q)/RA(S)/RA(Y)/RA(P)/RA(5)and is in daily German use.One gap worth a look, not an alarm: in
nix-config, onlymodules/nixos/keyboard.nixsets the variant.home-manager/niri/default.nixsetslayout = "us,ua,ru"with no variant and nolv3option, and niri does not readservices.xserver.xkb. Either something else supplies it or niri isn't used for German prose. Fix belongs in the compositor config, not the keymap.§1 missed a blocker that applies to every display option.
totem_dongle.overlayincludestotem.dtsi, so the dongle declares a full GPIO matrix it has no keys for: rows&xiao_d 0,1,2,3+ cols&xiao_d 8,9,10,5,4— every XIAO pin except D6/D7. That consumes D4/D5 (I2C) and D8/D9/D10 (SPI), i.e. both buses every display needs. It fails at runtime, not build time. Needs a mock kscan on the dongle:keeping
zmk,physical-layouton the real transform so Studio still shows the TOTEM layout. Worth doing whether or not a display is ever added.New gaps found in the keymap:
-and no_on any layer. OnlyKP_MINUS/KP_PLUSon NAV.Shift+KP_MINUSis not_, so underscore is unreachable — snake_case, kebab-case, shell flags. The Sofle'ssymlayer hasMINUS,UNDER,PIPE,PLUS,EQUALand all brackets, so this is a genuine regression against the incumbent.KP_N*, which is NumLock-dependent; niri does not set NumLock.config/boards/shields/totem/totem.keymapis dead weight — a completely different upstream default (10 layers: Base/Media/Nav/Mouse/Sym/Num/Fun/Button/Game/Gameplus).config/totem.keymapis the live one.&mt ESC Q,< GER GRAVE, or the 0+1 ESC combo; NAV shows blanks whereESC/BT_CLR/EQUALsit).hold-trigger-key-positionsare correct against the 38-key position map.Tooling gap vs. the Sofle repo: that repo has a Nix flake (
nix build .#firmware,nix run .#draw) and keymap-drawer wired up. This one has neither and can only build via CI.§1 + §2 + §3 — display: englmaxi, and CI is unaffected
Ranking for this setup:
zmk@mainmaintargets it (README:mainwith zmk main,v0.3if pinned)mainis Zephyr 3.5; 4.1 on WIP branchfeat/new-status-screens, last traced ~Feb 2026, merge status unverifiedupgrade-4.1(issue #29), unverifiedseeeduino_xiao_ble, no maintenance signalenglmaxi is cheapest (~€3 SSD1306 128×64 I2C, 4 wires), lightest on flash/RAM (monochrome, no colour LVGL), and ships its own SSD1306 node on
xiao_i2c— no display devicetree to write. Needs only D4/D5 freed, i.e. the mock-kscan fix. Integration: awest.ymlremote+project, thenshield: totem_dongle dongle_displayon the dongle row.Riding unpinned
zmk@mainwhile depending on someone's unmerged WIP branch is the specific risk that rules out the other two for now.§3 CI: no change needed. Verified
build-user-config.yml:109passes-DSHIELD="$shield"through verbatim, so a space-separated second shield works as-is. The upload-artifact v3 pin is untouched, artifact count stays 4. Only effect: the module cache key hashes**/west.ymland**/build.yaml, so the first build after the change is a cold cache.Useful prior art if Prospector/YADS is revisited: azumafuji/totem-yads already builds TOTEM + YADS on Zephyr 4.1.
§4 — layout: two real candidates
Since there is no Totem habit to protect, the cost of adopting an alt layout is unusually low: the ergo-board learning cost gets paid once either way, so learning QWERTY-on-38-keys now and switching later is the one option that clearly pays twice.
PUQ30 (AdNW project) — read off the driver's on-screen keyboard:
◌= diacritics key (identified by elimination: 26 letters +,.'= 29, leaving one slot). Name self-checks (p u … , q). Home row is identical to AdNW'sh i e a o / d t r n s.Why it is the best structural fit: designed for a straight/matrix board with thumb-shift and exactly 30 character keys, so it needs no modification. Zero wasted core slots, and
,.'are included. The diacritics slot takes&mo GERdirectly, so the existing AltGr scheme supplies umlauts — no host driver, no coupling.For contrast, the ISO 4-row German layouts all need surgery. Grids extracted from xkeyboard-config
symbols/de(verified against 2.47 and 2.48):q(~0.02% DE)j(~0.27%)f(~1.7% DE)f+zyCaveat: those five are 4-row ISO definitions optimized across the full board. Lifting out a 3×10 core and relocating the displaced letter yields a modified layout — the published metrics no longer apply. This caveat does not apply to PUQ30.
The alternative is Colemak-DH, on ecosystem grounds: it is the only candidate with published German-corpus analysis, and keybr/monkeytype support it natively. Modern top layouts sit within ~0.3–0.4pp SFB of each other — below perceptibility — so ecosystem may matter more than metrics.
Genuinely open:
Also:
RALTmust not get swallowed by a home-row mod, and the current base layer has&hmr RALT SEMIon the right pinky.§5 — training plan (now writable)
Sequencing: adopt the target alphabet from day one. The NCSU learning-curve study found split-QWERTY typists within 10% of baseline after 20 trials, while changing the key layout was dramatically slower. Community reports: 2–3 weeks to comfort on a compact board with QWERTY vs 2–3 months to parity on a new alphabet. Learning QWERTY-on-Totem first costs weeks and trains the interference to be fought later.
With a concession that lands where the Sofle habits already are: alphabet + minimal board, not alphabet + full Miryoku.
&mo, not&sl. Momentary-on-thumb is the lower-cognition default and matches existing habit; sticky is for rare layers. The current keymap uses&sl NAV/&sl SYM.require-prior-idle-ms ≥ 10500 / relaxed WPM→ 260–350 ms at a beginner's 30–40 wpm, not the 150 currently in the keymap.&mo. No HRMs, no combos.require-prior-idle-msdownTwo 30-min sessions beat one 60-min block. Accuracy 97–99% before pushing speed. The Sofle stays QWERTY throughout — different hardware keeps the two layouts in separate motor contexts, which is the standard arrangement and is free here.
Tools: keybr.com (weeks 1–2, adaptive letter introduction — only if the layout is supported), ngram-type + ngram-type-ger for targeted bigrams incl. German
ch/sch/ei/en, Monkeytype for real words (german 10k; stop-on-error =word; layout emulator off since the layout lives in firmware), KeyZen/typing.io for symbols last.Counter-argument, stated honestly: Getreuer holds that speed is a function of practice, not layout, and that the split columnar board, a good symbol layer, and vim each matter more than the alphabet. The RSI literature is confounded. The payoff is comfort, not wpm.
Revised plan — next steps
No-regrets, independent of every open decision:
-and_; reconsiderKP_N*→ plain number keycodes.config/boards/shields/totem/totem.keymap(confirm against a build first).Decision gates:
zmk@mainwith no v0.4 release. englmaxi tracksmain, so no pressure to pin; revisit if a display module forces a WIP branch.§4 update — the Kissboard 30-key layouts, with grids and metrics
Source: PUQ-Vars documentation (Modifizierter NeoVars für Truly Ergonomic Keyboard, rev. 254 / 01.16), pages 1 and 3, supplied from the PDF. This supersedes the "genuinely open" items in the previous comment.
There are three, not one
All computed with Andreas Wettstein's optimizer — the same tool and objective family as AdNW and KOY, so these are properly optimized layouts, not hand-tweaks. Doc's own framing: "Umlautfreie Belegungen für 30 Zeichentasten … Bi- und Trigrammoptimierung, getrennte Silben. Deutsch-Englisch 1:1."
That last part is the decisive property: optimized on a 1:1 German/English corpus. No English-optimized layout (Colemak-DH, Gallium, Graphite) offers this, and the earlier note that no German-corpus scoring exists for them still stands.
Each is 26 letters + the diacritics key
¨+,.'= exactly 30. PUQ30 and PUO30 share an identical right hand. The PUQ30 grid here matches the independent reading from the driver's on-screen keyboard in the previous comment.Metrics (Wettstein model — lower is better)
PUQ30 is the pick for balanced DE+EN. The doc states it: PUO30 suits German better (
eandinot adjacent —h i a e ,vs PUQ30'sh i e a o) but is "deutlich schlechter in Englisch"; PUQ30's German/English ratio is balanced and "Der Lageaufwand ist bei PUQ in jedem Fall besser", which the table confirms in both languages. Combined total effort is a tie (382.5 vs 382.3, 0.05%), so position effort breaks it.OUMF30 trades hand alternation for same-hand runs (Handwechsel 64.6 vs 72.4) at ~4.5% worse total effort and the highest adjacent-finger load. Only for a deliberate roll preference.
PUQ30 row distribution: ~70% home row (EN), ~67% (DE), bottom row under 9%.
Comparison caveat: these figures are not comparable to Colemak-DH's ~1.39% SFB or Gallium's ~0.6–1.0%. Wettstein's model measures its own quantities (Gesamtaufwand / Lageaufwand / Kollisionen), not oxeylyzer-style SFB. PUQ30's Kollisionen of 0.86 EN / 0.36 DE looks Gallium-class if the unit is percent-of-bigrams, but the unit is unverified — no cross-model claim should be made either way.
The diacritics key: postfix, and host-side
Mechanism, now confirmed: type the letter first, then the key —
a☠→ä,s☠→ß,c☠→ç. Repeated presses cycle diacritics (i☠☠☠→î) with per-language ordering menus; Shift turns it into a Compose key (AE♫→Æ,oe♫→œ,tm♫→™). Its position was co-optimized (~1.7% of German characters, comparable tof), not parked in a leftover slot.It is implemented host-side in AutoHotKey (PUQ-Vars is a NeoVars fork, GPLv3/CC-BY-SA) — Windows only, therefore irrelevant here; the layout would be implemented in ZMK regardless.
ZMK cannot do postfix natively (it would need to know what was just typed). Two substitutes:
&mo GER/< GERin the¨slot and keep the existing AltGr passthrough. Same key, same finger, prefix-hold instead of postfix-tap. Recommended — it is what the Sofle already does.¨position →RA(Q). Same two keys as upstream but simultaneous rather than sequential. Adds timing ambiguity, so a later refinement, consistent with the "combos last" note in §5.Either way the base alpha arrangement is untouched — the coupling question raised earlier is closed: there is none.
Totem fit
Concept is "Kissboard": 30 character keys plus mod keys, dropping the TECK's two outermost keys, with the freed pair holding
'and the diacritics key.The Totem has 30 keys in the 3×5+3×5 block, plus 2 outer bottom-row keys, plus 6 thumbs. So the core maps exactly onto the alpha block and both outer keys are spare — currently
< GER GRAVEand&kp SINGLE_QUOTE, and since'moves in-core, genuinely free (grave, a layer key, or whatever the symbol layer wants).Revised §4 decision
The layout choice is now PUQ30 vs Colemak-DH, and the trade is no longer metrics-vs-structure — PUQ30 has real optimizer numbers on a 1:1 DE/EN corpus, which is strictly better evidence for this use case than anything available for the English layouts. The remaining gap is purely ecosystem:
Running dariogoetz's optimizer is no longer needed to decide — PUQ30's numbers exist. It would only be useful to score PUQ30 against Colemak-DH in a single model, which remains the one genuinely unanswered comparison.
Also still open: whether OUMF30/PUO30 are worth a second look if the roll/alternation preference turns out to differ from assumption. Neither changes the near-term plan.
No change to the next steps in the previous comment — the Nix flake port remains first and is independent of this decision.
Prep for step 1 — porting the Nix flake + keymap-drawer from the Sofle
Groundwork so this can be picked up cold. Source:
ma3s7r0/zmk-sofle-eyelash-config(branchmain;display-tweaksalso touchesflake.nixandbuild.sh, so diff both before copying — it adds apatches/directory applied to the display modules, which this repo does not need).What exists there
flake.nix(149 lines)drawappflake.locknixpkgs-unstable+zmk-nixkeymap_drawer.config.yamlkeymap-drawer/eyelash_sofle.{yaml,svg}build.sh(78 lines)glyphs.sh(56 lines).github/workflows/draw.ymlzephyr/module.ymlIt builds on
lilyinstarlight/zmk-nixviazmk.buildKeyboard, exposingfirmware,keymap,flash,update,default, adevShells.default, and anapps.draw.The one real risk:
zmk-nixvsxiao_ble//zmkon Zephyr 4.1The Sofle flake hardcodes
board = "nice_nano_v2"and itsconfig/west.ymlpins ZMK tov0.3.0— deliberately, because that repo's own board definitions do not survive Zephyr 4.1 / hardware-model-v2.This repo is the opposite case: unpinned
zmk@main, i.e. on the 4.1 line, andbuild.yamluses the board qualifierxiao_ble//zmk(see #1). So the open question, and the thing to settle first:If it does not, the options are: patch/override
zmk-nix, or pin ZMK here too — the latter conflicts with the current display plan, since englmaxi'smaintargets the 4.1 line. Resolve this before writing any of the rest; it determines whether the port is an afternoon or a project.Other adaptations needed
zephyrDepsHash— a fixed-output hash of the west dependency tree. It will differ (differentwest.yml, different ZMK revision). Regenerate withnix run .#update; the Sofle's value is meaningless here.build.yaml:totem_left,totem_right,totem_dongle,settings_reset, all onxiao_ble//zmk. Keep the flake's "Mirrors build.yaml. Keep the two in sync." comment honest.totem_left,totem_rightandtotem_dongleall pass-DCONFIG_ZMK_STUDIO=y(onlysettings_resetdoes not), versus one of four on the Sofle. So the flake's nanopb /pkg_resourcesworkaround will apply to three targets — thatpostConfiguresed exists because nanopb's generator importspkg_resources, which setuptools dropped in 81 while nixpkgs is on 83. Carry it over; it is not optional.-DCONFIG_ZMK_STUDIO_LOCKING=n(unlocked). This repo does not —config/totem.keymaphas a real&studio_unlockbinding on the ADJ layer. So dropextraCmakeFlagsand leave locking at its default.snippet: studio-rpc-usb-uart. The Sofle flake never passes a snippet — it relies onenableZmkStudio. Check whetherenableZmkStudioalready implies the snippet, or whether it must be passed explicitly for the dongle target.-d boards/shields/eyelash_sofle/eyelash_sofle-layouts.dtsi. There is no separate-layouts.dtsihere — the physical layout (default_layout/key_physical_attrs) lives inconfig/boards/shields/totem/totem.dtsi. Point-dthere. Same rationale applies: reading the layout from devicetree avoids the network lookup azmk_keyboard:key would trigger.config/totem.keymap, outputkeymap-drawer/totem.{yaml,svg}.zephyr/module.ymlneeded. The Sofle keeps its shield at the repo root (boards/shields/eyelash_sofle/) and needs the module manifest for ZMK to find it. This repo keeps its shield insideconfig/(config/boards/shields/totem/), whereZMK_CONFIGalready picks it up. Simpler — do not copy that file.sourceFilesBySuffices— the Sofle filters.conf .defconfig .dtsi .h .keymap .overlay .shield .yaml .yml. That set covers everything here;config/info.jsonis drawer/QMK metadata and is not part of the ZMK build, so.jsonneed not be added unless the drawer is pointed at it.Deliberately skip
.github/workflows/draw.yml— disabled upstream for a Forgejo reason that applies identically here:caksoylar/keymap-drawer'sdraw-zmk.ymlline 112 iscontinue-on-error: ${{ !inputs.fail_on_error }}, and Forgejo typescontinue-on-erroras a strict bool and rejects the expression. Valid on GitHub, fatal on git.cl12.de, not fixable from the calling file. Regenerate the drawing locally vianix run .#drawand commit the output, as the Sofle does.Note also that the Sofle README states its GitHub workflows do not run on Forgejo at all and local builds are the supported path — whereas this repo's
build.yml+ vendoredbuild-user-config.ymldo work (that was #1's whole point). So the flake here is additive: local iteration alongside working CI, not a replacement for it.Verification
nix build .#firmware→ four UF2s named perbuild.yaml.nix run .#draw→keymap-drawer/totem.{yaml,svg}, and the SVG should show&mt ESC Q,< GER GRAVEand the 0+1 ESC combo that the drifted ASCII art omits — which is the point of steps 4/5 in the plan.nix develop→ toolchain shell.build.yamlis untouched.Suggested branch
Off
totem-dongle. Keep the flake port as its own change, separate from the mock-kscan fix and the-/_addition — the flake is what makes those two verifiable locally, so it should land first and alone.Steps 1 and 2 done, and §1 reverses: Prospector is the pick, not englmaxi
Branch
nix-flakeofftotem-dongle, four commits, not pushed. Hardware fact that changes the plan: the Prospector board is already owned. Home = Prospector, travel = bare dongle, so both dongle images are wanted, not one.§1 correction — the reason to defer Prospector has expired
The previous comment deferred it on "merge status unverified" for
feat/new-status-screens, last traced ~Feb 2026. Checked the branch directly. Its HEAD ised98221f(2026-02-17), "update readme, use new xiao_ble//zmk board target" — it targets exactly this repo's line. The README's install instructions now literally say:It also ships
boards/shields/prospector_adapter/boards/xiao_ble_zmk.overlay, i.e. a board overlay written for the ZMK variant. Not a WIP that happens to compile — it was ported to this target on purpose.Prior art found for the exact combination: eigatech/zmk-config@totem-prospector, last touched 2026-04-20 ("update board variant"). Same TOTEM shield tree as this repo, same board qualifier, same module branch. The structure below follows it.
Checked GEIGEIGEIST/zmk-config-totem for comparison: upstream has no dongle at all, so there is nothing newer to take from there. The stale
totem-prospector@originbranch in this repo is an older copy of eigatech's, safe to delete as the original body assumed.The pin conflict is worse than reported, and it is a bug today
Previously flagged as D4/D5 + D8/D9/D10. The real overlap, resolved through
seeed_xiao_connector.dtsi(D0=P0.02, D1=P0.03, D2=P0.28, D3=P0.29, D4=P0.04, D5=P0.05, D6=P1.11, D7=P1.12, D8=P1.13, D9=P1.14, D10=P1.15):int-gpiosi2c1SDA (P0.04)i2c1SCL (P0.05)Seven collisions, not five. Prospector occupies every pin except D0 and D1. Devicetree does not diagnose this, so it fails at runtime, and it is a defect on the bare dongle too — it currently drives nine GPIOs for a board with no keys.
What changed
fix(keymap): restore the missing ADJ binding)— unrelated to the display, found by keymap-drawer on its first run. ADJ had 37 bindings where every other layer has 38, so the whole bottom row was shifted one slot and the last key fell off;&bt BT_PRVsat under RESET/BOOTLOAD instead of under BT_CLR/BT_NXT. The ASCII art above the block already showed the intended placement. Confirms §5's "the committed keymap has never been tested" reading — this could not have survived a day of use.ci: select the ZMK variant of xiao_ble— #1's stated fix was never actually committed.build.yamlstill asked for barexiao_ble. Nowxiao_ble//zmk, confirmed by the local build log:Board: xiao_ble, qualifiers: nrf52840/zmk.build: port the Nix flake and keymap-drawer from the Sofle config— step 1 of the plan.feat(dongle): free the dongle's pins and add a Prospector build—kscan0loses itscompatibleandrow-gpiosintotem.dtsi; the halves declare the matrix driver themselves;totem_dongle.overlayoverrideszmk,kscanwith a mock. It still includestotem.dtsifor the transform and physical layout, so Studio keeps showing TOTEM.Both dongles build from one shield:
prospector_adapteronly decides whether the display code compiles in — same board, same keymap, so either image runs on either XIAO. Status-screen choice sits inconfig/prospector_adapter.conf; verified the build merges it for the Prospector target only.The module is pinned to
ed98221frather than tracking the branch. Riding a WIP branch tip would break the flake'szephyrDepsHashat random times for no benefit.Step-1 open questions, answered
zmk-nix+xiao_ble//zmkon Zephyr 4.1: works, no patching, no pinning.boardis passed verbatim towest build -b.enableZmkStudiodoes implystudio-rpc-usb-uart— and unconditionally, on every target, which contradictsbuild.yaml. Bypassed:extraCmakeFlags+ explicit protobuf/grpcio inputs, withsnippetsset per target.-dpoints atconfig/boards/shields/totem/totem.dtsi; no separate-layouts.dtsihere.zephyr/module.ymlneeded — the shield lives underconfig/, whereZMK_CONFIGfinds it.&studio_unlockis real here.Not ported:
build.sh(docker, redundant now CI works and the flake builds locally), the Sofle'sraw_binding_mapand its per-binding CSS, and the vendored MDI glyphs plusglyphs.sh— no legend here uses a glyph yet, so drawing is offline-safe as is.Verification
All five images built locally via
nix build .#firmware:display_st7789v.c,apds9960.c,brightness.c,custom_status_screen.c.kscan_mock.c; halves still linkkscan_gpio_matrix.c. Pin conflict gone.CONFIG_LV_Z_VDB_SIZE=25is upstream's documented escape hatch if a future change tips it over.nix run .#drawregenerateskeymap-drawer/totem.{yaml,svg}.Note on a pre-existing quirk
-DCONFIG_ZMK_STUDIO=ywithout the snippet is a no-op:totem_left/totem_rightrun zero nanopb steps and are 138 KB smaller than the dongle, so Studio is not actually compiled into the halves. That is existingbuild.yamlbehaviour, mirrored faithfully rather than silently changed. Either drop the flag from the two peripherals or add the snippet — separate decision.Revised next steps
Port the Nix flake + keymap-drawer.Done.Fix the dongle mock kscan.Done, and it was blocking the Prospector outright.-and_; reconsiderKP_N*→ plain number keycodes. Still open, still independent.config/boards/shields/totem/totem.keymap. Still open — now cheap to confirm against a local build.display-nameto each keymap layer. The Prospector layer widget reads it and falls back to the bare layer index without it.§1 is now decided by hardware rather than by cost, so englmaxi and YADS drop out. §2 pinning is unchanged: still unpinned
zmk@main, and now with one pinned module hanging off it. §4 and §5 are untouched by any of this.Pushed. Steps 3, 4 and 6 also done — plus a correction to my own last comment
totem-dongleis now atbe1e7e6c, four commits ahead of where it was.Correction: #1's fix was committed, and the vendored workflow does exist
My previous comment claimed "#1's stated fix was never actually committed." Wrong. The local clone was simply stale —
jj git fetchhad been failing silently.origin/totem-donglealready carried three commits I had not seen:So the original issue body was right on both counts:
build.yamlalready usedxiao_ble//zmk, and.github/workflows/build-user-config.ymlis a real vendored copy with the artifact-v3 pin. My duplicateci: select the ZMK variant of xiao_blecommit has been abandoned; the rest rebased onto the true tip, with thebuild.yamlconflict resolved in favour of the five-entry matrix. Rebuilt all five images after the rebase before pushing.Landed
f608abdfix(keymap): restore the missing ADJ binding0f20287build: port the Nix flake and keymap-drawer from the Sofle configc3fd34afeat(dongle): free the dongle's pins and add a Prospector buildbe1e7e6fix(keymap): reachable -, _, =, +, ~, |; drop the keypad codesStep 3 — the symbols, and where they went
-and_were unreachable on every layer, as reported. They now sit on SYM's left home row, which until now held a second copy of the arrow cluster that SYM's right half already has — and NAV has a third. SYM is entered by a right-thumb sticky, so the left hand is free and the symbols land on the strongest fingers:|goes on the right pinky, next to the\already on the row below. That closes every gap the earlier audit named:- _ = + ~ |were all missing,\and the shifted-number row were already there.KP_N* dropped. NAV used
KP_N0–KP_N9plusKP_PLUS/KP_MINUS/KP_MULTIPLY. All NumLock-dependent, and niri never sets NumLock, so what the host actually produced was a coin flip. Now plainN0–N9,PLUS,MINUS,ASTRK.Step 6 —
display-name, and it was overdue anywaylabelon azmk,keymaplayer is marked deprecated in ZMK's own binding (zmk,keymap.yaml), not merely superseded. All five layers now usedisplay-name, which is also the property the Prospector's layer widget reads — without it the screen falls back to bare layer indices.Step 4 — dead keymap deleted
config/boards/shields/totem/totem.keymapis gone. It was the upstream 10-layer default (Base/Media/Nav/Mouse/Sym/Num/Fun/Button/Game/Gameplus), shadowed byconfig/totem.keymapand never built. Confirmed by building all five images after removal.Step 5 — needs a decision, not a commit
NAV and SYM's ASCII art is updated to match the new bindings. Along the way the art turned out to have been lying in more places than the earlier audit caught — on NAV alone:
ESCandBT CLRshown as blanks,=shown as blank,DELshown as blank,BSPCmissing, and the whole thumb row wrong.BASE's art is still wrong and I left it alone, because fixing it is the wrong move if the art is going away:
&mt ESC Qshows as plainQ,< GER GRAVEas plain`, the sticky thumbs as plainNAV/SYM, and the 0+1 ESC combo is absent entirely.The tradeoff, stated plainly: the art is nice to have next to the bindings, but it has drifted on every layer, it cannot express hold-taps, stickies or combos, and
keymap-drawer/totem.svgnow shows all of them correctly and regenerates with one command. Keeping both means maintaining the same information twice, by hand, in the format that is worse at it.Ask: drop the per-layer art in favour of the drawer output, or keep it and I fix BASE too? Not doing either silently.
Verification
All five images rebuilt after every step:
nix build .#keymapandnix run .#drawboth clean; the committed SVG now shows the new SYM row.What is left
zmk@mainwith one pinned module (prospector-zmk-moduleated98221f) hanging off it.README added — and the GER layer is broken on the daily driver
totem-dongleat6ef25618.I was wrong twice. Both corrections matter.
1.
-DCONFIG_ZMK_STUDIO=yon the halves is not dead config. I said it was a no-op and offered to drop it. I tested that by removing it and diffing — the image changed: 1248 B less flash, and Zephyr'ssubsys/pm/device.c+device_system_managed.cno longer compiled. The reason is inzmk/app/src/studio/Kconfig:Peripherals are deliberately excluded from the RPC — which is exactly why they run zero nanopb steps and looked inert to me — but
PM_DEVICEis selected unconditionally, and physical-layout switching needs it. Dropping the flag would have broken layout switching from Studio. Reverted, and a comment now sits at the call site inflake.nixso nobody cleans it up again. Delete that item from the plan; there is nothing to decide.2. §4's "not documented anywhere which OS layout that assumes" is now documented — and the assumption does not hold where it matters.
Verified all five bindings against xkeyboard-config 2.47 (
symbols/us):&kp RA(Q)adiaeresisä<AD01>&kp RA(S)ssharpß<AC02>&kp RA(Y)udiaeresisü<AD06>&kp RA(P)odiaeresisö<AD10>&kp RA(5)EuroSign€<AE05>All five correct for
us+ variantaltgr-intl.us(intl)supplies the symbols, andaltgr-intlends withinclude "level3(ralt_switch)", so the variant carries the level-3 shift itself — no separatelv3:ralt_switchoption is needed, contrary to what the earlier comment implied.The niri gap is real, and worse than "either something else supplies it"
The earlier comment hedged: "Either something else supplies it or niri isn't used for German prose." Checked. Nothing supplies it.
nix-config/modules/nixos/keyboard.nixis correct:nix-config/home-manager/niri/default.nix:36is not:niri does not read
services.xserver.xkb. So on the Wayland session:variantat all. Plainushas no level-3 symbols on q/s/y/p/5.optionsis replaced, not merged —lv3:ralt_switchandeurosign:eare both gone, so RightAlt stays a plainAlt_Rmodifier.grp:alt_shift_toggleacross three layouts is a live hazard for this keymap. The base layer puts Alt and Shift on home-row mods (&hml LALT A,&hml LSHFT F,&hmr RSHFT J,&hmr RALT SEMI). An HRM misfire that lands Alt+Shift together silently switches the host to Ukrainian or Russian.The fix belongs in
nix-config, not here — positional variant list,altgr-intlapplying to the first layout only:and
grp:alt_shift_toggleis worth reconsidering independently of the above. Not touching that repo without a say-so — want me to?This is the single highest-impact finding so far: it means the German half of the keymap has never actually worked under niri, regardless of what the firmware does. Worth confirming by hand before I change anything: on the Wayland session, does AltGr+q produce
äor open a menu?README
The repo had none.
README.mdnow covers what was only in issue comments and in my head:us+altgr-intlrequirement above — the actual point of writing it;zephyrDepsHashrefresh;It embeds
keymap-drawer/totem.svgand says plainly that the drawing is authoritative and the in-file ASCII art is not.Still open
nix-config— needs your go-ahead, and ideally a manual confirmation of the symptom first.§5 rewritten against the verified Sofle baseline
Step 5 is done (
cd10b8c3— per-layer ASCII grids dropped, drawing is authoritative). What remains is §4 and §5, and §5 can now be written properly because the baseline has been checked rather than summarised.Baseline, read from the source not the README summary
One correction to my own reading first: the Sofle's
keymap_drawer.config.yamlcarries araw_binding_mapfull of&hm LEFT_GUI Aand<q 1 BACKSPACE. That is borrowed boilerplate from englmaxi's config (the file credits it), not a description of the keymap.layer0row 3 is&kp LSHFT &kp A &kp S &kp D &kp F &kp G— plain&kpthroughout, and the README states it outright: "no hold-taps, combos or macros." The earlier premise stands. Dropping that map during the flake port was the right call.Hardware, from the README rather than assumed: EC11 encoder on the left, 5-way hat switch on the right (matrix column 7, rows 0–4) — not a trackball. Arrows on the hat on
qwerty, mouse movement on the other layers.&momomentary&slsticky&mt,&hml,&hmr,<RA(Q/S/Y/P/5)layer_1Settled
CONFIG_ZMK_POINTINGplus somewhere to put&mmv/&mkp— cheapest on an existing layer, and worth revisiting only once the layout has settled.The number-layer / window-manager friction, concretely
Worth naming because the obvious workaround is bad. With numbers on NAV,
Mod+1becomes hold-a-home-row-mod plus a sticky layer plus a digit. That is a genuinely awkward three-part action, and worse,&slis one-shot — it is consumed by the next key press, which may be the modifier rather than the digit.The clean fix is to bind the whole chord, not compose it: put
&kp LG(N1)…&kp LG(N9)directly on a layer, so switching desktop is one key press on a layer with no modifier held at all. No HRM involvement, no sticky-layer interaction.Less urgent than it looked, since i3 is no longer the daily desktop — Plasma's defaults sit on
Ctrl+F1…F4, and the ADJ layer already carries F1–F12. Decide it when the WM is settled; it does not gate anything.The blocker nobody has named: the committed keymap is not trainable
Everything in §5 says start minimal. The keymap in this repo is the opposite of minimal, and starting from it contradicts the plan on four counts:
&hml/&hmracross all eight home positions, against a baseline with zero hold-tap experience of any kind.require-prior-idle-ms = <150>. urob's rule is10500 / relaxed WPM; at a beginner's 30–40 wpm that is 260–350 ms. 150 is tuned for someone already fast, and will misfire constantly at 25 wpm.&sl NAV,&sl SYM) where the existing habit is momentary&mo.&mt ESC Qand< GER GRAVE, both on keys that also carry a letter.So the first deliverable of §5 is not a schedule, it is a stripped starting keymap: target alphabet, mods on thumbs and outer keys the way the Sofle has them,
&mofor layers, one nav/number layer, no HRMs, no combos, no&mt/<. The full keymap becomes the destination, reached in stages, not the starting point.Revised schedule
Given the Sofle keeps all real work, the risk that made a gradual alphabet change tempting is gone — which is exactly why the alphabet should change on day one rather than after a QWERTY-on-38-keys detour that would train interference to be unlearned later.
&mo. No HRMs.require-prior-idle-msdown from 350&mt/<last of allTwo 15–30 min sessions beat one long block. Accuracy 97–99% before pushing speed. Stopping permanently at shift-only HRMs is a legitimate endpoint.
Tools: ngram-type + ngram-type-ger for bigrams including
ch/sch/ei/en, Monkeytype (german 10k, stop-on-error =word, layout emulator off — the layout lives in firmware), symbols last. keybr only if the chosen alphabet is supported.This still gates on §4, and here is the recommendation
Phase 0 cannot be built without knowing the alphabet.
The ecosystem argument for Colemak-DH is weaker than it looked, specifically because the Sofle stays in service. Its main concrete benefit is keybr's adaptive letter introduction, which is a weeks-1–2 tool; ngram-type and Monkeytype cover everything after that and are layout-agnostic. Against that, PUQ30 has real optimizer numbers on a 1:1 German/English corpus — which no English-optimized layout offers — an exact 30-key fit with zero wasted slots, two spare outer keys, and umlauts already handled by the GER scheme that is proven in daily use.
Recommendation: PUQ30. Say yes and I will build the phase-0 keymap; say Colemak-DH and I will build that instead. Either way the SYM row added in
be1e7e6csurvives, since it is not part of the 30-key core.Addendum to §5 — two Sofles, and what the fallback actually costs
New constraint: there are two Sofles, one on-prem and one in the home office, and the QWERTY fallback is wanted deliberately — a board to switch to under stress or in an emergency, when there is no capacity to fight an unfamiliar layout.
That is a reasonable thing to want and it is cheap to have. Three points follow, one of which inverts part of the plan above.
The separation is about as clean as it gets
58-key row-staggered with a number row versus 38-key column-staggered with layers. Different hand span, different thumb positions, different key travel path. Motor-context separation by hardware works best when the two boards feel nothing alike, and these do not. Keeping QWERTY on both Sofles and never putting it on the TOTEM is the right arrangement.
The honest cost: the fallback is free only while it stays rare
Occasional fallback costs nothing measurable. Regular fallback keeps refreshing the competing motor pattern and the new layout never crosses into fluent — you end up with two mediocre layouts instead of one good one and one fluent one.
The failure mode is specific and worth naming: switching on a capacity judgement made in the moment. "Do I have the headroom for the TOTEM right now?" is a question that gets answered no exactly when you are tired, rushed, or behind — which is most of the time during weeks 3–8, which is precisely the stretch where the reps have to accumulate.
The fix is to make the switch contextual, not discretionary: a rule decided in advance and not re-litigated under pressure. Something like TOTEM at the home desk for everything; Sofle on-prem; Sofle anywhere if a real deadline is on fire. The emergency escape hatch stays fully intact — it is just no longer a decision you make forty times a day.
Suggested tripwire: if the TOTEM goes untouched for a full week during phases 1–4, that is the signal the rule has quietly stopped being followed, not that the layout is wrong.
The travel-board inversion
This is the part that contradicts the plan above. The hardware split we built is bare dongle = travel, Prospector = desk. But travel is the worst possible early context for the new layout — unfamiliar rooms, client sites, conference talks, other people waiting. Exactly where a half-learned alphabet fails and where a fallback is least available, since the Sofles are both bolted to desks.
So: keep the TOTEM at a desk through phases 1–4, and do not travel with it until phase 5 at the earliest, when it is comfortably past parity. The bare-dongle image is built and ready; it just should not get used early. Nothing to change in the firmware — this is a usage rule, not a config.
Effect on §4
It strengthens the PUQ30 recommendation rather than weakening it. PUQ30's only real disadvantage was ecosystem — no keybr, likely no trainer support at all. A permanent, always-available QWERTY board at both work sites de-risks exactly that: the downside of picking the less-supported layout is "drilling is more manual", not "work stops". With the fallback guaranteed, the layout with better evidence on a 1:1 DE/EN corpus is the safer pick, not the braver one.
No change to the schedule table or to the phase-0 stripped-keymap deliverable. Still waiting on the §4 call before building it.
§4 decision aid — PUQ30 vs Colemak-DH, side by side
Base layers only, 30-key core:
Both are exactly 30 keys, and neither carries a full punctuation set:
¨,.';/;,./'The TOTEM has two spare outer keys (currently
< GER GRAVEand&kp SINGLE_QUOTE), so either layout's leftovers are covered without touching the core.PUQ30
For
,.'in-core. ~70% home row (EN), ~67% (DE); bottom row under 9%.¨slot was co-optimized at ~1.7% of German characters, not parked in a leftover position, and it takes&mo GERdirectly — reusing theRA(Q/S/Y/P/5)scheme already in daily production on the Sofle.h i e a o / d t r n sis identical to AdNW's, which has a long track record.Against
benachbartDE is 11.8 against PUO30's 6.9, becauseiandesit side by side andei/ieare pervasive in German. This is the one metric where PUQ30 clearly loses, and it is the reason the doc says PUO30 suits German better.Colemak-DH
For
z x c vstay in their QWERTY positions, so Ctrl+Z/X/C/V keep their muscle memory. This was missed in the earlier analysis and it is a daily-friction item rather than a theoretical one. PUQ30's bottom-left isk y . ' x— all four move.Against
'in the 30-key core.The honest frame
Modern well-designed layouts sit within a fraction of a percentage point of each other on effort metrics — below the threshold of perceptibility. Getreuer's position that the column-staggered board, a good symbol layer and vim each matter more than the alphabet is probably correct.
So this is corpus fit versus ecosystem, not comfort.
Recommendation, unchanged: PUQ30
The corpus match is the one property that cannot be retrofitted later; trainer support can be worked around with more manual drilling. The two-Sofle QWERTY fallback (see the previous comment) converts the ecosystem gap from a risk into an inconvenience — the downside becomes "drilling is more manual", not "work stops".
The strongest argument the other way is Ctrl+Z/X/C/V. If editor shortcut use is heavy, that is a real, permanent, daily cost that no amount of drilling removes, and it deserves weighing properly before committing.
Either choice leaves the SYM row from
be1e7e6cintact, since it is not part of the 30-key core. Once the call is made, phase 0 is the stripped starting keymap described in the §5 rewrite: target alphabet, mods on thumbs and outer keys as on the Sofle,&molayers, one nav/number layer, no HRMs, no combos,require-prior-idle-msparked at 350 for when they land in phase 4.§5 decided: train on the TOTEM directly. Plus three corrections to my own analysis.
Decision: option (b) — go straight to the TOTEM with the phase-0 stripped keymap. Both Sofles stay stock QWERTY. The alternative, learning the new alphabet on a Sofle first, was considered seriously and rejected; reasoning below, because it was closer than the outcome suggests.
Retraction 1 — Ctrl+Z/X/C/V is not a real argument
I listed it as the strongest case for Colemak-DH. It mostly dissolves under scrutiny.
The keymap has
&hml LCTRL Dand&hmr RCTRL K, both withhold-trigger-key-positionsrestricted to the opposite hand. So Ctrl+letter is always a cross-hand chord. On the Sofle it is same-hand — dedicatedLCTRLon the outer column plus left-handz x c v. The chord shape is therefore being relearned regardless of which alphabet is chosen; preserving the letter positions preserves one half of a gesture whose other half changes anyway.What survives is minor: Colemak-DH keeps all four on the left hand, so all four use right Ctrl — consistent. PUQ30 has
xon the left andz,c,von the right, soxuses right Ctrl and the others use left. A small inconsistency, absorbed by feel within days.Downgraded from "strongest counter-argument" to "minor."
hjkl — one half is safe, the other is lost on both layouts
Worth separating, because they behave completely differently:
&kp LEFT/DOWN/UP/RIGHTare not letters. They stay exactly where they are put, on any alphabet. Nothing at risk.hjklbreaks on both candidates. Neither preserves the adjacency, and adjacency is the entire point:Colemak-DH crushes all four onto two fingers across two rows; PUQ30 splits them across both hands. Both unusable as-is. The difference is prior art only: Colemak users converged on
n e i o(right home row) and there are configs to copy; on PUQ30 the equivalent would bet r n s, defined from scratch.Third option, probably the best one: do not remap vim at all — use the nav layer's real arrows. Layout-independent, already built, and it sidesteps the plugin problem below entirely.
Remap effort is smaller than "vi, tmux and fish" implies
The distinction that shrinks it:
Ctrl+A,Ctrl+E,dd,yy,gg,ciwall still mean what they meant; the key simply lives elsewhere, and that is relearned as part of learning the alphabet. No config at all.That reduces the actual work to:
fish_vi_key_bindingslands in the vim case.bindlines, or useprefix + arrows, which already works.Same on either layout; Colemak-DH just offers more to copy-paste.
Trainers — the ecosystem gap is smaller than assumed
frontend/static/layouts/, rows 1–5, each key[unshifted, shifted, altgr, shift+altgr], plus the name inLayoutNameSchema) buys only the on-screen keymap display — a real early learning aid, about an hour's work, fork or PR.packages/keybr-keyboard-generator/layout,npm run generate-layouts, then an entry in theLayoutclass. The hosted site has no custom-layout upload — issue #463 is that exact request, still open — so it means running a fork. Roughly half a day.Net effect: the ecosystem argument for Colemak-DH is weaker than the earlier comparison claimed.
Retraction 2 — the AdNW family is not ruled out by the key count
The question was whether the other AdNW-adjacent layouts are all unusable at 30 keys. The answer splits:
The 4-row ISO layouts genuinely are a poor fit. Neo 2, Bone, AdNW, KOY and Noted were optimized across a full ISO board where
ä ö ü(andßfor KOY) hold core keys. Extracting a 3×10 core evicts 3–4 slots and forces a letter out —ffor AdNW (~1.7% of German),f+zfor KOY,qfor Bone,jfor Noted,yfor Neo 2. Worse, relocating a letter invalidates the published metrics, since the layout was optimized as a unit. Adopting one means adopting a modified layout with no numbers behind it.But the family already produced 30-key members. PUQ30, PUO30 and OUMF30 come from the same optimizer and the same objective family, designed for exactly 30 keys with thumb shift. Those are the candidates.
OUMF30 is out: ~4.5% worse total effort (399.9 vs ~382) and the highest adjacent-finger load in both languages, bought in exchange for same-hand rolls over alternation (Handwechsel 64.6 vs 72.4). Only for a deliberate roll preference.
Retraction 3 — PUQ30 vs PUO30 is much closer than I presented
They tie on combined effort — 0.05% apart. PUQ30 wins English and position effort by ~1–1.5%. PUO30 wins German by ~1.7% and wins German adjacent-finger load by 42% (6.9 vs 11.8), which is the largest single gap anywhere in the table. The cause is precisely the
i/eadjacency in PUQ30'sh i e a oagainst PUO30'sh i a e ,— andei/ieare pervasive in German.Calling PUQ30 the clear pick overstated it.
The fact that de-risks this: PUQ30 and PUO30 have an identical right hand. Switching later is a left-hand-only relearn, roughly half a layout change. Not a one-way door.
Why the Sofle-first route lost
The proposal: learn the new alphabet on one Sofle first, move to the TOTEM once partly trained.
Two of my three objections were answered outright. The two Sofles are not identical — one is Cherry MX, one low-profile Choc, so the tactile cue is strong and hands know which board they are on; and carrying both means the QWERTY fallback travels too. Both objections withdrawn.
The third stood. The reason to train on a Sofle is that only the alphabet changes. The proposed fix for PUQ30 not fitting a 58-key row-staggered board was to strip the number row and outer column down to ~3×5+thumbs — but that changes the board as well, and lands on hardware that is still row-staggered, where PUQ30's ~70% home-row figure and travel numbers do not hold. That path pays for both approaches at once: a second keymap, a modified keyboard, and still not the TOTEM's geometry.
The deciding evidence is the NCSU study already cited here: split-board QWERTY typists were within 10% of baseline after ~20 trials, while changing the alphabet was dramatically slower. The board is the cheap variable. Separating it out buys little and costs an extra adaptation cycle.
Two coherent versions existed — Sofle fully stock with an alphabet-only substitution, or straight to the TOTEM. The middle path (a stripped Sofle) was the one to avoid. (b) chosen.
Where this leaves things
Settled: encoders irrelevant; mouse keys a future candidate; both Sofles stay stock QWERTY and travel together as the fallback; numbers on a layer accepted; TOTEM stays at a desk through phases 1–4; asymmetric friction — TOTEM centred, Sofle to the side.
Phase 0 is now the only blocker, and it needs the alphabet call: PUQ30 or PUO30. The deciding question is the German/English split in prose specifically, as opposed to code. If German prose dominates and English is mostly code and comments, PUO30's German advantage and much lower adjacent-finger load likely outweigh PUQ30's English edge. If genuinely balanced, PUQ30's better position effort in both languages breaks the tie.
Colemak-DH is now the outside option rather than the co-favourite: its ecosystem edge shrank once Monkeytype turned out to need nothing and keybr turned out to be self-hostable, and its Ctrl+ZXCV edge mostly dissolved.
§4 scored: all candidates in one model, with a crossover
tools/layout_score.py(8c45c8fd). The comparison that never existed — PUQ30, PUO30, OUMF30, Colemak-DH and QWERTY on the same axes, against a weighted corpus, swept across the German share of prose.Corpus
deu_mixed_1mngram tables (1M-token Leipzig-derived)Kotlin-weighted over Java deliberately. Nothing sensitive is retained — the script reduces everything to frequency counts.
Scored on the 26 letters plus
,and.— the subset every candidate holds in core — so no layout is flattered by simply lacking a character. What each pushes onto a layer is reported ascover%.Calibration check: QWERTY lands at 7.0–8.6% SFB and Colemak-DH at 2.36% for pure English. Published figures are ~6.5% and ~1.4–1.9% for prose alone; ours run higher because 40% of the corpus is code and CLI, which are SFB-dense. Directionally right, so the model is behaving.
Results
Colemak-DH is out, and for a reason the earlier comparison missed
Adjacent-finger load of 16–20%, roughly double the AdNW-family layouts. And it degrades as German rises — SFB 2.36 → 3.14, adjacent 15.97 → 20.51 from 0% to 100% German. That is what an English-optimized layout does when it meets German. It also has the lowest hand alternation (58% → 56%).
It wins two things: scissors (0.27–0.38 against ~0.7) and coverage (99.8% vs 98.7%, from holding
;and/in core). Neither offsets double the adjacent-finger load.OUMF30 is confirmed out — worst adjacent load among the three AdNW variants and the lowest alternation, as its documentation implies.
The crossover, and it is real
PUQ30 and PUO30 trade places on adjacent-finger load at ~52–55% German prose:
This independently confirms the Wettstein
benachbartsignal (11.8 vs 6.9 for German) in a completely different model. Thei/eadjacency inh i e a ois real and it does cost, in German specifically.But PUQ30 wins everything else, at every share
Finger load, 50/50 prose (L4 = left pinky … R4 = right pinky):
PUQ30 spreads the left hand better; PUO30 pushes 2.5pp more onto the left index. Right hands are identical, as expected — they are the same.
The sizes matter here. PUQ30's home-row advantage is 5 percentage points and holds at every German share. PUO30's adjacent-finger advantage is 0.3pp at 60% German and 1.0pp at 80%. The persistent 5pp beats the conditional 1pp.
Verdict: PUQ30
Now on evidence rather than on a reading of a PDF. It wins SFB, home row, scissors and finger balance at every plausible corpus mix, and only loses adjacent-finger load above ~55% German — by a margin far smaller than what it wins elsewhere.
If you later find adjacent-finger strain is the thing that actually bothers you, PUO30 remains a left-hand-only relearn, since the right hands are identical.
Drill list, as a by-product
Worst bigrams on PUQ30 at 50/50 prose, weighted by frequency and row distance — this is what ngram practice should target instead of generic language ngrams:
e.andoaare the ones that will bite daily — sentence endings aftere, ando/asharing the left index.Limitations, stated
Phase 0 is unblocked. Say the word and I build the stripped PUQ30 keymap.
Phase 0 built: PUQ30 on a stripped keymap — and a binding that never worked
totem-dongleat37623fb9. All five images build; drawing regenerated.A bug found while placing the GER layer, in both repos
&kp RA(5)— the € binding — sends nothing at all. Not a wrong character: nothing.RA(x)expands toAPPLY_MODS(MOD_RALT, x), which sets modifier bits in the top byte and leaves the rest untouched.keys.hdefinesN5asZMK_HID_USAGE(HID_USAGE_KEY, HID_USAGE_KEY_KEYBOARD_5_AND_PERCENT), but a bare5is not defined anywhere — it stays the literal integer 5, giving usage page 0. Andzmk_hid_pressinapp/src/hid.c:Page 0 matches neither branch, so it returns
-EINVALand no keycode is emitted.RA(Q),RA(S),RA(Y)andRA(P)were always fine — those letters are realZMK_HID_USAGEmacros. Only the numeric one is affected. Correct form isRA(N5), which is what phase 0 uses.ma3s7r0/zmk-sofle-eyelash-confighas the identical bug inlayer_1and needs the same one-character fix in its own repo. Worth a sanity check on the daily driver: does AltGr+5 currently produce€or nothing?The keymap
with
LCTRLandLGUIon the two outer keys, and thumbsLALT / NAV / SPACE | BSPC / LSHFT / RET.Three layers only: BASE, NAV, GER.
&mt, no<. The Sofle baseline has none of these — not one hold-tap anywhere — so there is nothing to build on and they all arrive later. Mods are plain keys on thumbs and outer keys, which is where the Sofle keeps them.&mo, held with the left thumb, so the numpad goes on the free right hand and the cursor cluster on the left.N0–N9, never theKP_variants — those depend on a NumLock nothing here sets.¨slot keeps its PUQ30 position but becomes&mo GER. ZMK cannot reproduce the upstream postfix behaviour, where the key follows the letter it modifies, so it is a prefix hold instead. Umlauts sit on the free right hand ordered by German frequency onto the stronger fingers:€ ü ä ö ß.&studio_unlock,&bootloaderand&bt BT_CLRare on NAV, since there is no ADJ layer any more and Studio locking is still at its default.No symbol layer. That is phase 2, and real work stays on the Sofle until then, so drills lose nothing by it. Esc and Tab living on NAV is the same trade.
The old QWERTY keymap is not lost — it is the destination, reached in stages, and sits at
cd10b8c3.What lands when
require-prior-idle-msstarting at 350 and walked down, never the 150 that suited an already-fast typist&mt/<, last of allAlso in this push
tools/layout_score.py(47567ec3) — the scorer from the previous comment, as its own commit.Next
Flash
totem_prospector.uf2to the desk dongle andtotem_left/totem_rightto the halves, then pair left first, then right. Phase 1 is 2×15 min drills a day with everything real staying on the Sofle.Open, and worth deciding before drilling starts: whether to build the staged-wordlist generator for Monkeytype custom text. The drill list from the scorer is ready either way —
Resolved: niri now sets the
altgr-intlvariantThe open blocker from this thread — niri not carrying the host layout the GER
layer depends on — is fixed.
ma3s7r0/nix-configcommit19775ac, 2026-08-27,home-manager/niri/default.nix:The cause was that niri does not read
services.xserver.xkb, somodules/nixos/keyboard.nix'sus+altgr-intl+lv3:ralt_switchneverreached the Wayland session. Plain
ushas no level-3 symbols and leavesRightAlt as
Alt_R, so the whole GER layer emitted Alt+letter, whichapplications take as a shortcut rather than a character.
Two details worth keeping, both verified against xkeyboard-config 2.47
symbols/us:lv3:ralt_switchoption is needed.us(intl)suppliesadiaeresis,ssharp,udiaeresis,odiaeresisandEuroSignat level 3 onq/s/y/p/5,and
altgr-intladdsinclude "level3(ralt_switch)"itself — the variantcarries the level-3 shift. This matches what the repo README already claims.
altgr-intl, notintl.intlleavesdead_acute,dead_diaeresis,dead_grave,dead_tildeanddead_circumflexat levels 1–2, which wouldstop
',",`,~and^from being ordinary characters — bad on akeymap where
'is a base-layer alpha key.altgr-intlexists precisely tomove those to level 3.
The same commit also dropped the
uaandrulayouts that came in with theimported niri config, and
grp:alt_shift_togglewith them. That toggle was itsown hazard here: once home-row mods arrive in phase 4, Alt and Shift sit on the
home row, and a mistimed roll could silently switch the host to Ukrainian or
Russian. Generated
config.kdlchecked withniri validate.Still open, and not covered by this
The Sofle's
&kp RA(5)for€remains broken there. A bare5is not a definedusage macro in
keys.h, so it yields usage page 0 andzmk_hid_pressrejects it— nothing is sent, regardless of how correct the host layout now is. The TOTEM
was fixed to
RA(N5);ma3s7r0/zmk-soflewas not.