{"id":13590761,"url":"https://github.com/urob/zmk-config","last_synced_at":"2025-05-14T19:03:58.230Z","repository":{"id":37384510,"uuid":"501412238","full_name":"urob/zmk-config","owner":"urob","description":"Personal ZMK firmware configuration for various boards (34-keys, Corneish Zen, Planck)","archived":false,"fork":false,"pushed_at":"2025-04-02T19:42:51.000Z","size":2135,"stargazers_count":887,"open_issues_count":2,"forks_count":461,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-04-06T13:02:42.781Z","etag":null,"topics":["corneish-zen","keyboard-layout","nix","zmk","zmk-config"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/urob.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-08T21:11:51.000Z","updated_at":"2025-04-03T03:40:23.000Z","dependencies_parsed_at":"2023-02-12T03:45:57.531Z","dependency_job_id":"77945a54-2dcd-4ab7-9fa1-fe2227305670","html_url":"https://github.com/urob/zmk-config","commit_stats":{"total_commits":276,"total_committers":5,"mean_commits":55.2,"dds":0.0905797101449275,"last_synced_commit":"b2d0c12eeddbf130b61873e1957c94163833a30a"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/urob","download_url":"https://codeload.github.com/urob/zmk-config/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248741147,"owners_count":21154249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["corneish-zen","keyboard-layout","nix","zmk","zmk-config"],"created_at":"2024-08-01T16:00:50.241Z","updated_at":"2025-04-13T15:50:48.979Z","avatar_url":"https://github.com/urob.png","language":"C++","funding_links":[],"categories":["C++","Community zmk-config user configurations"],"sub_categories":["Additional Resources"],"readme":"# urob's zmk-config\n\nThis is my personal [ZMK firmware](https://github.com/zmkfirmware/zmk/)\nconfiguration. It consists of a 34-keys base layout that is re-used for various\nboards, including my Corneish Zen and my Planck.\n\nMy configuration currently builds against `v0.2` of upstream ZMK.\nCustom functionality is added through various\n[ZMK modules](https://github.com/search?q=topic%3Azmk-module+fork%3Atrue+owner%3Aurob+\u0026type=repositories).\nThe state of the entire firmware is pinned in my `west`\n[manifest](https://github.com/urob/zmk-config/blob/main/config/west.yml).\n\n## Highlights\n\n- [\"Timeless\" homerow mods](#timeless-homerow-mods)\n- Combos instead of symbol layer\n- Auto-toggle off numbers and mouse layers\n- Magic thumb quadrupling as Repeat/Sticky-shift/Capsword/Shift\n- Leader key sequences for Unicode input and system commands\n- Arrow-cluster doubles as \u003ckbd\u003ehome\u003c/kbd\u003e, \u003ckbd\u003eend\u003c/kbd\u003e, \u003ckbd\u003ebegin/end of\n  document\u003c/kbd\u003e on long-press\n- Shifted actions that make sense: \u003ckbd\u003e, ↦ ;\u003c/kbd\u003e, \u003ckbd\u003e. ↦ :\u003c/kbd\u003e and \u003ckbd\u003e?\n  ↦ !\u003c/kbd\u003e\n- Simpler Devicetree syntax using helper macros from\n  [zmk-helpers](https://github.com/urob/zmk-helpers)\n- Fully automated, nix-powered [local build environment](#local-build-environment)\n\n![](draw/keymap.png)\n([Click here](https://raw.githubusercontent.com/urob/zmk-config/refs/heads/main/draw/base.svg)\nfor a breakdown by layer - powered by\n[keymap-drawer](https://github.com/caksoylar/keymap-drawer).)\n\n## Timeless homerow mods\n\n[Homerow mods](https://precondition.github.io/home-row-mods) (aka \"HRMs\") can be\na game changer -- at least in theory. In practice, they require some finicky\ntiming: In its most naive implementation, in order to produce a \"mod\", they must\nbe held _longer_ than `tapping-term-ms`. In order to produce a \"tap\", they must\nbe held _less_ than `tapping-term-ms`. This requires very consistent typing\nspeeds that, alas, I do not possess. Hence my quest for a \"timer-less\" HRM\nsetup.\n\nAfter months of tweaking, I eventually ended up with a HRM setup that is\nessentially timer-less, resulting in virtually no misfires.[^1] Yet it provides\na fluent typing experience with mostly no delays.\n\nLet's suppose for a moment we set `tapping-term-ms` to something ridiculously\nlarge, say 5 seconds. This makes the configuration timer-less of sorts. But it\nhas two problems: (1) To activate a mod we will have to hold the HRM keys for\nwhat feels like eternity. (2) During regular typing, there are delays between\nthe press of a key and the time it appears on the screen.[^2] Enter two of my\nfavorite ZMK features:\n\n- To address the first problem, I use ZMK's `balanced` flavor, which produces a\n  \"hold\" if another key is both pressed and released within the tapping-term.\n  Because that is exactly what I normally do with HRMs, there is virtually never\n  a need to wait past my long tapping term (see below for two exceptions).\n- To address the typing delay, I use ZMK's `require-prior-idle-ms` property,\n  which immediately resolves a HRM as \"tap\" when it is pressed shortly _after_\n  another key has been tapped. This all but completely eliminates the delay.\n\nThis is great but there are still a few rough edges:\n\n- When rolling keys, I sometimes unintentionally end up with \"nested\" key\n  sequences: `key-1` down, `key-2` down and up, `key-1` up. Because of the\n  `balanced` flavor, this would falsely register `key-1` as a mod. As a remedy,\n  I use ZMK's \"positional hold-tap\" feature to force HRMs to always resolve as\n  \"tap\" when the _next_ key is on the same side of the keyboard. Problem solved.\n- ... or at least almost. By default, positional-hold-tap performs the\n  positional check when the next key is _pressed_. This is not ideal, because it\n  prevents combining multiple modifiers on the same hand. To fix this, I use the\n  `hold-trigger-on-release` setting, which delays the positional-hold-tap\n  decision until the next key's _release_. With this, mods can be combined when\n  held while positional hold-tap continues to work as expected when keys are\n  tapped.\n- So far, nothing of the configuration depends on the duration of\n  `tapping-term-ms`. In practice, there are two reasons why I don't set it to\n  infinity:\n  1. Sometimes, in rare circumstances, I want to combine a mod with a alpha-key\n     _on the same hand_ (e.g., when using the mouse with the other hand). My\n     positional hold-tap configuration prevents this _within_ the tapping term.\n     By setting the tapping term to something large but not crazy large (I use\n     280ms), I can still use same-hand `mod` + `alpha` shortcuts by holding the\n     mod for just a little while before tapping the alpha-key.\n  2. Sometimes, I want to press a modifier without another key (e.g., on\n     Windows, tapping `Win` opens the search menu). Because the `balanced`\n     flavour only kicks in when another key is pressed, this also requires\n     waiting past `tapping-term-ms`.\n- Finally, it is worth noting that this setup works best in combination with a\n  dedicated shift for capitalization during normal typing (I like sticky-shift\n  on a home-thumb). This is because shifting alphas is the one scenario where\n  pressing a mod may conflict with `require-prior-idle-ms`, which may result in\n  false negatives for fast typers.\n\nHere's my configuration (I use a bunch of\n[helper macros](https://github.com/urob/zmk-helpers) to simplify the syntax, but\nthey are not necessary):\n\n```C++\n#include \"zmk-helpers/key-labels/36.h\"                                      // Source key-labels.\n#define KEYS_L LT0 LT1 LT2 LT3 LT4 LM0 LM1 LM2 LM3 LM4 LB0 LB1 LB2 LB3 LB4  // Left-hand keys.\n#define KEYS_R RT0 RT1 RT2 RT3 RT4 RM0 RM1 RM2 RM3 RM4 RB0 RB1 RB2 RB3 RB4  // Right-hand keys.\n#define THUMBS LH2 LH1 LH0 RH0 RH1 RH2                                      // Thumb keys.\n\n/* Left-hand HRMs. */\nZMK_HOLD_TAP(hml,\n    flavor = \"balanced\";\n    tapping-term-ms = \u003c280\u003e;\n    quick-tap-ms = \u003c175\u003e;\n    require-prior-idle-ms = \u003c150\u003e;\n    bindings = \u003c\u0026kp\u003e, \u003c\u0026kp\u003e;\n    hold-trigger-key-positions = \u003cKEYS_R THUMBS\u003e;\n    hold-trigger-on-release;\n)\n\n/* Right-hand HRMs. */\nZMK_HOLD_TAP(hmr,\n    flavor = \"balanced\";\n    tapping-term-ms = \u003c280\u003e;\n    quick-tap-ms = \u003c175\u003e;\n    require-prior-idle-ms = \u003c150\u003e;\n    bindings = \u003c\u0026kp\u003e, \u003c\u0026kp\u003e;\n    hold-trigger-key-positions = \u003cKEYS_L THUMBS\u003e;\n    hold-trigger-on-release;\n)\n```\n\n### Troubleshooting\n\nHopefully, the above configuration \"just works\". If it doesn't, here's a few\nsmaller (and larger) things to try.\n\n- **Noticeable delay when tapping HRMs:** Increase `require-prior-idle-ms`. As a\n  rule of thumb, you want to set it to at least `10500/x` where `x` is your\n  (relaxed) WPM for English prose.[^3]\n- **False negatives (same-hand):** Reduce `tapping-term-ms` (or disable\n  `hold-trigger-key-positions`)\n- **False negatives (cross-hand):** Reduce `require-prior-idle-ms` (or set\n  flavor to `hold-preferred` -- to continue using `hold-trigger-on-release`, you\n  must apply this\n  [patch](https://github.com/celejewski/zmk/commit/d7a8482712d87963e59b74238667346221199293)\n  to ZMK\n- **False positives (same-hand):** Increase `tapping-term-ms`\n- **False positives (cross-hand):** Increase `require-prior-idle-ms` (or set\n  flavor to `tap-preferred`, which requires holding HRMs past tapping term to\n  activate)\n\n## Using combos instead of a symbol layer\n\nI am a big fan of combos for all sort of things. In terms of comfort, I much\nprefer them over accessing layers that involve lateral thumb movements to be\nactivated, especially when switching between layers in rapid succession.\n\nOne common concern about overloading the layout with combos is that they lead to\nmisfires. Fortunately, the above-mentioned `require-prior-idle-ms` option also\nworks for combos, which in my experience all but completely eliminates misfires\n-- even when rolling keys on the home row!\n\nMy combo layout aims to place the most used symbols in easy-to-access locations\nwhile also making them easy to remember. Specifically:\n\n- the top vertical-combo row replicates the symbols on a standard numbers row\n  (except `+` and `\u0026` being swapped)\n- the bottom vertical-combo row is symmetric to the top row (subscript `_`\n  aligns with superscript `^`; minus `-` aligns with `+`; division `/` aligns\n  with multiplication `*`; logical-or `|` aligns with logical-and `\u0026`)\n- parenthesis, braces, brackets are set up symmetrically as horizontal combos\n  with `\u003c`, `\u003e`, `{` and `}` being accessed from the Navigation layer (or when\n  combined with `Shift`)\n- left-hand side combos for `tap`, `esc`, `cut` (on \u003ckbd\u003eX\u003c/kbd\u003e +\n  \u003ckbd\u003eD\u003c/kbd\u003e), `copy` and `paste` that go well with right-handed mouse usage\n\n## Smart layers and other gimmicks\n\n##### Numword\n\nInspired by Jonas Hietala's\n[Numword](https://www.jonashietala.se/blog/2021/06/03/the-t-34-keyboard-layout/#where-are-the-digits)\nfor QMK, I implemented my own\n[Auto-layer behavior](https://github.com/urob/zmk-auto-layer) for ZMK to set up\nNumword. It is triggered via a single tap on \"Smart-Num\". Numword continues to\nbe activated as long as I type numbers, and deactivates automatically on any\nother keypress (holding it activates a non-sticky num layer).\n\nAfter using Numword for more than a year now, I have been overall very happy\nwith it. When typing single digits, it effectively is a sticky-layer but with\nthe added advantage that I can also use it to type multiple digits.\n\nThe main downside is that if a sequence of numbers is _immediately_ followed by\nany of the letters on which my numpad is located (WFPRSTXCD), then the automatic\ndeactivation won't work. But this is rare -- most number sequences are\nterminated by `space`, `return` or some form of punctuation/delimination. To\ndeal with the rare cases where they aren't, there is a `CANCEL` key on the\nnavigation-layer that deactivates Numword, Capsword and Smart-mouse. (It also\ntoggles off when pressing `Numword` again, but I find it cognitively easier to\nhave a dedicated \"off-switch\" than keeping track of which modes are currently\nactive.)\n\n##### Smart-Mouse\n\nSimilarly to Numword, I have a smart-mouse layer (activated by comboing\n\u003ckbd\u003eW\u003c/kbd\u003e + \u003ckbd\u003eP\u003c/kbd\u003e), which replaces the navigation cluster with scroll\nand mouse-movements, and replaces the right thumbs with mouse buttons. Pressing\nany other key automatically deactivates the layer.\n\n##### Magic Repeat/Shift/Capsword\n\nMy right thumb triggers three variations of shift as well as repeat: Tapping\nafter any alpha key yields key-repeat (to reduce SFUs). Tapping after any other\nkeycode yields sticky-shift (used to capitalize alphas). Holding activates a\nregular shift, and double-tapping (or equivalently shift + tap) activates ZMK's\nCaps-word behavior.\n\nOne minor technical detail: While it would be possible to implement the\ndouble-tap functionality as a tap-dance, this would add a delay when using\nsingle taps. To avoid the delays, I instead implemented the double-tap\nfunctionality as a mod-morph.\n\n##### Multi-purpose Navigation cluster\n\nTo economize on keys, I am using hold-taps on my navigation cluster, which yield\n`home`, `end`, `begin/end of document`, and `delete word forward/backward` on\nlong-presses. The exact implementation is tweaked so that `Ctrl` is silently\nabsorbed in combination with `home` and `end` to avoid accidental document-wide\noperations (which are accessible via the dedicated `begin/end document keys`.)\n\n##### Swapper\n\nI am using [Nick Conway](https://github.com/nickconway)'s fantastic\n[tri-state](https://github.com/zmkfirmware/zmk/pull/1366) behavior for a\none-handed Alt-Tab switcher (`PWin` and `NWin`).\n\n##### Leader key\n\nI am using my own implementation of a\n[Leader key](https://github.com/urob/zmk-leader-key) (activated by comboing\n\u003ckbd\u003eS\u003c/kbd\u003e + \u003ckbd\u003eT\u003c/kbd\u003e) to bind various behaviors to my layout without\nreserving dedicated keys. Currently, I am using them to bind German Umlauts,\nGreek letters for math usage, and various system commands (e.g., to toggle\nBluetooth). See\n[`leader.dtsi`](https://github.com/urob/zmk-config/blob/main/config/leader.dtsi)\nfor the full list of leader key sequences.\n\n## Local build environment\n\nI streamline my local build process using `nix`, `direnv` and `just`. This\nautomatically sets up a virtual development environment with `west`, the\n`zephyr-sdk` and all its dependencies when `cd`-ing into the ZMK-workspace. The\nenvironment is _completely isolated_ and won't pollute your system.\n\n### Setup\n\n#### Pre-requisites\n\n1. Install the `nix` package manager:\n\n   ```bash\n   # Install Nix with flake support enabled\n   curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix |\n      sh -s -- install --no-confirm\n\n   # Start the nix daemon without restarting the shell\n   . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh\n   ```\n\n2. Install [`direnv`](https://direnv.net/) (and optionally but recommended\n   [`nix-direnv`](https://github.com/nix-community/nix-direnv)[^4]) using your\n   package manager of choice. E.g., using the `nix` package manager that we just\n   installed[^5]:\n\n   ```\n   nix profile install nixpkgs#direnv nixpkgs#nix-direnv\n   ```\n\n3. Set up the `direnv` [shell-hook](https://direnv.net/docs/hook.html) for your\n   shell. E.g., for `bash`:\n\n   ```bash\n   # Install the shell-hook\n   echo 'eval \"$(direnv hook bash)\"' \u003e\u003e ~/.bashrc\n\n   # Enable nix-direnv (if installed in the previous step)\n   mkdir -p ~/.config/direnv\n   echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' \u003e\u003e ~/.config/direnv/direnvrc\n\n   # Optional: make direnv less verbose\n   echo '[global]\\nwarn_timeout = \"2m\"\\nhide_env_diff = true' \u003e\u003e ~/.config/direnv/direnv.toml\n\n   # Source the bashrc to activate the hook (or start a new shell)\n   source ~/.bashrc\n   ```\n\n#### Set up the workspace\n\n1. Clone _your fork_ of this repository. I like to name my local clone\n   `zmk-workspace` as it will be the toplevel of the development environment.\n\n   ```bash\n   # Replace `urob` with your username\n   git clone https://github.com/urob/zmk-config zmk-workspace\n   ```\n\n2. Enter the workspace and set up the environment.\n\n   ```bash\n   # The first time you enter the workspace, you will be prompted to allow direnv\n   cd zmk-workspace\n\n   # Allow direnv for the workspace, which will set up the environment (this takes a while)\n   direnv allow\n\n   # Initialize the Zephyr workspace and pull in the ZMK dependencies\n   # (same as `west init -l config \u0026\u0026 west update \u0026\u0026 west zephyr-export`)\n   just init\n   ```\n\n### Usage\n\nAfter following the steps above your workspace should look like this:\n\n```\nzmk-workspace\n├── config\n├── firmware (created after building)\n├── modules\n├── zephyr\n└── zmk\n```\n\n#### Building the firmware\n\nTo build the firmware, simply type `just build all` from anywhere in the\nworkspace. This will parse `build.yaml` and build the firmware for all board and\nshield combinations listed there.\n\nTo only build the firmware for a specific target, use `just build \u003ctarget\u003e`.\nThis will build the firmware for all matching board and shield combinations. For\ninstance, to build the firmware for my Corneish Zen, I can type\n`just build zen`, which builds both `corneish_zen_v2_left` and\n`corneish_zen_v2_right`. (`just list` shows all valid build targets.)\n\nAdditional arguments to `just build` are passed on to `west`. For instance, a\npristine build can be triggered with `just build all -p`.\n\n(For this particular example, there is also a `just clean` recipe, which clears\nthe build cache. To list all available recipes, type `just`. Bonus tip: `just`\nprovides\n[completion scripts](https://github.com/casey/just?tab=readme-ov-file#shell-completion-scripts)\nfor many shells.)\n\n#### Drawing the keymap\n\nThe build environment packages\n[keymap-drawer](https://github.com/caksoylar/keymap-drawer). `just draw` parses\n`base.keymap` and draws it to `draw/base.svg`.\n\n#### Hacking the firmware\n\nTo make changes to the ZMK source or any of the modules, simply edit the files\nor use `git` to pull in changes.\n\nTo switch to any remote branches or tags, use `git fetch` inside a module\ndirectory to make the remote refs locally available. Then switch to the desired\nbranch with `git checkout \u003cbranch\u003e` as usual. You may also want to register\nadditional remotes to work with or consider making them the default in\n`config/west.yml`.\n\n#### Updating the build environment\n\nTo update the ZMK dependencies, use `just update`. This will pull in the latest\nversion of ZMK and all modules specified in `config/west.yml`. Make sure to\ncommit and push all local changes you have made to ZMK and the modules before\nrunning this command, as this will overwrite them.\n\nTo upgrade the Zephyr SDK and Python build dependencies, use `just upgrade-sdk`. (Use with care --\nRunning this will upgrade all Nix packages and may end up breaking the build environment. When in\ndoubt, I recommend keeping the environment pinned to `flake.lock`, which is [continuously\ntested](https://github.com/urob/zmk-config/actions/workflows/test-build-env.yml) on all systems.)\n\n## Bonus: A (moderately) faster Github Actions Workflow\n\nUsing the same Nix-based environment, I have set up a drop-in replacement for\nthe default ZMK Github Actions build workflow. While mainly a proof-of-concept,\nit does run moderately faster, especially with a cold cache.\n\n## Issues and workarounds\n\nSince I switched from QMK to ZMK I have been very impressed with how easy it is\nto set up relatively complex layouts in ZMK. For the most parts I don't miss any\nfunctionality (to the contrary, I found that ZMK supports many features natively\nthat would require complex user-space implementations in QMK). Below are a few\nremaining issues:\n\n- ZMK does not yet support \"tap-only\" combos\n  ([#544](https://github.com/zmkfirmware/zmk/issues/544)), requiring a brief\n  pause when wanting to chord HRMs that overlap with combo positions. As a\n  workaround, I implemented all homerow combos as homerow-mod-combos. This is\n  good enough for day-to-day, but does not address all edge cases (eg changing\n  active mods).\n- Very minor: `\u0026bootloader` doesn't work with stm32 boards like the Planck\n  ([#1086](https://github.com/zmkfirmware/zmk/issues/1086))\n\n## Related resources\n\n- The\n  [collection](https://github.com/search?q=topic%3Azmk-module+fork%3Atrue+owner%3Aurob+\u0026type=repositories)\n  of ZMK modules used in this configuration.\n- A ZMK-centric\n  [introduction to Git](https://gist.github.com/urob/68a1e206b2356a01b876ed02d3f542c7)\n  (useful for maintaining your own ZMK fork with a custom selection of PRs).\n\n[^1]:\n    I call it \"timer-less\", because the large tapping-term makes the behavior\n    insensitive to the precise timings. One may say that there is still the\n    `require-prior-idle` timeout. However, with both a large tapping-term and\n    positional-hold-taps, the behavior is _not_ actually sensitive to the\n    `require-prior-idle` timing: All it does is reduce the delay in typing;\n    i.e., variations in typing speed won't affect _what_ is being typed but\n    merely _how fast_ it appears on the screen.\n\n[^2]:\n    The delay is determined by how quickly a key is released and is not directly\n    related to the tapping-term. But regardless of its duration, most people\n    still find it noticeable and disruptive.\n\n[^3]:\n    E.g, if your WPM is 70 or larger, then the default of 150ms (=10500/70)\n    should work well. The rule of thumb is based on an average character length\n    of 4.7 for English words. Taking into account 1 extra tap for `space`, this\n    yields a minimum `require-prior-idle-ms` of (60 _ 1000) / (5.7 _ x) ≈ 10500\n    / x milliseconds. The approximation errs on the safe side, as in practice\n    home row taps tend to be faster than average.\n\n[^4]:\n    `nix-direnv` provides a vastly improved caching experience compared to only\n    having `direnv`, making entering and exiting the workspace instantaneous\n    after the first time.\n\n[^5]:\n    This will permanently install the packages into your local profile, forgoing\n    many of the benefits that make Nix uniquely powerful. A better approach,\n    though beyond the scope of this document, is to use `home-manager` to\n    maintain your user environment.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furob%2Fzmk-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Furob%2Fzmk-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furob%2Fzmk-config/lists"}