Feat/winusb phase2a usbdk - #1
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the pre-existing uncommitted working-tree state handed to Phase 1 (Interception removal, keyboard.ipc/main/preload edits, usb dependency, asInvoker execution level) as a clean baseline so subsequent task commits stay atomic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the Raw Input + low-level hook reader. keyboard:list now enumerates USB keyboards via node-usb; keyboard:select opens the selected device with the WinUSB reader and forwards assigned keys over the unchanged keyboard:event IPC. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… errors Composite keyboards expose extra interrupt IN endpoints (consumer/vendor) that raise LIBUSB_ERROR_IO when polled; claiming every interface spammed errors and the tear-down-on-error path caused reopen/"Polling is not active" storms. Now only the HID boot-keyboard interface is claimed, endpoint errors are recovered by restarting the poll, and the IPC layer logs instead of tearing down. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wdi-simple build from libwdi proved too brittle; UsbDk ships a signed MSI and captures by VID/PID from code, with node-usb useUsbDkBackend() support. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User plays kernel-anti-cheat games; empirically verify whether UsbDk's loaded driver is flagged before building the integration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packaged app intermittently got a "stuck key" that blocked all other macro
keys until restart (dev was fine). Root cause, confirmed via the debug log:
App.tsx and KeyboardSelector both call loadDevices() on mount, firing
keyboard:select twice. findByIds() returns a singleton libusb Device per
VID/PID, so the two selects opened two readers on the same handle; the second's
poll collided with the first's teardown ("Can't close device with a pending
request" -> LIBUSB_ERROR_NOT_FOUND), killing the poll. It only surfaced in the
packaged build, whose faster startup races the two selects close together.
- keyboard.ipc: make keyboard:select idempotent (activeDeviceKey) and serialize
every reader open/close (opChain), awaiting teardown before each open so a
close never races its own open. Self-heal re-open uses the same queue.
- hid-keyboard: add a watchdog so a poll error whose 'end' never fires escalates
to a full re-open instead of wedging the poll forever.
- main: backgroundThrottling=false so the hidden tray window keeps running macros.
- flush held keys to the renderer on reader teardown so nothing stays
stuck-highlighted (preload onFlush + keyboardStore.clearKeys + App.tsx).
Committed together with the in-progress WinUSB macro-keyboard work this fix
builds on (wdi-simple.exe driver-swap flow, debug file logger). Also refreshes
README to the WinUSB flow and ignores resources build artifacts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep aeExpressions required (mirrors aeScripts), seed its default in main.ts, and widen AeScriptEditor's scriptType prop so the renderer tree compiles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a portable JSON backup so a user can restore their full MacroDeck config (macros across all profiles, app settings, and the AE script/ expression libraries) after reinstalling — no manual re-setup per key. - settings-transfer.ts: pure buildExportBundle/parseImportBundle with a versioned, identifying header and strict validation (unit-tested). - main.ts: settings:export / settings:import IPC with save/open dialogs; import validates before a wholesale store replace, then re-syncs the AE panel files and tray menu. - preload.ts: backup.export / backup.import bridge. - TitleBar: Export/Import buttons in the settings dropdown; import reloads every store from disk and re-syncs macro keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hint The target select, name input and Save button were crammed into one flex row, so in the narrow settings panel the Save button overflowed off-screen (had to scroll sideways) and the name field was hidden. Save is disabled until both expression and name are filled, so users who only typed the expression couldn't tell why it wouldn't save. Stack the name row and a full-width Save button vertically (like Test Expression), and show a hint when a name is still needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erflow The target select kept flex-shrink-0, so its long labels (e.g. "Selected property") pushed the name input off the right edge, forcing a horizontal scroll. Give both flex-1 min-w-0 so they split the row evenly and shrink together in the narrow panel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking a saved expression's name (or its new pencil button) now loads it into the editor in edit mode: Save becomes "Update Expression", a Cancel button appears, and saving patches the existing record instead of creating a duplicate. The delete (trash) button is now always visible in the expression list rather than only on row hover, matching the custom-JSX list behavior the user expected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AE's built-in "Create Shapes from Text" command is disabled when more than one layer is selected, forcing users to convert text layers one at a time. This preset snapshots the selected text layers, then isolates and runs the command on each in turn under a single undo group — one click converts the whole selection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Decompose-text tools keep the full source string on every single-character layer and reveal one glyph via a range selector. "Create Shapes from Text" ignores that selector and outlines the entire string, so a layer named "y" ends up containing B/o/u/n/c/y groups. After converting, keep only the glyph group whose name matches the layer name and remove the rest. Guarded by an exact name match so normal multi-character text layers are left untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Name-matching alone can't disambiguate duplicate characters: decomposing "Hello" gives the second "l" layer two groups both named "l", and keeping "every group named l" left both. Now read the decompose range selector on the text layer (before conversion) to learn which character index it reveals, and keep the glyph group at that ordinal position. Falls back to name matching when the character is unique, and leaves the layer untouched when a letter repeats and no index is available — never deletes the wrong glyph. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion" This reverts commit bd4b6a9.
📝 WalkthroughWalkthroughThe change replaces Interception-based keyboard capture with WinUSB HID reading and driver dedication, adds a resident After Effects CEP panel with file-based execution and saved libraries, introduces Force Quit and AE macros, adds settings backup import/export, enriches app/audio metadata, and updates packaging, documentation, tests, and UI flows. ChangesMacroDeck platform
Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
resources/AudioSessions.cs (1)
120-155: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPer-session commands report
{ ok: true }even when the target session doesn't exist. All three per-session branches default their result fields to zero/false and never track whether a matching session was found, so a missing/exited target is indistinguishable from a real success. Fix by recording amatchedflag in the session loop and exiting non-zero (or emittingok: false) when no session matchestarget.
resources/AudioSessions.cs#L120-L155: inAdjustVolume, set amatchedflag when the target session is found; emit failure when it stays false instead of{ ok:true, previousValue:0, currentValue:0 }.resources/AudioSessions.cs#L171-L200: inSetVolume, apply the samematchedguard before emitting{ ok:true, ..., currentValue:clampedValue }.resources/AudioSessions.cs#L216-L247: inToggleMute, apply the samematchedguard before emitting{ ok:true, isMuted:false, volume:0 }.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@resources/AudioSessions.cs` around lines 120 - 155, Track whether a target session was found in AdjustVolume, SetVolume, and ToggleMute by setting a matched flag inside each session loop. For resources/AudioSessions.cs lines 120-155, 171-200, and 216-247, emit failure or exit non-zero when matched remains false; only return the existing success payloads after a matching session is processed.
🧹 Nitpick comments (4)
src/App.tsx (1)
132-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
flex-0is not a valid Tailwind utility.Tailwind's
flexshorthand utilities areflex-1,flex-auto,flex-initial,flex-none—flex-0generates no CSS. It's also redundant alongsideflex-1here. Drop it (or useflex-[0]/flex-noneifflex: 0was actually intended).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/App.tsx` at line 132, Update the container div in App.tsx by removing the invalid and redundant flex-0 class from its className, while preserving the existing flex-1 and other layout utilities.src/components/MacroSettings/AppLaunchSettings.tsx (1)
30-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove leftover debug
console.log.This logs the full installed-apps payload (including base64 icon data URLs) to the console on every load. Drop it before merge.
♻️ Proposed cleanup
const result = await electronAPI.apps.getInstalled(); - console.log(result); - setApps(result);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/MacroSettings/AppLaunchSettings.tsx` around lines 30 - 31, Remove the leftover console.log(result) statement from the app-loading flow in AppLaunchSettings, leaving the installed-apps payload handling unchanged.src/components/MacroSettings/MacroSettings.tsx (1)
215-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
h-500is not a valid Tailwind class.Numeric height utilities follow Tailwind's spacing scale (…
h-96);500isn't in it, soh-500generates no CSS and the intended fixed height is silently dropped. Use an arbitrary value if a fixed height is wanted:h-[500px]. Note it also competes with the existingflex-1.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/MacroSettings/MacroSettings.tsx` at line 215, Replace the invalid h-500 utility on the scrollable container with the appropriate valid fixed-height class, using an arbitrary value such as h-[500px] if 500px is required. Review the existing flex-1 on the same element and remove or retain it according to the intended height-versus-flex behavior.src/components/MacroSettings/AeCommandSettings.tsx (1)
21-29: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd rejection handling to the polling and detect IPC calls.
panelStatus()is polled every 2s with no.catch; a rejecting IPC call produces an unhandled rejection on every tick. Thedetect()call on Line 106 has the same gap and additionally dereferencesr.foundwithout guarding against an undefined result.Proposed hardening
const poll = () => { - electronAPI?.ae.panelStatus().then((s: PanelStatus) => { if (active) setStatus(s); }); + electronAPI?.ae.panelStatus() + .then((s: PanelStatus) => { if (active) setStatus(s); }) + .catch(() => {}); };useEffect(() => { - electronAPI?.ae.detect().then((r: any) => setAeFound(r.found)); + electronAPI?.ae.detect() + .then((r: any) => setAeFound(!!r?.found)) + .catch(() => {}); }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/MacroSettings/AeCommandSettings.tsx` around lines 21 - 29, Update the polling effect around panelStatus to handle rejected IPC promises without unhandled rejections, while preserving the active-state guard before updating status. Also update the detect() call near the existing detection flow to handle rejection and guard the response before accessing r.found, treating an absent result as not found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@electron/debug-log.ts`:
- Around line 15-16: Add an asynchronous 'error' listener to the write stream
created in the debug-log setup, ensuring stream failures are handled without
propagating as unhandled exceptions or crashing the Electron main process. Keep
the existing synchronous setup behavior unchanged and make the handler safely
ignore or report the stream error without relying on the logger being
initialized.
- Around line 30-36: Update the console interception in the debug-log setup to
prevent raw HID report bytes emitted by electron/native/hid-keyboard.ts from
being persisted, while preserving normal console output and existing log levels.
Ensure the filtering targets the HID report logging path before write(level,
args) persists its arguments.
In `@electron/ipc/ae-bridge.ts`:
- Around line 119-142: Serialize executeViaPanel calls with an in-process queue
or mutex so each invocation waits for the previous request to resolve or time
out before calling writeRequest. Preserve the existing per-request polling,
success, script-error, panel-not-open, and timeout behavior while ensuring
concurrent callers cannot overwrite the shared request/response slot.
In `@electron/ipc/ae-install.ts`:
- Around line 57-77: The install flow in install must avoid deleting the
existing panel before a replacement is ready. Copy the source into a temporary
sibling directory, then replace dest only after copyDirRecursive completes
successfully, cleaning up the temporary directory on failure while preserving
the previous installation.
In `@electron/ipc/ae.ipc.ts`:
- Line 19: Reorder the AE_YEARS array into strictly descending year order by
moving '2026' to the front, ahead of '2025', while preserving all existing year
entries.
In `@electron/ipc/apps.ipc.ts`:
- Line 244: Update the PowerShell app-discovery command around Add-App to
replace the PowerShell 7 null-coalescing expression on $item.Publisher with a
PowerShell 5.1-compatible null check, preserving the empty-string fallback when
Publisher is null. Apply the same compatibility fix to any corresponding
$publisher expression in the generated script.
- Around line 41-330: Fix the embedded PS_SCRIPT so JavaScript preserves every
PowerShell regex backslash, either by escaping the backslashes in the template
literal or by loading the script from a separate .ps1 file. Also replace the
unsupported null-coalescing expressions in PS_SCRIPT, including the Add-App
calls using ??, with Windows PowerShell 5.1-compatible null/default handling so
the script parses and scans successfully.
In `@electron/ipc/keyboard.ipc.ts`:
- Around line 190-208: Update the keyboard:dedicate handler to await
stopReader() after validating the device and driver-tool availability, but
before calling dedicate(), so any active reader or retry loop is released before
the driver swap. Keep the existing dedication result handling and
startReader(deviceId, true) behavior unchanged.
In `@electron/native/appvolume.ts`:
- Line 180: Update the per-session "set-volume" branch to clamp the requested
percentage to [0,100] before computing and emitting currentValue, matching the
clamped value applied by SAVSetVol. Reuse that clamped value for the setter
input and JSON response while preserving previousValue behavior.
In `@electron/native/usb-enum.ts`:
- Around line 48-53: Eliminate the per-device getDriverService calls in the
enumeration block by fetching DEVPKEY_Device_Service in bulk within
loadPnpDevices, storing the result on each PnpDeviceInfo, and using that cached
service value to assign dev.driverState. Preserve the existing WinUSB
case-insensitive matching and normal-state fallback when the service is absent.
In `@electron/native/winusb-driver.ts`:
- Around line 200-201: Optimize device queries by passing the wildcard directly
to Get-PnpDevice via -InstanceId instead of filtering a parameterless query.
Apply this in electron/native/winusb-driver.ts at lines 200-201 and 226-227,
preserving Select-Object -First 1; at line 289, preserve the array wrapper
around the filtered result. Apply the same -InstanceId 'USB\VID_*' change in
electron/native/device-names.ts lines 73-74, retaining the existing exclusion of
'*MI_*'.
In `@resources/README.md`:
- Around line 44-46: Update the fenced code block containing the wdi-simple-x64
invocation in the README to specify a language identifier, using a shell fence
as requested, without changing the command itself.
In `@src/components/MacroSettings/MultimediaSettings.tsx`:
- Line 70: Replace unsupported Tailwind utilities at all affected sites: update
the container in src/components/MacroSettings/MultimediaSettings.tsx at lines
70-70 to use max-h-[200px], and update the h-500 and flex-0 usages in
src/App.tsx at lines 132-132 and src/components/MacroSettings/MacroSettings.tsx
at lines 215-215 to use h-[500px] and flex-none (or remove flex-0 if redundant).
Ensure the intended sizing and flex behavior remain unchanged.
---
Outside diff comments:
In `@resources/AudioSessions.cs`:
- Around line 120-155: Track whether a target session was found in AdjustVolume,
SetVolume, and ToggleMute by setting a matched flag inside each session loop.
For resources/AudioSessions.cs lines 120-155, 171-200, and 216-247, emit failure
or exit non-zero when matched remains false; only return the existing success
payloads after a matching session is processed.
---
Nitpick comments:
In `@src/App.tsx`:
- Line 132: Update the container div in App.tsx by removing the invalid and
redundant flex-0 class from its className, while preserving the existing flex-1
and other layout utilities.
In `@src/components/MacroSettings/AeCommandSettings.tsx`:
- Around line 21-29: Update the polling effect around panelStatus to handle
rejected IPC promises without unhandled rejections, while preserving the
active-state guard before updating status. Also update the detect() call near
the existing detection flow to handle rejection and guard the response before
accessing r.found, treating an absent result as not found.
In `@src/components/MacroSettings/AppLaunchSettings.tsx`:
- Around line 30-31: Remove the leftover console.log(result) statement from the
app-loading flow in AppLaunchSettings, leaving the installed-apps payload
handling unchanged.
In `@src/components/MacroSettings/MacroSettings.tsx`:
- Line 215: Replace the invalid h-500 utility on the scrollable container with
the appropriate valid fixed-height class, using an arbitrary value such as
h-[500px] if 500px is required. Review the existing flex-1 on the same element
and remove or retain it according to the intended height-versus-flex behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7e0ad69d-1ede-4702-9303-d743e0932df4
⛔ Files ignored due to path filters (15)
Interception/command line installer/install-interception.exeis excluded by!**/*.exeInterception/library/x64/interception.dllis excluded by!**/*.dllInterception/library/x86/interception.dllis excluded by!**/*.dllInterception/licenses/commercial-usage/Interception API.pdfis excluded by!**/*.pdfInterception/licenses/commercial-usage/Interception.pdfis excluded by!**/*.pdfInterception/samples/x86/axes.exeis excluded by!**/*.exeInterception/samples/x86/cadstop.exeis excluded by!**/*.exeInterception/samples/x86/caps2esc.exeis excluded by!**/*.exeInterception/samples/x86/hardwareid.exeis excluded by!**/*.exeInterception/samples/x86/identify.exeis excluded by!**/*.exeInterception/samples/x86/interception.dllis excluded by!**/*.dllInterception/samples/x86/mathpointer.exeis excluded by!**/*.exeInterception/samples/x86/x2y.exeis excluded by!**/*.exepackage-lock.jsonis excluded by!**/package-lock.jsonresources/wdi-simple.exeis excluded by!**/*.exe
📒 Files selected for processing (99)
.claudeignore.gitignoreInterception/library/interception.hInterception/library/x64/interception.libInterception/library/x86/interception.libInterception/licenses/non-commercial-usage/LGPL 3.0.txtREADME.mdae-panel/.debugae-panel/CSXS/manifest.xmlae-panel/index.htmlae-panel/js/CSInterface.jsae-panel/js/main.jsae-panel/jsx/runner.jsxdocs/superpowers/plans/2026-07-10-winusb-macro-keyboard-phase1-reading.mddocs/superpowers/plans/2026-07-10-winusb-phase2a-spike-guide.mddocs/superpowers/plans/2026-07-10-winusb-phase2a-usbdk-spike-guide.mddocs/superpowers/plans/2026-07-14-ae-macros.mddocs/superpowers/plans/2026-07-15-ae-cep-panel.mddocs/superpowers/plans/2026-07-20-ae-expression-library.mddocs/superpowers/plans/2026-07-20-ae-panel-script-list.mddocs/superpowers/specs/2026-07-10-winusb-macro-keyboard-design.mddocs/superpowers/specs/2026-07-11-single-toggle-macro-keyboard-design.mddocs/superpowers/specs/2026-07-14-ae-macros-design.mddocs/superpowers/specs/2026-07-15-ae-cep-panel-design.mddocs/superpowers/specs/2026-07-20-ae-expression-library-design.mddocs/superpowers/specs/2026-07-20-ae-panel-script-list-design.mdelectron/debug-log.tselectron/ipc/ae-bridge.test.tselectron/ipc/ae-bridge.tselectron/ipc/ae-install.test.tselectron/ipc/ae-install.tselectron/ipc/ae.ipc.tselectron/ipc/apps.ipc.tselectron/ipc/audio.ipc.tselectron/ipc/keyboard.ipc.tselectron/ipc/macro.ipc.tselectron/ipc/settings-transfer.test.tselectron/ipc/settings-transfer.tselectron/ipc/system.ipc.tselectron/main.tselectron/native/appvolume.tselectron/native/boot-report.test.tselectron/native/boot-report.tselectron/native/device-names.test.tselectron/native/device-names.tselectron/native/hid-keyboard.tselectron/native/hid-usage-map.test.tselectron/native/hid-usage-map.tselectron/native/usb-device-id.test.tselectron/native/usb-device-id.tselectron/native/usb-enum.tselectron/native/winusb-driver.test.tselectron/native/winusb-driver.tselectron/notification-window.tselectron/preload.tsinstaller.nshpackage.jsonresources/AudioSessions.csresources/README.mdresources/obj/AudioSessions.csproj.nuget.dgspec.jsonresources/obj/AudioSessions.csproj.nuget.g.propsresources/obj/AudioSessions.csproj.nuget.g.targetsresources/obj/Debug/net6.0-windows/win-x64/.NETCoreApp,Version=v6.0.AssemblyAttributes.csresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.AssemblyInfo.csresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.AssemblyInfoInputs.cacheresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.GeneratedMSBuildEditorConfig.editorconfigresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.GlobalUsings.g.csresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.assets.cacheresources/obj/Debug/net6.0-windows/win-x64/AudioSessions.csproj.AssemblyReference.cacheresources/obj/project.assets.jsonresources/obj/project.nuget.cachesrc/App.tsxsrc/components/KeyboardSelector/KeyboardSelector.tsxsrc/components/MacroList/AssignedMacroList.tsxsrc/components/MacroList/MacroTypeCard.tsxsrc/components/MacroSettings/AeCommandSettings.tsxsrc/components/MacroSettings/AppLaunchSettings.tsxsrc/components/MacroSettings/ForceQuitSettings.tsxsrc/components/MacroSettings/MacroSettings.tsxsrc/components/MacroSettings/MultimediaSettings.tsxsrc/components/MacroSettings/ae/AeScriptEditor.tsxsrc/components/MacroSettings/ae/AeShortcutPicker.tsxsrc/components/MacroSettings/ae/aePresets.test.tssrc/components/MacroSettings/ae/aePresets.tssrc/components/MacroSettings/ae/aeShortcuts.test.tssrc/components/MacroSettings/ae/aeShortcuts.tssrc/components/MacroSettings/ae/compileExpression.test.tssrc/components/MacroSettings/ae/compileExpression.tssrc/components/TitleBar/TitleBar.tsxsrc/components/Toast/NotificationApp.tsxsrc/notification-main.tsxsrc/stores/aeExpressionStore.tssrc/stores/aeScriptStore.tssrc/stores/keyboardStore.tssrc/stores/macroStore.tssrc/types/macro.types.tstsconfig.electron.jsontsconfig.jsonvitest.config.ts
💤 Files with no reviewable changes (14)
- resources/obj/project.assets.json
- resources/obj/AudioSessions.csproj.nuget.g.targets
- resources/obj/AudioSessions.csproj.nuget.dgspec.json
- resources/obj/Debug/net6.0-windows/win-x64/AudioSessions.GlobalUsings.g.cs
- resources/obj/Debug/net6.0-windows/win-x64/AudioSessions.AssemblyInfoInputs.cache
- resources/obj/Debug/net6.0-windows/win-x64/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs
- resources/obj/AudioSessions.csproj.nuget.g.props
- resources/obj/project.nuget.cache
- Interception/licenses/non-commercial-usage/LGPL 3.0.txt
- resources/obj/Debug/net6.0-windows/win-x64/AudioSessions.AssemblyInfo.cs
- installer.nsh
- resources/obj/Debug/net6.0-windows/win-x64/AudioSessions.GeneratedMSBuildEditorConfig.editorconfig
- Interception/library/interception.h
- electron/ipc/system.ipc.ts
| const file = path.join(app.getPath('userData'), 'macrodeck-debug.log'); | ||
| const stream = fs.createWriteStream(file, { flags: 'w' }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Missing error handler on the write stream can crash the app.
The try/catch around setup only covers synchronous errors. If the stream itself emits an async 'error' event later (disk full, permission denied, path issue) with no listener attached, Node throws an unhandled exception, crashing the Electron main process — the opposite of "logging must never break startup."
🛡️ Proposed fix
const stream = fs.createWriteStream(file, { flags: 'w' });
+ stream.on('error', () => { /* never let logging crash the app */ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const file = path.join(app.getPath('userData'), 'macrodeck-debug.log'); | |
| const stream = fs.createWriteStream(file, { flags: 'w' }); | |
| const file = path.join(app.getPath('userData'), 'macrodeck-debug.log'); | |
| const stream = fs.createWriteStream(file, { flags: 'w' }); | |
| stream.on('error', () => { /* never let logging crash the app */ }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/debug-log.ts` around lines 15 - 16, Add an asynchronous 'error'
listener to the write stream created in the debug-log setup, ensuring stream
failures are handled without propagating as unhandled exceptions or crashing the
Electron main process. Keep the existing synchronous setup behavior unchanged
and make the handler safely ignore or report the stream error without relying on
the logger being initialized.
| (['log', 'warn', 'error'] as const).forEach((level) => { | ||
| const orig = console[level].bind(console); | ||
| console[level] = (...args: unknown[]) => { | ||
| write(level, args); | ||
| orig(...args); | ||
| }; | ||
| }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether HID/keyboard modules log potentially sensitive per-key data.
rg -nP --type=ts -C2 'console\.(log|warn|error)\(' electron/native/hid-keyboard.ts electron/ipc/keyboard.ipc.tsRepository: Troughie/MacroDeck
Length of output: 7687
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== electron/native/hid-keyboard.ts (outline) ==\n'
ast-grep outline electron/native/hid-keyboard.ts --view expanded || true
printf '\n== electron/native/hid-keyboard.ts (relevant slices) ==\n'
sed -n '1,240p' electron/native/hid-keyboard.ts
printf '\n== electron/ipc/keyboard.ipc.ts (relevant slices) ==\n'
sed -n '1,260p' electron/ipc/keyboard.ipc.tsRepository: Troughie/MacroDeck
Length of output: 19000
Remove HID report bytes from the persistent debug log
electron/native/hid-keyboard.ts logs the first 8 bytes of incoming boot reports for the first 30 packets, and debug-log.ts persists all console.log/warn/error output to disk. Those bytes can include modifier state and keycodes, so raw keystroke data may be written in plaintext.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/debug-log.ts` around lines 30 - 36, Update the console interception
in the debug-log setup to prevent raw HID report bytes emitted by
electron/native/hid-keyboard.ts from being persisted, while preserving normal
console output and existing log levels. Ensure the filtering targets the HID
report logging path before write(level, args) persists its arguments.
| export async function executeViaPanel(jsx: string, opts: ExecuteViaPanelOptions = {}): Promise<void> { | ||
| const now = opts.now ?? Date.now; | ||
| const sleep = opts.sleep ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms))); | ||
| const pollIntervalMs = opts.pollIntervalMs ?? 50; | ||
| const timeoutMs = opts.timeoutMs ?? 5000; | ||
|
|
||
| if (!isPanelAlive(now())) { | ||
| throw new Error(PANEL_NOT_OPEN); // NO CLI fallback (by design) | ||
| } | ||
|
|
||
| const id = genRequestId(now()); | ||
| writeRequest(id, jsx, now()); | ||
|
|
||
| const deadline = now() + timeoutMs; | ||
| while (now() < deadline) { | ||
| const res = readResponse(); | ||
| if (res && res.id === id) { | ||
| if (res.ok) return; | ||
| throw new Error(res.error || 'After Effects script error'); | ||
| } | ||
| await sleep(pollIntervalMs); | ||
| } | ||
| throw new Error('After Effects not responding (timeout).'); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Concurrent executeViaPanel calls race on the shared request.json/response.json.
There's only one request/response slot per bridge dir. If a second call writes request.json while an earlier call is still polling for its own id, the panel (single-slot poller) only ever executes the newer script; the first call silently times out after 5s instead of failing fast or being queued behind the in-flight one. Given AE macros can be triggered rapidly from a macro keyboard, this is a plausible failure mode that surfaces as spurious "not responding" errors.
Consider serializing calls (an in-process queue/mutex) so a new request is only written once the previous one has resolved or timed out.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/ipc/ae-bridge.ts` around lines 119 - 142, Serialize executeViaPanel
calls with an in-process queue or mutex so each invocation waits for the
previous request to resolve or time out before calling writeRequest. Preserve
the existing per-request polling, success, script-error, panel-not-open, and
timeout behavior while ensuring concurrent callers cannot overwrite the shared
request/response slot.
| export async function install(source: SourceEnv): Promise<{ ok: boolean; error?: string }> { | ||
| try { | ||
| // ① Enable unsigned panels for CEP 11 (AE 2024) and CEP 12 (AE 2025+). | ||
| // HKCU only — no admin rights needed. | ||
| for (const csxs of ['CSXS.11', 'CSXS.12']) { | ||
| await execFileAsync('reg', [ | ||
| 'add', `HKCU\\Software\\Adobe\\${csxs}`, | ||
| '/v', 'PlayerDebugMode', '/t', 'REG_SZ', '/d', '1', '/f', | ||
| ], { timeout: 5000, windowsHide: true } as any); | ||
| } | ||
| // ② Copy panel into the CEP extensions folder (overwrite to support upgrades). | ||
| const src = panelSourceDir(source); | ||
| if (!fs.existsSync(src)) return { ok: false, error: `Panel source not found: ${src}` }; | ||
| const dest = panelDestDir(); | ||
| fs.rmSync(dest, { recursive: true, force: true }); | ||
| copyDirRecursive(src, dest); | ||
| return { ok: true }; | ||
| } catch (err: any) { | ||
| return { ok: false, error: err?.message?.slice(0, 200) ?? 'Install failed' }; | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Install upgrade path is destructive with no rollback on partial failure.
fs.rmSync(dest, ...) wipes the existing panel before copyDirRecursive runs. If the copy fails partway (permission issue, file lock, disk error), the previous working install is already gone and the new one is incomplete — the extension is left broken with no automatic recovery.
Consider copying into a temporary sibling directory first, then atomically renaming it over dest only after the copy fully succeeds.
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/ipc/ae-install.ts` around lines 57 - 77, The install flow in install
must avoid deleting the existing panel before a replacement is ready. Copy the
source into a temporary sibling directory, then replace dest only after
copyDirRecursive completes successfully, cleaning up the temporary directory on
failure while preserving the previous installation.
|
|
||
| // ─── AE Path Detection ──────────────────────────────────────────────────────── | ||
|
|
||
| const AE_YEARS = ['2025', '2024', '2023', '2022', '2021', '2020', '2026']; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
AE_YEARS ordering puts 2026 after 2020, preferring older AE installs.
The list is otherwise strictly descending (2025→2020) but '2026' is appended at the end instead of the front. With multiple AE versions installed side by side, findAeExeByFilesystem will return the 2020 install before checking 2026.
🛠️ Proposed fix
-const AE_YEARS = ['2025', '2024', '2023', '2022', '2021', '2020', '2026'];
+const AE_YEARS = ['2026', '2025', '2024', '2023', '2022', '2021', '2020'];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const AE_YEARS = ['2025', '2024', '2023', '2022', '2021', '2020', '2026']; | |
| const AE_YEARS = ['2026', '2025', '2024', '2023', '2022', '2021', '2020']; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/ipc/ae.ipc.ts` at line 19, Reorder the AE_YEARS array into strictly
descending year order by moving '2026' to the front, ahead of '2025', while
preserving all existing year entries.
| case "set-volume": float sv=float.Parse(args[2])/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("OK"); break; | ||
| case "volume-up": float d=args.Length>2?float.Parse(args[2]):10f; float c=SAVGetVol(savs[0]); foreach(var p in savs) SAVSetVol(p,Math.Min(1f,c+d/100f)); Console.WriteLine("OK"); break; | ||
| case "volume-down": float dd=args.Length>2?float.Parse(args[2]):10f; float dc=SAVGetVol(savs[0]); foreach(var p in savs) SAVSetVol(p,Math.Max(0f,dc-dd/100f)); Console.WriteLine("OK"); break; | ||
| case "set-volume": { float prev=(float)Math.Round(SAVGetVol(savs[0])*100); float sv=float.Parse(args[2])/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("{\\"ok\\":true,\\"previousValue\\":" + (int)prev + ",\\"currentValue\\":" + (int)Math.Round(float.Parse(args[2])) + "}"); break; } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Per-session set-volume reports an unclamped currentValue.
SAVSetVol clamps the applied level to [0,1], but currentValue is emitted from float.Parse(args[2]) without clamping — unlike the master branch (line 168) which clamps to [0,100]. A caller passing a value outside 0..100 (e.g. macro.ipc's executeVolumeAdjust, which forwards settings.setValue unclamped) will surface a currentValue that doesn't match the actual volume.
🐛 Proposed fix
- case "set-volume": { float prev=(float)Math.Round(SAVGetVol(savs[0])*100); float sv=float.Parse(args[2])/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("{\\"ok\\":true,\\"previousValue\\":" + (int)prev + ",\\"currentValue\\":" + (int)Math.Round(float.Parse(args[2])) + "}"); break; }
+ case "set-volume": { float prev=(float)Math.Round(SAVGetVol(savs[0])*100); float cv=Math.Max(0f,Math.Min(100f,float.Parse(args[2]))); float sv=cv/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("{\\"ok\\":true,\\"previousValue\\":" + (int)prev + ",\\"currentValue\\":" + (int)Math.Round(cv) + "}"); break; }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case "set-volume": { float prev=(float)Math.Round(SAVGetVol(savs[0])*100); float sv=float.Parse(args[2])/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("{\\"ok\\":true,\\"previousValue\\":" + (int)prev + ",\\"currentValue\\":" + (int)Math.Round(float.Parse(args[2])) + "}"); break; } | |
| case "set-volume": { float prev=(float)Math.Round(SAVGetVol(savs[0])*100); float cv=Math.Max(0f,Math.Min(100f,float.Parse(args[2]))); float sv=cv/100f; foreach(var p in savs) SAVSetVol(p,sv); Console.WriteLine("{\\"ok\\":true,\\"previousValue\\":" + (int)prev + ",\\"currentValue\\":" + (int)Math.Round(cv) + "}"); break; } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/native/appvolume.ts` at line 180, Update the per-session
"set-volume" branch to clamp the requested percentage to [0,100] before
computing and emitting currentValue, matching the clamped value applied by
SAVSetVol. Reuse that clamped value for the setter input and JSON response while
preserving previousValue behavior.
| // Detect which keyboards are already bound to WinUSB (dedicated). Runs in | ||
| // parallel; a null service leaves the device as 'normal'. | ||
| await Promise.all(result.map(async (dev) => { | ||
| const service = await getDriverService(dev.vendorId, dev.productId); | ||
| dev.driverState = service && /^winusb$/i.test(service) ? 'dedicated' : 'normal'; | ||
| })); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
Avoid N+1 PowerShell executions during enumeration.
Calling getDriverService for each device spawns a new powershell.exe process per keyboard, creating an N+1 bottleneck that will cause a multi-second delay when opening the device selector.
Consider fetching the service state in bulk. You can update loadPnpDevices in device-names.ts to retrieve the DEVPKEY_Device_Service property for all nodes, store it on PnpDeviceInfo, and then check that cached state here instead of spawning new PowerShell processes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/native/usb-enum.ts` around lines 48 - 53, Eliminate the per-device
getDriverService calls in the enumeration block by fetching
DEVPKEY_Device_Service in bulk within loadPnpDevices, storing the result on each
PnpDeviceInfo, and using that cached service value to assign dev.driverState.
Preserve the existing WinUSB case-insensitive matching and normal-state fallback
when the service is absent.
| `$d = Get-PnpDevice -PresentOnly -ErrorAction SilentlyContinue | ` + | ||
| `Where-Object { $_.InstanceId -like '${like}' } | Select-Object -First 1;` + |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Optimize Get-PnpDevice performance.
Piping a parameterless Get-PnpDevice to Where-Object causes PowerShell to enumerate every device on the system before filtering, which is notoriously slow (taking ~1-2 seconds per call). You can eliminate this latency by pushing the wildcard filter directly into the cmdlet using the -InstanceId parameter.
electron/native/winusb-driver.ts#L200-L201: replace the pipeline with$d = Get-PnpDevice -InstanceId '${like}' -PresentOnly -ErrorAction SilentlyContinue | Select-Object -First 1;electron/native/winusb-driver.ts#L226-L227: replace the pipeline with$d = Get-PnpDevice -InstanceId '${like}' -PresentOnly -ErrorAction SilentlyContinue | Select-Object -First 1;electron/native/winusb-driver.ts#L289-L289: replace the pipeline with$devs = @(Get-PnpDevice -InstanceId '${like}' -PresentOnly -ErrorAction SilentlyContinue)electron/native/device-names.ts#L73-L74: replace the pipeline with$nodes += Get-PnpDevice -InstanceId 'USB\\VID_*' -PresentOnly -ErrorAction SilentlyContinue | Where-Object { $_.InstanceId -notlike '*MI_*' }
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
📍 Affects 2 files
electron/native/winusb-driver.ts#L200-L201(this comment)electron/native/winusb-driver.ts#L226-L227electron/native/winusb-driver.ts#L289-L289electron/native/device-names.ts#L73-L74
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/native/winusb-driver.ts` around lines 200 - 201, Optimize device
queries by passing the wildcard directly to Get-PnpDevice via -InstanceId
instead of filtering a parameterless query. Apply this in
electron/native/winusb-driver.ts at lines 200-201 and 226-227, preserving
Select-Object -First 1; at line 289, preserve the array wrapper around the
filtered result. Apply the same -InstanceId 'USB\VID_*' change in
electron/native/device-names.ts lines 73-74, retaining the existing exclusion of
'*MI_*'.
| ``` | ||
| wdi-simple-x64.exe --vid 0xXXXX --pid 0xXXXX --type 0 --name "MacroDeck Keyboard (WinUSB)" | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language to the fenced code block. markdownlint flags MD040 here; use a shell fence for the invocation example.
✏️ Proposed fix
-```
+```text
wdi-simple-x64.exe --vid 0xXXXX --pid 0xXXXX --type 0 --name "MacroDeck Keyboard (WinUSB)"</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)
[warning] 44-44: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@resources/README.md` around lines 44 - 46, Update the fenced code block
containing the wdi-simple-x64 invocation in the README to specify a language
identifier, using a shell fence as requested, without changing the command
itself.
Source: Linters/SAST tools
| <div> | ||
| <label className="text-text-secondary text-xs font-medium mb-1.5 block">Target</label> | ||
| <div className="space-y-1 max-h-28 overflow-y-auto"> | ||
| <div className="space-y-1 max-h-50 overflow-y-auto"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Tailwind/config files ---'
git ls-files | rg '(^|/)(tailwind\.config|tailwind\.config\.[jt]s|postcss\.config|vite\.config|src/App\.tsx|src/components/MacroSettings/MultimediaSettings\.tsx|src/components/MacroSettings/MacroSettings\.tsx)$'
printf '\n%s\n' '--- Relevant snippets ---'
for f in src/App.tsx src/components/MacroSettings/MultimediaSettings.tsx src/components/MacroSettings/MacroSettings.tsx; do
echo "### $f"
sed -n '1,260p' "$f" | nl -ba | rg -n 'flex-0|h-500|max-h-50|overflow-hidden|overflow-y-auto|flex-1|flex-none|max-h-|h-'
echo
done
printf '%s\n' '--- Tailwind config content (if present) ---'
for f in $(git ls-files | rg '(^|/)(tailwind\.config|tailwind\.config\.[jt]s)$'); do
echo "### $f"
cat "$f"
echo
done
printf '%s\n' '--- Search for custom scale/extensions that could make these classes valid ---'
rg -n --hidden --glob '!.git' --glob '!node_modules' 'flex-0|h-500|max-h-50|theme:\s*{|extend:\s*{|spacing:|height:|maxHeight:' .Repository: Troughie/MacroDeck
Length of output: 396
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- tailwind.config.js ---'
cat -n tailwind.config.js
echo
echo '--- relevant class usages ---'
for f in src/App.tsx src/components/MacroSettings/MultimediaSettings.tsx src/components/MacroSettings/MacroSettings.tsx; do
echo "### $f"
sed -n '1,260p' "$f" | grep -nE 'flex-0|h-500|max-h-50|overflow-hidden|overflow-y-auto|flex-1'
echo
doneRepository: Troughie/MacroDeck
Length of output: 3439
🌐 Web query:
Tailwind CSS default spacing scale max-h-50 h-500 flex-0 utilities
💡 Result:
In modern Tailwind CSS (v4+), the spacing scale has transitioned to a generative system based on a central --spacing unit (defaulting to 0.25rem/4px) [1]. This means that utilities like h-{number} or max-h-{number} are no longer limited to a fixed set of predefined keys [1]. Instead, you can use virtually any numeric value (e.g., h-50, h-500), and Tailwind will calculate the result by multiplying your input by the base spacing unit [1][2][3]. Regarding the specific utilities mentioned: 1. flex-0: This utility is a standard part of Tailwind CSS. It sets the flex-basis property to 0, typically applied via the flex-none (flex: none) or by using custom arbitrary values like flex- if a specific shorthand is required [4]. 2. max-h-50 and h-500: In versions using the new generative spacing system, these utilities will generate CSS such as max-height: calc(var(--spacing) * 50) and height: calc(var(--spacing) * 500) respectively [1][2][3]. In older versions of Tailwind (v3 and earlier), these specific classes were not part of the default theme and would not function unless explicitly added to your tailwind.config.js or used via arbitrary value syntax (e.g., max-h-[50px], h-[500px]) [5][4][6][7]. If you are using a newer version of Tailwind and find these utilities are not working, ensure your configuration is not locking the theme to a legacy, restricted spacing scale [1]. If you are on an older version, you should use the arbitrary value syntax (e.g., h-[500px]) to achieve these specific dimensions [2][3].
Citations:
- 1: Replace default explicit spacing scale with multiplier system tailwindlabs/tailwindcss#14857
- 2: https://tailwindcss.com/docs/max-height
- 3: https://tailwindcss.com/docs/height
- 4: https://v3.tailwindcss.com/docs/theme
- 5: https://v3.tailwindcss.com/docs/customizing-spacing
- 6: https://v2.tailwindcss.com/docs/customizing-spacing
- 7: https://v3.tailwindcss.com/docs/max-height
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- package.json ---'
cat -n package.json | sed -n '1,220p'
echo
echo '--- lockfile/package manager hints ---'
git ls-files | rg '(^|/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb)$' || true
echo
echo '--- Tailwind references ---'
rg -n '"tailwindcss"|tailwindcss' package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb 2>/dev/null || trueRepository: Troughie/MacroDeck
Length of output: 4847
Replace unsupported Tailwind utilities Tailwind 3.4.19 here won’t generate max-h-50, h-500, or flex-0, and tailwind.config.js doesn’t extend spacing to add them. Those classes are dropped, so the intended sizing/scroll cap never applies. Use arbitrary values like max-h-[200px] and h-[500px], and replace flex-0 with flex-none or remove it if it’s redundant.
📍 Affects 3 files
src/components/MacroSettings/MultimediaSettings.tsx#L70-L70(this comment)src/App.tsx#L132-L132src/components/MacroSettings/MacroSettings.tsx#L215-L215
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/MacroSettings/MultimediaSettings.tsx` at line 70, Replace
unsupported Tailwind utilities at all affected sites: update the container in
src/components/MacroSettings/MultimediaSettings.tsx at lines 70-70 to use
max-h-[200px], and update the h-500 and flex-0 usages in src/App.tsx at lines
132-132 and src/components/MacroSettings/MacroSettings.tsx at lines 215-215 to
use h-[500px] and flex-none (or remove flex-0 if redundant). Ensure the intended
sizing and flex behavior remain unchanged.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation