Problem
Side-button actions are exported but cannot be planned or applied. Today the only
way to change what a side button does is the DJ TechTools MIDI Fighter Utility,
which means a device that is otherwise fully configurable from the CLI still needs
a detour through a GUI app for one setting class.
This bit me on a real show rig. The Twister driving a live Chromatik show had no
side button assigned to any bank action — the six were ccToggle, shiftPage2Toggle,
ccToggle, ccToggle, shiftPage1Toggle, ccToggle. Banks 2–4 were fully
configured (correct CCs, colors, indicators) and completely unreachable from the
hardware. Pressing the side buttons toggled empty shift pages, which reads as
"bank 2 is broken/dark" rather than "no button is bound to bank switching."
Diagnosing that took an export; fixing it can't be done with this tool at all.
Why this looks small
The read path already exists and already knows every action code.
src/model.ts:124 reads all six from global tags 2–7:
const sideButtons = Array.from({ length: 6 }, (_, index) => {
const code = required(values, index + 2, `Side button ${index + 1}`);
return { number: index + 1, action: named(code, SIDE_ACTIONS) };
});
SIDE_ACTIONS (src/model.ts:60) already enumerates the bank actions we'd want:
| code |
action |
|
code |
action |
| 0 |
ccHold |
|
8 |
bankUp |
| 1 |
ccToggle |
|
9 |
bankDown |
| 2 |
noteHold |
|
10 |
bankSelect |
| 3 |
noteToggle |
|
11 |
bank1 |
| 4 |
shiftPage1Hold |
|
12 |
bank2 |
| 5 |
shiftPage2Hold |
|
13 |
bank3 |
| 6 |
shiftPage1Toggle |
|
14 |
bank4 |
| 7 |
shiftPage2Toggle |
|
|
|
Verified against a live export: button 2 reports code: 7 / shiftPage2Toggle,
button 5 reports code: 6 / shiftPage1Toggle. The indexing is confirmed correct.
Meanwhile GLOBAL_FIELDS in src/planner.ts:50 already writes other global tags
(0, 1, 8, 9, 31–38) through the same tag-based mechanism — including
sideButtonsBanked at tag 1, which sits immediately next to the six side-button
tags. Nothing structurally distinguishes tags 2–7 from tags already supported.
Proposed change
Add six planner paths:
globals.sideButtons.1.action.code → tag 2
...
globals.sideButtons.6.action.code → tag 7
as { kind: "number", min: 0, max: 14 }, matching the SIDE_ACTIONS length.
Accepting the action name as well as the code would be nicer at the call site
(--set globals.sideButtons.3.action.code=bank2), but the numeric form alone
would unblock this.
Then drop the two "read-only" caveats:
.claude/skills/mft-configurator/references/settings.md:71
- the "Side-button actions are read-only" bullet in the consuming skill's rules
The actual unknown
Whether the device accepts writes to global tags 2–7 at all. The read side
proves the tags exist and are reported; it does not prove they are writable. If
the firmware treats them as read-only, this closes as won't-fix — but that should
be established by trying a single-target plan against a real device and reading
the verification outcome, not assumed. Given apply re-reads and verifies after
every changed target, a rejected write should surface cleanly as failed rather
than silently corrupting state.
Worth checking a backup exists before the first attempt, since there is still no
restore command (#14 territory).
Problem
Side-button actions are exported but cannot be planned or applied. Today the only
way to change what a side button does is the DJ TechTools MIDI Fighter Utility,
which means a device that is otherwise fully configurable from the CLI still needs
a detour through a GUI app for one setting class.
This bit me on a real show rig. The Twister driving a live Chromatik show had no
side button assigned to any bank action — the six were
ccToggle,shiftPage2Toggle,ccToggle,ccToggle,shiftPage1Toggle,ccToggle. Banks 2–4 were fullyconfigured (correct CCs, colors, indicators) and completely unreachable from the
hardware. Pressing the side buttons toggled empty shift pages, which reads as
"bank 2 is broken/dark" rather than "no button is bound to bank switching."
Diagnosing that took an export; fixing it can't be done with this tool at all.
Why this looks small
The read path already exists and already knows every action code.
src/model.ts:124reads all six from global tags 2–7:SIDE_ACTIONS(src/model.ts:60) already enumerates the bank actions we'd want:Verified against a live export: button 2 reports
code: 7 / shiftPage2Toggle,button 5 reports
code: 6 / shiftPage1Toggle. The indexing is confirmed correct.Meanwhile
GLOBAL_FIELDSinsrc/planner.ts:50already writes other global tags(0, 1, 8, 9, 31–38) through the same tag-based mechanism — including
sideButtonsBankedat tag 1, which sits immediately next to the six side-buttontags. Nothing structurally distinguishes tags 2–7 from tags already supported.
Proposed change
Add six planner paths:
as
{ kind: "number", min: 0, max: 14 }, matching theSIDE_ACTIONSlength.Accepting the action name as well as the code would be nicer at the call site
(
--set globals.sideButtons.3.action.code=bank2), but the numeric form alonewould unblock this.
Then drop the two "read-only" caveats:
.claude/skills/mft-configurator/references/settings.md:71The actual unknown
Whether the device accepts writes to global tags 2–7 at all. The read side
proves the tags exist and are reported; it does not prove they are writable. If
the firmware treats them as read-only, this closes as won't-fix — but that should
be established by trying a single-target plan against a real device and reading
the verification outcome, not assumed. Given
applyre-reads and verifies afterevery changed target, a rejected write should surface cleanly as
failedratherthan silently corrupting state.
Worth checking a backup exists before the first attempt, since there is still no
restore command (#14 territory).