For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Expose all official Impact 80 lighting effects with zone-aware CLI targeting, defaulting every command to Logo, Backlight, and Side.
Architecture: Put protocol-neutral zone names, channel IDs, effect catalogs, aliases, and resolution in internal/rgb. Keep internal/via responsible only for QMK Raw HID channel/value transport. Resolve target zones in the Cobra command layer, emit warnings for unsupported default targets, and keep rgb info machine-readable with a per-zone array plus a compatibility summary.
Tech Stack: Go 1.26+, Cobra, existing Linux hidraw/QMK v12 transport, table-driven Go tests, fake transport, hardware smoke tests.
Spec: docs/superpowers/specs/2026-09-25-impact80-effects-design.md
logo, backlight, and side, mapped to VIA channels 2, 3, and 4.--zone, commands target all three zones in Logo, Backlight, Side order.--zone, commands target exactly one zone and invalid values fail before opening HID.0x07, 32-byte payloads, and validated acknowledgements.rgb info fields remain a summary of the first selected zone.--zone must never be sent to Logo or Side.--zone value must fail before device discovery.rgb info must return valid JSON even when one selected zone cannot be queried.multisplash and 41 solid_multisplash.Files:
internal/rgb/impact80.gointernal/rgb/impact80_test.gointernal/rgb/effects.go (remove the partial Impact80EffectName/ParseImpact80Effect implementation after the new API owns it)internal/rgb/effects_test.go (retain HSV/color/QMK value tests; remove TestImpact80EffectName and TestParseImpact80Effect, which move to impact80_test.go)Interfaces:
type Zone string, AllZones() []Zone, ParseZone(string) (Zone, error), (Zone).Channel() uint8.type EffectTarget struct { Zone Zone; ID uint8 }.ResolveEffect(name string, zones []Zone) ([]EffectTarget, []Zone, error), EffectName(zone Zone, id uint8) string, and DefaultEffect(zone Zone) uint8.The resolver returns supported targets in the caller's zone order, skipped zones separately, and an error for unknown names. A one-zone request is treated as explicit and errors when unsupported; a three-zone request is treated as the default and skips unsupported zones.
[ ] Step 1: Write the failing zone/catalog tests
Create table-driven tests with literal expectations:
func TestAllImpact80Zones(t *testing.T) {
want := []Zone{ZoneLogo, ZoneBacklight, ZoneSide}
got := AllZones()
if !reflect.DeepEqual(got, want) {
t.Fatalf("AllZones() = %v, want %v", got, want)
}
}
func TestImpact80ZoneChannels(t *testing.T) {
cases := []struct { zone Zone; want uint8 }{
{ZoneLogo, 2}, {ZoneBacklight, 3}, {ZoneSide, 4},
}
for _, tc := range cases {
if got := tc.zone.Channel(); got != tc.want {
t.Errorf("%s.Channel() = %d, want %d", tc.zone, got, tc.want)
}
}
}
func TestResolveBacklightOnlyEffect(t *testing.T) {
targets, skipped, err := ResolveEffect("rainbow_moving_chevron", AllZones())
if err != nil { t.Fatal(err) }
if len(targets) != 1 || targets[0] != (EffectTarget{ZoneBacklight, 17}) { ... }
if !reflect.DeepEqual(skipped, []Zone{ZoneLogo, ZoneSide}) { ... }
}
Add a complete 0–45 Backlight table test, the seven Logo/Side values, aliases (off, breathe, rainbow, rainbow_wave, solid), explicit unsupported behavior, and EffectName/DefaultEffect tests.
Run: go test ./internal/rgb -run 'Test(AllImpact80|Impact80|Resolve)' -count=1
Expected: compile failure because Zone, ResolveEffect, and related functions do not exist yet.
Implement Zone, channel mapping, canonical profile tables, alias resolution, and ResolveEffect. Use a table for every Backlight ID 0–45; do not derive names from numeric ranges. ResolveEffect must return zero targets plus an error for an unknown name, and must return an error for an explicit zone that does not support the requested name.
Run: go test ./internal/rgb -run 'Test(AllImpact80|Impact80|Resolve)' -count=1
Expected: PASS.
git add internal/rgb/impact80.go internal/rgb/impact80_test.go internal/rgb/effects.go internal/rgb/effects_test.go
git commit -m "Add Impact 80 zone effect profiles"
Files:
cmd/wobkey/rgb/rgb.go:12-73cmd/wobkey/rgb/rgb_test.gocmd/wobkey/rgb/zones.go (small target resolver helper)Interfaces:
--zone flag with values logo, backlight, and side.selectedZones() ([]intrgb.Zone, error) and zoneChannels([]intrgb.Zone) []via.LEDType for all commands.Validation occurs before OpenDevice().
[ ] Step 1: Write failing target-selection tests
func TestSelectedZonesDefaultsToAll(t *testing.T) {
old := targetZone
defer func() { targetZone = old }()
targetZone = ""
got, err := selectedZones()
// assert Logo, Backlight, Side
}
func TestSelectedZonesRejectsUnknownZone(t *testing.T) {
targetZone = "matrix"
if _, err := selectedZones(); err == nil { t.Fatal(...) }
}
Also test explicit Logo/Backlight/Side and channel conversion [2,3,4].
Run: go test ./cmd/wobkey/rgb -run 'TestSelectedZones|TestZone' -count=1
Expected: compile failure because selectedZones and zoneChannels do not exist.
Add targetZone, register PersistentFlags().StringVar, parse through intrgb.ParseZone, return intrgb.AllZones() for an empty value, and convert channel IDs to via.LEDType without importing CLI policy into internal/via.
Run: go test ./cmd/wobkey/rgb -run 'TestSelectedZones|TestZone|TestDeviceFlag' -count=1
Expected: PASS.
git add cmd/wobkey/rgb/rgb.go cmd/wobkey/rgb/zones.go cmd/wobkey/rgb/rgb_test.go
git commit -m "Add RGB zone selection"
Files:
cmd/wobkey/rgb/effect.gocmd/wobkey/rgb/effect_test.gointernal/via/protocol_test.goInterfaces:
selectedZones, intrgb.ResolveEffect, and via.SetValue.resolveEffectTargets(name string, zones []intrgb.Zone) ([]intrgb.EffectTarget, []intrgb.Zone, error) for command-policy tests and the Cobra command.Produces one Effect report for each supported target; default unsupported zones generate a warning; explicit unsupported zones return an error before opening HID.
[ ] Step 1: Write failing command-policy tests
Test the pure command policy with a fake resolver or extracted helper, not a real device. Assert:
targets, skipped, err := resolveEffectTargets("rainbow_moving_chevron", intrgb.AllZones())
// target channel 3/id 17, skipped Logo and Side
Also assert breathing targets IDs 4, 5, 4 and that explicit backlight rejects a Logo-only effect.
Run: go test ./cmd/wobkey/rgb -run 'TestResolveEffectTargets' -count=1
Expected: compile failure because the command policy helper is missing.
Validate zones and effect names before OpenDevice. Print a warning such as effect not supported on zone(s): logo, side to stderr when a default target is skipped. For each EffectTarget, call proto.SetValue(via.LEDType(target.Zone.Channel()), uint8(intrgb.EffectID), target.ID). Preserve the success message only after all writes succeed.
Run: go test ./cmd/wobkey/rgb -run 'TestResolveEffectTargets' -count=1
Expected: PASS.
git add cmd/wobkey/rgb/effect.go cmd/wobkey/rgb/effect_test.go
git commit -m "Apply effects per lighting zone"
Files:
cmd/wobkey/rgb/brightness.gocmd/wobkey/rgb/speed.gocmd/wobkey/rgb/color.gocmd/wobkey/rgb/disable.gocmd/wobkey/rgb/enable.gocmd/wobkey/rgb/mode.gocmd/wobkey/rgb/rgb.go (shared iteration helper)cmd/wobkey/rgb/commands_test.goInterfaces:
brightness, speed, and color send to every selected channel.disable sends Effect 0 then Brightness 0 to every selected channel.enable sends intrgb.DefaultEffect(zone) then Brightness 160 to every selected channel.mode <index> sends the raw numeric ID to every selected channel.
[ ] Step 1: Write failing selected-channel tests
Use the existing fake transport pattern to assert that a default operation produces reports for channels 2, 3, and 4, while --zone side produces only channel 4. Cover brightness, speed, color, disable, enable, and mode with table-driven expectations.
Run: go test ./cmd/wobkey/rgb -run 'TestSelectedChannel' -count=1
Expected: failure because commands still hard-code RGBLight or the test helper is absent.
Replace hard-coded channel calls with zoneChannels(selectedZones) and a small helper that stops on the first error. Do not duplicate the channel list in each command.
Run: go test ./cmd/wobkey/rgb -run 'TestSelectedChannel' -count=1
Expected: PASS.
git add cmd/wobkey/rgb/brightness.go cmd/wobkey/rgb/speed.go cmd/wobkey/rgb/color.go cmd/wobkey/rgb/disable.go cmd/wobkey/rgb/enable.go cmd/wobkey/rgb/mode.go cmd/wobkey/rgb/rgb.go cmd/wobkey/rgb/commands_test.go
git commit -m "Target all RGB commands by zone"
rgb info to Per-Zone StateFiles:
cmd/wobkey/rgb/info.gocmd/wobkey/rgb/info_test.go (new)internal/via/protocol_test.go (add TestGetValueReturnsTwoByteColor)Interfaces:
zones entries containing zone, channel, enabled, effect, effectId, brightness, speed, and color.enabled, mode, brightness, and speed from the first selected zone.Emits an error field for a zone that cannot be queried and exits non-zero after serializing available results.
[ ] Step 1: Write failing JSON-shape tests
Test a successful zone record and an error record with hand-written expected structs/JSON keys. Assert the top-level summary comes from the first selected zone, not an implicit hard-coded channel.
Run: go test ./cmd/wobkey/rgb -run 'TestInfo' -count=1
Expected: compile failure because the zone info model/helper does not exist.
For each selected channel, query Brightness, Effect, Speed, and Color (0x01, 0x02, 0x03, 0x04). Use intrgb.EffectName(zone, effectID) and lightingEnabled(effectID, brightness). Preserve valid JSON when one zone fails; collect errors, print JSON, then exit non-zero.
Run: go test ./cmd/wobkey/rgb -run 'TestInfo' -count=1
Expected: PASS.
git add cmd/wobkey/rgb/info.go cmd/wobkey/rgb/info_test.go internal/via/protocol_test.go
git commit -m "Report RGB state for each zone"
Files:
README.mdPLAN.mdAGENTS.mdInterfaces:
Documents the three zones, default-all behavior, explicit-zone behavior, unsupported-effect warning semantics, and the complete effect-name families.
[ ] Step 1: Update documentation examples
Document rgb effect breathing, rgb effect rainbow_moving_chevron, rgb effect rainbow_moving_chevron --zone backlight, and the default-all behavior. Remove any example that implies a generic effect ID is valid on every zone.
Run:
gofmt -w .
go test -count=1 ./...
go vet ./...
go build -o /tmp/wobkey ./cmd/wobkey/
Expected: all commands exit 0 and git diff --check is empty.
Run:
/tmp/wobkey keyboard info
/tmp/wobkey rgb effect breathing
/tmp/wobkey rgb effect rainbow_moving_chevron
/tmp/wobkey rgb effect rainbow_moving_chevron --zone backlight
/tmp/wobkey rgb info
/tmp/wobkey rgb color 00ff00
/tmp/wobkey rgb disable
/tmp/wobkey rgb enable
Expected: the Impact 80 raw interface is selected, unsupported default targets produce warnings only, explicit Backlight works, JSON contains three zones, and the final state is enabled.
git add README.md PLAN.md AGENTS.md
git commit -m "Document zone-aware RGB commands"
Do not stage graphify-out/ unless the user explicitly requests generated graph artifacts.
feature/impact80-effects through commit 62852e7.go test -count=1 ./..., go vet ./..., go build, gofmt, and
git diff --check pass./dev/hidraw7, but the device was root-owned mode 600; it
later disappeared, so the next operator must discover the current path.PLAN.md, record the result, and
merge the feature branch into main only after the three-zone JSON and final
enabled state have been verified.