# configure-macro-keyboard Skill

> End-to-end Karabiner workflow for cheap 3-key USB-C/Bluetooth macro pads. Identifies the device, writes a device-scoped Karabiner rule, handles dual-transport (USB + BT) configurations, and packages reusable patterns (`simultaneous` matchers, tap-vs-double-tap pairs, `device_if`).

**Hub**: [Plugin CLAUDE.md](../../CLAUDE.md) | **Sibling skills**: [emit-fn-key-on-macos](../emit-fn-key-on-macos/CLAUDE.md) · [diagnose-hid-keycodes](../diagnose-hid-keycodes/CLAUDE.md)

## What This Skill Owns

| File                                                                                            | Role                                                                                                                                                                                                                                                                                                                                 | Edit policy                                                                                                                                                                                                                                                                                                              |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SKILL.md`                                                                                      | User-invocable instructions for configuring a new pad. Loaded into context when the skill fires.                                                                                                                                                                                                                                     | Edit when the 5-step workflow changes shape. Keep terse — references carry the depth.                                                                                                                                                                                                                                    |
| `references/raw/karabiner-rule.json`                                                            | **SSoT for the live MacroKeyBot rule.** Verbatim 12-manipulator export.                                                                                                                                                                                                                                                              | Always update in lockstep with `~/.config/karabiner/karabiner.json`. The repo file is the source — patch live config from it, not the other way around.                                                                                                                                                                  |
| `references/02-usb-wired-configuration.md`                                                      | Live USB rule walkthrough + Karabiner-side tap/double-tap mechanism + troubleshooting matrix. Notes the BT bottom-button asymmetry but defers details to `08-`.                                                                                                                                                                      | Keep mapping table, abridged JSON, and "How the tap/double-tap pattern works" section in sync with `raw/karabiner-rule.json`. When changing the bottom button on USB, also check whether the BT path needs a parallel update (and vice versa).                                                                           |
| `references/08-bluetooth-configuration.md`                                                      | Live BT rule walkthrough (mode-4 firmware) + numbered manipulator structure. **Documents the bottom-button asymmetry**: pad firmware emits two different keycodes for single-vs-double-tap on the bottom button (`equal_sign` / `Option+Z`), so manipulators 11 + 12 are immediate translations, NOT a software-discrimination pair. | When the rule changes, update the structure list (1-12), the "Why USB and BT use different mechanisms" explanation, and the "Switching Modes" / "Other BT Modes" sections that reference manipulator numbers. Re-test both transports of any button you change in EventViewer to catch new firmware-side discrimination. |
| `references/09-turnkey-walkthrough.md`                                                          | Copy-paste-ready 30-minute MacroKeyBot recipe with VID/PID placeholders.                                                                                                                                                                                                                                                             | The full JSON in this file is meant to be lifted as-is. Keep it equivalent to `raw/karabiner-rule.json` modulo the `macrokeybot_*` vs `jieli_*` variable rename.                                                                                                                                                         |
| `references/03-patterns.md`                                                                     | Reusable techniques (live examples are device-specific; the patterns are not).                                                                                                                                                                                                                                                       | When a new live example proves a pattern, add it under the pattern's "Live examples" line. Don't fork patterns per device.                                                                                                                                                                                               |
| `references/04-anti-patterns.md`                                                                | Dead-ends + historical narrative of the original Ctrl+C → Fn rule.                                                                                                                                                                                                                                                                   | Append-only. Don't rewrite history when the rule evolves.                                                                                                                                                                                                                                                                |
| `references/overview.md`                                                                        | TL;DR mapping table + device signatures.                                                                                                                                                                                                                                                                                             | First place to update when the live mapping changes.                                                                                                                                                                                                                                                                     |
| `references/05-bluetooth-roadmap.md`                                                            | Historical pre-pairing roadmap.                                                                                                                                                                                                                                                                                                      | Don't update — frozen in time.                                                                                                                                                                                                                                                                                           |
| `references/06-bluetooth-landscape-survey.md`                                                   | 2026 ecosystem context.                                                                                                                                                                                                                                                                                                              | Update only on major ecosystem shifts.                                                                                                                                                                                                                                                                                   |
| `references/07-bluetooth-toolbox.md`                                                            | Tier-ranked FOSS BT tools.                                                                                                                                                                                                                                                                                                           | Update when a tool is replaced or its install command changes.                                                                                                                                                                                                                                                           |
| `references/raw/{lsusb-verbose,system-profiler,system-profiler-bluetooth,ioreg-hid-device}.txt` | Frozen hardware dumps from 2026-04-21.                                                                                                                                                                                                                                                                                               | Re-capture only if the pad firmware or macOS HID stack changes meaningfully.                                                                                                                                                                                                                                             |
| `references/raw/karabiner-bluetooth-device.json`                                                | Karabiner's view of the paired Free3-P.                                                                                                                                                                                                                                                                                              | Re-capture when the BT pairing identity changes.                                                                                                                                                                                                                                                                         |

## Critical Invariants

1. **`raw/karabiner-rule.json` and `~/.config/karabiner/karabiner.json` MUST stay in sync.** When updating the rule, edit the repo file first, then patch the live config (the live config has 4 sibling rules — never replace the whole file). Backup the live config before patching: `cp ~/.config/karabiner/karabiner.json ~/.config/karabiner/karabiner.json.bak.$(date +%Y%m%d-%H%M%S)`.
2. **Manipulator counts are doc-load-bearing.** The number "12" appears in `02-`, `08-`, `09-`, `overview.md`, `SKILL.md`, plugin `CLAUDE.md`, plugin `README.md`, plugin `CHANGELOG.md`, and this file. When the count changes, grep all docs and update consistently — `grep -rEn "[0-9]+[ -]manipulator|[0-9]+ manipulators|twelve manipulator|ten manipulator|eight manipulator" plugins/macro-keyboard/`. Also update the `comm`-based self-check command in `plugins/CLAUDE.md` if you add new entries to the plugin list.
3. **Each Karabiner-side-discriminated button needs its own variable name.** Live config uses `jieli_top_tap`, `jieli_middle_tap`, and `jieli_bottom_tap`. Sharing a variable across buttons would let a tap on one arm the double-tap detector on another. Variables are NOT used for the BT bottom button — that path uses pad-firmware-decided keycode translation instead (see invariant #8).
4. **`device_if` is non-optional on every manipulator.** Dropping it remaps the MacBook's built-in keyboard and breaks Apple's native keys. Both VID/PID identifiers (USB + BT) must be in the `identifiers` array.
5. **`apple_vendor_top_case_key_code: keyboard_fn` is the only path to real Fn.** `key_code: fn` and `modifiers: ["fn"]` are no-ops. See sibling skill `emit-fn-key-on-macos/CLAUDE.md` for the why.
6. **The top-button tap/double-tap is incompatible with Typeless push-to-talk.** Fn fires only after the 200ms detection window expires, so press-and-hold doesn't sustain Fn-down. The live config assumes Typeless is in tap-to-toggle mode. To restore PTT, collapse the top-button pair into a single immediate-Fn manipulator per transport (recipe in `09-turnkey-walkthrough.md`'s "Adapt for your pad" bullet).
7. **The bottom-button tap/double-tap defeats arrow-key auto-repeat on both transports.** On USB the single-tap target (`up_arrow`) fires once after the 200ms detection window; on BT the pad's firmware emits one discrete `equal_sign` event per gesture. Either way: no auto-repeat stream. Acceptable for cursor-nudge; not for fast-scroll. To restore auto-repeat on USB, collapse the pair into a single immediate-`up_arrow` manipulator. On BT you can't restore it — the firmware doesn't emit a stream.
8. **The bottom button uses ASYMMETRIC mechanisms across transports** (verified 2026-05-02 via Karabiner-EventViewer). USB: pad emits `Ctrl+X` for every press → Karabiner-side software discrimination via `set_variable` + `to_delayed_action` (manipulators 9 + 10). BT mode 4: pad firmware does its own double-tap detection → emits `equal_sign` for single tap, `Option+Z` for double tap → Karabiner just translates each immediately (manipulators 11 + 12, no variable, no delayed action). **Always re-test both transports of every button before assuming software discrimination is needed** — see `04-anti-patterns.md` → "Assuming the pad emits the same keycode on single-tap and double-tap on every transport". Top + middle buttons do NOT have this asymmetry; they use software discrimination on both transports.

## Recent Changes

- **2026-05-02 (afternoon, 2nd revision)** — Bottom button targets revised to `up_arrow` (single tap) / `down_arrow` (double tap). During this change, **discovered via Karabiner-EventViewer that the pad's BT firmware does its own double-tap discrimination on the bottom button only** — emitting `equal_sign` for single tap and `Option+Z` for double tap (top + middle still emit `page_up` / `page_down` on every press). Restructured the BT bottom path: removed the software discrimination (manipulators 11 + 12 no longer use `variable_if` or `to_delayed_action`); replaced with two simple immediate-translation manipulators (`equal_sign` → `up_arrow`, `Option+Z` → `down_arrow`). USB bottom path kept its software discrimination unchanged (USB still emits `Ctrl+X` on every press). Manipulator count stays at 12 but the structural meaning shifted. Live config backup: `~/.config/karabiner/karabiner.json.bak.before-bottom-arrows-20260502-141718`. New pattern documented in `03-patterns.md` → "Translate pad-firmware-decided keycodes". New anti-pattern entry in `04-anti-patterns.md` → "Assuming the pad emits the same keycode on single-tap and double-tap on every transport".
- **2026-05-02 (morning, 1st revision)** — Bottom button gained tap/double-tap pair (single → `down_arrow` cursor nudge, double → `Cmd+Delete` line-clear). Manipulator count: 10 → 12. New variable: `jieli_bottom_tap`. No-auto-repeat caveat documented. Live config backup: `~/.config/karabiner/karabiner.json.bak.before-bottom-tap-20260502-130639`. (Superseded by afternoon revision.)
- **2026-04-24** — Top button gained tap/double-tap pair (single → Fn for Typeless toggle, double → `Cmd+V` paste). Manipulator count: 8 → 10. New variable: `jieli_top_tap`. PTT-incompatibility caveat documented across all relevant references. Live config backup: `~/.config/karabiner/karabiner.json.bak.before-top-tap-20260424-143528`.
- **2026-04-23** — Middle button gained tap/double-tap pair (single → `Shift+Return`, double → `Return`). Initial introduction of the `set_variable` + `to_delayed_action` pattern.
- **2026-04-21** — Bluetooth support added (mode-4 firmware: page_up / page_down / equal_sign). Same rule, dual `device_if` identifiers.

## Common Edits

- **Change a button's target keycode**: edit `to[0]` in the relevant manipulator(s) in `raw/karabiner-rule.json`, then patch the live config. For software-discriminated tap/double-tap pairs (top + middle on both transports; bottom on USB), the single-tap target is in `to_delayed_action.to_if_invoked[0]` and the double-tap target is in the detector manipulator's `to[0]`. For the BT bottom path (firmware-discriminated, manipulators 11 + 12), the single-tap target is in manipulator 11's `to[0]` and the double-tap target is in manipulator 12's `to[0]` — both are simple, no delayed action.
- **Tune the double-tap window**: change `parameters.basic.to_delayed_action_delay_milliseconds` (default 200ms). Top, middle, and bottom-USB pairs tune independently. **The BT bottom path is NOT tunable from Karabiner** — its window is set by the pad's firmware.
- **Add a 4th button binding**: append a manipulator with the new `from` keycode. Reuse the same `device_if` identifiers. Test both transports in EventViewer to see whether the pad's firmware discriminates single-vs-double-tap on the new button (if it emits a different keycode on double-tap, use the firmware-translation pattern; otherwise use the software-discrimination pattern).
- **Support a new pad**: add its USB VID/PID and BT VID/PID to every manipulator's `device_if.identifiers` array. If the pad emits different keycodes, add transport-specific manipulators (don't try to alias). **Always test both transports of every button for firmware-side double-tap discrimination before assuming software discrimination is needed** — see `04-anti-patterns.md`.

## Validation

```bash
# Rule JSON parses + manipulator count is what you expect
/usr/bin/env python3 -c "import json; d=json.load(open('plugins/macro-keyboard/skills/configure-macro-keyboard/references/raw/karabiner-rule.json')); print(len(d['manipulators']))"

# Live config matches
/usr/bin/env python3 -c "
import json
live = json.load(open('/Users/terryli/.config/karabiner/karabiner.json'))
for r in live['profiles'][0]['complex_modifications']['rules']:
    if r['description'].startswith('Jieli/Free3-P'):
        print(len(r['manipulators']))
"

# Karabiner reloaded the file
tail -5 /var/log/karabiner/core_service.log
# Look for: "core_configuration is updated."
```
