Repository navigation
feat(incott): macro card in the Macro tab - #1382
vladidraganov wants to merge 2 commits into
Conversation
Incott macros can be built, recorded and assigned to buttons from the app. The mouse accepts a macro buffer and never returns one, so macros are kept in this browser (`device/incott-macros.ts`) and a button only shows a macro while the mouse's own button mapping says it runs one. - library of named macros; a button runs the buffer carrying its own index, so assigning a macro uploads it into that button's buffer - recorder for keys and the three mouse buttons, timing (fast, recorded, fixed) kept beside the steps so switching back restores the recording - repeat: while held, until a key is pressed, or a set number of times - step editor: press or release, key, delay, reorder, delete - every save uploads the buffer and then re-binds the button, as the vendor does; hardware-confirmed that the loop mode only takes effect on the binding write, so skipping it kept the old mode Reuses the K-snake macro card's styles; the new CSS is a library row, a button grid and a wrapping step row. Strings in every locale. JS budget raised with a measured note: the card and its strings add about 33 kB. Needs the @openmouse/protocol release carrying incottMacroSlotForButton, INCOTT_MACRO_MOUSE_KEYS and incottMacroKeyLabel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shaped on the G23 Pro hardware test read in mouse-protocol `captures/incott-g23-pro/`, with Back shown running a macro. Opens with `?preview=incott`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Tip 🎯 Customize your risk assessments
✅ Automatic approvals, with your rules
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
| uid: slot + 1, | ||
| steps: stepsForDevice(macro), | ||
| }); | ||
| control.applyDeviceButtonMapping(button, "Macro"); |
There was a problem hiding this comment.
🟠 High cards/IncottMacroCard.tsx:186
In game-profile mode, assigning a macro stages the button mapping as Macro even though applyIncottMacro rejects the buffer write, so the profile runs an old or uninitialized buffer instead of the selected macro. Since this mapping is staged independently, gate the whole assignment flow in game-profile mode rather than rejecting only the buffer write.
🤖 Copy this AI Prompt to have your agent fix this:
In file @src/features/devices/cards/IncottMacroCard.tsx around line 186:
In game-profile mode, assigning a macro stages the button mapping as `Macro` even though `applyIncottMacro` rejects the buffer write, so the profile runs an old or uninitialized buffer instead of the selected macro. Since this mapping is staged independently, gate the whole assignment flow in game-profile mode rather than rejecting only the buffer write.
| assignments: Partial<Record<IncottButtonName, string>>; | ||
| } | ||
|
|
||
| const KEY = "openmouse.incott-macros"; |
There was a problem hiding this comment.
🟠 High device/incott-macros.ts:60
A saved assignment for Back on one Incott mouse is reused for every other mouse, so incottMacroAssignment identifies the second mouse's unrelated Macro buffer as the saved macro and edits can overwrite that buffer. KEY stores all assignments under one global key, with no device identity; keep the shared macro library if desired, but scope assignments to the connected device.
🤖 Copy this AI Prompt to have your agent fix this:
In file @src/device/incott-macros.ts around line 60:
A saved assignment for `Back` on one Incott mouse is reused for every other mouse, so `incottMacroAssignment` identifies the second mouse's unrelated `Macro` buffer as the saved macro and edits can overwrite that buffer. `KEY` stores all assignments under one global key, with no device identity; keep the shared macro library if desired, but scope assignments to the connected device.
| } | ||
| const macro = store.macros.find((entry) => entry.id === id); | ||
| if (!macro) return; | ||
| persist({ ...store, assignments: { ...assignments, [button]: id } }); |
There was a problem hiding this comment.
🟠 High cards/IncottMacroCard.tsx:205
Discarding a staged assignment leaves the mouse running macro A while the card reports macro B, and the mismatch survives reload. assign persists store.assignments[button] before the upload is committed, while incottMacroAssignment trusts that mapping; keep the assignment pending and persist it only after a successful flash.
🤖 Copy this AI Prompt to have your agent fix this:
In file @src/features/devices/cards/IncottMacroCard.tsx around line 205:
Discarding a staged assignment leaves the mouse running macro A while the card reports macro B, and the mismatch survives reload. `assign` persists `store.assignments[button]` before the upload is committed, while `incottMacroAssignment` trusts that mapping; keep the assignment pending and persist it only after a successful flash.
| const controlsDisabled = disabled || isRecording; | ||
|
|
||
| const finishRecording = (): void => { | ||
| const recorded = recordedRef.current; |
There was a problem hiding this comment.
🟠 High cards/IncottMacroCard.tsx:77
Recording can save a macro with a key press but no matching release, leaving that key held during playback. finishRecording copies recordedRef.current verbatim, and when Escape stops recording the keyup listener is removed before a held key is released; likewise, events dropped at the step limit can include those releases. Close held keys before saving, and stop or reject recordings at the limit unless they can remain balanced.
Also found in 1 other location(s)
src/device/incott-macros.ts:109
At the step limit,
appendRecordedStepdiscards every subsequent event, including releases for keys pressed in the retained steps. The recorder inIncottMacroCardcontinues recording and saves this truncated array without balancing outstanding presses. For example, reaching the limit with a key-down and then releasing that key produces a saved macro with no corresponding release, rather than the recorded tap. Reserve room for pending releases or stop at a balanced boundary instead of silently dropping them.
🤖 Copy this AI Prompt to have your agent fix this:
In file @src/features/devices/cards/IncottMacroCard.tsx around line 77:
Recording can save a macro with a key press but no matching release, leaving that key held during playback. `finishRecording` copies `recordedRef.current` verbatim, and when Escape stops recording the `keyup` listener is removed before a held key is released; likewise, events dropped at the step limit can include those releases. Close held keys before saving, and stop or reject recordings at the limit unless they can remain balanced.
Also found in 1 other location(s):
- src/device/incott-macros.ts:109 -- At the step limit, `appendRecordedStep` discards every subsequent event, including releases for keys pressed in the retained steps. The recorder in `IncottMacroCard` continues recording and saves this truncated array without balancing outstanding presses. For example, reaching the limit with a key-down and then releasing that key produces a saved macro with no corresponding release, rather than the recorded tap. Reserve room for pending releases or stop at a balanced boundary instead of silently dropping them.
| const unbind = (button: IncottButtonName): void => { | ||
| // There is no command to empty a buffer; re-pointing the button is all | ||
| // unassigning can do. | ||
| if (assignment(button) !== null) control.applyDeviceButtonMapping(button, INCOTT_MACRO_DEFAULT_ACTION[button]); |
There was a problem hiding this comment.
🟠 High cards/IncottMacroCard.tsx:192
Unassigning a button can still bind it to Macro when the user clicks Apply, even though the UI shows it as unassigned. unbind stages the factory mapping, but because that mapping already matches the device, the staged mapping is discarded while the incott-macro-${button} upload from upload remains; cancel that pending macro upload when unbinding.
Also found in 1 other location(s)
src/device/controller.ts:7228
The unconditional binding write survives cancellation of the button assignment. With instant flash off and a button initially on its factory action, assign a macro and then unassign it before Apply.
IncottMacroCard.uploadstages thisincott-macro-${button}entry plusbutton-${button}; unassigning callsapplyDeviceButtonMappingwith the original action, sostageChangesees that the device already matches and drops onlybutton-${button}. The macro entry remains and this line binds the button toMacroon Apply, despite the UI showing it unassigned. Coordinate cancellation/overrides of the macro entry with button remaps so reverting an assignment cannot activate the macro.
🤖 Copy this AI Prompt to have your agent fix this:
In file @src/features/devices/cards/IncottMacroCard.tsx around line 192:
Unassigning a button can still bind it to `Macro` when the user clicks Apply, even though the UI shows it as unassigned. `unbind` stages the factory mapping, but because that mapping already matches the device, the staged mapping is discarded while the `incott-macro-${button}` upload from `upload` remains; cancel that pending macro upload when unbinding.
Also found in 1 other location(s):
- src/device/controller.ts:7228 -- The unconditional binding write survives cancellation of the button assignment. With instant flash off and a button initially on its factory action, assign a macro and then unassign it before Apply. `IncottMacroCard.upload` stages this `incott-macro-${button}` entry plus `button-${button}`; unassigning calls `applyDeviceButtonMapping` with the original action, so `stageChange` sees that the device already matches and drops only `button-${button}`. The macro entry remains and this line binds the button to `Macro` on Apply, despite the UI showing it unassigned. Coordinate cancellation/overrides of the macro entry with button remaps so reverting an assignment cannot activate the macro.
Adds an Incott macro card to the Macro tab: build or record a macro, then give it to a button. Hardware-tested on a G23V2 Pro over the 8K dongle.
What it does
The mouse accepts a macro buffer and has no command to read one back. Macros are therefore kept in this browser (
src/device/incott-macros.ts), and the card says so. What the mouse does report wins: a button only shows a macro while its read-back mapping isMacro. A button remapped elsewhere stops showing a stale assignment, and one set by the vendor tool shows "A macro from elsewhere".Macro. One macro on two buttons is uploaded twice. Unassigning puts the button's factory action back, since there is no command to empty a buffer.Macro, so without this a re-saved macro kept its old loop mode.The card shows only when the driver offers the per-button
Macroaction (availability.incottMacros), so it stays hidden on older protocol builds. Uploads go through the pending-changes bar like other settings. They are refused while a game profile is being edited, because a profile cannot restore a macro.Design and size
styles/areas/library.css). The heading carries theCommandglyph viaCardSlot.macro.incott*keys in every locale, and existingmacro.*keys are reused where they fit.build/check-bundle-size.ts. The card and its strings add about 33 kB (3,892,361 to 3,925,511 bytes against the protocol build it needs; the rest of that build's growth is protocol drivers released since 0.52.0). Main is 1,431,450 bytes and CSS 169,383, both under their caps.Preview
?preview=incottopens an Incott G23 Pro fixture based on the hardware-test read in mouse-protocolcaptures/incott-g23-pro/. Back is shown running a macro.Testing
npm run build,npm test(650 pass) andnpm run size, run against the protocol PR linked locally.🤖 Generated with Claude Code
Note
Add Incott macro editor card and recorder to the Macro tab
IncottMacroCard, a browser-local macro editor that lets users create, name, edit, and delete macros and assign them to device buttons (IncottMacroCard.tsx)applyIncottMacroin controller.ts📊 Macroscope summarized 493a6d4. 22 files reviewed, 7 issues evaluated, 2 issues filtered, 5 comments posted
🗂️ Filtered Issues
src/device/controller.ts — 0 comments posted, 1 evaluated, 1 filtered
IncottMacroCard.uploadstages thisincott-macro-${button}entry plusbutton-${button}; unassigning callsapplyDeviceButtonMappingwith the original action, sostageChangesees that the device already matches and drops onlybutton-${button}. The macro entry remains and this line binds the button toMacroon Apply, despite the UI showing it unassigned. Coordinate cancellation/overrides of the macro entry with button remaps so reverting an assignment cannot activate the macro. [ Cross-file consolidated ]src/device/incott-macros.ts — 1 comment posted, 2 evaluated, 1 filtered
appendRecordedStepdiscards every subsequent event, including releases for keys pressed in the retained steps. The recorder inIncottMacroCardcontinues recording and saves this truncated array without balancing outstanding presses. For example, reaching the limit with a key-down and then releasing that key produces a saved macro with no corresponding release, rather than the recorded tap. Reserve room for pending releases or stop at a balanced boundary instead of silently dropping them. [ Cross-file consolidated ]